# The sixteenth change: on Ghost's paths, some fields skip the rules that refused # ordinary Ghost requests there. 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 body applies only when the query has no parameter of that name, so it # matters only while SWW_WAF_BODY_LIMIT is set. # # Ghost's admin and its integrations call the Admin API under /ghost/api/admin/, # themes and Ghost's own scripts call the Content API under /ghost/api/content/, # and members call /members/api/. Coraza names a field of a JSON body by its # path, so the title of the first post saved is json.posts.0.title. # filter, a query in Ghost's own filter language, skips 932120 on the Admin API # and the Content API: filter=tag:powershell, filter=name:~'powershell'. SecRule REQUEST_FILENAME "@rx ^/ghost/api/(?:admin|content)/" \ "id:900350,phase:1,pass,nolog,\ ctl:ruleRemoveTargetById=932120;ARGS:filter" # search, the search for a member by name or address, skips four rules on the # Admin API: # 930120: search=.htaccess # 932120: search=powershell # 932250: search=ls -la # 932260: search=python3 SecRule REQUEST_FILENAME "@beginsWith /ghost/api/admin/" \ "id:900351,phase:1,pass,nolog,\ ctl:ruleRemoveTargetById=930120;ARGS:search,\ ctl:ruleRemoveTargetById=932120;ARGS:search,\ ctl:ruleRemoveTargetById=932250;ARGS:search,\ ctl:ruleRemoveTargetById=932260;ARGS:search" # url, the address the editor looks up for an embed or a bookmark, skips two # rules: # 930120: url=https://github.com/example/site/blob/main/.htaccess # 932120: url=https://example.com/blog/powershell-tips SecRule REQUEST_FILENAME "@streq /ghost/api/admin/oembed/" \ "id:900352,phase:1,pass,nolog,\ ctl:ruleRemoveTargetById=930120;ARGS:url,\ ctl:ruleRemoveTargetById=932120;ARGS:url" # The fields a person types plain text into skip seven rules on the Admin API: # 930120: the title "Using python3 and ls -la to tidy .htaccess files" # 932120: the page's slug about-python3-powershell-htaccess # 932125, 932230, 932250: the tag "Linux & ls -la" # 932260: the tag python3 # 932380: the newsletter "Shell tips weekly: python3 & powershell" # These fields hold plain text: # title, name and slug of posts, pages, tags, labels, newsletters and # snippets; a post's tags and a member's labels, by name or slug # custom_excerpt, description, note and sender_name # meta_title, meta_description, og_title, og_description, twitter_title and # twitter_description SecRule REQUEST_FILENAME "@beginsWith /ghost/api/admin/" \ "id:900353,phase:1,pass,nolog,chain" SecRule &ARGS_GET:/^json\.(?:posts|pages|tags|labels|members|newsletters|snippets)\.\d+\.(?:title|name|slug|custom_excerpt|description|note|sender_name|(?:meta|og|twitter)_(?:title|description)|(?:tags|labels)\.\d+(?:\.(?:name|slug))?)$/ "@eq 0" \ "ctl:ruleRemoveTargetById=930120;ARGS:/^json\.(?:posts|pages|tags|labels|members|newsletters|snippets)\.\d+\.(?:title|name|slug|custom_excerpt|description|note|sender_name|(?:meta|og|twitter)_(?:title|description)|(?:tags|labels)\.\d+(?:\.(?:name|slug))?)$/,\ ctl:ruleRemoveTargetById=932120;ARGS:/^json\.(?:posts|pages|tags|labels|members|newsletters|snippets)\.\d+\.(?:title|name|slug|custom_excerpt|description|note|sender_name|(?:meta|og|twitter)_(?:title|description)|(?:tags|labels)\.\d+(?:\.(?:name|slug))?)$/,\ ctl:ruleRemoveTargetById=932125;ARGS:/^json\.(?:posts|pages|tags|labels|members|newsletters|snippets)\.\d+\.(?:title|name|slug|custom_excerpt|description|note|sender_name|(?:meta|og|twitter)_(?:title|description)|(?:tags|labels)\.\d+(?:\.(?:name|slug))?)$/,\ ctl:ruleRemoveTargetById=932230;ARGS:/^json\.(?:posts|pages|tags|labels|members|newsletters|snippets)\.\d+\.(?:title|name|slug|custom_excerpt|description|note|sender_name|(?:meta|og|twitter)_(?:title|description)|(?:tags|labels)\.\d+(?:\.(?:name|slug))?)$/,\ ctl:ruleRemoveTargetById=932250;ARGS:/^json\.(?:posts|pages|tags|labels|members|newsletters|snippets)\.\d+\.(?:title|name|slug|custom_excerpt|description|note|sender_name|(?:meta|og|twitter)_(?:title|description)|(?:tags|labels)\.\d+(?:\.(?:name|slug))?)$/,\ ctl:ruleRemoveTargetById=932260;ARGS:/^json\.(?:posts|pages|tags|labels|members|newsletters|snippets)\.\d+\.(?:title|name|slug|custom_excerpt|description|note|sender_name|(?:meta|og|twitter)_(?:title|description)|(?:tags|labels)\.\d+(?:\.(?:name|slug))?)$/,\ ctl:ruleRemoveTargetById=932380;ARGS:/^json\.(?:posts|pages|tags|labels|members|newsletters|snippets)\.\d+\.(?:title|name|slug|custom_excerpt|description|note|sender_name|(?:meta|og|twitter)_(?:title|description)|(?:tags|labels)\.\d+(?:\.(?:name|slug))?)$/" # name, the name a member saves at /members/api/, skips the same seven rules. SecRule REQUEST_FILENAME "@beginsWith /members/api/" \ "id:900359,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=932125;ARGS:/^json\.name$/,\ ctl:ruleRemoveTargetById=932230;ARGS:/^json\.name$/,\ ctl:ruleRemoveTargetById=932250;ARGS:/^json\.name$/,\ ctl:ruleRemoveTargetById=932260;ARGS:/^json\.name$/,\ ctl:ruleRemoveTargetById=932380;ARGS:/^json\.name$/" # A member's email address skips 930120, 932120 and 932260, which refused # erin.htaccess@example.com, dave.powershell@example.com and # python3.dev@example.com. These fields hold it: # members..email, when the admin adds or edits a member under # /ghost/api/admin/members/ # email, when a member signs up or in at /members/api/ # An address holds no spaces, so it keeps 932250, which needs ls -la. SecRule REQUEST_FILENAME "@beginsWith /ghost/api/admin/members/" \ "id:900354,phase:1,pass,nolog,chain" SecRule &ARGS_GET:/^json\.members\.\d+\.email$/ "@eq 0" \ "ctl:ruleRemoveTargetById=930120;ARGS:/^json\.members\.\d+\.email$/,\ ctl:ruleRemoveTargetById=932120;ARGS:/^json\.members\.\d+\.email$/,\ ctl:ruleRemoveTargetById=932260;ARGS:/^json\.members\.\d+\.email$/" SecRule REQUEST_FILENAME "@beginsWith /members/api/" \ "id:900360,phase:1,pass,nolog,chain" SecRule &ARGS_GET:/^json\.email$/ "@eq 0" \ "ctl:ruleRemoveTargetById=930120;ARGS:/^json\.email$/,\ ctl:ruleRemoveTargetById=932120;ARGS:/^json\.email$/,\ ctl:ruleRemoveTargetById=932260;ARGS:/^json\.email$/" # The articles skip these rules on the Admin API: # 930120: ls -la /var/www/html/.htaccess in a code block # 932120: powershell -ExecutionPolicy Bypass -File build.ps1 # 932125, 932230, 932235, 932250: cat /etc/hosts | grep example # 932160: #!/usr/bin/env python3 # 932260: python3 -m venv .venv # 932370: the same list in the html of a post saved through the API # 932380: a code block whose second line starts with powershell # 934100: document.querySelectorAll('a').forEach((a) => console.log(a.href)) # 941100, 941160: an HTML card with an iframe # 941120: onclick= in an HTML card # 941180: window.onload in a code block # 941390: console.log( # 942190: a code block with an SQL query # These fields hold an article: # posts..lexical and pages..lexical, which the editor saves, and the # same fields mobiledoc, in Ghost's older format, and html, which # integrations send # snippets..lexical and snippets..mobiledoc, a part of a post saved for # reuse # newsletters..footer_content, the footer of a newsletter # A