Skip to content

Commit 2644c7e

Browse files
committed
Add 19 more high fidelity rules: served credential files and exposed source
Each new rule fires only on proof the server itself hands back: a 200 whose body carries an unmistakable marker, that differs from a known not found control, and that is not an HTML page echoing the words. Served secrets and credentials: WordPress wp-config backups, a TLS private key, an SSH key, a .netrc, a MySQL client config, a Composer auth file, a Rails secrets file, a Django settings module, an SFTP deployment config, an IIS web.config with connection strings, and an environment file backup. Exposed source and config: a .git/HEAD, a Subversion wc.db, a docker-compose file, a Vim swap file, a Laravel log, a .NET appsettings.json, and a .DS_Store. Each carries its severity floor, how to reproduce it and how to fix it. They stay off until a ruleset is enabled, like the rest of the curated set.
1 parent 9d492ee commit 2644c7e

19 files changed

Lines changed: 616 additions & 0 deletions
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
{
2+
"id": "misconfiguration-appsettings-json-served",
3+
"class": "misconfiguration",
4+
"safety": "read",
5+
"severity_floor": "medium",
6+
"title": "A .NET appsettings.json is served",
7+
"summary": "The application settings file is downloadable, which names the connection strings and other configuration the app runs with.",
8+
"needs": [],
9+
"probe": [
10+
{
11+
"request": {
12+
"method": "GET",
13+
"path": "/appsettings.json"
14+
}
15+
}
16+
],
17+
"confirm": {
18+
"control": "A path that cannot exist, so the site's not found answer is known.",
19+
"differential": "status==200 and body_contains(\"ConnectionStrings\") and body_differs(control) and not body_contains(\"<html\")",
20+
"retest": 2
21+
},
22+
"reproduce": "curl -s https://host/appsettings.json",
23+
"remediate": "Block config files at the web server, and rotate any secrets the file named.",
24+
"references": [],
25+
"mappings": {
26+
"cwe": "CWE-538",
27+
"owasp": "A05:2021"
28+
},
29+
"rulesets": [
30+
"dotnet"
31+
]
32+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
{
2+
"id": "misconfiguration-composer-auth-served",
3+
"class": "misconfiguration",
4+
"safety": "read",
5+
"severity_floor": "high",
6+
"title": "A Composer auth.json is served",
7+
"summary": "The Composer auth file is downloadable, which carries the registry tokens and HTTP credentials it authenticates with.",
8+
"needs": [],
9+
"probe": [
10+
{
11+
"request": {
12+
"method": "GET",
13+
"path": "/auth.json"
14+
}
15+
}
16+
],
17+
"confirm": {
18+
"control": "A path that cannot exist, so the site's not found answer is known.",
19+
"differential": "status==200 and (body_contains(\"http-basic\") or body_contains(\"github-oauth\") or body_contains(\"gitlab-token\")) and body_differs(control) and not body_contains(\"<html\")",
20+
"retest": 2
21+
},
22+
"reproduce": "curl -s https://host/auth.json",
23+
"remediate": "Remove the file from the web root and rotate the tokens.",
24+
"references": [],
25+
"mappings": {
26+
"cwe": "CWE-522",
27+
"owasp": "A05:2021"
28+
},
29+
"rulesets": [
30+
"php",
31+
"composer"
32+
]
33+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
{
2+
"id": "misconfiguration-django-settings-served",
3+
"class": "misconfiguration",
4+
"safety": "read",
5+
"severity_floor": "high",
6+
"title": "A Django settings.py is served as source",
7+
"summary": "The Django settings module is downloadable as source, which carries the SECRET_KEY and the database configuration in plain text.",
8+
"needs": [],
9+
"probe": [
10+
{
11+
"request": {
12+
"method": "GET",
13+
"path": "/settings.py"
14+
}
15+
}
16+
],
17+
"confirm": {
18+
"control": "A path that cannot exist, so the site's not found answer is known.",
19+
"differential": "status==200 and body_contains(\"SECRET_KEY\") and body_contains(\"DATABASES\") and body_differs(control) and not body_contains(\"<html\")",
20+
"retest": 2
21+
},
22+
"reproduce": "curl -s https://host/settings.py",
23+
"remediate": "Keep source out of the web root, and rotate the SECRET_KEY and any database credentials.",
24+
"references": [],
25+
"mappings": {
26+
"cwe": "CWE-538",
27+
"owasp": "A05:2021"
28+
},
29+
"rulesets": [
30+
"django",
31+
"python"
32+
]
33+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
{
2+
"id": "misconfiguration-docker-compose-served",
3+
"class": "misconfiguration",
4+
"safety": "read",
5+
"severity_floor": "medium",
6+
"title": "A docker-compose.yml is served",
7+
"summary": "The compose file is downloadable, which names the services, images and often the environment variables the stack runs with.",
8+
"needs": [],
9+
"probe": [
10+
{
11+
"request": {
12+
"method": "GET",
13+
"path": "/docker-compose.yml"
14+
}
15+
}
16+
],
17+
"confirm": {
18+
"control": "A path that cannot exist, so the site's not found answer is known.",
19+
"differential": "status==200 and body_contains(\"services:\") and (body_contains(\"image:\") or body_contains(\"environment:\")) and \"html\" not in header(\"content-type\") and body_differs(control)",
20+
"retest": 2
21+
},
22+
"reproduce": "curl -s https://host/docker-compose.yml",
23+
"remediate": "Keep deployment files out of the web root, and rotate any secrets the file named.",
24+
"references": [],
25+
"mappings": {
26+
"cwe": "CWE-538",
27+
"owasp": "A05:2021"
28+
},
29+
"rulesets": [
30+
"docker"
31+
]
32+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
{
2+
"id": "misconfiguration-ds-store-served",
3+
"class": "misconfiguration",
4+
"safety": "read",
5+
"severity_floor": "low",
6+
"title": "A .DS_Store file is served",
7+
"summary": "A .DS_Store file is downloadable, which lists the names of the files in its directory and reveals the layout of the site.",
8+
"needs": [],
9+
"probe": [
10+
{
11+
"request": {
12+
"method": "GET",
13+
"path": "/.DS_Store"
14+
}
15+
}
16+
],
17+
"confirm": {
18+
"control": "A path that cannot exist, so the site's not found answer is known.",
19+
"differential": "status==200 and body_contains(\"Bud1\") and body_differs(control)",
20+
"retest": 2
21+
},
22+
"reproduce": "curl -s https://host/.DS_Store",
23+
"remediate": "Remove .DS_Store files from the web root and block them at the web server.",
24+
"references": [],
25+
"mappings": {
26+
"cwe": "CWE-538",
27+
"owasp": "A05:2021"
28+
},
29+
"rulesets": [
30+
"apache"
31+
]
32+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
{
2+
"id": "misconfiguration-env-backup-served",
3+
"class": "misconfiguration",
4+
"safety": "read",
5+
"severity_floor": "high",
6+
"title": "An environment file backup is served (/.env.bak)",
7+
"summary": "A backup of the environment file is downloadable, which holds database credentials, API keys and other secrets in plain text.",
8+
"needs": [],
9+
"probe": [
10+
{
11+
"request": {
12+
"method": "GET",
13+
"path": "/.env.bak"
14+
}
15+
}
16+
],
17+
"confirm": {
18+
"control": "A path that cannot exist, so the site's not found answer is known.",
19+
"differential": "status==200 and (body_contains(\"APP_\") or body_contains(\"DB_\") or body_contains(\"SECRET\")) and body_differs(control) and not body_contains(\"<html\")",
20+
"retest": 2
21+
},
22+
"reproduce": "curl -s https://host/.env.bak",
23+
"remediate": "Keep environment files and their backups out of the web root and rotate any exposed secrets.",
24+
"references": [],
25+
"mappings": {
26+
"cwe": "CWE-538",
27+
"owasp": "A05:2021"
28+
}
29+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
{
2+
"id": "misconfiguration-git-head-served",
3+
"class": "misconfiguration",
4+
"safety": "read",
5+
"severity_floor": "medium",
6+
"title": "A .git/HEAD file is served",
7+
"summary": "The git HEAD is downloadable, which proves the .git directory is exposed and the source history can be rebuilt.",
8+
"needs": [],
9+
"probe": [
10+
{
11+
"request": {
12+
"method": "GET",
13+
"path": "/.git/HEAD"
14+
}
15+
}
16+
],
17+
"confirm": {
18+
"control": "A path that cannot exist, so the site's not found answer is known.",
19+
"differential": "status==200 and body_contains(\"ref:\") and body_contains(\"refs/\") and \"html\" not in header(\"content-type\") and body_differs(control)",
20+
"retest": 2
21+
},
22+
"reproduce": "curl -s https://host/.git/HEAD",
23+
"remediate": "Block the .git directory at the web server, or deploy without it.",
24+
"references": [],
25+
"mappings": {
26+
"cwe": "CWE-538",
27+
"owasp": "A05:2021"
28+
},
29+
"rulesets": [
30+
"git",
31+
"apache"
32+
]
33+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
{
2+
"id": "misconfiguration-laravel-log-served",
3+
"class": "misconfiguration",
4+
"safety": "read",
5+
"severity_floor": "medium",
6+
"title": "A Laravel log is served (/storage/logs/laravel.log)",
7+
"summary": "The application log is downloadable, which carries stack traces, query values and other data from live requests.",
8+
"needs": [],
9+
"probe": [
10+
{
11+
"request": {
12+
"method": "GET",
13+
"path": "/storage/logs/laravel.log"
14+
}
15+
}
16+
],
17+
"confirm": {
18+
"control": "A path that cannot exist, so the site's not found answer is known.",
19+
"differential": "status==200 and (body_contains(\"production.ERROR\") or body_contains(\"local.ERROR\") or body_contains(\"stacktrace\")) and body_differs(control) and not body_contains(\"<html\")",
20+
"retest": 2
21+
},
22+
"reproduce": "curl -s https://host/storage/logs/laravel.log",
23+
"remediate": "Keep the storage directory out of the web root.",
24+
"references": [],
25+
"mappings": {
26+
"cwe": "CWE-532",
27+
"owasp": "A09:2021"
28+
},
29+
"rulesets": [
30+
"laravel",
31+
"php"
32+
]
33+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
{
2+
"id": "misconfiguration-my-cnf-served",
3+
"class": "misconfiguration",
4+
"safety": "read",
5+
"severity_floor": "high",
6+
"title": "A MySQL client config is served (/.my.cnf)",
7+
"summary": "The MySQL client config is downloadable, which commonly carries the database user and password in plain text.",
8+
"needs": [],
9+
"probe": [
10+
{
11+
"request": {
12+
"method": "GET",
13+
"path": "/.my.cnf"
14+
}
15+
}
16+
],
17+
"confirm": {
18+
"control": "A path that cannot exist, so the site's not found answer is known.",
19+
"differential": "status==200 and body_contains(\"[client]\") and body_contains(\"password\") and body_differs(control) and not body_contains(\"<html\")",
20+
"retest": 2
21+
},
22+
"reproduce": "curl -s https://host/.my.cnf",
23+
"remediate": "Remove the file from the web root and rotate the database credentials.",
24+
"references": [],
25+
"mappings": {
26+
"cwe": "CWE-522",
27+
"owasp": "A05:2021"
28+
},
29+
"rulesets": [
30+
"secrets",
31+
"mysql"
32+
]
33+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
{
2+
"id": "misconfiguration-netrc-served",
3+
"class": "misconfiguration",
4+
"safety": "read",
5+
"severity_floor": "high",
6+
"title": "A .netrc credentials file is served",
7+
"summary": "The .netrc file is downloadable, which carries the login and password it uses for other machines in plain text.",
8+
"needs": [],
9+
"probe": [
10+
{
11+
"request": {
12+
"method": "GET",
13+
"path": "/.netrc"
14+
}
15+
}
16+
],
17+
"confirm": {
18+
"control": "A path that cannot exist, so the site's not found answer is known.",
19+
"differential": "status==200 and body_contains(\"machine\") and body_contains(\"password\") and body_differs(control) and not body_contains(\"<html\")",
20+
"retest": 2
21+
},
22+
"reproduce": "curl -s https://host/.netrc",
23+
"remediate": "Remove the file from the web root and rotate the credentials.",
24+
"references": [],
25+
"mappings": {
26+
"cwe": "CWE-522",
27+
"owasp": "A05:2021"
28+
},
29+
"rulesets": [
30+
"secrets"
31+
]
32+
}

0 commit comments

Comments
 (0)