Compare commits

..
1 Commits
Author SHA1 Message Date
sneak 5b5f77f8c3 Core Rule Set: random passwords pass on the remaining password fields (closes #288)
check / check (push) Failing after 2m32s
Each app's sign-in, sign-up, password change and reset fields that had no
exclusion now skip the 35 rules random passwords set off, on that app's
paths and only when the query has no field of that name. Added gitea,
Vaultwarden's admin token, WordPress pass1/pass2/post_password and its REST
password, Drupal's forms and JSON, MediaWiki retype and password on the
pages that take them, Mastodon's challenge, and Nextcloud's confirm, reset,
change, share, LDAP renewal, encryption and OCS fields. Vaultwarden's
clients send a hash, which needs no exclusion.

Model: opus-5-5
2026-11-10 18:36:18 +00:00
3 changed files with 59 additions and 8 deletions

No files matched your search

+24 -6
View File
@@ -874,9 +874,10 @@ The changes hold in front of every app, and no setting undoes them:
- `password` in the JSON body of `/api/v1/admin/users` and each user
under it.
None applies when the query has a parameter of the same name.
`' OR '1'='1`, `../../etc/passwd`, `; cat /etc/passwd` and
`<img src=x onerror=x()>` sent as the password reach gitea;
Each field listed skips these rules on every path listed, not only on
the paths given for it. None applies when the query has a parameter of
the same name. `' OR '1'='1`, `../../etc/passwd`, `; cat /etc/passwd`
and `<img src=x onerror=x()>` sent as the password reach gitea;
`<script>alert(1)</script>` and `javascript:alert(1)` are still refused.
Elsewhere these fields keep every rule. The paths are those of gitea at the
@@ -955,9 +956,16 @@ The changes hold in front of every app, and no setting undoes them:
- `pass1`, `pass2` and `post_password` on `/wp-login.php`, which reset a
password and sign in to a protected post, for the same rules.
- `pass1` and `pass2` on `/wp-admin/profile.php`,
`/wp-admin/user-edit.php` and `/wp-admin/user-new.php`, for the same
rules.
- `pass1` and `pass2` on these pages, for the same rules:
- `/wp-admin/profile.php`;
- `/wp-admin/user-edit.php`;
- `/wp-admin/user-new.php`;
- `/wp-admin/network/user-edit.php` and
`/wp-admin/user/profile.php`, on a multisite install.
Each of `pass1`, `pass2` and `post_password` skips these rules on
all of these pages and on `/wp-login.php`.
- `password` in form data or JSON on the REST API's `/wp/v2/users` and
the user under it, for the same rules.
- `title`, `excerpt` and `name` in a JSON body sent to any REST API
@@ -1046,6 +1054,10 @@ The changes hold in front of every app, and no setting undoes them:
`/user/register` and `/user/<id>`;
- `data.attributes.pass.value` and `data.attributes.pass.existing` in
the JSON:API body of `/jsonapi/user/user` and each user under it.
Each field listed skips these rules on every path listed, not only on
the paths given for it.
- `name` when resetting a password through Drupal's JSON format on
`/user/password`, for the same four rules.
- `mail` in that same reset, for 932120 and 932260 only.
@@ -1293,6 +1305,9 @@ The changes hold in front of every app, and no setting undoes them:
933100, 933135, 933180, 933210, 941100, 941120, 941160, 941180, 942100,
942151, 942190, 942230, 942290, 942320, 942540, 942550, 944150.
Each field in the first two items skips these rules on every path in those
items.
`' OR '1'='1`, `../../etc/passwd`, `; cat /etc/passwd` and
`<img src=x onerror=x()>` sent as a password reach Mastodon;
`<script>alert(1)</script>` and `javascript:alert(1)` are still refused.
@@ -1476,6 +1491,9 @@ The changes hold in front of every app, and no setting undoes them:
`password`. A body with more than one `key`, or a `key` or `value` in the
query, gets none of this.
Each field listed but `value` skips these rules on every path listed, not
only on the paths given for it.
`password` on `/login/flow/apptoken` and `/login/v2/apptoken` holds a
generated app token, so it needs no exclusion.
+31
View File
@@ -979,6 +979,35 @@ func TestPasswordFieldsStayCheckedInTheQuery(t *testing.T) {
}
}
// The password exclusions apply in front of every app. So each field keeps
// every rule on a nearby path that its exclusion does not name.
func TestPasswordExclusionsAreNarrow(t *testing.T) {
t.Parallel()
const threshold = 5 // SWW_WAF_ANOMALY_THRESHOLD's default
crs := readingBodies(t)
for _, sent := range []struct{ target, contentType, name string }{
{"/user/settings/profile", formData, passwordKey},
{"/api/v1/user/repos", jsonBody, passwordKey},
{"/admin/users/overview", formData, "token"},
{"/wp-admin/options.php", formData, "pass1"},
{"/wp-admin/post.php", formData, "post_password"},
{"/user/password", formData, "pass[pass1]"},
{"/settings/apps", formData, passwordKey},
{"/ocs/v2.php/apps/spreed/api/v4/room", jsonBody, passwordKey},
{"/settings/profile", formData, "form_challenge[current_password]"},
} {
r := request{http.MethodPost, sent.target, []string{sent.contentType}}
body := passwordBody(t, r, []string{sent.name}, "' OR '1'='1")
if got, _ := inspectBody(t, crs, r, body); got.Score < threshold {
t.Errorf("POST %s %s let through: %+v", sent.target, sent.name, got)
}
}
}
func TestGiteaAttacksAreRefused(t *testing.T) {
t.Parallel()
@@ -1616,6 +1645,8 @@ func TestWordPressPasswordFieldsPass(t *testing.T) {
{"/wp-admin/profile.php", formData, pass},
{"/wp-admin/user-edit.php?user_id=2", formData, pass},
{"/wp-admin/user-new.php", formData, pass},
{"/wp-admin/network/user-edit.php?user_id=2", formData, pass},
{"/wp-admin/user/profile.php", formData, pass},
{"/wp-json/wp/v2/users", jsonBody, []string{passwordKey}},
{"/wp-json/wp/v2/users/me", formData, []string{passwordKey}},
{"/index.php?rest_route=/wp/v2/users/2", jsonBody, []string{passwordKey}},
+4 -2
View File
@@ -138,9 +138,11 @@ SecRule REQUEST_FILENAME "@streq /wp-login.php" \
# The other passwords, which skip the same rules:
# pass1, pass2: resetting the password at /wp-login.php, changing it on the
# profile page and the page that edits a user, and adding a user
# profile page and the page that edits a user, and adding a user; a
# multisite install also changes it under /wp-admin/network/ and
# /wp-admin/user/
# post_password: the password of a protected post, sent to /wp-login.php
SecRule REQUEST_FILENAME "@rx ^/(?:wp-login\.php|wp-admin/(?:profile|user-edit|user-new)\.php)$" \
SecRule REQUEST_FILENAME "@rx ^/(?:wp-login\.php|wp-admin/(?:profile|user-edit|user-new|network/user-edit|user/profile)\.php)$" \
"id:900715,phase:1,pass,nolog,chain"
SecRule &ARGS_GET:/^(?:pass[12]|post_password)$/ "@eq 0" \
"ctl:ruleRemoveTargetById=920270;ARGS:/^(?:pass[12]|post_password)$/,\