@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,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)
|