Core Rule Set: random passwords pass on the remaining password fields (closes #288)
check / check (push) Failing after 5m45s
check / check (push) Failing after 5m45s
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
This commit is contained in:
1 parent
8ab4280792
commit
dee803562f
15 files changed
+992
-104
No files matched your search
@@ -334,6 +334,111 @@ func TestNextcloudPasswordPasses(t *testing.T) {
|
||||
wantPasswordAttacksRefused(t, r, "password")
|
||||
}
|
||||
|
||||
func TestNextcloudPasswordFieldsPass(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
password := []string{passwordKey}
|
||||
change := []string{"oldPassword", "newPassword"}
|
||||
recovery := []string{"oldPassword", "newPassword", "confirmPassword"}
|
||||
adminRecovery := []string{"recoveryPassword", "confirmPassword"}
|
||||
|
||||
wantPasswordFieldsPass(t, []passwordFields{
|
||||
{"/ocs/v1.php/person/check", formData, password},
|
||||
{"/ocs/v2.php/person/check", jsonBody, password},
|
||||
{"/ocs/v2.php/core/apppassword/confirm", jsonBody, password},
|
||||
{nextcloudShares + "/42/send-email", jsonBody, password},
|
||||
{"/apps/user_ldap/renewpassword", formData, change},
|
||||
{"/index.php/apps/user_ldap/renewpassword", formData, change},
|
||||
{"/apps/encryption/ajax/updatePrivateKeyPassword", formData, change},
|
||||
{"/index.php/apps/encryption/ajax/updatePrivateKeyPassword", formData, change},
|
||||
{"/apps/encryption/ajax/changeRecoveryPassword", formData, recovery},
|
||||
{"/index.php/apps/encryption/ajax/changeRecoveryPassword", formData, recovery},
|
||||
{"/apps/encryption/ajax/adminRecovery", formData, adminRecovery},
|
||||
{"/index.php/apps/encryption/ajax/adminRecovery", formData, adminRecovery},
|
||||
{"/login/confirm", jsonBody, password},
|
||||
{"/index.php/login/confirm", formData, password},
|
||||
{"/lostpassword/set/AbCdEf/alice", jsonBody, password},
|
||||
{
|
||||
"/settings/personal/changepassword", jsonBody,
|
||||
[]string{"oldpassword", "newpassword"},
|
||||
},
|
||||
{
|
||||
"/index.php/settings/users/changepassword", formData,
|
||||
[]string{passwordKey, "recoveryPassword"},
|
||||
},
|
||||
{nextcloudShare + "/authenticate/showShare", formData, password},
|
||||
{nextcloudUsers, jsonBody, password},
|
||||
{"/ocs/v1.php/cloud/users/alice", formData, password},
|
||||
{nextcloudUsers + "/alice", jsonBody, password},
|
||||
{nextcloudShares, jsonBody, password},
|
||||
{nextcloudShares + "/42", formData, password},
|
||||
})
|
||||
}
|
||||
|
||||
// TestNextcloudUserPasswordInValuePasses sends a password as Nextcloud's
|
||||
// provisioning API takes it, as the value of the field the key names.
|
||||
func TestNextcloudUserPasswordInValuePasses(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const threshold = 5 // SWW_WAF_ANOMALY_THRESHOLD's default
|
||||
|
||||
crs := readingBodies(t)
|
||||
target := nextcloudUsers + "/alice"
|
||||
|
||||
form := request{http.MethodPut, target, []string{formData}}
|
||||
jsonRequest := request{http.MethodPut, target, []string{jsonBody}}
|
||||
|
||||
formPassword := func(passwords ...string) string {
|
||||
return "key=password&" + formBody("value", passwords...)
|
||||
}
|
||||
jsonPassword := func(passwords ...string) string {
|
||||
body := passwordBody(t, jsonRequest, []string{"value"}, passwords...)
|
||||
|
||||
return `{"key":"password",` + body[1:]
|
||||
}
|
||||
|
||||
misfiring := misfiringPasswords(t)
|
||||
|
||||
for _, sent := range []struct {
|
||||
r request
|
||||
body string
|
||||
}{
|
||||
{form, formPassword(misfiring...)},
|
||||
{jsonRequest, jsonPassword(misfiring...)},
|
||||
{form, formPassword("' OR '1'='1", "<img src=x onerror=x()>")},
|
||||
{jsonRequest, jsonPassword("; cat /etc/passwd")},
|
||||
} {
|
||||
if got, _ := inspectBody(t, crs, sent.r, sent.body); got.Score >= threshold {
|
||||
t.Errorf("%s %.40s refused: %+v", sent.r.headers[0], sent.body, got)
|
||||
}
|
||||
}
|
||||
|
||||
// Any other key keeps every rule, and so does a second key.
|
||||
for _, sent := range []struct {
|
||||
r request
|
||||
body string
|
||||
}{
|
||||
{form, "key=displayname&value=" + url.QueryEscape("' OR '1'='1")},
|
||||
{form, formPassword(scriptTag)},
|
||||
{form, "key=password&key=displayname&value=" + url.QueryEscape("' OR '1'='1")},
|
||||
{jsonRequest, `{"key":"email","value":"' OR '1'='1"}`},
|
||||
} {
|
||||
if got, _ := inspectBody(t, crs, sent.r, sent.body); got.Score < threshold {
|
||||
t.Errorf("%s %.40s let through: %+v", sent.r.headers[0], sent.body, got)
|
||||
}
|
||||
}
|
||||
|
||||
// A value in the query keeps every rule.
|
||||
r := request{
|
||||
http.MethodPut, target + "?value=%27%20OR%20%271%27%3D%271",
|
||||
[]string{formData},
|
||||
}
|
||||
|
||||
if got, _ := inspectBody(t, crs, r, "key=password"); got.Score < threshold {
|
||||
t.Errorf("%s let through: %+v", r.target, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNextcloudSentencesPassInTheTexts(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
Reference in new issue
Block a user