Insecure Third Party Resource
Description
Insecure Third Party Resource occurs when a web application includes executable code or resources from a third-party source that could be maliciously modified. This includes JavaScript libraries loaded from CDNs, external stylesheets, fonts, images, and iframes. If the third-party resource is compromised, modified, or served over an insecure connection, attackers can inject malicious code into websites that depend on these resources. This is a form of supply chain attack targeting web applications.
Risk
Third-party resource compromise has caused massive security incidents. The Magecart attacks have affected thousands of e-commerce sites by compromising shared JavaScript libraries. British Airways suffered a breach affecting 380,000 customers through compromised third-party scripts. CDN compromises can affect millions of websites simultaneously. Even legitimate CDNs can serve corrupted content due to cache poisoning. Man-in-the-middle attacks on HTTP resources allow code injection.
Solution
Use Subresource Integrity (SRI) to verify that fetched resources match expected hashes. Load resources over HTTPS only. Self-host critical JavaScript libraries instead of relying on CDNs. Implement Content Security Policy (CSP) to restrict resource sources. Regularly audit third-party dependencies. Use npm audit, Snyk, or similar tools for dependency scanning. Minimize third-party dependencies. Monitor for changes in third-party resources.
Common Consequences
| Impact | Details |
|---|---|
| Integrity | Scope: Code Injection Compromised third-party resources can execute arbitrary JavaScript in users' browsers. |
| Confidentiality | Scope: Data Theft Malicious scripts can steal credentials, payment information, and session tokens. |
| Availability | Scope: Service Disruption Defaced or broken resources can render websites unusable. |
Example Code + Solution Code
Vulnerable Code
<!-- VULNERABLE: No integrity check on CDN resources -->
<script src="https://cdn.example.com/jquery-3.6.0.min.js"></script>
<script src="https://cdn.example.com/bootstrap.min.js"></script>
<!-- VULNERABLE: Loading over HTTP -->
<script src="http://cdn.example.com/library.js"></script>
<!-- VULNERABLE: Third-party analytics without integrity -->
<script src="https://analytics.thirdparty.com/tracker.js"></script>
<!-- VULNERABLE: External stylesheets -->
<link rel="stylesheet" href="https://cdn.example.com/styles.css">
<!-- VULNERABLE: Third-party iframe without sandbox -->
<iframe src="https://widget.thirdparty.com/embed"></iframe>
<!-- VULNERABLE: Dynamic script loading -->
<script>
var script = document.createElement('script');
script.src = 'https://cdn.example.com/dynamic.js';
document.head.appendChild(script);
</script>
# VULNERABLE: Server-side inclusion of third-party resource
import requests
@app.route('/proxy/script')
def proxy_script():
# Fetching and serving third-party script without verification
response = requests.get('https://cdn.example.com/library.js')
return response.text, 200, {'Content-Type': 'application/javascript'}
# VULNERABLE: Template including external resources
@app.route('/page')
def render_page():
# External resource URL from config - could be compromised
cdn_url = config.get('CDN_URL')
return render_template('page.html', cdn_url=cdn_url)
// VULNERABLE: Dynamic resource loading in Node.js/Express
app.get('/widget', (req, res) => {
const widgetUrl = req.query.url; // User-controlled!
res.send(`<script src="${widgetUrl}"></script>`);
});
// VULNERABLE: npm package without lock file
// package.json allows floating versions
{
"dependencies": {
"lodash": "^4.0.0", // Could get compromised version
"express": "*" // Any version!
}
}
Fixed Code
<!-- SAFE: Subresource Integrity (SRI) -->
<script
src="https://cdn.jsdelivr.net/npm/[email protected]/dist/jquery.min.js"
integrity="sha384-vtXRMe3mGCbOeY7l30aIg8H9p3GdeSe4IFlP6G8JMa7o7lXvnz3GFKzPxzJdPfGK"
crossorigin="anonymous">
</script>
<script
src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.min.js"
integrity="sha384-ODmDIVzN+pFdexxHEHFBQH3/9EggsS7T6UFxMIUY4OHsW9A7EjdgLCw3K9cPRmFw"
crossorigin="anonymous">
</script>
<!-- SAFE: SRI for stylesheets -->
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css"
integrity="sha384-gH2yIJqKdNHPEq0n4Mqa/HGKIhSkIHeL5AyhkYV8i59U5AR6csBvApHHNl/vI1Bx"
crossorigin="anonymous">
<!-- SAFE: Sandboxed iframe -->
<iframe
src="https://widget.trusted.com/embed"
sandbox="allow-scripts allow-same-origin"
loading="lazy">
</iframe>
<!-- SAFE: Content Security Policy -->
<meta http-equiv="Content-Security-Policy"
content="script-src 'self' https://cdn.jsdelivr.net;
style-src 'self' https://cdn.jsdelivr.net;
img-src 'self' data:;
frame-src https://widget.trusted.com;">
# SAFE: Server-side resource verification
import requests
import hashlib
KNOWN_HASHES = {
'jquery-3.6.0.min.js': 'sha384-vtXRMe3mGCbOeY7l30aIg8H9p3GdeSe4IFlP6G8JMa7o7lXvnz3GFKzPxzJdPfGK',
}
def verify_resource(url, content):
"""Verify resource content matches known hash."""
filename = url.split('/')[-1]
expected_hash = KNOWN_HASHES.get(filename)
if not expected_hash:
raise ValueError(f"Unknown resource: {filename}")
# Parse the hash
algorithm, expected = expected_hash.split('-')
if algorithm == 'sha384':
actual = base64.b64encode(
hashlib.sha384(content).digest()
).decode()
if actual != expected:
raise ValueError(f"Resource integrity check failed for {filename}")
return True
@app.route('/proxy/script')
def proxy_script_safe():
url = 'https://cdn.jsdelivr.net/npm/[email protected]/dist/jquery.min.js'
response = requests.get(url)
# Verify integrity before serving
verify_resource(url, response.content)
return response.text, 200, {'Content-Type': 'application/javascript'}
# SAFE: Self-host critical resources
@app.route('/static/js/<filename>')
def serve_local_script(filename):
# Serve from local verified copy
allowed_files = {'jquery.min.js', 'bootstrap.min.js'}
if filename not in allowed_files:
abort(404)
return send_from_directory('static/js', filename)
# SAFE: CSP headers
@app.after_request
def add_security_headers(response):
csp = (
"default-src 'self'; "
"script-src 'self' https://cdn.jsdelivr.net 'sha256-...'; "
"style-src 'self' https://cdn.jsdelivr.net; "
"img-src 'self' data:; "
"frame-ancestors 'self';"
)
response.headers['Content-Security-Policy'] = csp
return response
// SAFE: Generate SRI hashes at build time
const crypto = require('crypto');
const fs = require('fs');
function generateSRI(filePath) {
const content = fs.readFileSync(filePath);
const hash = crypto.createHash('sha384').update(content).digest('base64');
return `sha384-${hash}`;
}
// Build script to generate integrity hashes
const scripts = [
{ file: 'vendor/jquery.min.js', output: 'jquery' },
{ file: 'vendor/bootstrap.min.js', output: 'bootstrap' }
];
const integrityHashes = {};
scripts.forEach(script => {
integrityHashes[script.output] = generateSRI(script.file);
});
fs.writeFileSync('sri-hashes.json', JSON.stringify(integrityHashes, null, 2));
// SAFE: Locked dependencies
// package-lock.json or yarn.lock ensures exact versions
{
"dependencies": {
"lodash": "4.17.21", // Exact version
"express": "4.18.2" // Exact version
}
}
// SAFE: Dynamic loading with integrity check
async function loadScriptWithSRI(url, expectedHash) {
const script = document.createElement('script');
script.src = url;
script.integrity = expectedHash;
script.crossOrigin = 'anonymous';
return new Promise((resolve, reject) => {
script.onload = resolve;
script.onerror = () => reject(new Error(`Failed to load ${url}`));
document.head.appendChild(script);
});
}
// Usage
await loadScriptWithSRI(
'https://cdn.jsdelivr.net/npm/[email protected]/lodash.min.js',
'sha384-...'
);
// SAFE: CSP configuration in Express
const helmet = require('helmet');
app.use(helmet.contentSecurityPolicy({
directives: {
defaultSrc: ["'self'"],
scriptSrc: [
"'self'",
"https://cdn.jsdelivr.net",
// Allow specific inline scripts by hash
"'sha256-abc123...'"
],
styleSrc: ["'self'", "https://cdn.jsdelivr.net"],
imgSrc: ["'self'", "data:"],
frameSrc: ["'none'"],
upgradeInsecureRequests: []
}
}));
Exploited in the Wild
British Airways Data Breach (2018)
Attackers compromised a third-party JavaScript library on British Airways' website, injecting code that skimmed payment card details from 380,000 customers over a two-week period.
Magecart Attacks (2018-Present)
Multiple e-commerce sites were compromised through shared JavaScript libraries, with attackers injecting payment skimming code affecting millions of customers across thousands of websites.
event-stream npm Package Compromise (2018)
A popular npm package (event-stream) was compromised by a malicious maintainer who added code to steal cryptocurrency from specific applications.
Tools to test/exploit
-
SRI Hash Generator — generate SRI hashes for resources.
-
CSP Evaluator — analyze Content Security Policy.
-
npm audit — check for vulnerable dependencies.
-
Snyk — dependency vulnerability scanning.
CVE Examples
-
CVE-2018-11776 — Apache Struts remote code execution via compromised library.
-
CVE-2021-44228 — Log4j (library compromise impact).
-
CVE-2019-10744 — lodash prototype pollution.
References
-
MITRE. "CWE-1035: Insecure Third Party Resource." https://cwe.mitre.org/data/definitions/1035.html
-
MDN. "Subresource Integrity." https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity