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

ImpactDetails
IntegrityScope: Code Injection

Compromised third-party resources can execute arbitrary JavaScript in users' browsers.
ConfidentialityScope: Data Theft

Malicious scripts can steal credentials, payment information, and session tokens.
AvailabilityScope: 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


CVE Examples


References

  1. MITRE. "CWE-1035: Insecure Third Party Resource." https://cwe.mitre.org/data/definitions/1035.html

  2. MDN. "Subresource Integrity." https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity