@intentsolutionsio/penetration-tester 2.0.0 → 3.0.4
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.
- package/.claude-plugin/plugin.json +8 -3
- package/README.md +8 -0
- package/commands/pentest.md +5 -0
- package/package.json +8 -3
- package/skills/analyzing-tls-config/SKILL.md +221 -0
- package/skills/analyzing-tls-config/references/AUTHORIZATION.md +133 -0
- package/skills/analyzing-tls-config/references/PLAYBOOK.md +267 -0
- package/skills/analyzing-tls-config/references/THEORY.md +128 -0
- package/skills/analyzing-tls-config/scripts/analyze_tls.py +415 -0
- package/skills/auditing-cors-policy/SKILL.md +186 -0
- package/skills/auditing-cors-policy/references/PLAYBOOK.md +220 -0
- package/skills/auditing-cors-policy/references/THEORY.md +142 -0
- package/skills/auditing-cors-policy/scripts/audit_cors.py +350 -0
- package/skills/auditing-npm-dependencies/SKILL.md +254 -0
- package/skills/auditing-npm-dependencies/references/PLAYBOOK.md +175 -0
- package/skills/auditing-npm-dependencies/references/THEORY.md +122 -0
- package/skills/auditing-npm-dependencies/scripts/audit_npm.py +408 -0
- package/skills/auditing-python-dependencies/SKILL.md +251 -0
- package/skills/auditing-python-dependencies/references/PLAYBOOK.md +193 -0
- package/skills/auditing-python-dependencies/references/THEORY.md +122 -0
- package/skills/auditing-python-dependencies/scripts/audit_python.py +459 -0
- package/skills/checking-http-security-headers/SKILL.md +176 -0
- package/skills/checking-http-security-headers/references/PLAYBOOK.md +212 -0
- package/skills/checking-http-security-headers/references/THEORY.md +137 -0
- package/skills/checking-http-security-headers/scripts/check_headers.py +362 -0
- package/skills/checking-license-compliance/SKILL.md +225 -0
- package/skills/checking-license-compliance/references/PLAYBOOK.md +161 -0
- package/skills/checking-license-compliance/references/THEORY.md +152 -0
- package/skills/checking-license-compliance/scripts/check_licenses.py +461 -0
- package/skills/composing-vulnerability-report/SKILL.md +212 -0
- package/skills/composing-vulnerability-report/references/PLAYBOOK.md +180 -0
- package/skills/composing-vulnerability-report/references/THEORY.md +178 -0
- package/skills/composing-vulnerability-report/scripts/compose_report.py +396 -0
- package/skills/confirming-pentest-authorization/SKILL.md +247 -0
- package/skills/confirming-pentest-authorization/references/PLAYBOOK.md +189 -0
- package/skills/confirming-pentest-authorization/references/THEORY.md +167 -0
- package/skills/confirming-pentest-authorization/scripts/check_authorization.py +457 -0
- package/skills/defining-pentest-scope/SKILL.md +227 -0
- package/skills/defining-pentest-scope/references/PLAYBOOK.md +238 -0
- package/skills/defining-pentest-scope/references/THEORY.md +170 -0
- package/skills/defining-pentest-scope/scripts/define_scope.py +472 -0
- package/skills/detecting-command-injection-patterns/SKILL.md +144 -0
- package/skills/detecting-command-injection-patterns/references/PLAYBOOK.md +302 -0
- package/skills/detecting-command-injection-patterns/references/THEORY.md +206 -0
- package/skills/detecting-command-injection-patterns/scripts/scan_cmdi.py +290 -0
- package/skills/detecting-debug-endpoints/SKILL.md +207 -0
- package/skills/detecting-debug-endpoints/references/PLAYBOOK.md +402 -0
- package/skills/detecting-debug-endpoints/references/THEORY.md +218 -0
- package/skills/detecting-debug-endpoints/scripts/probe_debug.py +518 -0
- package/skills/detecting-directory-listing/SKILL.md +206 -0
- package/skills/detecting-directory-listing/references/PLAYBOOK.md +277 -0
- package/skills/detecting-directory-listing/references/THEORY.md +203 -0
- package/skills/detecting-directory-listing/scripts/probe_directory_listing.py +180 -0
- package/skills/detecting-eval-exec-usage/SKILL.md +128 -0
- package/skills/detecting-eval-exec-usage/references/PLAYBOOK.md +306 -0
- package/skills/detecting-eval-exec-usage/references/THEORY.md +159 -0
- package/skills/detecting-eval-exec-usage/scripts/scan_eval.py +223 -0
- package/skills/detecting-exposed-secrets-files/SKILL.md +179 -0
- package/skills/detecting-exposed-secrets-files/references/PLAYBOOK.md +274 -0
- package/skills/detecting-exposed-secrets-files/references/THEORY.md +174 -0
- package/skills/detecting-exposed-secrets-files/scripts/probe_secrets.py +207 -0
- package/skills/detecting-insecure-deserialization/SKILL.md +148 -0
- package/skills/detecting-insecure-deserialization/references/PLAYBOOK.md +333 -0
- package/skills/detecting-insecure-deserialization/references/THEORY.md +199 -0
- package/skills/detecting-insecure-deserialization/scripts/scan_deserialization.py +250 -0
- package/skills/detecting-sql-injection-patterns/SKILL.md +161 -0
- package/skills/detecting-sql-injection-patterns/references/PLAYBOOK.md +317 -0
- package/skills/detecting-sql-injection-patterns/references/THEORY.md +261 -0
- package/skills/detecting-sql-injection-patterns/scripts/scan_sqli.py +354 -0
- package/skills/detecting-ssl-cert-issues/SKILL.md +182 -0
- package/skills/detecting-ssl-cert-issues/references/PLAYBOOK.md +203 -0
- package/skills/detecting-ssl-cert-issues/references/THEORY.md +133 -0
- package/skills/detecting-ssl-cert-issues/scripts/check_cert_chain.py +481 -0
- package/skills/detecting-weak-cryptography/SKILL.md +147 -0
- package/skills/detecting-weak-cryptography/references/PLAYBOOK.md +466 -0
- package/skills/detecting-weak-cryptography/references/THEORY.md +194 -0
- package/skills/detecting-weak-cryptography/scripts/scan_weak_crypto.py +417 -0
- package/skills/fingerprinting-server-software/SKILL.md +191 -0
- package/skills/fingerprinting-server-software/references/PLAYBOOK.md +337 -0
- package/skills/fingerprinting-server-software/references/THEORY.md +183 -0
- package/skills/fingerprinting-server-software/scripts/fingerprint_server.py +347 -0
- package/skills/generating-executive-summary/SKILL.md +261 -0
- package/skills/generating-executive-summary/references/PLAYBOOK.md +201 -0
- package/skills/generating-executive-summary/references/THEORY.md +195 -0
- package/skills/generating-executive-summary/scripts/exec_summary.py +538 -0
- package/skills/mapping-findings-to-owasp-top10/SKILL.md +235 -0
- package/skills/mapping-findings-to-owasp-top10/references/PLAYBOOK.md +193 -0
- package/skills/mapping-findings-to-owasp-top10/references/THEORY.md +160 -0
- package/skills/mapping-findings-to-owasp-top10/scripts/map_owasp.py +540 -0
- package/skills/performing-penetration-testing/SKILL.md +282 -190
- package/skills/performing-penetration-testing/references/OWASP_TOP_10.md +22 -0
- package/skills/performing-penetration-testing/references/REMEDIATION_PLAYBOOK.md +46 -0
- package/skills/performing-penetration-testing/references/SECURITY_HEADERS.md +41 -0
- package/skills/performing-penetration-testing/scripts/code_security_scanner.py +144 -79
- package/skills/performing-penetration-testing/scripts/dependency_auditor.py +116 -93
- package/skills/performing-penetration-testing/scripts/security_scanner.py +574 -446
- package/skills/probing-dangerous-http-methods/SKILL.md +182 -0
- package/skills/probing-dangerous-http-methods/references/PLAYBOOK.md +234 -0
- package/skills/probing-dangerous-http-methods/references/THEORY.md +145 -0
- package/skills/probing-dangerous-http-methods/scripts/probe_methods.py +263 -0
- package/skills/recording-pentest-engagement/SKILL.md +253 -0
- package/skills/recording-pentest-engagement/references/PLAYBOOK.md +203 -0
- package/skills/recording-pentest-engagement/references/THEORY.md +195 -0
- package/skills/recording-pentest-engagement/scripts/record_engagement.py +461 -0
- package/skills/scanning-for-hardcoded-secrets/SKILL.md +215 -0
- package/skills/scanning-for-hardcoded-secrets/references/PLAYBOOK.md +325 -0
- package/skills/scanning-for-hardcoded-secrets/references/THEORY.md +175 -0
- package/skills/scanning-for-hardcoded-secrets/scripts/scan_secrets.py +395 -0
- package/skills/tracing-transitive-vulnerabilities/SKILL.md +235 -0
- package/skills/tracing-transitive-vulnerabilities/references/PLAYBOOK.md +233 -0
- package/skills/tracing-transitive-vulnerabilities/references/THEORY.md +138 -0
- package/skills/tracing-transitive-vulnerabilities/scripts/trace_vulns.py +484 -0
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: detecting-directory-listing
|
|
3
|
+
description: |
|
|
4
|
+
Probe a target for directories that return auto-generated index
|
|
5
|
+
listings instead of denying or serving a specific file — exposes
|
|
6
|
+
the full file tree under any reachable directory, including files
|
|
7
|
+
the application never linked to.
|
|
8
|
+
Use when: post-deploy verification on a static-asset host, security
|
|
9
|
+
audit before SOC2, or following up on a finding from skill #6
|
|
10
|
+
(exposed-files) where a backup-file path returned 200 with HTML
|
|
11
|
+
body instead of the expected file content (suggests autoindex
|
|
12
|
+
serving a directory listing).
|
|
13
|
+
Threshold: any directory-shaped path returns 200 with HTML body
|
|
14
|
+
matching the framework-specific autoindex fingerprint (nginx
|
|
15
|
+
fancyindex, Apache mod_autoindex Index of/, Caddy browse, Lighttpd
|
|
16
|
+
mod_dirlisting, etc.).
|
|
17
|
+
Trigger with: "directory listing check", "autoindex detection",
|
|
18
|
+
"open directory scan".
|
|
19
|
+
allowed-tools:
|
|
20
|
+
- Read
|
|
21
|
+
- Bash(python3:*)
|
|
22
|
+
- Bash(curl:*)
|
|
23
|
+
disallowed-tools:
|
|
24
|
+
- Bash(rm:*)
|
|
25
|
+
- Edit(/etc/*)
|
|
26
|
+
version: 3.0.0-dev
|
|
27
|
+
author: Jeremy Longshore <jeremy@intentsolutions.io>
|
|
28
|
+
license: MIT
|
|
29
|
+
compatibility: Designed for Claude Code
|
|
30
|
+
tags:
|
|
31
|
+
- security
|
|
32
|
+
- information-disclosure
|
|
33
|
+
- autoindex
|
|
34
|
+
- pentest
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
# Detecting Directory Listing
|
|
38
|
+
|
|
39
|
+
## Overview
|
|
40
|
+
|
|
41
|
+
Web servers can be configured to auto-generate an HTML index page
|
|
42
|
+
when a request hits a directory without a matching default file
|
|
43
|
+
(no `index.html` / `index.php` / etc.). The auto-generated page
|
|
44
|
+
lists every file in the directory. This is by design for static-
|
|
45
|
+
file servers and FTP-style file-sharing setups; it's a misconfiguration
|
|
46
|
+
on application servers where the file tree was never meant to be
|
|
47
|
+
public.
|
|
48
|
+
|
|
49
|
+
The risk compounds in two ways:
|
|
50
|
+
|
|
51
|
+
1. **File enumeration without prior knowledge.** Attackers find files
|
|
52
|
+
you didn't link to (backup files, log files, old uploads).
|
|
53
|
+
2. **Chaining with exposed-files (#6).** A `/.git/` request that
|
|
54
|
+
returns an autoindex listing reveals every object file under
|
|
55
|
+
`.git/objects/` and confirms the entire repo is reachable for
|
|
56
|
+
GitDumper-style reconstruction.
|
|
57
|
+
|
|
58
|
+
This skill probes commonly-vulnerable directory paths and grades
|
|
59
|
+
each based on the framework-specific autoindex fingerprint.
|
|
60
|
+
|
|
61
|
+
## When the skill produces findings
|
|
62
|
+
|
|
63
|
+
| Finding | Severity | Threshold | Affected control |
|
|
64
|
+
|---|---|---|---|
|
|
65
|
+
| Application-root directory listing | **HIGH** | `/` returns autoindex page | OWASP A05:2021 |
|
|
66
|
+
| Backup / upload / log directory listing | **HIGH** | `/backup/`, `/uploads/`, `/logs/`, `/dump/` autoindex | CWE-548 |
|
|
67
|
+
| Asset directory listing (`/assets/`, `/static/`) | **MEDIUM** | autoindex on asset dirs (enables file enumeration) | CWE-548 |
|
|
68
|
+
| Config directory listing | **CRITICAL** | `/config/`, `/conf/`, `/.config/` autoindex | NIST 800-53 SC-28 |
|
|
69
|
+
| VCS subdir listing (chains with skill #6) | **CRITICAL** | `/.git/`, `/.svn/`, `/.hg/` autoindex | NIST 800-53 SC-28 |
|
|
70
|
+
| Generic root reachable via autoindex | **MEDIUM** | `/` or arbitrary path autoindex on app server | OWASP A05:2021 |
|
|
71
|
+
|
|
72
|
+
## Prerequisites
|
|
73
|
+
|
|
74
|
+
- Python 3.9+ with `requests`
|
|
75
|
+
- Authorization for non-local targets
|
|
76
|
+
|
|
77
|
+
## Instructions
|
|
78
|
+
|
|
79
|
+
### Step 1 — Confirm Authorization
|
|
80
|
+
|
|
81
|
+
```text
|
|
82
|
+
"Do you have authorization to perform directory-listing discovery on
|
|
83
|
+
this target? I need confirmation before proceeding."
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Step 2 — Run the scanner
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
python3 ${CLAUDE_PLUGIN_ROOT}/skills/detecting-directory-listing/scripts/probe_directory_listing.py \
|
|
90
|
+
https://target.example.com \
|
|
91
|
+
--authorized
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Options:
|
|
95
|
+
|
|
96
|
+
```
|
|
97
|
+
Usage: probe_directory_listing.py URL [OPTIONS]
|
|
98
|
+
|
|
99
|
+
Options:
|
|
100
|
+
--authorized Attest authorization (required for non-local)
|
|
101
|
+
--output FILE Write findings to FILE
|
|
102
|
+
--format FMT json | jsonl | markdown (default: markdown)
|
|
103
|
+
--min-severity SEV (default: info)
|
|
104
|
+
--timeout SECS Per-probe timeout (default: 10)
|
|
105
|
+
--paths-file FILE Custom probe set (one path per line)
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
For each candidate directory path, the scanner appends a trailing
|
|
109
|
+
slash and sends a GET. If the response is 200 and the HTML body
|
|
110
|
+
matches one of the canonical autoindex fingerprints (Apache,
|
|
111
|
+
nginx, Caddy, Lighttpd, IIS, Python http.server, Node serve, etc.),
|
|
112
|
+
it's a finding.
|
|
113
|
+
|
|
114
|
+
### Step 3 — Interpret findings
|
|
115
|
+
|
|
116
|
+
CRITICAL = config or VCS directory listing → direct credential or
|
|
117
|
+
source-code exposure when combined with skill #6's secret-file
|
|
118
|
+
probe. Ship same-hour fix.
|
|
119
|
+
|
|
120
|
+
HIGH = backup / upload / log / app-root listing → significant file
|
|
121
|
+
enumeration. Often reveals backup files (`.bak`, `.swp`, `.orig`),
|
|
122
|
+
log files with embedded credentials in URLs, and orphaned uploads
|
|
123
|
+
from old releases. Ship within sprint.
|
|
124
|
+
|
|
125
|
+
MEDIUM = asset directory listing → file enumeration but bounded
|
|
126
|
+
risk if asset content is genuinely public. Still better to disable
|
|
127
|
+
than to leave on.
|
|
128
|
+
|
|
129
|
+
### Step 4 — Cross-skill chaining
|
|
130
|
+
|
|
131
|
+
After this skill, suggest:
|
|
132
|
+
|
|
133
|
+
- `detecting-exposed-secrets-files` (#6) — if any of the secret-
|
|
134
|
+
file paths (`.git/`, `.env`) return an autoindex page instead of
|
|
135
|
+
the expected file, that's the autoindex feature confirming repo
|
|
136
|
+
exposure.
|
|
137
|
+
- `detecting-debug-endpoints` (#7) — directory listings under
|
|
138
|
+
framework paths (`/static/`, `/public/`) sometimes expose
|
|
139
|
+
framework-debug artifacts.
|
|
140
|
+
|
|
141
|
+
## Examples
|
|
142
|
+
|
|
143
|
+
### Example 1 — Static-asset host audit
|
|
144
|
+
|
|
145
|
+
User: "We just spun up a new S3 + CloudFront. Verify autoindex isn't
|
|
146
|
+
on by accident."
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
python3 ${CLAUDE_PLUGIN_ROOT}/skills/detecting-directory-listing/scripts/probe_directory_listing.py \
|
|
150
|
+
https://cdn.example.com --authorized --min-severity medium
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
S3 buckets configured with `ListBucket` permissions return XML
|
|
154
|
+
directory listings; the scanner detects those too.
|
|
155
|
+
|
|
156
|
+
### Example 2 — Following up on .git exposure
|
|
157
|
+
|
|
158
|
+
User: "Skill #6 found .git/HEAD exposed. Check if the full directory
|
|
159
|
+
is browseable."
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
python3 ${CLAUDE_PLUGIN_ROOT}/skills/detecting-directory-listing/scripts/probe_directory_listing.py \
|
|
163
|
+
https://app.example.com --authorized --paths-file <(echo .git/)
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
If the `.git/` probe returns an autoindex listing instead of the
|
|
167
|
+
404 it should, the whole repository is reachable for reconstruction
|
|
168
|
+
via GitDumper-style tools.
|
|
169
|
+
|
|
170
|
+
### Example 3 — CI gate against autoindex regression
|
|
171
|
+
|
|
172
|
+
```yaml
|
|
173
|
+
- name: Directory-listing gate
|
|
174
|
+
run: |
|
|
175
|
+
python3 plugins/security/penetration-tester/skills/detecting-directory-listing/scripts/probe_directory_listing.py \
|
|
176
|
+
"${{ secrets.STAGING_URL }}" \
|
|
177
|
+
--authorized --min-severity high
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
Exit 1 fails the deploy if any HIGH or CRITICAL autoindex finding
|
|
181
|
+
lands. Catches the regression where a new vhost gets deployed
|
|
182
|
+
without the deny-autoindex directive.
|
|
183
|
+
|
|
184
|
+
## Output
|
|
185
|
+
|
|
186
|
+
JSON / JSONL / Markdown. Exit codes: 0 clean, 1 high/critical, 2 error.
|
|
187
|
+
|
|
188
|
+
## Error Handling
|
|
189
|
+
|
|
190
|
+
- **Target returns SPA index for every URL** → the scanner's
|
|
191
|
+
fingerprint check distinguishes a real autoindex from an SPA
|
|
192
|
+
catch-all. SPAs don't generate autoindex-shaped HTML.
|
|
193
|
+
- **WAF blocks the scanner** → expected behavior on
|
|
194
|
+
Cloudflare-fronted hosts. The fingerprint will be of the CDN's
|
|
195
|
+
block page, not the origin.
|
|
196
|
+
- **Connection error** → exit 2.
|
|
197
|
+
|
|
198
|
+
## Resources
|
|
199
|
+
|
|
200
|
+
- `references/THEORY.md` — Per-server autoindex behavior, fingerprint
|
|
201
|
+
patterns, S3 / GCS / Azure Blob considerations
|
|
202
|
+
- `references/PLAYBOOK.md` — Per-server config to disable
|
|
203
|
+
autoindex (nginx `autoindex off`, Apache `Options -Indexes`,
|
|
204
|
+
Caddy disabling browse, etc.) + cloud-storage equivalents
|
|
205
|
+
- `../analyzing-tls-config/references/AUTHORIZATION.md` — Active-scan
|
|
206
|
+
authorization pattern
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
# Directory-Listing Remediation Playbook
|
|
2
|
+
|
|
3
|
+
## Apache mod_autoindex
|
|
4
|
+
|
|
5
|
+
### Disable autoindex globally (preferred)
|
|
6
|
+
|
|
7
|
+
`httpd.conf` or vhost:
|
|
8
|
+
|
|
9
|
+
```apache
|
|
10
|
+
<Directory /var/www/html>
|
|
11
|
+
Options -Indexes
|
|
12
|
+
AllowOverride None
|
|
13
|
+
Require all granted
|
|
14
|
+
</Directory>
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
The `Options -Indexes` line is the critical part. Combined with no
|
|
18
|
+
`index.html` in a directory, requests return 403 (per Apache's
|
|
19
|
+
default fall-through) instead of an autoindex page.
|
|
20
|
+
|
|
21
|
+
### Per-directory override
|
|
22
|
+
|
|
23
|
+
If autoindex is needed in one specific directory (rare):
|
|
24
|
+
|
|
25
|
+
```apache
|
|
26
|
+
<Directory /var/www/html/public-archive>
|
|
27
|
+
Options +Indexes
|
|
28
|
+
IndexOptions +FancyIndexing +SuppressDescription
|
|
29
|
+
</Directory>
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
But this should require a deliberate decision, not a default.
|
|
33
|
+
|
|
34
|
+
### .htaccess (if `AllowOverride` permits)
|
|
35
|
+
|
|
36
|
+
```apache
|
|
37
|
+
Options -Indexes
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
In every directory you want to deny. The `AllowOverride` in vhost
|
|
41
|
+
must include `Options` for this to work.
|
|
42
|
+
|
|
43
|
+
## nginx autoindex
|
|
44
|
+
|
|
45
|
+
nginx defaults to autoindex off. The misconfiguration is when
|
|
46
|
+
someone explicitly enabled it:
|
|
47
|
+
|
|
48
|
+
```nginx
|
|
49
|
+
# In vhost or location block, REMOVE these lines if present:
|
|
50
|
+
autoindex on;
|
|
51
|
+
autoindex_format html;
|
|
52
|
+
autoindex_exact_size off;
|
|
53
|
+
autoindex_localtime on;
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
To explicitly assert off:
|
|
57
|
+
|
|
58
|
+
```nginx
|
|
59
|
+
location / {
|
|
60
|
+
autoindex off;
|
|
61
|
+
try_files $uri $uri/ =404;
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
The `try_files` directive ensures missing files return 404 instead
|
|
66
|
+
of any fallback behavior.
|
|
67
|
+
|
|
68
|
+
## Caddy file_server
|
|
69
|
+
|
|
70
|
+
Don't use `browse`:
|
|
71
|
+
|
|
72
|
+
```caddy
|
|
73
|
+
example.com {
|
|
74
|
+
root * /srv/site
|
|
75
|
+
file_server # NO browse keyword
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
If browse was previously enabled, remove it:
|
|
80
|
+
|
|
81
|
+
```caddy
|
|
82
|
+
# BEFORE (vulnerable):
|
|
83
|
+
example.com {
|
|
84
|
+
root * /srv/site
|
|
85
|
+
file_server browse
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
# AFTER:
|
|
89
|
+
example.com {
|
|
90
|
+
root * /srv/site
|
|
91
|
+
file_server
|
|
92
|
+
try_files {path} {path}/index.html =404
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Python http.server
|
|
97
|
+
|
|
98
|
+
Don't use in production. Period. If you absolutely must serve static
|
|
99
|
+
files with python in a constrained environment, use a real server
|
|
100
|
+
behind it OR a subclass that overrides `list_directory`:
|
|
101
|
+
|
|
102
|
+
```python
|
|
103
|
+
from http.server import HTTPServer, SimpleHTTPRequestHandler
|
|
104
|
+
|
|
105
|
+
class NoBrowse(SimpleHTTPRequestHandler):
|
|
106
|
+
def list_directory(self, path):
|
|
107
|
+
self.send_error(403, "Directory listing not permitted")
|
|
108
|
+
return None
|
|
109
|
+
|
|
110
|
+
HTTPServer(('0.0.0.0', 8000), NoBrowse).serve_forever()
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## Node serve / http-server
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
# serve — pass --single to disable directory listing
|
|
117
|
+
npx serve --single ./build
|
|
118
|
+
|
|
119
|
+
# http-server — pass -d false
|
|
120
|
+
npx http-server -d false ./build
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Both options return 404 for missing files instead of generating a
|
|
124
|
+
listing page.
|
|
125
|
+
|
|
126
|
+
## IIS
|
|
127
|
+
|
|
128
|
+
`web.config`:
|
|
129
|
+
|
|
130
|
+
```xml
|
|
131
|
+
<system.webServer>
|
|
132
|
+
<directoryBrowse enabled="false" />
|
|
133
|
+
<defaultDocument>
|
|
134
|
+
<files>
|
|
135
|
+
<add value="index.html" />
|
|
136
|
+
<add value="default.htm" />
|
|
137
|
+
</files>
|
|
138
|
+
</defaultDocument>
|
|
139
|
+
</system.webServer>
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## AWS S3
|
|
143
|
+
|
|
144
|
+
### Block public ListBucket
|
|
145
|
+
|
|
146
|
+
S3 bucket policy — explicitly deny ListBucket to public:
|
|
147
|
+
|
|
148
|
+
```json
|
|
149
|
+
{
|
|
150
|
+
"Version": "2012-10-17",
|
|
151
|
+
"Statement": [
|
|
152
|
+
{
|
|
153
|
+
"Sid": "DenyPublicList",
|
|
154
|
+
"Effect": "Deny",
|
|
155
|
+
"Principal": "*",
|
|
156
|
+
"Action": "s3:ListBucket",
|
|
157
|
+
"Resource": "arn:aws:s3:::my-public-cdn-bucket"
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
"Sid": "AllowPublicRead",
|
|
161
|
+
"Effect": "Allow",
|
|
162
|
+
"Principal": "*",
|
|
163
|
+
"Action": "s3:GetObject",
|
|
164
|
+
"Resource": "arn:aws:s3:::my-public-cdn-bucket/*"
|
|
165
|
+
}
|
|
166
|
+
]
|
|
167
|
+
}
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
Even better: enable S3 Block Public Access at the account level:
|
|
171
|
+
|
|
172
|
+
```bash
|
|
173
|
+
aws s3api put-public-access-block --bucket my-bucket \
|
|
174
|
+
--public-access-block-configuration \
|
|
175
|
+
BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
This blocks ALL public access at the bucket level. To then serve
|
|
179
|
+
content publicly, do it via CloudFront with Origin Access Control,
|
|
180
|
+
NOT via direct S3 URLs.
|
|
181
|
+
|
|
182
|
+
## AWS CloudFront in front of S3
|
|
183
|
+
|
|
184
|
+
When using CloudFront with Origin Access Control (recommended
|
|
185
|
+
pattern):
|
|
186
|
+
|
|
187
|
+
1. S3 bucket policy denies all public access.
|
|
188
|
+
2. Only the OAC's CloudFront distribution can read objects.
|
|
189
|
+
3. CloudFront's behavior settings determine what's reachable.
|
|
190
|
+
|
|
191
|
+
```bash
|
|
192
|
+
# Lock down the bucket
|
|
193
|
+
aws s3api put-bucket-policy --bucket my-bucket --policy '{
|
|
194
|
+
"Version": "2012-10-17",
|
|
195
|
+
"Statement": [{
|
|
196
|
+
"Sid": "AllowCloudFrontOAC",
|
|
197
|
+
"Effect": "Allow",
|
|
198
|
+
"Principal": {"Service": "cloudfront.amazonaws.com"},
|
|
199
|
+
"Action": "s3:GetObject",
|
|
200
|
+
"Resource": "arn:aws:s3:::my-bucket/*",
|
|
201
|
+
"Condition": {
|
|
202
|
+
"StringEquals": {
|
|
203
|
+
"AWS:SourceArn": "arn:aws:cloudfront::ACCOUNT:distribution/DIST_ID"
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}]
|
|
207
|
+
}'
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
## GCS
|
|
211
|
+
|
|
212
|
+
```bash
|
|
213
|
+
# Remove allUsers from objectViewer role
|
|
214
|
+
gsutil iam ch -d allUsers:objectViewer gs://my-bucket
|
|
215
|
+
|
|
216
|
+
# Make individual objects publicly readable instead (per-object ACL)
|
|
217
|
+
gsutil acl ch -u AllUsers:R gs://my-bucket/path/to/specific-file.png
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
Or use a Cloud CDN + signed URLs for true private serving.
|
|
221
|
+
|
|
222
|
+
## Azure Blob
|
|
223
|
+
|
|
224
|
+
```bash
|
|
225
|
+
# Set container access level to Private (not Container)
|
|
226
|
+
az storage container set-permission \
|
|
227
|
+
--account-name myaccount \
|
|
228
|
+
--name mycontainer \
|
|
229
|
+
--public-access off
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
Then use SAS tokens or Azure CDN with private origin for public-facing
|
|
233
|
+
content.
|
|
234
|
+
|
|
235
|
+
## CI integration
|
|
236
|
+
|
|
237
|
+
```yaml
|
|
238
|
+
- name: Directory-listing posture gate
|
|
239
|
+
run: |
|
|
240
|
+
python3 plugins/security/penetration-tester/skills/detecting-directory-listing/scripts/probe_directory_listing.py \
|
|
241
|
+
"${{ secrets.PROD_URL }}" \
|
|
242
|
+
--authorized \
|
|
243
|
+
--min-severity high \
|
|
244
|
+
--format json \
|
|
245
|
+
--output autoindex-report.json
|
|
246
|
+
- run: |
|
|
247
|
+
if jq 'any(.severity == "critical" or .severity == "high")' autoindex-report.json | grep -q true; then
|
|
248
|
+
echo "::error::Directory listing detected"
|
|
249
|
+
exit 1
|
|
250
|
+
fi
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
## After remediation: assume enumeration occurred
|
|
254
|
+
|
|
255
|
+
If autoindex was exposed for any period:
|
|
256
|
+
|
|
257
|
+
- For `/backup/` listings: assume all backup files were enumerated.
|
|
258
|
+
Rotate any credentials referenced in those files. Investigate
|
|
259
|
+
whether any backup files contained PII that needs breach
|
|
260
|
+
notification.
|
|
261
|
+
- For `/uploads/` listings: assume the file index was scraped.
|
|
262
|
+
If any uploaded files were private (e.g., user-uploaded
|
|
263
|
+
documents), assume those filenames are known to attackers
|
|
264
|
+
even if the file contents required separate auth to fetch.
|
|
265
|
+
- For `/.git/` listings: assume the entire repository was
|
|
266
|
+
reconstructed. Rotate every credential ever committed to the
|
|
267
|
+
repo, including credentials in past commits that were later
|
|
268
|
+
removed.
|
|
269
|
+
|
|
270
|
+
## Verification after remediation
|
|
271
|
+
|
|
272
|
+
```bash
|
|
273
|
+
python3 ${CLAUDE_PLUGIN_ROOT}/skills/detecting-directory-listing/scripts/probe_directory_listing.py \
|
|
274
|
+
https://example.com --authorized --min-severity info
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
Expected: exit 0, zero findings of any severity.
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
# Directory-Listing Theory
|
|
2
|
+
|
|
3
|
+
## The recurring pattern
|
|
4
|
+
|
|
5
|
+
Web servers default-handle a directory request in one of three ways:
|
|
6
|
+
|
|
7
|
+
1. **Serve the directory's `index` file** (`index.html`, `index.php`,
|
|
8
|
+
etc.) — the safe default.
|
|
9
|
+
2. **Generate an autoindex** — list every file in the directory as
|
|
10
|
+
HTML. The dangerous default.
|
|
11
|
+
3. **Return 403 / 404** — deny the listing entirely. The defensive
|
|
12
|
+
default.
|
|
13
|
+
|
|
14
|
+
Which behavior applies depends on the server's configuration and
|
|
15
|
+
whether an index file is present. The risk surface: a directory
|
|
16
|
+
without an `index.html`, where the server is configured to
|
|
17
|
+
fall back to autoindex.
|
|
18
|
+
|
|
19
|
+
This was the default in older Apache configurations. Modern stacks
|
|
20
|
+
(Caddy, nginx defaults) deny by default but legacy configs still
|
|
21
|
+
have it enabled.
|
|
22
|
+
|
|
23
|
+
## Per-server behavior
|
|
24
|
+
|
|
25
|
+
### Apache mod_autoindex
|
|
26
|
+
|
|
27
|
+
```apache
|
|
28
|
+
Options +Indexes # autoindex ENABLED — bad on app servers
|
|
29
|
+
Options -Indexes # autoindex DISABLED — safe default
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
The default in Apache 2.4 depends on the distribution: Debian /
|
|
33
|
+
Ubuntu have `Indexes` enabled in the default `<Directory /var/www/>`
|
|
34
|
+
block. Red Hat / CentOS disable it by default.
|
|
35
|
+
|
|
36
|
+
Fingerprint: `<title>Index of /<path></title>` + an HTML table of
|
|
37
|
+
file entries with mtime + size columns.
|
|
38
|
+
|
|
39
|
+
### nginx autoindex
|
|
40
|
+
|
|
41
|
+
```nginx
|
|
42
|
+
location /uploads/ {
|
|
43
|
+
autoindex on; # enabled — bad
|
|
44
|
+
}
|
|
45
|
+
location /uploads/ {
|
|
46
|
+
autoindex off; # disabled — safe (default)
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
nginx defaults to autoindex off; you have to explicitly enable it.
|
|
51
|
+
When seen, it's a deliberate config someone added.
|
|
52
|
+
|
|
53
|
+
Fingerprint: HTML body with `<pre>` formatting, lines like
|
|
54
|
+
`<a href="filename">filename</a>` with size + mtime columns.
|
|
55
|
+
|
|
56
|
+
### Caddy file_server browse
|
|
57
|
+
|
|
58
|
+
```caddy
|
|
59
|
+
example.com {
|
|
60
|
+
root * /srv/site
|
|
61
|
+
file_server browse # autoindex enabled
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
vs.
|
|
66
|
+
|
|
67
|
+
```caddy
|
|
68
|
+
example.com {
|
|
69
|
+
root * /srv/site
|
|
70
|
+
file_server # no browse — disabled (default)
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Caddy v2 defaults to NOT browse. The browse mode generates a styled
|
|
75
|
+
HTML directory listing.
|
|
76
|
+
|
|
77
|
+
Fingerprint: `<table class="listing">` with sortable column headers.
|
|
78
|
+
|
|
79
|
+
### Lighttpd mod_dirlisting
|
|
80
|
+
|
|
81
|
+
```lighttpd
|
|
82
|
+
dir-listing.activate = "enable"
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Less common in modern deployments but still found in IoT firmware
|
|
86
|
+
web UIs and embedded server setups.
|
|
87
|
+
|
|
88
|
+
### Python http.server (dev only)
|
|
89
|
+
|
|
90
|
+
`python3 -m http.server 8000` — the stdlib dev server defaults to
|
|
91
|
+
autoindex. Should never be in production but sometimes runs on a
|
|
92
|
+
forgotten port.
|
|
93
|
+
|
|
94
|
+
Fingerprint: `<title>Directory listing for /<path></title>`.
|
|
95
|
+
|
|
96
|
+
### Node `serve` / `http-server`
|
|
97
|
+
|
|
98
|
+
`npx serve` and `http-server` both default to autoindex. Common in
|
|
99
|
+
developer-facing tooling, hosting docs sites, or "I just need a
|
|
100
|
+
quick static server" setups.
|
|
101
|
+
|
|
102
|
+
### IIS
|
|
103
|
+
|
|
104
|
+
IIS disables directory browsing by default. If enabled in
|
|
105
|
+
`web.config`:
|
|
106
|
+
|
|
107
|
+
```xml
|
|
108
|
+
<system.webServer>
|
|
109
|
+
<directoryBrowse enabled="true" />
|
|
110
|
+
</system.webServer>
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Fingerprint: HTML body with "Directory browsing" title.
|
|
114
|
+
|
|
115
|
+
### AWS S3 ListBucket
|
|
116
|
+
|
|
117
|
+
S3 buckets aren't web servers in the conventional sense but expose
|
|
118
|
+
similar functionality. A bucket policy that grants `s3:ListBucket`
|
|
119
|
+
to the public lets unauthenticated requestors list every object.
|
|
120
|
+
|
|
121
|
+
Fingerprint: XML response with `<ListBucketResult>` root element,
|
|
122
|
+
listing every key in the bucket.
|
|
123
|
+
|
|
124
|
+
### Azure Blob Storage list-blob
|
|
125
|
+
|
|
126
|
+
Same pattern: `?restype=container&comp=list` on a public-list
|
|
127
|
+
container returns XML enumeration of every blob.
|
|
128
|
+
|
|
129
|
+
Fingerprint: `<EnumerationResults>` root + blob entries with
|
|
130
|
+
properties.
|
|
131
|
+
|
|
132
|
+
### GCS bucket "list" permission
|
|
133
|
+
|
|
134
|
+
GCP Cloud Storage equivalent: `storage.objects.list` granted to
|
|
135
|
+
`allUsers` lets anyone enumerate bucket contents.
|
|
136
|
+
|
|
137
|
+
Fingerprint: similar XML / JSON enumeration depending on whether
|
|
138
|
+
the request hit the JSON or XML API.
|
|
139
|
+
|
|
140
|
+
## Why directory listings escalate other findings
|
|
141
|
+
|
|
142
|
+
Directory listings rarely stand alone. They compound the impact of
|
|
143
|
+
other findings:
|
|
144
|
+
|
|
145
|
+
### Combined with `.git/` exposure
|
|
146
|
+
|
|
147
|
+
Skill #6 detects `.git/HEAD` reachable. If the parent `.git/`
|
|
148
|
+
directory ALSO has autoindex on, every object in `.git/objects/`
|
|
149
|
+
is enumerable. GitDumper-style tools become unnecessary — you can
|
|
150
|
+
just `wget -r` the directory.
|
|
151
|
+
|
|
152
|
+
### Combined with backup-file exposure
|
|
153
|
+
|
|
154
|
+
Skill #6 probes specific backup file paths (`backup.sql`,
|
|
155
|
+
`dump.sql`). Autoindex on `/backup/` lets the attacker see every
|
|
156
|
+
backup file the operator ever left, including ones with non-
|
|
157
|
+
canonical names (`db-snapshot-2024-03-15.sql.bz2`,
|
|
158
|
+
`pre-migration-dump.sql`).
|
|
159
|
+
|
|
160
|
+
### Combined with upload directories
|
|
161
|
+
|
|
162
|
+
If `/uploads/` lists, every user-uploaded file is enumerable
|
|
163
|
+
including potentially-sensitive private uploads that the
|
|
164
|
+
application's auth layer was supposed to gate.
|
|
165
|
+
|
|
166
|
+
## Why static-asset hosts are a common pitfall
|
|
167
|
+
|
|
168
|
+
A common pattern: front-end is hosted on a CDN (S3 plus CloudFront,
|
|
169
|
+
GCS plus Cloud CDN). The bucket policy is set to "allow public read
|
|
170
|
+
of specific objects" but the BUCKET listing permission is left at
|
|
171
|
+
default, which on S3 with an explicit grant is "public ListBucket."
|
|
172
|
+
|
|
173
|
+
The fix is to:
|
|
174
|
+
|
|
175
|
+
1. Make individual objects publicly readable (`s3:GetObject`).
|
|
176
|
+
2. NOT grant `s3:ListBucket` to `*`.
|
|
177
|
+
|
|
178
|
+
Result: clients can fetch `https://cdn.example.com/assets/logo.png`
|
|
179
|
+
if they know the path, but `https://cdn.example.com/assets/`
|
|
180
|
+
returns AccessDenied.
|
|
181
|
+
|
|
182
|
+
## Why the SPA case isn't autoindex
|
|
183
|
+
|
|
184
|
+
Single-Page Applications using client-side routing return the app's
|
|
185
|
+
`index.html` for any unknown route. That's NOT an autoindex page —
|
|
186
|
+
it's a deliberate routing decision. The fingerprint check
|
|
187
|
+
distinguishes:
|
|
188
|
+
|
|
189
|
+
- Autoindex → HTML body contains "Index of /<path>" or framework
|
|
190
|
+
banner
|
|
191
|
+
- SPA → HTML body is the application's own UI
|
|
192
|
+
|
|
193
|
+
A 200 response with HTML body that doesn't match any autoindex
|
|
194
|
+
fingerprint is an SPA catch-all and is NOT flagged.
|
|
195
|
+
|
|
196
|
+
## Primary sources
|
|
197
|
+
|
|
198
|
+
- [OWASP WSTG-CONF-04 — Review Old Backup and Unreferenced Files](https://owasp.org/www-project-web-security-testing-guide/v42/4-Web_Application_Security_Testing/02-Configuration_and_Deployment_Management_Testing/04-Review_Old_Backup_and_Unreferenced_Files_for_Sensitive_Information)
|
|
199
|
+
- [CWE-548 — Exposure of Information Through Directory Listing](https://cwe.mitre.org/data/definitions/548.html)
|
|
200
|
+
- [nginx autoindex module](https://nginx.org/en/docs/http/ngx_http_autoindex_module.html)
|
|
201
|
+
- [Apache mod_autoindex](https://httpd.apache.org/docs/2.4/mod/mod_autoindex.html)
|
|
202
|
+
- [Caddy file_server directive](https://caddyserver.com/docs/caddyfile/directives/file_server)
|
|
203
|
+
- [AWS S3 — Blocking public access](https://docs.aws.amazon.com/AmazonS3/latest/userguide/access-control-block-public-access.html)
|