Permissive Cross-domain Policy with Untrusted Domains

Description

Permissive Cross-domain Policy with Untrusted Domains occurs when software uses a cross-domain policy file (such as crossdomain.xml or clientaccesspolicy.xml) that includes domains that should not be trusted. These policy files control which external domains can make cross-origin requests to a web application. Overly permissive policies, especially those using wildcards (*), allow any website to interact with the application, potentially leading to data theft, CSRF attacks, and other cross-origin vulnerabilities.

Risk

Overly permissive CORS policies and cross-domain files expose applications to significant risk. Attackers can make authenticated requests from malicious sites, stealing user data or performing unauthorized actions. The wildcard (*) in Access-Control-Allow-Origin combined with credentials allows any site to impersonate users. Misconfigured CORS has led to numerous data breaches where attackers extracted sensitive information from authenticated sessions. APIs commonly misconfigure CORS, exposing internal data.

Solution

Never use wildcard (*) for Access-Control-Allow-Origin when credentials are involved. Maintain a strict whitelist of allowed origins. Validate the Origin header against the whitelist before reflecting it. Don't trust user-supplied origins. Remove or restrict crossdomain.xml and clientaccesspolicy.xml files. Implement proper CORS headers with specific origins. Use SameSite cookies as additional protection. Regularly audit cross-origin configurations.

Common Consequences

ImpactDetails
ConfidentialityScope: Data Theft

Malicious sites can read responses from the vulnerable application, stealing user data.
IntegrityScope: Unauthorized Actions

Cross-origin requests can perform state-changing operations as the authenticated user.
Access ControlScope: Authentication Bypass

Attackers can access authenticated resources by leveraging user sessions.

Example Code + Solution Code

Vulnerable Code

<!-- VULNERABLE: crossdomain.xml with wildcard -->
<?xml version="1.0"?>
<!DOCTYPE cross-domain-policy SYSTEM "http://www.adobe.com/xml/dtds/cross-domain-policy.dtd">
<cross-domain-policy>
    <allow-access-from domain="*" />  <!-- Allows ANY domain! -->
</cross-domain-policy>

<!-- VULNERABLE: clientaccesspolicy.xml -->
<?xml version="1.0" encoding="utf-8"?>
<access-policy>
    <cross-domain-access>
        <policy>
            <allow-from http-request-headers="*">
                <domain uri="*"/>  <!-- Allows ANY domain! -->
            </allow-from>
            <grant-to>
                <resource path="/" include-subpaths="true"/>
            </grant-to>
        </policy>
    </cross-domain-access>
</access-policy>
# VULNERABLE: Wildcard CORS with credentials
from flask import Flask
from flask_cors import CORS

app = Flask(__name__)
CORS(app, origins="*", supports_credentials=True)  # DANGEROUS!

# VULNERABLE: Reflecting origin without validation
@app.after_request
def add_cors_headers(response):
    origin = request.headers.get('Origin')
    # Reflects ANY origin!
    response.headers['Access-Control-Allow-Origin'] = origin
    response.headers['Access-Control-Allow-Credentials'] = 'true'
    return response

# VULNERABLE: Regex bypass
ALLOWED_ORIGINS_PATTERN = r'.*\.example\.com'

@app.after_request
def add_cors_regex(response):
    origin = request.headers.get('Origin')
    if re.match(ALLOWED_ORIGINS_PATTERN, origin):
        # Attacker uses: evil.com.example.com or evil-example.com
        response.headers['Access-Control-Allow-Origin'] = origin
        response.headers['Access-Control-Allow-Credentials'] = 'true'
    return response
// VULNERABLE: Spring CORS with wildcard
@Configuration
public class CorsConfig implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
            .allowedOrigins("*")  // Any origin!
            .allowCredentials(true)  // With credentials!
            .allowedMethods("*");
    }
}

// VULNERABLE: Filter reflecting origin
@WebFilter("/*")
public class VulnerableCorsFilter implements Filter {

    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
            throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) res;

        String origin = request.getHeader("Origin");
        // Reflects any origin!
        response.setHeader("Access-Control-Allow-Origin", origin);
        response.setHeader("Access-Control-Allow-Credentials", "true");

        chain.doFilter(req, res);
    }
}
// VULNERABLE: Express with permissive CORS
const cors = require('cors');

app.use(cors({
    origin: true,  // Reflects any origin
    credentials: true
}));

// VULNERABLE: Manual CORS with reflection
app.use((req, res, next) => {
    // Reflects whatever origin is sent
    res.header('Access-Control-Allow-Origin', req.headers.origin);
    res.header('Access-Control-Allow-Credentials', 'true');
    next();
});

// VULNERABLE: Insufficient origin check
app.use((req, res, next) => {
    const origin = req.headers.origin;
    // Weak check - attacker uses example.com.evil.com
    if (origin && origin.includes('example.com')) {
        res.header('Access-Control-Allow-Origin', origin);
        res.header('Access-Control-Allow-Credentials', 'true');
    }
    next();
});

Fixed Code

<!-- SAFE: Specific domains only -->
<?xml version="1.0"?>
<!DOCTYPE cross-domain-policy SYSTEM "http://www.adobe.com/xml/dtds/cross-domain-policy.dtd">
<cross-domain-policy>
    <site-control permitted-cross-domain-policies="master-only"/>
    <allow-access-from domain="app.example.com" secure="true"/>
    <allow-access-from domain="www.example.com" secure="true"/>
</cross-domain-policy>

<!-- Or better: Remove if not needed -->
<!-- If Flash/Silverlight not used, delete crossdomain.xml entirely -->
# SAFE: Strict origin whitelist
from flask import Flask, request
from flask_cors import CORS

app = Flask(__name__)

ALLOWED_ORIGINS = {
    'https://app.example.com',
    'https://www.example.com',
    'https://admin.example.com'
}

def check_origin(origin):
    return origin in ALLOWED_ORIGINS

CORS(app, origins=check_origin, supports_credentials=True)

# SAFE: Manual CORS with strict validation
@app.after_request
def add_cors_headers(response):
    origin = request.headers.get('Origin')

    if origin and origin in ALLOWED_ORIGINS:
        response.headers['Access-Control-Allow-Origin'] = origin
        response.headers['Access-Control-Allow-Credentials'] = 'true'
        response.headers['Access-Control-Allow-Methods'] = 'GET, POST, OPTIONS'
        response.headers['Access-Control-Allow-Headers'] = 'Content-Type, Authorization'
        response.headers['Access-Control-Max-Age'] = '86400'

    # Never use wildcard with credentials
    # Don't reflect untrusted origins

    return response

# SAFE: Proper subdomain validation
def is_valid_origin(origin):
    if not origin:
        return False

    try:
        from urllib.parse import urlparse
        parsed = urlparse(origin)

        # Must be HTTPS
        if parsed.scheme != 'https':
            return False

        # Exact match or valid subdomain
        allowed_domains = ['example.com', 'example.org']
        host = parsed.netloc.lower()

        for domain in allowed_domains:
            if host == domain or host.endswith('.' + domain):
                return True

        return False
    except:
        return False
// SAFE: Spring CORS with whitelist
@Configuration
public class SecureCorsConfig implements WebMvcConfigurer {

    private static final List<String> ALLOWED_ORIGINS = List.of(
        "https://app.example.com",
        "https://www.example.com",
        "https://admin.example.com"
    );

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/api/**")
            .allowedOrigins(ALLOWED_ORIGINS.toArray(new String[0]))
            .allowCredentials(true)
            .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
            .allowedHeaders("Content-Type", "Authorization")
            .maxAge(86400);
    }
}

// SAFE: Custom CORS filter with validation
@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class SecureCorsFilter implements Filter {

    private static final Set<String> ALLOWED_ORIGINS = Set.of(
        "https://app.example.com",
        "https://www.example.com"
    );

    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
            throws IOException, ServletException {

        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) res;

        String origin = request.getHeader("Origin");

        if (origin != null && ALLOWED_ORIGINS.contains(origin)) {
            response.setHeader("Access-Control-Allow-Origin", origin);
            response.setHeader("Access-Control-Allow-Credentials", "true");
            response.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
            response.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
            response.setHeader("Access-Control-Max-Age", "86400");
        }

        if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
            response.setStatus(HttpServletResponse.SC_OK);
            return;
        }

        chain.doFilter(req, res);
    }
}

// SAFE: Using Spring Security
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.cors().configurationSource(corsConfigurationSource());
    }

    @Bean
    public CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration configuration = new CorsConfiguration();
        configuration.setAllowedOrigins(List.of(
            "https://app.example.com",
            "https://www.example.com"
        ));
        configuration.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE"));
        configuration.setAllowCredentials(true);
        configuration.setAllowedHeaders(List.of("*"));
        configuration.setMaxAge(86400L);

        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration);
        return source;
    }
}
// SAFE: Express with strict origin whitelist
const cors = require('cors');

const ALLOWED_ORIGINS = new Set([
    'https://app.example.com',
    'https://www.example.com',
    'https://admin.example.com'
]);

const corsOptions = {
    origin: (origin, callback) => {
        // Allow requests with no origin (mobile apps, curl, etc.)
        // Only if you specifically need this
        if (!origin) {
            return callback(null, true);
        }

        if (ALLOWED_ORIGINS.has(origin)) {
            callback(null, true);
        } else {
            callback(new Error('Not allowed by CORS'));
        }
    },
    credentials: true,
    methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
    allowedHeaders: ['Content-Type', 'Authorization'],
    maxAge: 86400
};

app.use(cors(corsOptions));

// SAFE: Manual implementation with strict validation
function validateOrigin(origin) {
    if (!origin) return false;

    try {
        const url = new URL(origin);

        // Must be HTTPS
        if (url.protocol !== 'https:') return false;

        // Check against whitelist
        const allowedHosts = ['app.example.com', 'www.example.com'];
        return allowedHosts.includes(url.hostname);

    } catch {
        return false;
    }
}

app.use((req, res, next) => {
    const origin = req.headers.origin;

    if (validateOrigin(origin)) {
        res.header('Access-Control-Allow-Origin', origin);
        res.header('Access-Control-Allow-Credentials', 'true');
        res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
        res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
        res.header('Access-Control-Max-Age', '86400');
    }

    if (req.method === 'OPTIONS') {
        return res.sendStatus(200);
    }

    next();
});

Exploited in the Wild

Facebook CORS Misconfiguration (2019)

A CORS misconfiguration in Facebook's API allowed attackers to read private data by making cross-origin requests from malicious websites.

Various API Data Leaks

Multiple organizations have suffered data breaches due to CORS misconfigurations in their APIs, allowing any website to extract user data from authenticated sessions.

Adobe Flash Cross-Domain Attacks

Before Flash's deprecation, permissive crossdomain.xml files enabled numerous cross-site data theft attacks.


Tools to test/exploit


CVE Examples


References

  1. MITRE. "CWE-942: Permissive Cross-domain Policy with Untrusted Domains." https://cwe.mitre.org/data/definitions/942.html

  2. OWASP. "CORS Security." https://owasp.org/www-community/attacks/CORS_OriginHeaderScrutiny