@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.
Files changed (112) hide show
  1. package/.claude-plugin/plugin.json +8 -3
  2. package/README.md +8 -0
  3. package/commands/pentest.md +5 -0
  4. package/package.json +8 -3
  5. package/skills/analyzing-tls-config/SKILL.md +221 -0
  6. package/skills/analyzing-tls-config/references/AUTHORIZATION.md +133 -0
  7. package/skills/analyzing-tls-config/references/PLAYBOOK.md +267 -0
  8. package/skills/analyzing-tls-config/references/THEORY.md +128 -0
  9. package/skills/analyzing-tls-config/scripts/analyze_tls.py +415 -0
  10. package/skills/auditing-cors-policy/SKILL.md +186 -0
  11. package/skills/auditing-cors-policy/references/PLAYBOOK.md +220 -0
  12. package/skills/auditing-cors-policy/references/THEORY.md +142 -0
  13. package/skills/auditing-cors-policy/scripts/audit_cors.py +350 -0
  14. package/skills/auditing-npm-dependencies/SKILL.md +254 -0
  15. package/skills/auditing-npm-dependencies/references/PLAYBOOK.md +175 -0
  16. package/skills/auditing-npm-dependencies/references/THEORY.md +122 -0
  17. package/skills/auditing-npm-dependencies/scripts/audit_npm.py +408 -0
  18. package/skills/auditing-python-dependencies/SKILL.md +251 -0
  19. package/skills/auditing-python-dependencies/references/PLAYBOOK.md +193 -0
  20. package/skills/auditing-python-dependencies/references/THEORY.md +122 -0
  21. package/skills/auditing-python-dependencies/scripts/audit_python.py +459 -0
  22. package/skills/checking-http-security-headers/SKILL.md +176 -0
  23. package/skills/checking-http-security-headers/references/PLAYBOOK.md +212 -0
  24. package/skills/checking-http-security-headers/references/THEORY.md +137 -0
  25. package/skills/checking-http-security-headers/scripts/check_headers.py +362 -0
  26. package/skills/checking-license-compliance/SKILL.md +225 -0
  27. package/skills/checking-license-compliance/references/PLAYBOOK.md +161 -0
  28. package/skills/checking-license-compliance/references/THEORY.md +152 -0
  29. package/skills/checking-license-compliance/scripts/check_licenses.py +461 -0
  30. package/skills/composing-vulnerability-report/SKILL.md +212 -0
  31. package/skills/composing-vulnerability-report/references/PLAYBOOK.md +180 -0
  32. package/skills/composing-vulnerability-report/references/THEORY.md +178 -0
  33. package/skills/composing-vulnerability-report/scripts/compose_report.py +396 -0
  34. package/skills/confirming-pentest-authorization/SKILL.md +247 -0
  35. package/skills/confirming-pentest-authorization/references/PLAYBOOK.md +189 -0
  36. package/skills/confirming-pentest-authorization/references/THEORY.md +167 -0
  37. package/skills/confirming-pentest-authorization/scripts/check_authorization.py +457 -0
  38. package/skills/defining-pentest-scope/SKILL.md +227 -0
  39. package/skills/defining-pentest-scope/references/PLAYBOOK.md +238 -0
  40. package/skills/defining-pentest-scope/references/THEORY.md +170 -0
  41. package/skills/defining-pentest-scope/scripts/define_scope.py +472 -0
  42. package/skills/detecting-command-injection-patterns/SKILL.md +144 -0
  43. package/skills/detecting-command-injection-patterns/references/PLAYBOOK.md +302 -0
  44. package/skills/detecting-command-injection-patterns/references/THEORY.md +206 -0
  45. package/skills/detecting-command-injection-patterns/scripts/scan_cmdi.py +290 -0
  46. package/skills/detecting-debug-endpoints/SKILL.md +207 -0
  47. package/skills/detecting-debug-endpoints/references/PLAYBOOK.md +402 -0
  48. package/skills/detecting-debug-endpoints/references/THEORY.md +218 -0
  49. package/skills/detecting-debug-endpoints/scripts/probe_debug.py +518 -0
  50. package/skills/detecting-directory-listing/SKILL.md +206 -0
  51. package/skills/detecting-directory-listing/references/PLAYBOOK.md +277 -0
  52. package/skills/detecting-directory-listing/references/THEORY.md +203 -0
  53. package/skills/detecting-directory-listing/scripts/probe_directory_listing.py +180 -0
  54. package/skills/detecting-eval-exec-usage/SKILL.md +128 -0
  55. package/skills/detecting-eval-exec-usage/references/PLAYBOOK.md +306 -0
  56. package/skills/detecting-eval-exec-usage/references/THEORY.md +159 -0
  57. package/skills/detecting-eval-exec-usage/scripts/scan_eval.py +223 -0
  58. package/skills/detecting-exposed-secrets-files/SKILL.md +179 -0
  59. package/skills/detecting-exposed-secrets-files/references/PLAYBOOK.md +274 -0
  60. package/skills/detecting-exposed-secrets-files/references/THEORY.md +174 -0
  61. package/skills/detecting-exposed-secrets-files/scripts/probe_secrets.py +207 -0
  62. package/skills/detecting-insecure-deserialization/SKILL.md +148 -0
  63. package/skills/detecting-insecure-deserialization/references/PLAYBOOK.md +333 -0
  64. package/skills/detecting-insecure-deserialization/references/THEORY.md +199 -0
  65. package/skills/detecting-insecure-deserialization/scripts/scan_deserialization.py +250 -0
  66. package/skills/detecting-sql-injection-patterns/SKILL.md +161 -0
  67. package/skills/detecting-sql-injection-patterns/references/PLAYBOOK.md +317 -0
  68. package/skills/detecting-sql-injection-patterns/references/THEORY.md +261 -0
  69. package/skills/detecting-sql-injection-patterns/scripts/scan_sqli.py +354 -0
  70. package/skills/detecting-ssl-cert-issues/SKILL.md +182 -0
  71. package/skills/detecting-ssl-cert-issues/references/PLAYBOOK.md +203 -0
  72. package/skills/detecting-ssl-cert-issues/references/THEORY.md +133 -0
  73. package/skills/detecting-ssl-cert-issues/scripts/check_cert_chain.py +481 -0
  74. package/skills/detecting-weak-cryptography/SKILL.md +147 -0
  75. package/skills/detecting-weak-cryptography/references/PLAYBOOK.md +466 -0
  76. package/skills/detecting-weak-cryptography/references/THEORY.md +194 -0
  77. package/skills/detecting-weak-cryptography/scripts/scan_weak_crypto.py +417 -0
  78. package/skills/fingerprinting-server-software/SKILL.md +191 -0
  79. package/skills/fingerprinting-server-software/references/PLAYBOOK.md +337 -0
  80. package/skills/fingerprinting-server-software/references/THEORY.md +183 -0
  81. package/skills/fingerprinting-server-software/scripts/fingerprint_server.py +347 -0
  82. package/skills/generating-executive-summary/SKILL.md +261 -0
  83. package/skills/generating-executive-summary/references/PLAYBOOK.md +201 -0
  84. package/skills/generating-executive-summary/references/THEORY.md +195 -0
  85. package/skills/generating-executive-summary/scripts/exec_summary.py +538 -0
  86. package/skills/mapping-findings-to-owasp-top10/SKILL.md +235 -0
  87. package/skills/mapping-findings-to-owasp-top10/references/PLAYBOOK.md +193 -0
  88. package/skills/mapping-findings-to-owasp-top10/references/THEORY.md +160 -0
  89. package/skills/mapping-findings-to-owasp-top10/scripts/map_owasp.py +540 -0
  90. package/skills/performing-penetration-testing/SKILL.md +282 -190
  91. package/skills/performing-penetration-testing/references/OWASP_TOP_10.md +22 -0
  92. package/skills/performing-penetration-testing/references/REMEDIATION_PLAYBOOK.md +46 -0
  93. package/skills/performing-penetration-testing/references/SECURITY_HEADERS.md +41 -0
  94. package/skills/performing-penetration-testing/scripts/code_security_scanner.py +144 -79
  95. package/skills/performing-penetration-testing/scripts/dependency_auditor.py +116 -93
  96. package/skills/performing-penetration-testing/scripts/security_scanner.py +574 -446
  97. package/skills/probing-dangerous-http-methods/SKILL.md +182 -0
  98. package/skills/probing-dangerous-http-methods/references/PLAYBOOK.md +234 -0
  99. package/skills/probing-dangerous-http-methods/references/THEORY.md +145 -0
  100. package/skills/probing-dangerous-http-methods/scripts/probe_methods.py +263 -0
  101. package/skills/recording-pentest-engagement/SKILL.md +253 -0
  102. package/skills/recording-pentest-engagement/references/PLAYBOOK.md +203 -0
  103. package/skills/recording-pentest-engagement/references/THEORY.md +195 -0
  104. package/skills/recording-pentest-engagement/scripts/record_engagement.py +461 -0
  105. package/skills/scanning-for-hardcoded-secrets/SKILL.md +215 -0
  106. package/skills/scanning-for-hardcoded-secrets/references/PLAYBOOK.md +325 -0
  107. package/skills/scanning-for-hardcoded-secrets/references/THEORY.md +175 -0
  108. package/skills/scanning-for-hardcoded-secrets/scripts/scan_secrets.py +395 -0
  109. package/skills/tracing-transitive-vulnerabilities/SKILL.md +235 -0
  110. package/skills/tracing-transitive-vulnerabilities/references/PLAYBOOK.md +233 -0
  111. package/skills/tracing-transitive-vulnerabilities/references/THEORY.md +138 -0
  112. package/skills/tracing-transitive-vulnerabilities/scripts/trace_vulns.py +484 -0
@@ -0,0 +1,337 @@
1
+ # Server-Fingerprinting Remediation Playbook
2
+
3
+ ## Strip Server header
4
+
5
+ ### nginx
6
+
7
+ ```nginx
8
+ http {
9
+ server_tokens off;
10
+ }
11
+ ```
12
+
13
+ To go further (replace nginx with a custom token, requires
14
+ `headers-more-nginx-module`):
15
+
16
+ ```nginx
17
+ more_set_headers "Server: "; # send empty Server header
18
+ ```
19
+
20
+ ### Apache
21
+
22
+ ```apache
23
+ ServerTokens Prod
24
+ ServerSignature Off
25
+ ```
26
+
27
+ `ServerTokens Prod` makes the Server header read just `Apache` (no
28
+ version, no OS). `ServerSignature Off` removes the footer from
29
+ generated error pages.
30
+
31
+ ### Caddy
32
+
33
+ Caddy 2.x doesn't disclose by default. Verify with `caddy adapt
34
+ --pretty` and ensure no override.
35
+
36
+ ### IIS
37
+
38
+ PowerShell to remove via URL Rewrite outbound rule:
39
+
40
+ ```powershell
41
+ # Add outbound rule that strips Server header
42
+ $site = "Default Web Site"
43
+ $rule = @{
44
+ name = "Strip Server Header"
45
+ patternSyntax = "Wildcard"
46
+ match = "*"
47
+ serverVariable = "RESPONSE_SERVER"
48
+ action = "Rewrite"
49
+ value = ""
50
+ }
51
+ Add-WebConfigurationProperty -PSPath "IIS:\Sites\$site" -Filter "/system.webServer/rewrite/outboundRules" -Name "." -Value $rule
52
+ ```
53
+
54
+ ### AWS ALB / CloudFront
55
+
56
+ ALB: Listener → Add header policy → Drop `Server` header on response.
57
+
58
+ CloudFront: Behaviors → Response Headers Policy → Custom Headers →
59
+ add `Server:` (empty) override.
60
+
61
+ ### Cloudflare
62
+
63
+ Workers script:
64
+
65
+ ```javascript
66
+ addEventListener('fetch', event => event.respondWith(handleRequest(event.request)))
67
+ async function handleRequest(req) {
68
+ const resp = await fetch(req);
69
+ const newHeaders = new Headers(resp.headers);
70
+ newHeaders.delete('Server');
71
+ newHeaders.delete('X-Powered-By');
72
+ return new Response(resp.body, {
73
+ status: resp.status,
74
+ statusText: resp.statusText,
75
+ headers: newHeaders,
76
+ });
77
+ }
78
+ ```
79
+
80
+ ## Strip X-Powered-By
81
+
82
+ ### PHP
83
+
84
+ `php.ini`:
85
+
86
+ ```ini
87
+ expose_php = Off
88
+ ```
89
+
90
+ ### Express
91
+
92
+ ```javascript
93
+ app.disable('x-powered-by');
94
+ // or with helmet:
95
+ const helmet = require('helmet');
96
+ app.use(helmet.hidePoweredBy());
97
+ ```
98
+
99
+ ### ASP.NET
100
+
101
+ `web.config`:
102
+
103
+ ```xml
104
+ <system.webServer>
105
+ <httpProtocol>
106
+ <customHeaders>
107
+ <remove name="X-Powered-By" />
108
+ <remove name="X-AspNet-Version" />
109
+ <remove name="X-AspNetMvc-Version" />
110
+ </customHeaders>
111
+ </httpProtocol>
112
+ </system.webServer>
113
+ ```
114
+
115
+ Plus for X-AspNet-Version:
116
+
117
+ ```xml
118
+ <system.web>
119
+ <httpRuntime enableVersionHeader="false" />
120
+ </system.web>
121
+ ```
122
+
123
+ Plus for X-AspNetMvc-Version, in `Global.asax`:
124
+
125
+ ```csharp
126
+ protected void Application_Start()
127
+ {
128
+ MvcHandler.DisableMvcResponseHeader = true;
129
+ }
130
+ ```
131
+
132
+ ## Strip X-Runtime (Rails)
133
+
134
+ `config/application.rb`:
135
+
136
+ ```ruby
137
+ config.middleware.delete Rack::Runtime
138
+ # Or globally:
139
+ config.action_dispatch.runtime_response_header = nil
140
+ ```
141
+
142
+ ## Strip X-Generator
143
+
144
+ ### Drupal
145
+
146
+ ```php
147
+ // settings.php
148
+ $config['system.theme']['default']['generator'] = '';
149
+
150
+ // Or via custom module hook:
151
+ function MYMODULE_preprocess_html(&$variables) {
152
+ unset($variables['head_title']['generator']);
153
+ }
154
+ ```
155
+
156
+ ### WordPress
157
+
158
+ ```php
159
+ // functions.php
160
+ remove_action('wp_head', 'wp_generator');
161
+ ```
162
+
163
+ Also remove the `<meta name="generator">` tag with a similar hook
164
+ for the HTML body.
165
+
166
+ ## Rename framework-default cookies
167
+
168
+ ### Express
169
+
170
+ ```javascript
171
+ app.use(session({
172
+ name: 'sid', // not 'connect.sid'
173
+ secret: process.env.SESSION_SECRET,
174
+ cookie: { httpOnly: true, secure: true, sameSite: 'lax' },
175
+ }));
176
+ ```
177
+
178
+ ### Spring Boot
179
+
180
+ ```yaml
181
+ server:
182
+ servlet:
183
+ session:
184
+ cookie:
185
+ name: sid # not JSESSIONID
186
+ http-only: true
187
+ secure: true
188
+ ```
189
+
190
+ ### Rails
191
+
192
+ ```ruby
193
+ # config/initializers/session_store.rb
194
+ Rails.application.config.session_store :cookie_store, key: '_sid'
195
+ ```
196
+
197
+ ### Django
198
+
199
+ ```python
200
+ # settings.py
201
+ SESSION_COOKIE_NAME = 'sid' # not 'sessionid'
202
+ ```
203
+
204
+ ### Laravel
205
+
206
+ ```php
207
+ // .env
208
+ SESSION_COOKIE=sid
209
+
210
+ // or config/session.php
211
+ 'cookie' => env('SESSION_COOKIE', 'sid'),
212
+ ```
213
+
214
+ ### PHP (native)
215
+
216
+ ```ini
217
+ ; php.ini
218
+ session.name = sid ; not PHPSESSID
219
+ ```
220
+
221
+ ## Suppress production error stack traces
222
+
223
+ ### Django
224
+
225
+ ```python
226
+ # settings/production.py
227
+ DEBUG = False
228
+ ADMINS = [('Ops', 'ops@example.com')] # ops gets the trace via email, not the user
229
+ ```
230
+
231
+ ### Rails
232
+
233
+ ```ruby
234
+ # config/environments/production.rb
235
+ config.consider_all_requests_local = false
236
+ config.action_dispatch.show_exceptions = true
237
+ config.action_dispatch.show_detailed_exceptions = false
238
+ ```
239
+
240
+ ### Spring Boot
241
+
242
+ ```yaml
243
+ server:
244
+ error:
245
+ include-stacktrace: never
246
+ include-message: never
247
+ include-exception: false
248
+ include-binding-errors: never
249
+ ```
250
+
251
+ ### ASP.NET
252
+
253
+ `web.config`:
254
+
255
+ ```xml
256
+ <system.web>
257
+ <customErrors mode="RemoteOnly" defaultRedirect="~/error" />
258
+ </system.web>
259
+ ```
260
+
261
+ ### Express
262
+
263
+ ```javascript
264
+ // Default Express error handler shows stack in development;
265
+ // override in production:
266
+ app.use((err, req, res, next) => {
267
+ if (req.app.get('env') === 'production') {
268
+ res.status(500).send('Internal Server Error');
269
+ } else {
270
+ next(err);
271
+ }
272
+ });
273
+ ```
274
+
275
+ ### Flask
276
+
277
+ ```python
278
+ app.config['DEBUG'] = False
279
+ app.config['PROPAGATE_EXCEPTIONS'] = False
280
+
281
+ @app.errorhandler(500)
282
+ def internal_error(error):
283
+ return render_template('500.html'), 500
284
+ ```
285
+
286
+ ### Go (net/http stdlib)
287
+
288
+ ```go
289
+ // Don't use the default panic handler that prints stack
290
+ mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
291
+ defer func() {
292
+ if err := recover(); err != nil {
293
+ // Log internally, return generic message
294
+ log.Printf("panic: %v", err)
295
+ http.Error(w, "Internal Server Error", 500)
296
+ }
297
+ }()
298
+ // ... handler logic
299
+ })
300
+ ```
301
+
302
+ ## Fix Apache ETag fingerprint
303
+
304
+ ```apache
305
+ # In httpd.conf or vhost
306
+ FileETag MTime Size # drop inode
307
+ # Or:
308
+ FileETag None # disable ETags entirely
309
+ ```
310
+
311
+ ## CI integration
312
+
313
+ ```yaml
314
+ - name: Server-fingerprint posture
315
+ run: |
316
+ python3 plugins/security/penetration-tester/skills/fingerprinting-server-software/scripts/fingerprint_server.py \
317
+ "${{ secrets.PROD_URL }}" \
318
+ --authorized \
319
+ --min-severity medium \
320
+ --format json \
321
+ --output fingerprint-report.json
322
+ - run: |
323
+ if jq 'any(.severity == "high" or .severity == "medium")' fingerprint-report.json | grep -q true; then
324
+ echo "::warning::Server-fingerprint findings present"
325
+ fi
326
+ ```
327
+
328
+ ## Verification after remediation
329
+
330
+ ```bash
331
+ python3 ${CLAUDE_PLUGIN_ROOT}/skills/fingerprinting-server-software/scripts/fingerprint_server.py \
332
+ https://example.com --authorized --min-severity medium --trigger-error
333
+ ```
334
+
335
+ Expected: exit 0, zero MEDIUM-or-higher findings. LOW findings on
336
+ framework-default cookies are acceptable trade-offs if you've judged
337
+ the recon-cost-vs-rename-pain tradeoff and chose to keep defaults.
@@ -0,0 +1,183 @@
1
+ # Server-Fingerprinting Theory
2
+
3
+ ## Why version disclosure is the cheapest pre-attack recon
4
+
5
+ Every web server, application framework, and runtime has a published
6
+ list of CVEs. The CVE catalog is queryable by component name + version.
7
+ Disclosing "nginx 1.18.0" hands the attacker an exact filter to apply
8
+ against the CVE catalog and produce a list of unauthenticated remote
9
+ exploits affecting that version. The exploitation cost goes from "scan
10
+ for arbitrary issues" to "execute the specific exploits known to work
11
+ against this version."
12
+
13
+ Most fingerprinting disclosures are not in themselves vulnerabilities —
14
+ they're CVSS-low / informational findings. But the cumulative effect
15
+ is that the attacker arrives at the application with a pre-prepared
16
+ attack toolkit calibrated to the exact software in use. The defensive
17
+ posture is "make the attacker work for the recon" — strip the
18
+ identifying headers, even though doing so doesn't strictly fix any
19
+ specific CVE.
20
+
21
+ ## Header-by-header
22
+
23
+ ### `Server`
24
+
25
+ The classic. nginx, Apache, and IIS all default to including their
26
+ name + version. Most modern reverse proxies have been configured to
27
+ strip on outbound by default; legacy stacks still leak.
28
+
29
+ Worst case: `Server: Apache/2.2.15 (CentOS)` — three pieces of info
30
+ in one header. Apache version (2.2 is end-of-life). OS family
31
+ (CentOS). And the CentOS minor often differentiable by which Apache
32
+ backport-patch level shows up.
33
+
34
+ Fix is one config directive per server:
35
+
36
+ - nginx: `server_tokens off;`
37
+ - Apache: `ServerTokens Prod`
38
+ - Caddy: doesn't disclose since v2.0
39
+ - IIS: URL Rewrite module → outbound rule to strip
40
+
41
+ ### `X-Powered-By`
42
+
43
+ PHP, Express, ASP.NET — frameworks that announce their identity by
44
+ default.
45
+
46
+ - `X-Powered-By: PHP/7.4.21` — exact runtime version. Lookup CVEs
47
+ against PHP 7.4.x — the EOL date for PHP 7.4 was November 2022, so
48
+ anything 7.4-shaped is end-of-life and a target for unpatched-EOL
49
+ exploitation.
50
+ - `X-Powered-By: Express` — Node.js Express identification.
51
+ - `X-Powered-By: ASP.NET` — .NET stack identification.
52
+
53
+ Each is a single config line away from elimination.
54
+
55
+ ### `X-AspNet-Version` / `X-AspNetMvc-Version`
56
+
57
+ .NET runtime exact version. Higher severity than generic
58
+ X-Powered-By because the dotnet version pins the runtime + the BCL
59
+ version + the ASP.NET version simultaneously. CVE catalog hits multiply.
60
+
61
+ Disable via `web.config`:
62
+
63
+ ```xml
64
+ <httpRuntime enableVersionHeader="false" />
65
+ ```
66
+
67
+ ### `X-Generator`
68
+
69
+ CMS family marker. Drupal, Joomla, and WordPress all add a generator
70
+ header by default. Drupal includes the major version
71
+ (`X-Generator: Drupal 9`); WordPress used to include the full version
72
+ in `<meta name="generator">` HTML.
73
+
74
+ For CMS targets specifically, fingerprint → CVE chain is heavily
75
+ weighted toward the CMS itself (Drupal core + module CVEs) and the
76
+ themes / plugins layered on top.
77
+
78
+ ### `Via`
79
+
80
+ HTTP/1.1 standard header for proxy chain identification. Some setups
81
+ include the upstream's hostname + version, others use a generic token.
82
+
83
+ `Via: 1.1 varnish-v4` → reveals Varnish in front of origin.
84
+ `Via: 1.1 google` → CloudFront.
85
+
86
+ Less sensitive than direct server-version disclosure, but informs
87
+ network architecture.
88
+
89
+ ### Framework-default cookies
90
+
91
+ Frameworks set session cookies with predictable names:
92
+
93
+ | Cookie name | Framework |
94
+ |---|---|
95
+ | `PHPSESSID` | PHP |
96
+ | `JSESSIONID` | Java EE (Tomcat, WebSphere, WebLogic, Spring) |
97
+ | `ASP.NET_SessionId` | ASP.NET |
98
+ | `ASPSESSIONID*` | Classic ASP |
99
+ | `connect.sid` | Express with connect.session |
100
+ | `_session_id` | Rails |
101
+ | `laravel_session` | Laravel |
102
+ | `ci_session` | CodeIgniter |
103
+ | `frontend` | Magento |
104
+
105
+ Any of these in a Set-Cookie response identifies the stack family
106
+ even if all version headers are stripped. The remediation is to
107
+ rename the session cookie to a non-default value.
108
+
109
+ It's also a defense in depth: an attacker scanning by JSESSIONID
110
+ presence (a common "find me Java apps" scan) misses servers that
111
+ renamed the cookie.
112
+
113
+ ### `ETag` format
114
+
115
+ Apache's default ETag is the file's inode + size + mtime, encoded as
116
+ hex. The inode portion is filesystem-specific — across a load-
117
+ balanced cluster, two nodes have different inodes for the same file
118
+ on different filesystems. Sending requests against the balanced
119
+ endpoint and grouping by ETag reveals how many nodes back the
120
+ service.
121
+
122
+ ```
123
+ "6a7-5b9e..." <- node A
124
+ "3f1-5b9e..." <- node B
125
+ "a2c-5b9e..." <- node C
126
+ ```
127
+
128
+ The size/mtime portion is shared (same file content). The inode is
129
+ node-distinguishing.
130
+
131
+ Apache fix:
132
+
133
+ ```apache
134
+ FileETag MTime Size # drop inode
135
+ # Or completely:
136
+ FileETag None
137
+ ```
138
+
139
+ ## Error-page disclosure (CWE-209)
140
+
141
+ If the application returns a detailed error page on 500-level
142
+ responses, the body often includes:
143
+
144
+ - Server-internal file paths (`/home/app/src/handlers/auth.py`)
145
+ - Function names + line numbers (informs source-code structure)
146
+ - Framework banner ("Django 4.2", "Spring Boot 3.1", etc.)
147
+ - Stack traces with full module paths
148
+
149
+ Each of these is high-value recon. The fix is per-framework:
150
+
151
+ | Framework | Disable detailed errors in prod |
152
+ |---|---|
153
+ | Django | `DEBUG = False` |
154
+ | Rails | `config.consider_all_requests_local = false` |
155
+ | Spring Boot | `server.error.include-stacktrace=never` |
156
+ | ASP.NET | `customErrors mode="RemoteOnly"` |
157
+ | Express | `app.use(errorHandler({log:false, debug:false}))` |
158
+ | Flask | `app.config['DEBUG'] = False` + `propagate_exceptions=False` |
159
+
160
+ This is universally a HIGH finding. There's no operational reason
161
+ to ship stack traces to production responses.
162
+
163
+ ## The attacker's workflow that this defends against
164
+
165
+ 1. Send one GET to the target homepage.
166
+ 2. Read `Server`, `X-Powered-By`, `Set-Cookie` from the response.
167
+ 3. Conclude: nginx 1.18 + PHP 7.4 + Laravel 8.
168
+ 4. Query CVE catalog: filter to nginx 1.18.x + PHP 7.4.x + Laravel
169
+ 8.x. Returns ~15 CVEs.
170
+ 5. Pick the highest-CVSS unauthenticated-remote one. Execute exploit.
171
+
172
+ Total elapsed time: about 90 seconds. Defense-in-depth: strip the
173
+ headers, rename the cookie, suppress error stack traces. The attacker
174
+ now has to do real recon, which raises cost from minutes to hours.
175
+
176
+ ## Primary sources
177
+
178
+ - [OWASP WSTG-INFO-02 Fingerprint Web Server](https://owasp.org/www-project-web-security-testing-guide/v42/4-Web_Application_Security_Testing/01-Information_Gathering/02-Fingerprint_Web_Server)
179
+ - [OWASP WSTG-INFO-08 Fingerprint Web Application Framework](https://owasp.org/www-project-web-security-testing-guide/v42/4-Web_Application_Security_Testing/01-Information_Gathering/08-Fingerprint_Web_Application_Framework)
180
+ - [CWE-200 Information Exposure](https://cwe.mitre.org/data/definitions/200.html)
181
+ - [CWE-209 Information Exposure Through an Error Message](https://cwe.mitre.org/data/definitions/209.html)
182
+ - [nginx server_tokens directive](https://nginx.org/en/docs/http/ngx_http_core_module.html#server_tokens)
183
+ - [Apache ServerTokens directive](https://httpd.apache.org/docs/2.4/mod/core.html#servertokens)