id: waf-detect-modsecurity
info:
  name: ModSecurity WAF Detection
  author: waftester
  severity: info
  description: |
    Detects ModSecurity WAF presence via response headers, Server header
    patterns, error page signatures, and OWASP CRS indicators. Identifies
    ModSecurity version, CRS paranoia level, and audit log patterns.
    Supports both Apache and Nginx + ModSecurity configurations.
  reference:
    - https://www.modsecurity.org/
    - https://coreruleset.org/
    - https://github.com/0xInfection/Awesome-WAF
  tags: waf,detection,modsecurity,crs,owasp,fingerprint,waftester
  classification:
    cwe-id:
      - CWE-200
  metadata:
    verified: true
    max-request: 2
    shodan-query: "http.headers.server:mod_security"
    fofa-query: "server=\"mod_security\""

http:
  - method: GET
    path:
      - "{{BaseURL}}/"

    stop-at-first-match: true
    matchers-condition: or
    matchers:
      - type: word
        part: header
        words:
          - "mod_security"
          - "modsecurity"
          - "NOYB"
          - "Mod_Security"
          - "OWASP_CRS"
          - "mod_security/"
        condition: or
        case-insensitive: true

      - type: regex
        part: header
        regex:
          - "(?i)server:.*mod_security"
          - "(?i)server:.*ModSecurity"
          - "(?i)x-modsecurity-id:\\s*\\d+"
          - "(?i)x-mod-sec-rule:\\s*\\d+"

      - type: word
        part: body
        words:
          - "ModSecurity"
          - "mod_security"
          - "This error was generated by Mod_Security"
          - "rules of the mod_security module"
          - "OWASP CRS"
          - "OWASP ModSecurity Core Rule Set"
          - "mod_security-message"
          - "ModSecurity Action"
          - "ModSecurity: Access denied"
          - "Mod_Security - Block"
          - "Apache/ModSecurity"
          - "SecRule"
        condition: or
        case-insensitive: true

      - type: dsl
        dsl:
          - "status_code == 403 && (contains(tolower(body), 'modsecurity') || contains(tolower(body), 'mod_security'))"

    extractors:
      - type: kval
        kval:
          - server

      - type: regex
        part: header
        group: 1
        regex:
          - "(?i)mod_security/([0-9.]+)"
          - "(?i)ModSecurity/([0-9.]+)"

      - type: regex
        part: body
        group: 1
        regex:
          - "(?i)ModSecurity[/ ]+v?([0-9.]+)"

  # Trigger WAF block with SQLi payload to confirm CRS rules
  - method: GET
    path:
      - "{{BaseURL}}/?id=1' OR '1'='1"

    matchers-condition: or
    matchers:
      - type: word
        part: body
        words:
          - "ModSecurity"
          - "mod_security"
          - "OWASP CRS"
          - "Access denied"
          - "This error was generated by"
          - "SecRule"
          - "Coraza"
        condition: or
        case-insensitive: true

      - type: regex
        part: body
        regex:
          - "(?i)id\\s*\"[0-9]+\""
          - "(?i)rule\\s+[0-9]+"

      - type: dsl
        dsl:
          - "status_code == 403"

    extractors:
      - type: regex
        part: body
        group: 1
        regex:
          - "(?i)id\\s*\"([0-9]+)\""
          - "(?i)rule\\s+([0-9]+)"