Skip to content

Latest commit

 

History

7 Commits

Folders and files

Repository files navigation

CORS Testing Toolkit

Command-line tools for finding Cross-Origin Resource Sharing (CORS) misconfigurations in web applications — and, importantly, for judging whether a weak policy is actually exploitable rather than just flagging headers.

The tools probe an endpoint's CORS policy with a battery of crafted Origin headers, assess whether a weakness can really be abused (credential rideability, SameSite, data sensitivity, CSRF-token exposure, attacker-controllability), and produce a findings report with description, impact, evidence, and remediation for each issue. They work against a single endpoint or can spider an entire site.

There are three tools: cors_tester.py (test one endpoint, or drive the others), cors_spider.py (crawl a site and test everything it finds), and cors_report.py (the reporting engine). A small deliberately-vulnerable lab is bundled as well — not the point of the project, just a safe local target so you can watch the tools run and see every check fire before pointing them at a real system.

⚠️ Authorized testing only. These tools send crafted, attack-style requests to a target. Only run them against systems you own or have explicit written permission to test. The bundled lab is deliberately vulnerable — keep it local.

Responsible use & legal

These tools actively probe a target: they send requests carrying forged Origin headers, crawl linked pages and forms, and inspect authenticated responses. That is attack-style traffic. Running it against a system generates logs, may trip security monitoring, and — without authorization — may be unlawful.

By using this project you agree that:

  • You will only test systems you own or for which you hold explicit, written permission. Unauthorized testing of third-party systems may violate the U.S. Computer Fraud and Abuse Act, the UK Computer Misuse Act, and equivalent laws elsewhere. Bug-bounty participation does not imply permission beyond the program's stated scope.
  • You are solely responsible for how you use these tools and for staying within the law and any applicable rules of engagement.
  • The deliberately vulnerable lab (cors-lab/) is for local use only. It exposes fake sensitive data to any origin by design; never deploy it anywhere reachable from an untrusted network.
  • The software is provided "as is", without warranty of any kind, and the authors accept no liability for any damage or loss arising from its use, as set out in the LICENSE.

If you are not certain you are authorized to test a given target, you are not authorized. Stop and get written permission first.

What's in the box

cors-project/
├── cors_tester.py     # single-endpoint console tester; also --report and --spider
├── cors_report.py     # multi-endpoint report engine + finding knowledge base
├── cors_spider.py     # site crawler; also a library (run_spider) used by the tester
└── cors-lab/          # deliberately vulnerable Flask app in Docker (see its README)
    ├── app.py
    ├── Dockerfile, docker-compose.yml, requirements.txt
    ├── run_tests.sh, generate_report.sh
    ├── README.md      # lab-specific: endpoints, how to exercise each check
    └── CORS_Report.md # a sample report from a full spider run

The two Python tools that scripts reference (cors_tester.py, cors_report.py) and the crawler (cors_spider.py) live at the top level; the lab's helper scripts resolve them at ../. Keep this layout.

Requirements

  • Python 3.8+ with the requests library: pip install requests
  • Docker + Docker Compose (only for running the lab)

No other dependencies — parsing and crawling use the standard library.

Quick start

The tools run against any target you're authorized to test. For a real target it is just the tool plus your URL (and, for authenticated endpoints, your session):

# Test one endpoint
python3 cors_tester.py https://app.example.com/api/account -o https://app.example.com

# Crawl the whole site and write a consolidated report
python3 cors_tester.py https://app.example.com/ --spider \
    -o https://app.example.com -H "Cookie: session=..." --report report.md

The fastest way to try them out — and to see every check fire safely — is against the bundled lab. From cors-lab/:

docker compose up --build            # serves http://localhost:8080
# single endpoint
python3 ../cors_tester.py http://localhost:8080/vuln/reflect -o https://trusted.example.com

# spider the lab site into one report
python3 ../cors_tester.py http://localhost:8080/site --spider \
    -o https://trusted.example.com --report site_report.md

The lab's own README.md documents every endpoint and how to exercise each specific check, including the authenticated runs that demonstrate the mitigating-factor analysis.

The -o / --trusted-origin flag

Most of the crafted bypass payloads are built from the origin the application legitimately trusts — which, in a real assessment, is usually not the host you connect to (you might hit an IP or a staging URL while the app trusts https://app.example.com). Pass that trusted origin with -o so the prefix/suffix/subdomain/regex/normalization payloads are meaningful. Without it, the tools fall back to the target's own host, which still catches blanket reflection and null-origin trust but won't exercise host-pattern bypasses.

The tools

cors_tester.py — single endpoint (and entry point for everything)

Probes one URL and prints a graded result per test, a preflight breakdown, and a mitigating-factors summary. It's also the front door for the other two tools via --report and --spider.

# Basic console test
python3 cors_tester.py https://target/api/account -o https://app.example.com

# With an authenticated session, and inspect the cookie for SameSite
python3 cors_tester.py https://target/api/account -o https://app.example.com \
    -H "Cookie: session=..." --session-url https://target/login -v

# Also write a Markdown findings report for this endpoint
python3 cors_tester.py https://target/api/account -o https://app.example.com \
    --report account_report.md

# Spider mode: crawl from a seed and test everything discovered
python3 cors_tester.py https://target/ --spider -o https://app.example.com \
    --report site_report.md

Key options: -o/--trusted-origin, -H/--header (repeatable; for cookies or tokens), --session-url (for SameSite/cookie inspection), -m/--method, -t/--timeout, -k/--insecure, -v/--verbose, --report [FILE], and the spider options below.

cors_spider.py — crawl a site (and a library)

Crawls from a seed URL, discovers endpoints from anchor links, form actions, and URLs referenced in inline <script> blocks, then runs the full scan + mitigating-factor analysis on each and consolidates into one report.

python3 cors_spider.py https://target/ -o https://app.example.com --out report.md

It is also importable as a library — cors_tester.py --spider uses it this way:

from cors_spider import run_spider
result = run_spider("https://target/", trusted_origin="https://app.example.com",
                    base_headers={"Cookie": "session=..."}, max_pages=80)
# result -> {"targets", "findings", "raw_by_url", "contexts", "pages", ...}

Spider options (available on both cors_spider.py and cors_tester.py --spider): --max-pages (50), --max-depth (3), --max-endpoints (60), a politeness delay (--delay on the spider, --spider-delay on the tester; 0.2s), --include-subdomains, --scope-host, --no-forms, --no-js, and --allow-dangerous.

Safety rails. A crawler pointed at a live app can do damage, so by default the spider: limits scope to the seed host; caps pages/depth/endpoints and delays between requests; never submits forms (it only probes the action URL's CORS headers); and skips state-changing paths (logout, delete, reset, revoke, deactivate, …) so it never GETs something destructive. --allow-dangerous overrides the last — don't use it on a live authenticated session, or the crawler will log you out (or worse).

cors_report.py — multi-endpoint report engine

Runs the scan against one or more explicit URLs and writes the report. This is the engine the other two tools call for rendering; use it directly when you have a known list of endpoints rather than a site to crawl.

python3 cors_report.py https://target/api/a https://target/api/b \
    -o https://app.example.com -H "Cookie: session=..." \
    --session-url https://target/login --out report.md

How findings are rated

Detecting a permissive header is only half the job; the tools also estimate whether it's exploitable, and every finding carries both a base severity (the header issue in isolation) and an effective severity adjusted for the mitigating factors observed:

  • Credential rideability — ambient cookies ride cross-origin requests; bearer tokens in Authorization do not, which largely neutralises reflection.
  • SameSite — a Lax/Strict session cookie won't ride a cross-site fetch, mitigating credentialed reflection; None leaves it exploitable.
  • Data sensitivity — an authenticated-vs-unauthenticated differential: if the response is identical with and without a session, there's nothing to steal.
  • Attacker-controllability — whether an attacker can actually obtain a qualifying origin. Reflection of an arbitrary origin is real; a suffix under the target's own parent domain is latent; protocol-downgrade needs a network position. These are rated conditional/latent accordingly.

The same reflection bug can therefore land as Critical, High, or Low depending on context. These effective severities are heuristics to guide manual confirmation, not a verdict — the base severity is always shown alongside so nothing is silently downgraded out of view.

What it checks

The tools test each endpoint for the following 17 weaknesses. The base severity shown is the header issue in isolation; the effective severity in a report is adjusted per target by the mitigating factors (see How findings are rated).

Check Base What it detects
Arbitrary origin reflected with credentials High Any Origin reflected in ACAO with ACAC: true — no allow-list
null origin trusted with credentials High ACAO: null returned (reachable from sandboxed iframes / data: URLs)
Prefix-match bypass High An origin beginning with the trusted origin passes (startsWith)
Suffix-match bypass High An attacker host ending in the trusted host passes (endsWith)
Unescaped-regex bypass High Origin regex with unescaped dots (trustedxexample.com matches)
Overly broad subdomain trust Medium Any *.trusted.com trusted, including attacker-controllable subdomains
Protocol downgrade Medium The trusted host over plaintext http:// is accepted
Sensitive response headers exposed Medium Access-Control-Expose-Headers makes headers readable cross-origin
Permissive cross-origin methods Medium Wildcard Allow-Methods, or PUT/DELETE/PATCH allowed cross-origin
Private Network Access to public origins Medium Access-Control-Allow-Private-Network: true granted to a public origin
localhost / loopback trust Medium http://localhost or http://127.0.0.1 reflected (DNS-rebinding reachable)
Wildcard ACAO Low Access-Control-Allow-Origin: * (only a leak for sensitive data)
Arbitrary origin reflected without credentials Low Any origin reflected, but credentials not allowed
Missing Vary: Origin Low A reflected origin without Vary: Origin (cache-poisoning risk)
Loose origin normalization Low Case, trailing-dot, or non-standard-port variants accepted
Excessive preflight Max-Age Low Preflight decision cached for an excessive period
Loose origin matching (parser confusion) Low Userinfo @, backslash, or substring payloads accepted

The preflight (OPTIONS) request is parsed in full (Allow-Methods/Allow-Headers/Expose-Headers/Max-Age/Allow-Private-Network). Reflecting the trusted origin is treated as correct and never reported.

Each finding states its detection logic in plain language and shows the contrast that justifies it — the rejected arbitrary-origin baseline next to the accepted crafted probe — so a "suffix bypass" is visibly a specific bypass rather than blanket reflection.

The tools also detect CSRF tokens (in response bodies, headers, and cookies). Where a token sits somewhere cross-origin script could read it (a body field or header) and a permissive, credentialed CORS policy is present, this is flagged as an amplifier: the CORS flaw would let an attacker read the token and defeat the app's CSRF protection. Cookie-based tokens (not readable via fetch) are noted as a mitigating factor instead.

Mitigations and context it inspects

Alongside the weaknesses above, the tools actively check for the protective controls and context that decide whether a weakness is exploitable, and report each in the finding's exploitability notes and the report's "Testing Context" section:

  • Session cookie attributes — SameSite (None / Lax / Strict), plus Secure and HttpOnly, read from a Set-Cookie on the target or a --session-url you supply. Lax/Strict is treated as a mitigation because the cookie won't ride a cross-site fetch.
  • Authentication mechanism — whether the endpoint uses an ambient cookie (rides cross-origin requests) or a bearer token in Authorization (does not), which decides whether reflection can reach authenticated data at all.
  • Data sensitivity — an authenticated-vs-unauthenticated differential: if the response is identical with and without a session, there is nothing session- scoped to steal and the severity drops.
  • CSRF tokens — presence and location (body / header / cookie) and whether each is readable cross-origin, treated as an amplifier or a mitigation as described above.
  • Vary: Origin — whether a reflecting response is safe from cache poisoning.
  • Attacker-controllability — whether an attacker can actually obtain an origin that satisfies the bypass, so latent and network-position-dependent cases are rated conditional rather than as live findings.

These feed the base-vs-effective severity model described under How findings are rated; a mitigation being present lowers the effective severity (and is stated as the reason), while its absence is noted too, so a clean run doesn't quietly assume a protection that isn't there.

Limitations

The effective severities and the differential's "sensitive data" flag are heuristics; confirm them manually. The spider's JS-endpoint extraction is deliberately conservative (quoted URLs in inline scripts), so endpoints built dynamically or loaded from external .js files won't all be found — it's a strong first pass, not guaranteed full coverage. And a "weak regex" or suffix finding may require a domain an attacker can't actually register; treat those as "the check is loose, confirm exploitability" rather than proven bugs.

References

  • OWASP — Testing Cross Origin Resource Sharing (WSTG-CLNT-07)
  • PortSwigger Web Security Academy — CORS vulnerabilities
  • MDN — Cross-Origin Resource Sharing (CORS)

Releases

Packages

Contributors

Languages