Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4dbd134d49 |
No files matched your search
@@ -602,7 +602,7 @@ client's limits by tag. See "Request tags" in [`SPEC.md`](SPEC.md).
|
|||||||
- Nextcloud: its sync client gets through. On Nextcloud's own paths, its
|
- Nextcloud: its sync client gets through. On Nextcloud's own paths, its
|
||||||
WebDAV methods, searches, file names, account names, comments and
|
WebDAV methods, searches, file names, account names, comments and
|
||||||
documents do too.
|
documents do too.
|
||||||
- A random password, as a password manager makes one, gets through these forms:
|
- A password made of any printable characters gets through these forms:
|
||||||
- signing in, on WordPress, Drupal, MediaWiki and Nextcloud;
|
- signing in, on WordPress, Drupal, MediaWiki and Nextcloud;
|
||||||
- signing in, signing up, changing the password, and moving or deleting the
|
- signing in, signing up, changing the password, and moving or deleting the
|
||||||
account, on Mastodon.
|
account, on Mastodon.
|
||||||
|
|||||||
@@ -920,19 +920,13 @@ The changes hold in front of every app, and no setting undoes them:
|
|||||||
- `newcontent` of a plugin's or a theme's file saved from its editor,
|
- `newcontent` of a plugin's or a theme's file saved from its editor,
|
||||||
which is PHP.
|
which is PHP.
|
||||||
- `pwd`, the password when signing in, for each rule that random
|
- `pwd`, the password when signing in, for each rule that random
|
||||||
passwords set off:
|
passwords set off (920270, 920540, 921200, 930100, 930110, 930120,
|
||||||
|
932125, 932130, 932160, 932230, 932250, 932260, 932270, 932280,
|
||||||
920270, 920540, 921200, 930100, 930110, 930120, 932125, 932130,
|
932330, 932340, 932370, 932380, 933100, 933135, 933180, 933210,
|
||||||
932160, 932230, 932250, 932260, 932270, 932280, 932330, 932340,
|
941100, 941120, 941160, 941180, 942100, 942151, 942190, 942230,
|
||||||
932370, 932380, 933100, 933135, 933180, 933210, 941100, 941120,
|
942290, 942320, 942540, 942550, 944150). An SQL injection or a path
|
||||||
941160, 941180, 942100, 942151, 942190, 942230, 942290, 942320,
|
traversal sent as the password reaches WordPress. A script injection
|
||||||
942540, 942550, 944150.
|
is still refused.
|
||||||
|
|
||||||
`' OR '1'='1`, `../../etc/passwd`, `; cat /etc/passwd` and
|
|
||||||
`<img src=x onerror=x()>` sent as the password reach WordPress;
|
|
||||||
`<script>alert(1)</script>` and `javascript:alert(1)` are still
|
|
||||||
refused.
|
|
||||||
|
|
||||||
- `title`, `excerpt` and `name` in a JSON body sent to any REST API
|
- `title`, `excerpt` and `name` in a JSON body sent to any REST API
|
||||||
route under `/wp/v2/`, for the rules on system files and commands
|
route under `/wp/v2/`, for the rules on system files and commands
|
||||||
(930120, 932120, 932250, 932260).
|
(930120, 932120, 932250, 932260).
|
||||||
@@ -1001,17 +995,12 @@ The changes hold in front of every app, and no setting undoes them:
|
|||||||
JSON:API, which hold HTML and any code it shows.
|
JSON:API, which hold HTML and any code it shows.
|
||||||
- `name` and `pass` when signing in (930120, 932120, 932250, 932260).
|
- `name` and `pass` when signing in (930120, 932120, 932250, 932260).
|
||||||
- `pass`, the password when signing in, in form data or JSON, for each rule
|
- `pass`, the password when signing in, in form data or JSON, for each rule
|
||||||
that random passwords set off:
|
that random passwords set off (920270, 920540, 921200, 930100, 930110,
|
||||||
|
930120, 932125, 932130, 932160, 932230, 932250, 932260, 932270, 932280,
|
||||||
920270, 920540, 921200, 930100, 930110, 930120, 932125, 932130, 932160,
|
932330, 932340, 932370, 932380, 933100, 933135, 933180, 933210, 941100,
|
||||||
932230, 932250, 932260, 932270, 932280, 932330, 932340, 932370, 932380,
|
941120, 941160, 941180, 942100, 942151, 942190, 942230, 942290, 942320,
|
||||||
933100, 933135, 933180, 933210, 941100, 941120, 941160, 941180, 942100,
|
942540, 942550, 944150). An SQL injection or a path traversal sent as the
|
||||||
942151, 942190, 942230, 942290, 942320, 942540, 942550, 944150.
|
password reaches Drupal. A script injection is still refused.
|
||||||
|
|
||||||
`' OR '1'='1`, `../../etc/passwd`, `; cat /etc/passwd` and
|
|
||||||
`<img src=x onerror=x()>` sent as the password reach Drupal;
|
|
||||||
`<script>alert(1)</script>` and `javascript:alert(1)` are still refused.
|
|
||||||
|
|
||||||
- `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.
|
- `mail` in that same reset, for 932120 and 932260 only.
|
||||||
@@ -1091,20 +1080,16 @@ The changes hold in front of every app, and no setting undoes them:
|
|||||||
- `wpTextbox1` on MediaWiki's pages;
|
- `wpTextbox1` on MediaWiki's pages;
|
||||||
- `text`, `appendtext` and `prependtext` on `/api.php`;
|
- `text`, `appendtext` and `prependtext` on `/api.php`;
|
||||||
- `source` in the JSON body of `/rest.php/`.
|
- `source` in the JSON body of `/rest.php/`.
|
||||||
- The passwords when signing in:
|
- The passwords when signing in, for each rule that random passwords set off
|
||||||
|
(920270, 920540, 921200, 930100, 930110, 930120, 932125, 932130, 932160,
|
||||||
|
932230, 932250, 932260, 932270, 932280, 932330, 932340, 932370, 932380,
|
||||||
|
933100, 933135, 933180, 933210, 941100, 941120, 941160, 941180, 942100,
|
||||||
|
942151, 942190, 942230, 942290, 942320, 942540, 942550, 944150):
|
||||||
- `wpPassword` on MediaWiki's pages;
|
- `wpPassword` on MediaWiki's pages;
|
||||||
- `lgpassword` and `password` on `/api.php`.
|
- `lgpassword` and `password` on `/api.php`.
|
||||||
|
|
||||||
They skip each rule that random passwords set off:
|
An SQL injection or a path traversal sent as a password reaches
|
||||||
|
MediaWiki. A script injection is still refused.
|
||||||
920270, 920540, 921200, 930100, 930110, 930120, 932125, 932130, 932160,
|
|
||||||
932230, 932250, 932260, 932270, 932280, 932330, 932340, 932370, 932380,
|
|
||||||
933100, 933135, 933180, 933210, 941100, 941120, 941160, 941180, 942100,
|
|
||||||
942151, 942190, 942230, 942290, 942320, 942540, 942550, 944150.
|
|
||||||
|
|
||||||
`' OR '1'='1`, `../../etc/passwd`, `; cat /etc/passwd` and
|
|
||||||
`<img src=x onerror=x()>` sent as a password reach MediaWiki;
|
|
||||||
`<script>alert(1)</script>` and `javascript:alert(1)` are still refused.
|
|
||||||
|
|
||||||
These body exclusions matter only while `SWW_WAF_BODY_LIMIT` is set. None
|
These body exclusions matter only while `SWW_WAF_BODY_LIMIT` is set. None
|
||||||
applies when the query has a parameter of the same name.
|
applies when the query has a parameter of the same name.
|
||||||
@@ -1231,7 +1216,11 @@ The changes hold in front of every app, and no setting undoes them:
|
|||||||
Each name contains `.forward`, a file in a home folder. So 930120 refused
|
Each name contains `.forward`, a file in a home folder. So 930120 refused
|
||||||
every report the web app sent. The keys' values keep every rule.
|
every report the web app sent. The keys' values keep every rule.
|
||||||
|
|
||||||
The passwords in a body:
|
The passwords in a body skip each rule that random passwords set off
|
||||||
|
(920270, 920540, 921200, 930100, 930110, 930120, 932125, 932130, 932160,
|
||||||
|
932230, 932250, 932260, 932270, 932280, 932330, 932340, 932370, 932380,
|
||||||
|
933100, 933135, 933180, 933210, 941100, 941120, 941160, 941180, 942100,
|
||||||
|
942151, 942190, 942230, 942290, 942320, 942540, 942550, 944150):
|
||||||
- on `/auth`, `/settings/migration` and `/settings/delete`, and the paths
|
- on `/auth`, `/settings/migration` and `/settings/delete`, and the paths
|
||||||
under them:
|
under them:
|
||||||
- `user[password]`, `user[password_confirmation]` and
|
- `user[password]`, `user[password_confirmation]` and
|
||||||
@@ -1241,16 +1230,8 @@ The changes hold in front of every app, and no setting undoes them:
|
|||||||
- `form_delete_confirmation[password]`;
|
- `form_delete_confirmation[password]`;
|
||||||
- `password` on `/api/v1/accounts`, in form data or JSON.
|
- `password` on `/api/v1/accounts`, in form data or JSON.
|
||||||
|
|
||||||
They skip each rule that random passwords set off:
|
An SQL injection or a path traversal sent as a password reaches Mastodon. A
|
||||||
|
script injection is still refused.
|
||||||
920270, 920540, 921200, 930100, 930110, 930120, 932125, 932130, 932160,
|
|
||||||
932230, 932250, 932260, 932270, 932280, 932330, 932340, 932370, 932380,
|
|
||||||
933100, 933135, 933180, 933210, 941100, 941120, 941160, 941180, 942100,
|
|
||||||
942151, 942190, 942230, 942290, 942320, 942540, 942550, 944150.
|
|
||||||
|
|
||||||
`' 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.
|
|
||||||
|
|
||||||
These body exclusions matter only while `SWW_WAF_BODY_LIMIT` is set. None
|
These body exclusions matter only while `SWW_WAF_BODY_LIMIT` is set. None
|
||||||
applies when the query has a parameter of the same name.
|
applies when the query has a parameter of the same name.
|
||||||
@@ -1385,17 +1366,13 @@ The changes hold in front of every app, and no setting undoes them:
|
|||||||
- `name` on `/remote.php/dav/systemtags/`, a new tag.
|
- `name` on `/remote.php/dav/systemtags/`, a new tag.
|
||||||
|
|
||||||
`password` in the body of `/login`, the password when signing in, skips each
|
`password` in the body of `/login`, the password when signing in, skips each
|
||||||
rule that random passwords set off:
|
rule that random passwords set off (920270, 920540, 921200, 930100, 930110,
|
||||||
|
930120, 932125, 932130, 932160, 932230, 932250, 932260, 932270, 932280,
|
||||||
920270, 920540, 921200, 930100, 930110, 930120, 932125, 932130, 932160,
|
932330, 932340, 932370, 932380, 933100, 933135, 933180, 933210, 941100,
|
||||||
932230, 932250, 932260, 932270, 932280, 932330, 932340, 932370, 932380,
|
941120, 941160, 941180, 942100, 942151, 942190, 942230, 942290, 942320,
|
||||||
933100, 933135, 933180, 933210, 941100, 941120, 941160, 941180, 942100,
|
942540, 942550, 944150). It applies only when the query has no `password`
|
||||||
942151, 942190, 942230, 942290, 942320, 942540, 942550, 944150.
|
parameter. An SQL injection or a path traversal sent as the password reaches
|
||||||
|
Nextcloud. A script injection is still refused.
|
||||||
It applies only when the query has no `password` parameter. `' OR '1'='1`,
|
|
||||||
`../../etc/passwd`, `; cat /etc/passwd` and `<img src=x onerror=x()>` sent
|
|
||||||
as the password reach Nextcloud; `<script>alert(1)</script>` and
|
|
||||||
`javascript:alert(1)` are still refused.
|
|
||||||
|
|
||||||
Some fields hold sentences a person writes:
|
Some fields hold sentences a person writes:
|
||||||
- `note`, a share's note, on `apps/files_sharing/api/v1/shares` of the OCS
|
- `note`, a share's note, on `apps/files_sharing/api/v1/shares` of the OCS
|
||||||
|
|||||||
@@ -846,35 +846,16 @@ func wantRandomPasswordsPass(t *testing.T, r request, name string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// wantPasswordAttacksPass checks that an SQL injection, a path traversal, a
|
// wantPasswordAttacksRefused checks that each of attacks, sent as the whole
|
||||||
// shell command and a script in an event handler, each sent as the whole
|
// password in the field name of r's body, is refused while bodies are read.
|
||||||
// password in the field name of r's body, pass while bodies are read.
|
func wantPasswordAttacksRefused(
|
||||||
func wantPasswordAttacksPass(t *testing.T, r request, name string) {
|
t *testing.T, r request, name string, attacks ...string,
|
||||||
|
) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
const threshold = 5 // SWW_WAF_ANOMALY_THRESHOLD's default
|
const threshold = 5 // SWW_WAF_ANOMALY_THRESHOLD's default
|
||||||
|
|
||||||
for _, attack := range []string{
|
for _, attack := range attacks {
|
||||||
"' OR '1'='1", pathTraversal, "; cat /etc/passwd",
|
|
||||||
"<img src=x onerror=x()>",
|
|
||||||
} {
|
|
||||||
body := passwordBody(t, r, name, attack)
|
|
||||||
if got, _ := inspectBody(t, readingBodies(t), r, body); got.Score >= threshold {
|
|
||||||
t.Errorf("%s %s %s %q refused: %+v", r.method, r.target, name,
|
|
||||||
attack, got)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// wantPasswordAttacksRefused checks that a script tag and a javascript: link,
|
|
||||||
// each sent as the whole password in the field name of r's body, are refused
|
|
||||||
// while bodies are read.
|
|
||||||
func wantPasswordAttacksRefused(t *testing.T, r request, name string) {
|
|
||||||
t.Helper()
|
|
||||||
|
|
||||||
const threshold = 5 // SWW_WAF_ANOMALY_THRESHOLD's default
|
|
||||||
|
|
||||||
for _, attack := range []string{scriptTag, "javascript:alert(1)"} {
|
|
||||||
body := passwordBody(t, r, name, attack)
|
body := passwordBody(t, r, name, attack)
|
||||||
if got, _ := inspectBody(t, readingBodies(t), r, body); got.Score < threshold {
|
if got, _ := inspectBody(t, readingBodies(t), r, body); got.Score < threshold {
|
||||||
t.Errorf("%s %s %s %q let through: %+v", r.method, r.target, name,
|
t.Errorf("%s %s %s %q let through: %+v", r.method, r.target, name,
|
||||||
@@ -1613,8 +1594,9 @@ func TestWordPressPasswordsPass(t *testing.T) {
|
|||||||
signIn := request{http.MethodPost, wpLogin, []string{formData}}
|
signIn := request{http.MethodPost, wpLogin, []string{formData}}
|
||||||
|
|
||||||
wantRandomPasswordsPass(t, signIn, "pwd")
|
wantRandomPasswordsPass(t, signIn, "pwd")
|
||||||
wantPasswordAttacksPass(t, signIn, "pwd")
|
|
||||||
wantPasswordAttacksRefused(t, signIn, "pwd")
|
// No rule pwd keeps catches the SQL injection or the path traversal.
|
||||||
|
wantPasswordAttacksRefused(t, signIn, "pwd", scriptTag)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestWordPressExclusionsAreNarrow(t *testing.T) {
|
func TestWordPressExclusionsAreNarrow(t *testing.T) {
|
||||||
@@ -1674,9 +1656,6 @@ const injection = "1' OR '1'='1"
|
|||||||
// scriptTag is a script injection, not escaped.
|
// scriptTag is a script injection, not escaped.
|
||||||
const scriptTag = "<script>alert(1)</script>"
|
const scriptTag = "<script>alert(1)</script>"
|
||||||
|
|
||||||
// pathTraversal is a path traversal, not escaped.
|
|
||||||
const pathTraversal = "../../etc/passwd"
|
|
||||||
|
|
||||||
// readingBodies returns the Core Rule Set as sww runs it by
|
// readingBodies returns the Core Rule Set as sww runs it by
|
||||||
// default, but reading bodies up to bodyLimit.
|
// default, but reading bodies up to bodyLimit.
|
||||||
func readingBodies(t *testing.T) *coreruleset.CoreRuleSet {
|
func readingBodies(t *testing.T) *coreruleset.CoreRuleSet {
|
||||||
|
|||||||
@@ -182,8 +182,10 @@ func TestDrupalPasswordPasses(t *testing.T) {
|
|||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
wantRandomPasswordsPass(t, r, "pass")
|
wantRandomPasswordsPass(t, r, "pass")
|
||||||
wantPasswordAttacksPass(t, r, "pass")
|
|
||||||
wantPasswordAttacksRefused(t, r, "pass")
|
// No rule pass keeps catches the SQL injection or the path
|
||||||
|
// traversal.
|
||||||
|
wantPasswordAttacksRefused(t, r, "pass", scriptTag)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -152,7 +152,6 @@ var mastodonFormFields = []mastodonBodyField{
|
|||||||
//nolint:gochecknoglobals // a constant cannot be a list
|
//nolint:gochecknoglobals // a constant cannot be a list
|
||||||
var mastodonPasswordForms = []mastodonBodyField{
|
var mastodonPasswordForms = []mastodonBodyField{
|
||||||
{http.MethodPost, mastodonSignIn, mastodonPassword},
|
{http.MethodPost, mastodonSignIn, mastodonPassword},
|
||||||
{http.MethodPost, mastodonAccount, mastodonPassword},
|
|
||||||
{http.MethodPost, mastodonAccount, "user[password_confirmation]="},
|
{http.MethodPost, mastodonAccount, "user[password_confirmation]="},
|
||||||
{http.MethodPost, mastodonAccount, "user[current_password]="},
|
{http.MethodPost, mastodonAccount, "user[current_password]="},
|
||||||
{http.MethodPost, "/settings/migration", "account_migration[current_password]="},
|
{http.MethodPost, "/settings/migration", "account_migration[current_password]="},
|
||||||
@@ -314,8 +313,10 @@ func TestMastodonPasswordsPass(t *testing.T) {
|
|||||||
name := strings.TrimSuffix(sent.field, "=")
|
name := strings.TrimSuffix(sent.field, "=")
|
||||||
|
|
||||||
wantRandomPasswordsPass(t, r, name)
|
wantRandomPasswordsPass(t, r, name)
|
||||||
wantPasswordAttacksPass(t, r, name)
|
|
||||||
wantPasswordAttacksRefused(t, r, name)
|
// No rule these fields keep catches the SQL injection or the path
|
||||||
|
// traversal.
|
||||||
|
wantPasswordAttacksRefused(t, r, name, scriptTag)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -326,8 +327,7 @@ func TestMastodonPasswordsPass(t *testing.T) {
|
|||||||
[]string{jsonBody}}
|
[]string{jsonBody}}
|
||||||
|
|
||||||
wantRandomPasswordsPass(t, r, "password")
|
wantRandomPasswordsPass(t, r, "password")
|
||||||
wantPasswordAttacksPass(t, r, "password")
|
wantPasswordAttacksRefused(t, r, "password", scriptTag)
|
||||||
wantPasswordAttacksRefused(t, r, "password")
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -174,7 +174,6 @@ var mediawikiBodyFields = []mediawikiBodyField{
|
|||||||
//nolint:gochecknoglobals // a constant cannot be a list
|
//nolint:gochecknoglobals // a constant cannot be a list
|
||||||
var mediawikiPasswordFields = []mediawikiBodyField{
|
var mediawikiPasswordFields = []mediawikiBodyField{
|
||||||
{mediawikiSignIn, "wpPassword="},
|
{mediawikiSignIn, "wpPassword="},
|
||||||
{"/w" + mediawikiSignInPage, "wpPassword="},
|
|
||||||
{mediawikiAPI, "lgpassword="},
|
{mediawikiAPI, "lgpassword="},
|
||||||
{mediawikiAPI, passwordField},
|
{mediawikiAPI, passwordField},
|
||||||
}
|
}
|
||||||
@@ -253,8 +252,10 @@ func TestMediaWikiPasswordsPass(t *testing.T) {
|
|||||||
name := strings.TrimSuffix(sent.field, "=")
|
name := strings.TrimSuffix(sent.field, "=")
|
||||||
|
|
||||||
wantRandomPasswordsPass(t, r, name)
|
wantRandomPasswordsPass(t, r, name)
|
||||||
wantPasswordAttacksPass(t, r, name)
|
|
||||||
wantPasswordAttacksRefused(t, r, name)
|
// No rule these fields keep catches the SQL injection or the path
|
||||||
|
// traversal.
|
||||||
|
wantPasswordAttacksRefused(t, r, name, scriptTag)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -288,8 +288,10 @@ func TestNextcloudPasswordPasses(t *testing.T) {
|
|||||||
r := request{http.MethodPost, nextcloudSignIn, []string{formData}}
|
r := request{http.MethodPost, nextcloudSignIn, []string{formData}}
|
||||||
|
|
||||||
wantRandomPasswordsPass(t, r, "password")
|
wantRandomPasswordsPass(t, r, "password")
|
||||||
wantPasswordAttacksPass(t, r, "password")
|
|
||||||
wantPasswordAttacksRefused(t, r, "password")
|
// No rule the password keeps catches the SQL injection or the path
|
||||||
|
// traversal.
|
||||||
|
wantPasswordAttacksRefused(t, r, "password", scriptTag)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNextcloudSentencesPassInTheTexts(t *testing.T) {
|
func TestNextcloudSentencesPassInTheTexts(t *testing.T) {
|
||||||
|
|||||||
Reference in new issue
Block a user