sorx 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- sorx-0.1.0/LICENSE +21 -0
- sorx-0.1.0/MANIFEST.in +1 -0
- sorx-0.1.0/PKG-INFO +171 -0
- sorx-0.1.0/README.md +160 -0
- sorx-0.1.0/pyproject.toml +20 -0
- sorx-0.1.0/setup.cfg +4 -0
- sorx-0.1.0/src/sorx/__init__.py +3 -0
- sorx-0.1.0/src/sorx/checks/cors.py +147 -0
- sorx-0.1.0/src/sorx/checks/cors_analyze.py +80 -0
- sorx-0.1.0/src/sorx/checks/cors_rules.yaml +113 -0
- sorx-0.1.0/src/sorx/core/display.py +107 -0
- sorx-0.1.0/src/sorx/core/reporter.py +48 -0
- sorx-0.1.0/src/sorx/core/requester.py +65 -0
- sorx-0.1.0/src/sorx/data/loader/payload.py +12 -0
- sorx-0.1.0/src/sorx/data/loader/template.py +12 -0
- sorx-0.1.0/src/sorx/data/payloads/header.yaml +14 -0
- sorx-0.1.0/src/sorx/data/payloads/method.yaml +17 -0
- sorx-0.1.0/src/sorx/data/payloads/origin.yaml +20 -0
- sorx-0.1.0/src/sorx/data/templates/sensitive_headers.yaml +42 -0
- sorx-0.1.0/src/sorx/data/templates/sensitive_method.yaml +6 -0
- sorx-0.1.0/src/sorx/main.py +197 -0
- sorx-0.1.0/src/sorx.egg-info/PKG-INFO +171 -0
- sorx-0.1.0/src/sorx.egg-info/SOURCES.txt +25 -0
- sorx-0.1.0/src/sorx.egg-info/dependency_links.txt +1 -0
- sorx-0.1.0/src/sorx.egg-info/entry_points.txt +2 -0
- sorx-0.1.0/src/sorx.egg-info/requires.txt +3 -0
- sorx-0.1.0/src/sorx.egg-info/top_level.txt +1 -0
sorx-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 _unkn?wn
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
sorx-0.1.0/MANIFEST.in
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
recursive-include src/sorx *.yaml
|
sorx-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: sorx
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A tool for analyzing CORS headers
|
|
5
|
+
Description-Content-Type: text/markdown
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Requires-Dist: PyYAML>=6.0
|
|
8
|
+
Requires-Dist: requests>=2.32
|
|
9
|
+
Requires-Dist: colorama>=0.4.6
|
|
10
|
+
Dynamic: license-file
|
|
11
|
+
|
|
12
|
+
# sorx
|
|
13
|
+
|
|
14
|
+
> A lightweight CORS security analyzer for bug bounty hunters.
|
|
15
|
+
|
|
16
|
+
`sorx` is a CLI tool for analyzing CORS configurations and detecting potentially security-relevant misconfigurations with a focus on **low noise and useful findings**.
|
|
17
|
+
|
|
18
|
+
> [!NOTE]
|
|
19
|
+
> **sorx is currently under active development.**
|
|
20
|
+
>
|
|
21
|
+
> The project is not ready for use yet. The main executable/CLI is not available at this time, and functionality is still being implemented.
|
|
22
|
+
|
|
23
|
+
## Features
|
|
24
|
+
|
|
25
|
+
- CORS misconfiguration detection
|
|
26
|
+
- Active origin fuzzing
|
|
27
|
+
- Credential & origin reflection checks
|
|
28
|
+
- Sensitive header analysis
|
|
29
|
+
- HTTP method analysis
|
|
30
|
+
- CORS combination checks
|
|
31
|
+
- Configurable timeout, rate limit & threads
|
|
32
|
+
- JSON output
|
|
33
|
+
- Low-noise findings
|
|
34
|
+
|
|
35
|
+
## Why sorx?
|
|
36
|
+
|
|
37
|
+
- **CLI-first** — Designed to fit naturally into recon and bug bounty pipelines.
|
|
38
|
+
- **Free & open source** — Free to use, inspect, modify, and contribute to.
|
|
39
|
+
- **Actively developed** — Continuously improved with new checks, payloads, and features.
|
|
40
|
+
- **Low noise** — Focuses on security-relevant CORS behavior instead of reporting every configuration.
|
|
41
|
+
- **Automation-friendly** — Supports JSON output and configurable threads, rate limits, and timeouts.
|
|
42
|
+
- **Lightweight** — Simple setup with minimal dependencies.
|
|
43
|
+
- **Built for hunters and testers** — Designed around practical CORS testing workflows rather than generic HTTP scanning.
|
|
44
|
+
|
|
45
|
+
## Installation
|
|
46
|
+
|
|
47
|
+
### pip
|
|
48
|
+
|
|
49
|
+
Install:
|
|
50
|
+
|
|
51
|
+
```
|
|
52
|
+
pip install sorx
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Update:
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
pip install -U sorx
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### pipx
|
|
62
|
+
|
|
63
|
+
Install:
|
|
64
|
+
|
|
65
|
+
```
|
|
66
|
+
pipx install sorx
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Update:
|
|
70
|
+
|
|
71
|
+
```
|
|
72
|
+
pipx upgrade sorx
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### From Github
|
|
76
|
+
|
|
77
|
+
Install directly from GitHub:
|
|
78
|
+
|
|
79
|
+
```
|
|
80
|
+
pip install git+https://github.com/Pupsix/sorx.git
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Or with `pipx`:
|
|
84
|
+
|
|
85
|
+
```
|
|
86
|
+
pipx install git+https://github.com/Pupsix/sorx.git
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### From Source
|
|
90
|
+
|
|
91
|
+
Clone the repository:
|
|
92
|
+
|
|
93
|
+
```
|
|
94
|
+
git clone https://github.com/Pupsix/sorx.git
|
|
95
|
+
cd sorx
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Create a virtual environment:
|
|
99
|
+
|
|
100
|
+
```
|
|
101
|
+
python3 -m venv .venv
|
|
102
|
+
source .venv/bin/activate
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Install in editable mode:
|
|
106
|
+
|
|
107
|
+
```
|
|
108
|
+
pip install -e .
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
The editable installation allows changes to the source code to be reflected immediately without reinstalling the package.
|
|
112
|
+
|
|
113
|
+
> **Recommended:** Use `pip` or `pipx` for installation.
|
|
114
|
+
> Installing from source is intended for development.
|
|
115
|
+
|
|
116
|
+
## Usage
|
|
117
|
+
|
|
118
|
+
Basic scan:
|
|
119
|
+
|
|
120
|
+
```
|
|
121
|
+
sorx -u https://example.com
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Enable active CORS fuzzing:
|
|
125
|
+
|
|
126
|
+
```
|
|
127
|
+
sorx -u https://example.com -a
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Select testing mode:
|
|
131
|
+
|
|
132
|
+
```
|
|
133
|
+
sorx -u https://example.com -m quick
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
```
|
|
137
|
+
sorx -u https://example.com -m normal
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
```
|
|
141
|
+
sorx -u https://example.com -m deep
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
For all available options:
|
|
145
|
+
|
|
146
|
+
```
|
|
147
|
+
sorx --help
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
## Responsible Use
|
|
151
|
+
|
|
152
|
+
`sorx` is intended for authorized security testing, including:
|
|
153
|
+
|
|
154
|
+
- Bug bounty programs
|
|
155
|
+
- Penetration testing
|
|
156
|
+
- Security research
|
|
157
|
+
- Applications you own
|
|
158
|
+
|
|
159
|
+
Only test targets where you have permission to perform security testing.
|
|
160
|
+
|
|
161
|
+
Do not use active fuzzing against unauthorized systems.
|
|
162
|
+
|
|
163
|
+
## Status
|
|
164
|
+
|
|
165
|
+
`sorx` is currently under development.
|
|
166
|
+
|
|
167
|
+
The project focuses on useful findings rather than the number of checks. Some CORS configurations may be unusual without being vulnerabilities, so sorx attempts to prioritize security-relevant behavior and reduce unnecessary noise.
|
|
168
|
+
|
|
169
|
+
## License
|
|
170
|
+
|
|
171
|
+
MIT License
|
sorx-0.1.0/README.md
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
# sorx
|
|
2
|
+
|
|
3
|
+
> A lightweight CORS security analyzer for bug bounty hunters.
|
|
4
|
+
|
|
5
|
+
`sorx` is a CLI tool for analyzing CORS configurations and detecting potentially security-relevant misconfigurations with a focus on **low noise and useful findings**.
|
|
6
|
+
|
|
7
|
+
> [!NOTE]
|
|
8
|
+
> **sorx is currently under active development.**
|
|
9
|
+
>
|
|
10
|
+
> The project is not ready for use yet. The main executable/CLI is not available at this time, and functionality is still being implemented.
|
|
11
|
+
|
|
12
|
+
## Features
|
|
13
|
+
|
|
14
|
+
- CORS misconfiguration detection
|
|
15
|
+
- Active origin fuzzing
|
|
16
|
+
- Credential & origin reflection checks
|
|
17
|
+
- Sensitive header analysis
|
|
18
|
+
- HTTP method analysis
|
|
19
|
+
- CORS combination checks
|
|
20
|
+
- Configurable timeout, rate limit & threads
|
|
21
|
+
- JSON output
|
|
22
|
+
- Low-noise findings
|
|
23
|
+
|
|
24
|
+
## Why sorx?
|
|
25
|
+
|
|
26
|
+
- **CLI-first** — Designed to fit naturally into recon and bug bounty pipelines.
|
|
27
|
+
- **Free & open source** — Free to use, inspect, modify, and contribute to.
|
|
28
|
+
- **Actively developed** — Continuously improved with new checks, payloads, and features.
|
|
29
|
+
- **Low noise** — Focuses on security-relevant CORS behavior instead of reporting every configuration.
|
|
30
|
+
- **Automation-friendly** — Supports JSON output and configurable threads, rate limits, and timeouts.
|
|
31
|
+
- **Lightweight** — Simple setup with minimal dependencies.
|
|
32
|
+
- **Built for hunters and testers** — Designed around practical CORS testing workflows rather than generic HTTP scanning.
|
|
33
|
+
|
|
34
|
+
## Installation
|
|
35
|
+
|
|
36
|
+
### pip
|
|
37
|
+
|
|
38
|
+
Install:
|
|
39
|
+
|
|
40
|
+
```
|
|
41
|
+
pip install sorx
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Update:
|
|
45
|
+
|
|
46
|
+
```
|
|
47
|
+
pip install -U sorx
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### pipx
|
|
51
|
+
|
|
52
|
+
Install:
|
|
53
|
+
|
|
54
|
+
```
|
|
55
|
+
pipx install sorx
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Update:
|
|
59
|
+
|
|
60
|
+
```
|
|
61
|
+
pipx upgrade sorx
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### From Github
|
|
65
|
+
|
|
66
|
+
Install directly from GitHub:
|
|
67
|
+
|
|
68
|
+
```
|
|
69
|
+
pip install git+https://github.com/Pupsix/sorx.git
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Or with `pipx`:
|
|
73
|
+
|
|
74
|
+
```
|
|
75
|
+
pipx install git+https://github.com/Pupsix/sorx.git
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### From Source
|
|
79
|
+
|
|
80
|
+
Clone the repository:
|
|
81
|
+
|
|
82
|
+
```
|
|
83
|
+
git clone https://github.com/Pupsix/sorx.git
|
|
84
|
+
cd sorx
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Create a virtual environment:
|
|
88
|
+
|
|
89
|
+
```
|
|
90
|
+
python3 -m venv .venv
|
|
91
|
+
source .venv/bin/activate
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Install in editable mode:
|
|
95
|
+
|
|
96
|
+
```
|
|
97
|
+
pip install -e .
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
The editable installation allows changes to the source code to be reflected immediately without reinstalling the package.
|
|
101
|
+
|
|
102
|
+
> **Recommended:** Use `pip` or `pipx` for installation.
|
|
103
|
+
> Installing from source is intended for development.
|
|
104
|
+
|
|
105
|
+
## Usage
|
|
106
|
+
|
|
107
|
+
Basic scan:
|
|
108
|
+
|
|
109
|
+
```
|
|
110
|
+
sorx -u https://example.com
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Enable active CORS fuzzing:
|
|
114
|
+
|
|
115
|
+
```
|
|
116
|
+
sorx -u https://example.com -a
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Select testing mode:
|
|
120
|
+
|
|
121
|
+
```
|
|
122
|
+
sorx -u https://example.com -m quick
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
```
|
|
126
|
+
sorx -u https://example.com -m normal
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
```
|
|
130
|
+
sorx -u https://example.com -m deep
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
For all available options:
|
|
134
|
+
|
|
135
|
+
```
|
|
136
|
+
sorx --help
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## Responsible Use
|
|
140
|
+
|
|
141
|
+
`sorx` is intended for authorized security testing, including:
|
|
142
|
+
|
|
143
|
+
- Bug bounty programs
|
|
144
|
+
- Penetration testing
|
|
145
|
+
- Security research
|
|
146
|
+
- Applications you own
|
|
147
|
+
|
|
148
|
+
Only test targets where you have permission to perform security testing.
|
|
149
|
+
|
|
150
|
+
Do not use active fuzzing against unauthorized systems.
|
|
151
|
+
|
|
152
|
+
## Status
|
|
153
|
+
|
|
154
|
+
`sorx` is currently under development.
|
|
155
|
+
|
|
156
|
+
The project focuses on useful findings rather than the number of checks. Some CORS configurations may be unusual without being vulnerabilities, so sorx attempts to prioritize security-relevant behavior and reduce unnecessary noise.
|
|
157
|
+
|
|
158
|
+
## License
|
|
159
|
+
|
|
160
|
+
MIT License
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "sorx"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "A tool for analyzing CORS headers"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
dependencies = [
|
|
11
|
+
"PyYAML>=6.0",
|
|
12
|
+
"requests>=2.32",
|
|
13
|
+
"colorama>=0.4.6",
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
[tool.setuptools.packages.find]
|
|
17
|
+
where = ["src"]
|
|
18
|
+
|
|
19
|
+
[project.scripts]
|
|
20
|
+
sorx = "sorx.main:main"
|
sorx-0.1.0/setup.cfg
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import time
|
|
2
|
+
|
|
3
|
+
from sorx.checks.cors_analyze import analyze
|
|
4
|
+
from sorx.core.display import header
|
|
5
|
+
from sorx.core.requester import run as requester_run
|
|
6
|
+
from sorx.data.loader.payload import load_payload
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Stat:
|
|
10
|
+
def __init__(self, targets, mode, threads, config):
|
|
11
|
+
self.targets_list, self.targets = list(targets), len(targets)
|
|
12
|
+
self.mode, self.threads = mode, threads
|
|
13
|
+
self.header = config.get("headers", {})
|
|
14
|
+
self.scanned = 0
|
|
15
|
+
self.findings = {url: [] for url in targets}
|
|
16
|
+
self.errors = {url: [] for url in targets}
|
|
17
|
+
self.start_time = time.time()
|
|
18
|
+
self.error = self.request = 0
|
|
19
|
+
self.elapsed = "00:00"
|
|
20
|
+
self.output = config.get("output") or "None"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def create_job(url, method="GET", headers=None, data=None, timeout=10, origin=None):
|
|
24
|
+
return {
|
|
25
|
+
"url": url,
|
|
26
|
+
"method": method,
|
|
27
|
+
"headers": headers or {},
|
|
28
|
+
"data": data,
|
|
29
|
+
"timeout": timeout,
|
|
30
|
+
"origin": origin or "",
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def get_payloads(name, mode):
|
|
35
|
+
payloads = load_payload(name).get(name, {})
|
|
36
|
+
priority_map = {
|
|
37
|
+
"quick": ["high_priority"],
|
|
38
|
+
"normal": ["high_priority", "medium_priority"],
|
|
39
|
+
"deep": ["high_priority", "medium_priority", "low_priority"],
|
|
40
|
+
}
|
|
41
|
+
result = []
|
|
42
|
+
for priority in priority_map.get(mode, []):
|
|
43
|
+
result.extend(payloads.get(priority, []))
|
|
44
|
+
return result
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def passive(url, config):
|
|
48
|
+
return [
|
|
49
|
+
create_job(
|
|
50
|
+
url=url,
|
|
51
|
+
method=config.get("method", "GET"),
|
|
52
|
+
headers=config.get("headers", {}),
|
|
53
|
+
data=config.get("data"),
|
|
54
|
+
timeout=config.get("timeout", 10),
|
|
55
|
+
)
|
|
56
|
+
]
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def active(url, config):
|
|
60
|
+
mode, timeout, base_headers = config.get("mode"), config.get("timeout", 10), config.get("headers", {})
|
|
61
|
+
jobs = []
|
|
62
|
+
|
|
63
|
+
# Origin fuzzing
|
|
64
|
+
for origin in get_payloads("origin", mode):
|
|
65
|
+
jobs.append(create_job(url=url, method="GET", headers={**base_headers, "Origin": origin}, timeout=timeout, origin=origin))
|
|
66
|
+
|
|
67
|
+
# Preflight
|
|
68
|
+
jobs.append(
|
|
69
|
+
create_job(
|
|
70
|
+
url=url,
|
|
71
|
+
method="OPTIONS",
|
|
72
|
+
headers={
|
|
73
|
+
**base_headers,
|
|
74
|
+
"Origin": "https://attacker.example",
|
|
75
|
+
"Access-Control-Request-Method": "POST",
|
|
76
|
+
"Access-Control-Request-Headers": "Authorization",
|
|
77
|
+
},
|
|
78
|
+
timeout=timeout,
|
|
79
|
+
origin="https://attacker.example",
|
|
80
|
+
)
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
return jobs
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def update_elapsed(stat):
|
|
87
|
+
elapsed = time.time() - stat.start_time
|
|
88
|
+
stat.elapsed = f"{int(elapsed // 60):02d}:{int(elapsed % 60):02d}"
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def analyze_results(stat, results):
|
|
92
|
+
for result in results:
|
|
93
|
+
task = result.get("task")
|
|
94
|
+
if not task:
|
|
95
|
+
continue
|
|
96
|
+
|
|
97
|
+
target_url = task.get("url")
|
|
98
|
+
if target_url not in stat.findings:
|
|
99
|
+
continue
|
|
100
|
+
|
|
101
|
+
error = result.get("error")
|
|
102
|
+
if error is not None:
|
|
103
|
+
stat.error += 1
|
|
104
|
+
if error not in stat.errors[target_url]:
|
|
105
|
+
stat.errors[target_url].append(error)
|
|
106
|
+
continue
|
|
107
|
+
|
|
108
|
+
response = result.get("response")
|
|
109
|
+
if response is None:
|
|
110
|
+
continue
|
|
111
|
+
|
|
112
|
+
for finding in analyze(response=response, task=task):
|
|
113
|
+
if finding not in stat.findings[target_url]:
|
|
114
|
+
stat.findings[target_url].append(finding)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def run(urls, config, on_target_done=None):
|
|
118
|
+
mode = config.get("mode")
|
|
119
|
+
mode_name = "passive" if mode is None else mode
|
|
120
|
+
workers = config.get("workers", 10)
|
|
121
|
+
|
|
122
|
+
urls = [urls] if isinstance(urls, str) else list(urls)
|
|
123
|
+
stat = Stat(targets=urls, mode=mode_name, threads=workers, config=config)
|
|
124
|
+
|
|
125
|
+
header(stat)
|
|
126
|
+
|
|
127
|
+
for url in urls:
|
|
128
|
+
# Build jobs
|
|
129
|
+
jobs = passive(url=url, config=config) if mode is None else active(url=url, config=config)
|
|
130
|
+
|
|
131
|
+
# Count requests
|
|
132
|
+
stat.request += len(jobs)
|
|
133
|
+
|
|
134
|
+
# Send requests & Analyze responses
|
|
135
|
+
results = requester_run(jobs=jobs, workers=workers)
|
|
136
|
+
analyze_results(stat=stat, results=results)
|
|
137
|
+
|
|
138
|
+
# Target finished
|
|
139
|
+
stat.scanned += 1
|
|
140
|
+
update_elapsed(stat)
|
|
141
|
+
|
|
142
|
+
# Tell caller that this target is finished
|
|
143
|
+
if on_target_done:
|
|
144
|
+
on_target_done(url, stat.findings[url], stat.errors[url])
|
|
145
|
+
|
|
146
|
+
update_elapsed(stat)
|
|
147
|
+
return stat
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
from sorx.data.loader.template import load_template
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def analyze(response, task):
|
|
5
|
+
findings = []
|
|
6
|
+
|
|
7
|
+
# Load templates
|
|
8
|
+
header_template = load_template("sensitive_headers")
|
|
9
|
+
method_template = load_template("sensitive_method")
|
|
10
|
+
|
|
11
|
+
# Response headers & normalization
|
|
12
|
+
headers = response.headers
|
|
13
|
+
allow_origin = headers.get("Access-Control-Allow-Origin", "").strip().lower()
|
|
14
|
+
allow_credentials = headers.get("Access-Control-Allow-Credentials", "").strip().lower()
|
|
15
|
+
attacker_origin = (task.get("origin") or "").strip().lower()
|
|
16
|
+
|
|
17
|
+
# Parse headers and methods into sets
|
|
18
|
+
parse_csv = lambda val, case_fn: {case_fn(x.strip()) for x in val.split(",") if x.strip()} if val else set()
|
|
19
|
+
allowed_headers = parse_csv(headers.get("Access-Control-Allow-Headers", ""), str.lower)
|
|
20
|
+
allowed_methods = parse_csv(headers.get("Access-Control-Allow-Methods", ""), str.upper)
|
|
21
|
+
exposed_headers = parse_csv(headers.get("Access-Control-Expose-Headers", ""), str.lower)
|
|
22
|
+
|
|
23
|
+
# Sensitive lookup sets
|
|
24
|
+
sensitive_request_headers = {h.lower() for h in header_template["sensitive_request_headers"]}
|
|
25
|
+
sensitive_response_headers = {h.lower() for h in header_template["sensitive_response_headers"]}
|
|
26
|
+
sensitive_methods = {m.upper() for m in method_template["sensitive_methods"]}
|
|
27
|
+
|
|
28
|
+
# Basic check flags
|
|
29
|
+
wildcard_origin = allow_origin == "*"
|
|
30
|
+
origin_reflection = bool(attacker_origin and allow_origin == attacker_origin)
|
|
31
|
+
null_origin = allow_origin == "null"
|
|
32
|
+
credentials = allow_credentials == "true"
|
|
33
|
+
|
|
34
|
+
# Rule checks
|
|
35
|
+
if wildcard_origin:
|
|
36
|
+
findings.append(("CORS-001", "Wildcard origin"))
|
|
37
|
+
|
|
38
|
+
if origin_reflection:
|
|
39
|
+
findings.append(
|
|
40
|
+
("CORS-101", "Attacker origin reflected with credentials")
|
|
41
|
+
if credentials
|
|
42
|
+
else ("CORS-100", "Attacker origin reflected")
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
if null_origin:
|
|
46
|
+
findings.append(
|
|
47
|
+
("CORS-200", "Null origin with credentials")
|
|
48
|
+
if credentials
|
|
49
|
+
else ("CORS-201", "Null origin allowed")
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
# Header & Method checks
|
|
53
|
+
found_sensitive_request_header = bool(allowed_headers & sensitive_request_headers)
|
|
54
|
+
if found_sensitive_request_header:
|
|
55
|
+
findings.append(("CORS-300", "Sensitive request header allowed"))
|
|
56
|
+
|
|
57
|
+
if "*" in allowed_headers:
|
|
58
|
+
findings.append(("CORS-301", "Wildcard request headers"))
|
|
59
|
+
|
|
60
|
+
if "*" in allowed_methods:
|
|
61
|
+
findings.append(("CORS-302", "Wildcard request methods"))
|
|
62
|
+
|
|
63
|
+
found_sensitive_response_header = bool(exposed_headers & sensitive_response_headers)
|
|
64
|
+
if found_sensitive_response_header:
|
|
65
|
+
findings.append(("CORS-400", "Sensitive response header exposed"))
|
|
66
|
+
|
|
67
|
+
found_sensitive_method = bool(allowed_methods & sensitive_methods)
|
|
68
|
+
|
|
69
|
+
# Advanced correlation rules
|
|
70
|
+
if origin_reflection and credentials:
|
|
71
|
+
if "authorization" in allowed_headers:
|
|
72
|
+
findings.append(("CORS-900", "Origin reflection with credentials and Authorization"))
|
|
73
|
+
|
|
74
|
+
if found_sensitive_request_header or found_sensitive_response_header:
|
|
75
|
+
findings.append(("CORS-901", "Origin reflection with credentials and sensitive headers"))
|
|
76
|
+
|
|
77
|
+
if "*" in allowed_methods or found_sensitive_method:
|
|
78
|
+
findings.append(("CORS-902", "Origin reflection with credentials and broad methods"))
|
|
79
|
+
|
|
80
|
+
return findings
|