openhack 0.1.0__py3-none-any.whl
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.
- openhack/__init__.py +2 -0
- openhack/__main__.py +225 -0
- openhack/agents/__init__.py +30 -0
- openhack/agents/base.py +230 -0
- openhack/agents/browser_verifier.py +679 -0
- openhack/agents/browser_verifier_swarm.py +256 -0
- openhack/agents/checkpoint.py +89 -0
- openhack/agents/context_manager.py +356 -0
- openhack/agents/coordinator.py +1105 -0
- openhack/agents/endpoint_analyst.py +307 -0
- openhack/agents/feature_hunter.py +93 -0
- openhack/agents/hunter.py +481 -0
- openhack/agents/hunter_swarm.py +385 -0
- openhack/agents/llm.py +334 -0
- openhack/agents/recon.py +19 -0
- openhack/agents/sandbox_verifier.py +396 -0
- openhack/agents/sandbox_verifier_swarm.py +250 -0
- openhack/agents/session.py +286 -0
- openhack/agents/validator.py +217 -0
- openhack/agents/validator_swarm.py +106 -0
- openhack/auth.py +175 -0
- openhack/browser/__init__.py +12 -0
- openhack/browser/runner.py +385 -0
- openhack/categories.py +130 -0
- openhack/config.py +201 -0
- openhack/deterministic_recon.py +464 -0
- openhack/entry_points.py +745 -0
- openhack/framework_classifier.py +515 -0
- openhack/framework_detection.py +269 -0
- openhack/headless_scan.py +179 -0
- openhack/prompts/__init__.py +108 -0
- openhack/prompts/browser_verifier.py +171 -0
- openhack/prompts/coordinator.py +31 -0
- openhack/prompts/django/__init__.py +32 -0
- openhack/prompts/django/auth_bypass.py +76 -0
- openhack/prompts/django/csrf.py +62 -0
- openhack/prompts/django/data_exposure.py +67 -0
- openhack/prompts/django/idor.py +74 -0
- openhack/prompts/django/injection.py +67 -0
- openhack/prompts/django/misconfiguration.py +70 -0
- openhack/prompts/django/ssrf.py +64 -0
- openhack/prompts/endpoint_analyst.py +122 -0
- openhack/prompts/express/__init__.py +29 -0
- openhack/prompts/express/auth_bypass.py +71 -0
- openhack/prompts/express/data_exposure.py +77 -0
- openhack/prompts/express/idor.py +69 -0
- openhack/prompts/express/injection.py +75 -0
- openhack/prompts/express/misconfiguration.py +72 -0
- openhack/prompts/express/ssrf.py +63 -0
- openhack/prompts/feature_hunter.py +140 -0
- openhack/prompts/flask/__init__.py +29 -0
- openhack/prompts/flask/auth_bypass.py +86 -0
- openhack/prompts/flask/data_exposure.py +78 -0
- openhack/prompts/flask/idor.py +83 -0
- openhack/prompts/flask/injection.py +77 -0
- openhack/prompts/flask/misconfiguration.py +73 -0
- openhack/prompts/flask/ssrf.py +65 -0
- openhack/prompts/hunter.py +362 -0
- openhack/prompts/hunter_continuation_loop.py +12 -0
- openhack/prompts/hunter_continuation_no_findings.py +19 -0
- openhack/prompts/hunter_continuation_no_progress.py +22 -0
- openhack/prompts/hunter_tool_instructions.py +55 -0
- openhack/prompts/nextjs/__init__.py +42 -0
- openhack/prompts/nextjs/auth_bypass.py +80 -0
- openhack/prompts/nextjs/csrf.py +71 -0
- openhack/prompts/nextjs/data_exposure.py +88 -0
- openhack/prompts/nextjs/idor.py +64 -0
- openhack/prompts/nextjs/injection.py +65 -0
- openhack/prompts/nextjs/middleware_bypass.py +75 -0
- openhack/prompts/nextjs/misconfiguration.py +92 -0
- openhack/prompts/nextjs/server_actions.py +97 -0
- openhack/prompts/nextjs/ssrf.py +66 -0
- openhack/prompts/nextjs/xss.py +69 -0
- openhack/prompts/pr_analysis_system.py +80 -0
- openhack/prompts/pr_analysis_user.py +11 -0
- openhack/prompts/project_context.py +89 -0
- openhack/prompts/recon.py +199 -0
- openhack/prompts/reporter.py +88 -0
- openhack/prompts/researchers.py +434 -0
- openhack/prompts/sandbox_verifier.py +128 -0
- openhack/prompts/supabase/__init__.py +39 -0
- openhack/prompts/supabase/auth_tokens.py +131 -0
- openhack/prompts/supabase/edge_functions.py +150 -0
- openhack/prompts/supabase/graphql.py +102 -0
- openhack/prompts/supabase/postgrest.py +99 -0
- openhack/prompts/supabase/realtime.py +93 -0
- openhack/prompts/supabase/rls.py +110 -0
- openhack/prompts/supabase/rpc_functions.py +127 -0
- openhack/prompts/supabase/storage.py +110 -0
- openhack/prompts/supabase/tenant_isolation.py +118 -0
- openhack/prompts/validator.py +319 -0
- openhack/prompts/validator_continuation_incomplete.py +12 -0
- openhack/prompts/validator_tool_instructions.py +29 -0
- openhack/quality.py +231 -0
- openhack/sandbox/__init__.py +12 -0
- openhack/sandbox/orchestrator.py +517 -0
- openhack/sandbox/runner.py +177 -0
- openhack/scan_session.py +245 -0
- openhack/setup.py +452 -0
- openhack/static_validator.py +612 -0
- openhack/tools/__init__.py +1 -0
- openhack/tools/ast_tools.py +307 -0
- openhack/tools/coverage.py +1078 -0
- openhack/tools/filesystem.py +404 -0
- openhack/tools/nextjs.py +258 -0
- openhack/tools/registry.py +52 -0
- openhack/tui.py +3450 -0
- openhack/updates.py +170 -0
- openhack-0.1.0.dist-info/METADATA +189 -0
- openhack-0.1.0.dist-info/RECORD +113 -0
- openhack-0.1.0.dist-info/WHEEL +4 -0
- openhack-0.1.0.dist-info/entry_points.txt +2 -0
- openhack-0.1.0.dist-info/licenses/LICENSE +661 -0
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Feature Deep Dive hunter prompt templates.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
FEATURE_HUNTER_PROMPT = """You are a security researcher performing a deep audit of a codebase. You work exactly like a human security researcher — you read the code, understand the architecture, decide what's interesting, and go deep on the riskiest areas.
|
|
6
|
+
|
|
7
|
+
{project_context}
|
|
8
|
+
|
|
9
|
+
## Application Context
|
|
10
|
+
|
|
11
|
+
{recon_context}
|
|
12
|
+
|
|
13
|
+
## How to Work
|
|
14
|
+
|
|
15
|
+
You are NOT a pattern matcher. You are a researcher. Work like this:
|
|
16
|
+
|
|
17
|
+
### Step 1: Read the Map
|
|
18
|
+
Start by understanding the application's structure:
|
|
19
|
+
- Read the route definitions / URL config to see every endpoint
|
|
20
|
+
- Read the auth middleware / policies to understand how access control works
|
|
21
|
+
- Read the main config files to understand the tech stack
|
|
22
|
+
- Spend your first 10-15 iterations building a mental model of the app
|
|
23
|
+
|
|
24
|
+
### Step 2: Pick Your Targets
|
|
25
|
+
Based on what you read, identify 3-5 features that are MOST LIKELY to have vulnerabilities:
|
|
26
|
+
- Features that make outbound HTTP requests (webhooks, notifications, URL fetching, favicon download)
|
|
27
|
+
- Features that serve user-uploaded content (file downloads, image serving, attachments)
|
|
28
|
+
- Features that handle authentication or authorization (login, token exchange, permission checks)
|
|
29
|
+
- Features where similar functionality is implemented in multiple places (compare them for inconsistencies)
|
|
30
|
+
|
|
31
|
+
Say out loud which features you're targeting and why.
|
|
32
|
+
|
|
33
|
+
### Step 3: Go Deep on Each Feature
|
|
34
|
+
For each feature you picked:
|
|
35
|
+
|
|
36
|
+
**Read all the relevant files.** Not just the controller — follow the imports. Read the helper functions, the model definitions, the middleware. Understand the full data flow from user input to dangerous operation.
|
|
37
|
+
|
|
38
|
+
**Compare similar code paths.** This is where the real bugs are. If two endpoints both make outbound requests, compare their URL validation. If two endpoints both serve files, compare their Content-Type handling. Inconsistencies between similar features are the #1 source of CVEs.
|
|
39
|
+
|
|
40
|
+
**Check protection completeness.** When you find a security control (blocklist, sanitizer, auth check), ask: is it complete? Does it cover all cases? What's NOT blocked? What's NOT checked?
|
|
41
|
+
|
|
42
|
+
**Trace cross-file data flows.** When a value is set in file A and used in file B, is it re-validated? Or is it trusted because "it came from our own database"? Look for properties set during creation that change behavior during retrieval.
|
|
43
|
+
|
|
44
|
+
### Step 4: Report What You Found
|
|
45
|
+
For each finding, provide:
|
|
46
|
+
- The exact vulnerable code (file, line, snippet)
|
|
47
|
+
- The exact protection that's missing or incomplete
|
|
48
|
+
- If it's an inconsistency: show both the secure and insecure version
|
|
49
|
+
- The attack scenario: what request does an attacker send?
|
|
50
|
+
|
|
51
|
+
## What Makes a Real Finding
|
|
52
|
+
|
|
53
|
+
**Inconsistencies between similar endpoints** — endpoint A validates URLs, endpoint B doesn't. Both make outbound requests. That's an SSRF.
|
|
54
|
+
|
|
55
|
+
**Incomplete protection** — a blocklist that blocks 8 out of 50 dangerous values. A sanitizer that handles HTML but not SVG. An auth check that covers the API but not the websocket.
|
|
56
|
+
|
|
57
|
+
**Cross-file logic bugs** — upload sets mimeType, download uses mimeType to set Content-Type and decides whether to serve inline. If SVGs are processed as images, they get served inline with JavaScript execution.
|
|
58
|
+
|
|
59
|
+
**Missing await / async bugs** — an auth check that isn't awaited, so it returns a Promise (truthy) instead of the actual auth result. The check appears to pass but never actually runs.
|
|
60
|
+
|
|
61
|
+
## What is NOT a Finding
|
|
62
|
+
|
|
63
|
+
- Dev-only fallbacks (`process.env.SECRET || "default"`)
|
|
64
|
+
- UUIDs as object IDs — can't be brute-forced
|
|
65
|
+
- Intentionally public endpoints (forgot-password, signup)
|
|
66
|
+
- Missing security headers without a companion injection
|
|
67
|
+
- Features working as designed in well-maintained projects
|
|
68
|
+
|
|
69
|
+
## CRITICAL: Production Code Only
|
|
70
|
+
|
|
71
|
+
Before reporting ANY finding, look at the file path and ask: **"Does this code run in production and is it reachable by attackers?"**
|
|
72
|
+
|
|
73
|
+
DO NOT report findings in:
|
|
74
|
+
- Test, demo, example, sample, tutorial, playground, benchmark, or documentation code
|
|
75
|
+
- CLI tools or scripts that run on the developer's machine, not on a server
|
|
76
|
+
- Debug/diagnostic utilities not meant for production deployment
|
|
77
|
+
- Code generators, build scripts, or migration scripts
|
|
78
|
+
|
|
79
|
+
Use your judgment based on the full path and context. A vulnerability in `demos/http3/demo-server.c` is NOT a finding because nobody deploys demo servers. A vulnerability in `src/tls/handshake.c` IS a finding because it runs in every deployment.
|
|
80
|
+
|
|
81
|
+
**The rule: only report vulnerabilities in code that actually ships to production.**
|
|
82
|
+
|
|
83
|
+
## Severity Calibration
|
|
84
|
+
|
|
85
|
+
| Category | Default | Raise when... |
|
|
86
|
+
|------------------------|----------|----------------------------------------------------|
|
|
87
|
+
| SQL Injection | critical | -- |
|
|
88
|
+
| Command Injection | critical | -- |
|
|
89
|
+
| RCE | critical | -- |
|
|
90
|
+
| Authentication Bypass | critical | -- |
|
|
91
|
+
| SSRF | high | Internal service reachable, metadata endpoint hit |
|
|
92
|
+
| Path Traversal | high | Sensitive files readable |
|
|
93
|
+
| IDOR | high | Enumerable IDs, sensitive data exposed |
|
|
94
|
+
| Stored XSS | high | No CSP, account takeover possible |
|
|
95
|
+
| Authorization Bypass | high | Privilege escalation to admin |
|
|
96
|
+
| Data Exposure | high | PII or credentials |
|
|
97
|
+
| Open Redirect | medium | Only raise if chained with token theft |
|
|
98
|
+
| CSRF | medium | State-changing on sensitive resource |
|
|
99
|
+
|
|
100
|
+
Think out loud at every step. Explain what you're reading, what you're looking for, what you found, and why it matters.
|
|
101
|
+
"""
|
|
102
|
+
|
|
103
|
+
# Keep the extraction prompt for backward compatibility but it's no longer used
|
|
104
|
+
# in the primary flow
|
|
105
|
+
FEATURE_EXTRACTION_PROMPT = """You are analyzing a security reconnaissance report to identify high-risk features for deep-dive vulnerability analysis.
|
|
106
|
+
|
|
107
|
+
Given the reconnaissance summary below, extract the 3-5 features that are MOST LIKELY to contain security vulnerabilities.
|
|
108
|
+
|
|
109
|
+
## What Makes a Feature High-Risk
|
|
110
|
+
|
|
111
|
+
Prioritize features that:
|
|
112
|
+
1. **Handle user-controlled data crossing trust boundaries** — file uploads, URL fetching/scraping, deserialization, template rendering, webhook/callback URLs
|
|
113
|
+
2. **Have custom security logic** — hand-rolled sanitizers, custom blocklists, bespoke auth checks (NOT library-provided protections like Django ORM, Prisma, etc.)
|
|
114
|
+
3. **Span multiple files** — data set in one place, transformed in another, used in a third. Cross-file data flows are where logic bugs hide.
|
|
115
|
+
4. **Make outbound requests** — webhook delivery, favicon fetching, URL previews, notification services, RSS fetching
|
|
116
|
+
5. **Serve user content** — file downloads, image serving, PDF generation, markdown rendering
|
|
117
|
+
|
|
118
|
+
Do NOT select:
|
|
119
|
+
- Standard framework-provided features (login with Passport.js, ORM queries, session management)
|
|
120
|
+
- Features that only exist in test/CLI/docs code
|
|
121
|
+
- Pure client-side features with no server component
|
|
122
|
+
|
|
123
|
+
## Reconnaissance Summary
|
|
124
|
+
|
|
125
|
+
{recon_summary}
|
|
126
|
+
|
|
127
|
+
## Attack Surface Data
|
|
128
|
+
|
|
129
|
+
{attack_surface}
|
|
130
|
+
|
|
131
|
+
## Output Format
|
|
132
|
+
|
|
133
|
+
Return a JSON array of features. Each feature must have:
|
|
134
|
+
- `name`: short snake_case identifier (e.g., "file_uploads", "webhook_delivery")
|
|
135
|
+
- `description`: one sentence describing what the feature does
|
|
136
|
+
- `entry_files`: list of 2-5 key file paths to start the analysis from
|
|
137
|
+
- `risk_reason`: one sentence explaining WHY this feature is high-risk
|
|
138
|
+
|
|
139
|
+
Return ONLY the JSON array, no other text. Keep descriptions under 20 words.
|
|
140
|
+
"""
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Flask vulnerability detection prompts, organized by attack type.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from .injection import FLASK_INJECTION_PROMPT
|
|
6
|
+
from .auth_bypass import FLASK_AUTH_BYPASS_PROMPT
|
|
7
|
+
from .idor import FLASK_IDOR_PROMPT
|
|
8
|
+
from .ssrf import FLASK_SSRF_PROMPT
|
|
9
|
+
from .data_exposure import FLASK_DATA_EXPOSURE_PROMPT
|
|
10
|
+
from .misconfiguration import FLASK_MISCONFIGURATION_PROMPT
|
|
11
|
+
|
|
12
|
+
FLASK_PROMPTS = {
|
|
13
|
+
"injection": FLASK_INJECTION_PROMPT,
|
|
14
|
+
"auth_bypass": FLASK_AUTH_BYPASS_PROMPT,
|
|
15
|
+
"idor": FLASK_IDOR_PROMPT,
|
|
16
|
+
"ssrf": FLASK_SSRF_PROMPT,
|
|
17
|
+
"data_exposure": FLASK_DATA_EXPOSURE_PROMPT,
|
|
18
|
+
"misconfiguration": FLASK_MISCONFIGURATION_PROMPT,
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
__all__ = [
|
|
22
|
+
"FLASK_PROMPTS",
|
|
23
|
+
"FLASK_INJECTION_PROMPT",
|
|
24
|
+
"FLASK_AUTH_BYPASS_PROMPT",
|
|
25
|
+
"FLASK_IDOR_PROMPT",
|
|
26
|
+
"FLASK_SSRF_PROMPT",
|
|
27
|
+
"FLASK_DATA_EXPOSURE_PROMPT",
|
|
28
|
+
"FLASK_MISCONFIGURATION_PROMPT",
|
|
29
|
+
]
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Flask authentication/authorization bypass detection prompt.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
FLASK_AUTH_BYPASS_PROMPT = """## Authentication/Authorization Bypass in Flask
|
|
6
|
+
|
|
7
|
+
### What to Look For
|
|
8
|
+
|
|
9
|
+
1. **Missing @login_required decorators**
|
|
10
|
+
2. **Broken Flask-Login is_authenticated checks**
|
|
11
|
+
3. **Unprotected Blueprint routes**
|
|
12
|
+
4. **Flask-Admin without auth**
|
|
13
|
+
|
|
14
|
+
### Flask-Specific Patterns
|
|
15
|
+
|
|
16
|
+
**Missing Decorator (Vulnerable)**:
|
|
17
|
+
```python
|
|
18
|
+
# VULNERABLE: No auth on sensitive endpoint
|
|
19
|
+
@app.route('/admin/users', methods=['GET', 'DELETE'])
|
|
20
|
+
def manage_users():
|
|
21
|
+
if request.method == 'DELETE':
|
|
22
|
+
User.query.filter_by(id=request.form['id']).delete()
|
|
23
|
+
db.session.commit()
|
|
24
|
+
return jsonify([u.to_dict() for u in User.query.all()])
|
|
25
|
+
|
|
26
|
+
# SAFE: Auth required
|
|
27
|
+
@app.route('/admin/users')
|
|
28
|
+
@login_required
|
|
29
|
+
def manage_users():
|
|
30
|
+
...
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
**Blueprint Without Auth (Vulnerable)**:
|
|
34
|
+
```python
|
|
35
|
+
# VULNERABLE: Entire blueprint has no auth
|
|
36
|
+
admin_bp = Blueprint('admin', __name__, url_prefix='/admin')
|
|
37
|
+
|
|
38
|
+
@admin_bp.route('/settings')
|
|
39
|
+
def admin_settings():
|
|
40
|
+
return jsonify(get_all_settings()) # Exposed!
|
|
41
|
+
|
|
42
|
+
# SAFE: before_request hook for entire blueprint
|
|
43
|
+
@admin_bp.before_request
|
|
44
|
+
@login_required
|
|
45
|
+
def admin_auth():
|
|
46
|
+
if not current_user.is_admin:
|
|
47
|
+
abort(403)
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
**Flask-Admin Exposure (Vulnerable)**:
|
|
51
|
+
```python
|
|
52
|
+
# VULNERABLE: Flask-Admin with no auth
|
|
53
|
+
from flask_admin import Admin
|
|
54
|
+
admin = Admin(app)
|
|
55
|
+
admin.add_view(ModelView(User, db.session)) # Anyone can CRUD users!
|
|
56
|
+
|
|
57
|
+
# SAFE: Custom ModelView with auth
|
|
58
|
+
class SecureModelView(ModelView):
|
|
59
|
+
def is_accessible(self):
|
|
60
|
+
return current_user.is_authenticated and current_user.is_admin
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
**JWT Misconfiguration (Vulnerable)**:
|
|
64
|
+
```python
|
|
65
|
+
# VULNERABLE: No algorithm restriction
|
|
66
|
+
import jwt
|
|
67
|
+
decoded = jwt.decode(token, secret, algorithms=None) # Accepts 'none'!
|
|
68
|
+
|
|
69
|
+
# VULNERABLE: Weak/hardcoded secret
|
|
70
|
+
app.config['JWT_SECRET_KEY'] = 'super-secret'
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Search Patterns
|
|
74
|
+
|
|
75
|
+
1. `grep` for: `@app\\.route`, `@.*\\.route` -- check for `@login_required`
|
|
76
|
+
2. `grep` for: `Blueprint\\(` -- check if before_request has auth
|
|
77
|
+
3. `grep` for: `Flask-Admin`, `ModelView` -- check for `is_accessible`
|
|
78
|
+
4. `grep` for: `jwt\\.decode`, `JWT_SECRET_KEY`
|
|
79
|
+
|
|
80
|
+
### Severity Assessment
|
|
81
|
+
|
|
82
|
+
- **Critical**: Admin panels or management endpoints without auth
|
|
83
|
+
- **High**: User data modification without auth
|
|
84
|
+
- **Medium**: Read access to non-sensitive data
|
|
85
|
+
- **Low**: Informational endpoints exposed
|
|
86
|
+
"""
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Flask data exposure detection prompt.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
FLASK_DATA_EXPOSURE_PROMPT = """## Data Exposure in Flask
|
|
6
|
+
|
|
7
|
+
### What to Look For
|
|
8
|
+
|
|
9
|
+
1. **jsonify(model.__dict__) leaking internal fields**
|
|
10
|
+
2. **Marshmallow schemas exposing sensitive fields**
|
|
11
|
+
3. **DEBUG=True with Werkzeug debugger (RCE!)**
|
|
12
|
+
4. **Hardcoded app.secret_key**
|
|
13
|
+
|
|
14
|
+
### Flask-Specific Patterns
|
|
15
|
+
|
|
16
|
+
**Model Dump (Vulnerable)**:
|
|
17
|
+
```python
|
|
18
|
+
# VULNERABLE: Dumping full model including password hash
|
|
19
|
+
@app.route('/api/users/<int:uid>')
|
|
20
|
+
def get_user(uid):
|
|
21
|
+
user = User.query.get_or_404(uid)
|
|
22
|
+
return jsonify(user.__dict__) # Includes _sa_instance_state, password_hash!
|
|
23
|
+
|
|
24
|
+
# VULNERABLE: vars() or to_dict() without filtering
|
|
25
|
+
return jsonify(vars(user))
|
|
26
|
+
return jsonify({c.name: getattr(user, c.name) for c in user.__table__.columns})
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
**Marshmallow Over-Exposure (Vulnerable)**:
|
|
30
|
+
```python
|
|
31
|
+
# VULNERABLE: Includes sensitive fields
|
|
32
|
+
class UserSchema(Schema):
|
|
33
|
+
class Meta:
|
|
34
|
+
fields = ('id', 'username', 'email', 'password_hash', 'is_admin', 'api_key')
|
|
35
|
+
|
|
36
|
+
# SAFE: Exclude sensitive fields
|
|
37
|
+
class UserSchema(Schema):
|
|
38
|
+
class Meta:
|
|
39
|
+
fields = ('id', 'username', 'email')
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
**Werkzeug Debugger (Critical)**:
|
|
43
|
+
```python
|
|
44
|
+
# VULNERABLE: DEBUG=True in production = Remote Code Execution!
|
|
45
|
+
app.run(debug=True, host='0.0.0.0')
|
|
46
|
+
# The Werkzeug debugger allows arbitrary Python execution in the browser
|
|
47
|
+
# via the interactive console at /__debugger__
|
|
48
|
+
|
|
49
|
+
# Also check:
|
|
50
|
+
app.config['DEBUG'] = True
|
|
51
|
+
FLASK_DEBUG=1 # in .env or environment
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
**Secret Key Exposure (Vulnerable)**:
|
|
55
|
+
```python
|
|
56
|
+
# VULNERABLE: Hardcoded secret (session forgery)
|
|
57
|
+
app.secret_key = 'my-secret-key'
|
|
58
|
+
app.config['SECRET_KEY'] = 'dev-secret'
|
|
59
|
+
|
|
60
|
+
# VULNERABLE: Weak fallback
|
|
61
|
+
app.secret_key = os.environ.get('SECRET_KEY', 'fallback-insecure')
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Search Patterns
|
|
65
|
+
|
|
66
|
+
1. `grep` for: `jsonify\\(.*__dict__`, `jsonify\\(vars\\(`, `to_dict\\(`
|
|
67
|
+
2. `grep` for: `debug=True`, `DEBUG = True`, `FLASK_DEBUG`
|
|
68
|
+
3. `grep` for: `secret_key =`, `SECRET_KEY`
|
|
69
|
+
4. `grep` for: `class.*Schema` -- check Meta.fields for sensitive columns
|
|
70
|
+
|
|
71
|
+
### Severity Assessment
|
|
72
|
+
|
|
73
|
+
- **Critical**: DEBUG=True in production (Werkzeug debugger = RCE)
|
|
74
|
+
- **Critical**: Password hashes or API keys in API responses
|
|
75
|
+
- **High**: Hardcoded SECRET_KEY (session forgery)
|
|
76
|
+
- **High**: PII exposed to unauthorized users
|
|
77
|
+
- **Medium**: Non-sensitive internal data leakage
|
|
78
|
+
"""
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Flask IDOR (Insecure Direct Object Reference) detection prompt.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
FLASK_IDOR_PROMPT = """## IDOR (Insecure Direct Object Reference) Detection in Flask
|
|
6
|
+
|
|
7
|
+
### What to Look For
|
|
8
|
+
|
|
9
|
+
1. **URL parameters used directly in SQLAlchemy queries without ownership check**
|
|
10
|
+
2. **Missing filter_by(user_id=current_user.id)**
|
|
11
|
+
3. **Flask-RESTful resources without ownership validation**
|
|
12
|
+
|
|
13
|
+
### Flask-Specific Patterns
|
|
14
|
+
|
|
15
|
+
**Direct Query (Vulnerable)**:
|
|
16
|
+
```python
|
|
17
|
+
# VULNERABLE: No ownership check
|
|
18
|
+
@app.route('/api/invoices/<int:invoice_id>')
|
|
19
|
+
@login_required
|
|
20
|
+
def get_invoice(invoice_id):
|
|
21
|
+
invoice = Invoice.query.get_or_404(invoice_id)
|
|
22
|
+
return jsonify(invoice.to_dict()) # Any user can view any invoice
|
|
23
|
+
|
|
24
|
+
# SAFE: Scoped to user
|
|
25
|
+
@app.route('/api/invoices/<int:invoice_id>')
|
|
26
|
+
@login_required
|
|
27
|
+
def get_invoice(invoice_id):
|
|
28
|
+
invoice = Invoice.query.filter_by(
|
|
29
|
+
id=invoice_id, user_id=current_user.id
|
|
30
|
+
).first_or_404()
|
|
31
|
+
return jsonify(invoice.to_dict())
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
**db.session.get (Vulnerable)**:
|
|
35
|
+
```python
|
|
36
|
+
# VULNERABLE: Direct primary key lookup
|
|
37
|
+
@app.route('/api/documents/<int:doc_id>', methods=['PUT'])
|
|
38
|
+
@login_required
|
|
39
|
+
def update_document(doc_id):
|
|
40
|
+
doc = db.session.get(Document, doc_id) # No ownership check!
|
|
41
|
+
doc.title = request.json['title']
|
|
42
|
+
db.session.commit()
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
**Flask-RESTful (Vulnerable)**:
|
|
46
|
+
```python
|
|
47
|
+
# VULNERABLE: Resource without ownership
|
|
48
|
+
class OrderResource(Resource):
|
|
49
|
+
@login_required
|
|
50
|
+
def get(self, order_id):
|
|
51
|
+
order = Order.query.get_or_404(order_id)
|
|
52
|
+
return marshal(order, order_fields) # Any user's order!
|
|
53
|
+
|
|
54
|
+
@login_required
|
|
55
|
+
def delete(self, order_id):
|
|
56
|
+
order = Order.query.get_or_404(order_id)
|
|
57
|
+
db.session.delete(order) # Can delete anyone's order!
|
|
58
|
+
db.session.commit()
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
**Marshmallow Nested (Vulnerable)**:
|
|
62
|
+
```python
|
|
63
|
+
# VULNERABLE: User can set owner in request body
|
|
64
|
+
class ProjectSchema(Schema):
|
|
65
|
+
id = fields.Int(dump_only=True)
|
|
66
|
+
name = fields.Str(required=True)
|
|
67
|
+
owner_id = fields.Int() # Writable! User can claim any owner
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Search Patterns
|
|
71
|
+
|
|
72
|
+
1. `grep` for: `\\.get_or_404\\(`, `\\.get\\(`, `db\\.session\\.get\\(`
|
|
73
|
+
2. `grep` for: `query\\.filter_by\\(id=` -- check for user scoping
|
|
74
|
+
3. Check Flask-RESTful `Resource` classes for ownership checks
|
|
75
|
+
4. Look for `request.args`, `request.form`, URL params flowing to queries
|
|
76
|
+
|
|
77
|
+
### Severity Assessment
|
|
78
|
+
|
|
79
|
+
- **Critical**: Access to other users' sensitive data (PII, financial)
|
|
80
|
+
- **High**: Modification/deletion of other users' resources
|
|
81
|
+
- **Medium**: Read access to non-sensitive resources
|
|
82
|
+
- **Low**: Informational leaks with no impact
|
|
83
|
+
"""
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Flask injection vulnerabilities detection prompt.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
FLASK_INJECTION_PROMPT = """## Injection Vulnerabilities in Flask
|
|
6
|
+
|
|
7
|
+
### What to Look For
|
|
8
|
+
|
|
9
|
+
1. **SQL injection via SQLAlchemy raw queries**
|
|
10
|
+
2. **Jinja2 Server-Side Template Injection (SSTI)**
|
|
11
|
+
3. **Command injection via subprocess/os**
|
|
12
|
+
4. **eval/exec with request data**
|
|
13
|
+
|
|
14
|
+
### Flask-Specific Patterns
|
|
15
|
+
|
|
16
|
+
**SQLAlchemy Injection (Vulnerable)**:
|
|
17
|
+
```python
|
|
18
|
+
# VULNERABLE: text() with f-string
|
|
19
|
+
from sqlalchemy import text
|
|
20
|
+
result = db.session.execute(text(f"SELECT * FROM users WHERE name = '{name}'"))
|
|
21
|
+
|
|
22
|
+
# VULNERABLE: db.engine.execute with format string
|
|
23
|
+
db.engine.execute("SELECT * FROM users WHERE id = %s" % user_id)
|
|
24
|
+
|
|
25
|
+
# VULNERABLE: filter with string interpolation
|
|
26
|
+
User.query.filter(f"name = '{request.args['name']}'")
|
|
27
|
+
|
|
28
|
+
# SAFE: Parameterized
|
|
29
|
+
result = db.session.execute(text("SELECT * FROM users WHERE name = :name"), {"name": name})
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
**Jinja2 SSTI (Vulnerable)**:
|
|
33
|
+
```python
|
|
34
|
+
# VULNERABLE: render_template_string with user input
|
|
35
|
+
from flask import render_template_string
|
|
36
|
+
@app.route('/greet')
|
|
37
|
+
def greet():
|
|
38
|
+
name = request.args.get('name')
|
|
39
|
+
return render_template_string(f'Hello {{name}}!') # SSTI!
|
|
40
|
+
# Attack: ?name={{config.SECRET_KEY}} or {{''.__class__.__mro__[1].__subclasses__()}}
|
|
41
|
+
|
|
42
|
+
# SAFE: Use render_template with separate template file
|
|
43
|
+
return render_template('greet.html', name=name)
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
**Command Injection (Vulnerable)**:
|
|
47
|
+
```python
|
|
48
|
+
# VULNERABLE: shell=True with user input
|
|
49
|
+
import subprocess
|
|
50
|
+
subprocess.call(f"ping {request.form['host']}", shell=True)
|
|
51
|
+
os.system(f"nslookup {request.args['domain']}")
|
|
52
|
+
|
|
53
|
+
# SAFE: Argument list
|
|
54
|
+
subprocess.call(["ping", "-c", "1", request.form['host']])
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
**Eval/Exec (Vulnerable)**:
|
|
58
|
+
```python
|
|
59
|
+
# VULNERABLE: eval with request data
|
|
60
|
+
result = eval(request.form['expression'])
|
|
61
|
+
exec(request.json['code'])
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Search Patterns
|
|
65
|
+
|
|
66
|
+
1. `grep` for: `db\\.session\\.execute`, `db\\.engine\\.execute`, `text\\(`
|
|
67
|
+
2. `grep` for: `render_template_string` -- almost always dangerous
|
|
68
|
+
3. `grep` for: `subprocess`, `os\\.system`, `os\\.popen`, `shell=True`
|
|
69
|
+
4. `grep` for: `eval\\(`, `exec\\(`, `compile\\(`
|
|
70
|
+
|
|
71
|
+
### Severity Assessment
|
|
72
|
+
|
|
73
|
+
- **Critical**: SQL injection allowing data extraction
|
|
74
|
+
- **Critical**: SSTI (leads to RCE via Jinja2 sandbox escape)
|
|
75
|
+
- **Critical**: Command injection
|
|
76
|
+
- **High**: eval/exec with partially controlled input
|
|
77
|
+
"""
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Flask security misconfiguration detection prompt.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
FLASK_MISCONFIGURATION_PROMPT = """## Security Misconfiguration in Flask
|
|
6
|
+
|
|
7
|
+
### What to Look For
|
|
8
|
+
|
|
9
|
+
1. **app.secret_key hardcoded or weak**
|
|
10
|
+
2. **DEBUG=True in production (Werkzeug debugger = RCE)**
|
|
11
|
+
3. **Missing session cookie security flags**
|
|
12
|
+
4. **Overly permissive CORS**
|
|
13
|
+
|
|
14
|
+
### Flask-Specific Patterns
|
|
15
|
+
|
|
16
|
+
**Secret Key (Vulnerable)**:
|
|
17
|
+
```python
|
|
18
|
+
# VULNERABLE: Hardcoded secret key
|
|
19
|
+
app.secret_key = 'super-secret-key-123'
|
|
20
|
+
app.config['SECRET_KEY'] = 'development'
|
|
21
|
+
|
|
22
|
+
# VULNERABLE: Predictable or short secret
|
|
23
|
+
app.secret_key = 'secret'
|
|
24
|
+
|
|
25
|
+
# VULNERABLE: Weak fallback default
|
|
26
|
+
app.secret_key = os.environ.get('SECRET_KEY', 'default-key')
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
**Cookie Security (Vulnerable)**:
|
|
30
|
+
```python
|
|
31
|
+
# VULNERABLE: Missing security flags
|
|
32
|
+
app.config['SESSION_COOKIE_SECURE'] = False # Sent over HTTP
|
|
33
|
+
app.config['SESSION_COOKIE_HTTPONLY'] = False # JS accessible
|
|
34
|
+
app.config['SESSION_COOKIE_SAMESITE'] = None # Cross-site allowed
|
|
35
|
+
app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(days=365) # 1 year sessions!
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
**CORS Misconfiguration (Vulnerable)**:
|
|
39
|
+
```python
|
|
40
|
+
# VULNERABLE: Allow all origins
|
|
41
|
+
from flask_cors import CORS
|
|
42
|
+
CORS(app) # Default allows all origins
|
|
43
|
+
|
|
44
|
+
# VULNERABLE: Wildcard with credentials
|
|
45
|
+
CORS(app, origins='*', supports_credentials=True)
|
|
46
|
+
|
|
47
|
+
# SAFE: Specific origins
|
|
48
|
+
CORS(app, origins=['https://app.example.com'], supports_credentials=True)
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
**Host Header (Vulnerable)**:
|
|
52
|
+
```python
|
|
53
|
+
# VULNERABLE: Running on 0.0.0.0 without SERVER_NAME
|
|
54
|
+
app.run(host='0.0.0.0', port=5000)
|
|
55
|
+
# No SERVER_NAME set -- accepts any Host header
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Search Patterns
|
|
59
|
+
|
|
60
|
+
1. `grep` for: `secret_key`, `SECRET_KEY` in app config
|
|
61
|
+
2. `grep` for: `debug=True`, `DEBUG = True`, `FLASK_DEBUG`
|
|
62
|
+
3. `grep` for: `SESSION_COOKIE_SECURE`, `SESSION_COOKIE_HTTPONLY`
|
|
63
|
+
4. `grep` for: `CORS\\(`, `flask_cors`
|
|
64
|
+
5. `grep` for: `app\\.run\\(` -- check for debug and host params
|
|
65
|
+
|
|
66
|
+
### Severity Assessment
|
|
67
|
+
|
|
68
|
+
- **Critical**: DEBUG=True on public host (Werkzeug debugger = RCE)
|
|
69
|
+
- **High**: Hardcoded or weak SECRET_KEY (session forgery, cookie tampering)
|
|
70
|
+
- **Medium**: Missing cookie security flags
|
|
71
|
+
- **Medium**: CORS allowing all origins with credentials
|
|
72
|
+
- **Low**: Missing HTTPS enforcement headers
|
|
73
|
+
"""
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Flask SSRF detection prompt.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
FLASK_SSRF_PROMPT = """## SSRF (Server-Side Request Forgery) in Flask
|
|
6
|
+
|
|
7
|
+
### What to Look For
|
|
8
|
+
|
|
9
|
+
1. **User-controlled URLs passed to requests/urllib/httpx**
|
|
10
|
+
2. **Webhook/callback URL injection**
|
|
11
|
+
3. **URL fetching in import, preview, or proxy features**
|
|
12
|
+
|
|
13
|
+
### Flask-Specific Patterns
|
|
14
|
+
|
|
15
|
+
**Requests Library (Vulnerable)**:
|
|
16
|
+
```python
|
|
17
|
+
# VULNERABLE: User controls URL
|
|
18
|
+
@app.route('/api/preview')
|
|
19
|
+
def preview():
|
|
20
|
+
url = request.args.get('url')
|
|
21
|
+
response = requests.get(url) # SSRF!
|
|
22
|
+
return jsonify({"content": response.text[:500]})
|
|
23
|
+
|
|
24
|
+
# VULNERABLE: Webhook callback
|
|
25
|
+
@app.route('/api/webhooks', methods=['POST'])
|
|
26
|
+
@login_required
|
|
27
|
+
def create_webhook():
|
|
28
|
+
url = request.json['callback_url']
|
|
29
|
+
Webhook.create(url=url, user_id=current_user.id)
|
|
30
|
+
# Later: requests.post(webhook.url, json=event) -- hits internal services
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
**urllib (Vulnerable)**:
|
|
34
|
+
```python
|
|
35
|
+
# VULNERABLE: urlopen with user URL
|
|
36
|
+
from urllib.request import urlopen
|
|
37
|
+
@app.route('/api/import')
|
|
38
|
+
def import_data():
|
|
39
|
+
url = request.form['source']
|
|
40
|
+
data = urlopen(url).read() # file://, http://169.254.169.254, etc.
|
|
41
|
+
return process(data)
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
**httpx / aiohttp (Vulnerable)**:
|
|
45
|
+
```python
|
|
46
|
+
# VULNERABLE: async HTTP client with user URL
|
|
47
|
+
import httpx
|
|
48
|
+
async with httpx.AsyncClient() as client:
|
|
49
|
+
r = await client.get(request.json['url'])
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Search Patterns
|
|
53
|
+
|
|
54
|
+
1. `grep` for: `requests\\.get\\(`, `requests\\.post\\(`, `requests\\.head\\(`
|
|
55
|
+
2. `grep` for: `urlopen\\(`, `urllib\\.request`
|
|
56
|
+
3. `grep` for: `httpx`, `aiohttp`
|
|
57
|
+
4. Trace whether the URL argument originates from `request.args`, `request.form`, `request.json`
|
|
58
|
+
|
|
59
|
+
### Severity Assessment
|
|
60
|
+
|
|
61
|
+
- **High**: SSRF reaching internal services or cloud metadata
|
|
62
|
+
- **High**: SSRF with response body returned to attacker
|
|
63
|
+
- **Medium**: Blind SSRF (request sent, no response returned)
|
|
64
|
+
- **Low**: SSRF limited to specific protocols/hosts by validation
|
|
65
|
+
"""
|