AWS WAF ReactJS RCE Attempt via Body


Description

Detects AWS WAF ReactJSRCE_BODY managed rule matches indicating React2Shell (CVE-2025-55182) ReactJS RCE attempts via HTTP body. Monitors all WAF sources: ALB, CloudFront, API Gateway, AppSync.

Query · python

RULE_ID = "ReactJSRCE_BODY"


def rule(event):
    # Direct check of terminating rule ID
    if RULE_ID in event.get("terminatingRuleId", ""):
        return True

    # Check non-terminating rules
    for matching_rule in event.get("nonTerminatingMatchingRules", []) or []:
        if RULE_ID in matching_rule.get("ruleId", ""):
            return True

    # Check rule groups
    for group in event.get("ruleGroupList", []) or []:
        terminating = group.get("terminatingRule") or {}
        if RULE_ID in terminating.get("ruleId", ""):
            return True

        for matching_rule in group.get("nonTerminatingMatchingRules", []) or []:
            if RULE_ID in matching_rule.get("ruleId", ""):
                return True

    return False


def title(event):
    client_ip = event.deep_get("httpRequest", "clientIp", default="<UNKNOWN_CLIENT_IP>")
    action = event.get("action", default="<UNKNOWN_ACTION>")
    source = event.get("httpSourceName", default="<UNKNOWN_SOURCE>")
    return f"AWS WAF {RULE_ID} Match - {action} from {client_ip} via {source}"


def alert_context(event):
    http_request = event.get("httpRequest", {})
    headers = http_request.get("headers", [])
    user_agent = next(
        (h.get("value") for h in headers if h.get("name", "").lower() == "user-agent"), None
    )

    context = {
        "client_ip": http_request.get("clientIp"),
        "country": http_request.get("country"),
        "http_method": http_request.get("httpMethod"),
        "uri": http_request.get("uri"),
        "user_agent": user_agent,
        "action": event.get("action"),
        "source": event.get("httpSourceName"),
        "source_id": event.get("httpSourceId"),
        "terminating_rule_id": event.get("terminatingRuleId"),
        "terminating_rule_type": event.get("terminatingRuleType"),
    }

    # Add matched data if available
    terminating_matches = event.get("terminatingRuleMatchDetails", [])
    if terminating_matches:
        context["matched_data"] = [
            {
                "condition_type": m.get("conditionType"),
                "location": m.get("location"),
                "matched_strings": m.get("matchedData", []),
            }
            for m in terminating_matches
        ]

    return context


def severity(event):
    action = event.get("action", "")
    if action == "ALLOW":
        return "CRITICAL"
    if action == "BLOCK":
        return "HIGH"
    if action == "COUNT":
        return "MEDIUM"
    return "DEFAULT"

Analyst notes

  1. Review alert context for source IP, URI, and body content
  2. Check action: BLOCK (HIGH) or ALLOW (CRITICAL - investigate immediately)
  3. If allowed, check target application for compromise
  4. Review backend server logs for suspicious activity
  5. Block repeat offender IPs and validate ReactJS input handling
Raw source AWS WAF ReactJS RCE Attempt via Body · Panther Python
Esc
Published by panther-labs/panther-analysis ↗, licensed under Apache 2.0 ↗. Reproduced here unmodified.
AnalysisType: rule
Filename: aws_waf_reactjsrce_body.py
RuleID: "AWS.WAF.ReactJSRCE.Body"
DisplayName: "AWS WAF ReactJS RCE Attempt via Body"
Enabled: true
LogTypes:
  - AWS.WAFWebACL
Tags:
  - AWS
  - WAF
  - React2Shell
  - Initial Access:Exploit Public-Facing Application
  - Execution:Command and Scripting Interpreter
Reports:
  MITRE ATT&CK:
    - TA0001:T1190
    - TA0002:T1059
Severity: High
Description: >
  Detects AWS WAF ReactJSRCE_BODY managed rule matches indicating React2Shell (CVE-2025-55182)
  ReactJS RCE attempts via HTTP body. Monitors all WAF sources: ALB, CloudFront, API Gateway, AppSync.
Runbook: |
  1. Review alert context for source IP, URI, and body content
  2. Check action: BLOCK (HIGH) or ALLOW (CRITICAL - investigate immediately)
  3. If allowed, check target application for compromise
  4. Review backend server logs for suspicious activity
  5. Block repeat offender IPs and validate ReactJS input handling
Reference: https://docs.aws.amazon.com/waf/latest/developerguide/aws-managed-rule-groups-list.html
Tests:
  - Name: Blocked via terminatingRuleId
    ExpectedResult: true
    Log:
      timestamp: "2024-03-20T10:30:00.000Z"
      webaclId: "arn:aws:wafv2:us-east-1:123456789012:regional/webacl/test/a1b2c3d4"
      terminatingRuleId: "ReactJSRCE_BODY"
      action: "BLOCK"
      httpSourceName: "ALB"
      httpRequest:
        clientIp: "203.0.113.45"
        country: "US"
        uri: "/api/endpoint"
        httpMethod: "POST"

  - Name: Allowed via nonTerminatingMatchingRules (Critical)
    ExpectedResult: true
    Log:
      timestamp: "2024-03-20T10:35:00.000Z"
      webaclId: "arn:aws:wafv2:us-east-1:123456789012:regional/webacl/test/a1b2c3d4"
      terminatingRuleId: "Default_Action"
      action: "ALLOW"
      httpSourceName: "ALB"
      httpRequest:
        clientIp: "198.51.100.22"
        country: "CN"
        uri: "/admin/upload"
      nonTerminatingMatchingRules:
        - ruleId: "ReactJSRCE_BODY"
          action: "COUNT"

  - Name: Match in ruleGroupList
    ExpectedResult: true
    Log:
      timestamp: "2024-03-20T10:40:00.000Z"
      webaclId: "arn:aws:wafv2:us-east-1:123456789012:regional/webacl/test/a1b2c3d4"
      terminatingRuleId: "AWS-AWSManagedRulesKnownBadInputsRuleSet"
      action: "BLOCK"
      httpSourceName: "ALB"
      httpRequest:
        clientIp: "192.0.2.100"
        country: "RU"
        uri: "/api/v1/process"
      ruleGroupList:
        - ruleGroupId: "AWS-AWSManagedRulesKnownBadInputsRuleSet"
          terminatingRule:
            ruleId: "ReactJSRCE_BODY"
            action: "BLOCK"

  - Name: CloudFront source detected
    ExpectedResult: true
    Log:
      timestamp: "2024-03-20T10:45:00.000Z"
      webaclId: "arn:aws:wafv2:us-east-1:123456789012:regional/webacl/test/a1b2c3d4"
      terminatingRuleId: "ReactJSRCE_BODY"
      action: "BLOCK"
      httpSourceName: "CF"
      httpRequest:
        clientIp: "203.0.113.45"
        uri: "/api/endpoint"

  - Name: Different rule - no alert
    ExpectedResult: false
    Log:
      timestamp: "2024-03-20T10:50:00.000Z"
      webaclId: "arn:aws:wafv2:us-east-1:123456789012:regional/webacl/test/a1b2c3d4"
      terminatingRuleId: "SQLi_BODY"
      action: "BLOCK"
      httpSourceName: "ALB"
      httpRequest:
        clientIp: "203.0.113.45"

  - Name: Normal traffic - no alert
    ExpectedResult: false
    Log:
      timestamp: "2024-03-20T10:55:00.000Z"
      webaclId: "arn:aws:wafv2:us-east-1:123456789012:regional/webacl/test/a1b2c3d4"
      terminatingRuleId: "Default_Action"
      action: "ALLOW"
      httpSourceName: "ALB"
      httpRequest:
        clientIp: "198.51.100.10"

DedupPeriodMinutes: 60
Threshold: 1


# ------ paired body: aws_waf_reactjsrce_body.py ------

RULE_ID = "ReactJSRCE_BODY"


def rule(event):
    # Direct check of terminating rule ID
    if RULE_ID in event.get("terminatingRuleId", ""):
        return True

    # Check non-terminating rules
    for matching_rule in event.get("nonTerminatingMatchingRules", []) or []:
        if RULE_ID in matching_rule.get("ruleId", ""):
            return True

    # Check rule groups
    for group in event.get("ruleGroupList", []) or []:
        terminating = group.get("terminatingRule") or {}
        if RULE_ID in terminating.get("ruleId", ""):
            return True

        for matching_rule in group.get("nonTerminatingMatchingRules", []) or []:
            if RULE_ID in matching_rule.get("ruleId", ""):
                return True

    return False


def title(event):
    client_ip = event.deep_get("httpRequest", "clientIp", default="<UNKNOWN_CLIENT_IP>")
    action = event.get("action", default="<UNKNOWN_ACTION>")
    source = event.get("httpSourceName", default="<UNKNOWN_SOURCE>")
    return f"AWS WAF {RULE_ID} Match - {action} from {client_ip} via {source}"


def alert_context(event):
    http_request = event.get("httpRequest", {})
    headers = http_request.get("headers", [])
    user_agent = next(
        (h.get("value") for h in headers if h.get("name", "").lower() == "user-agent"), None
    )

    context = {
        "client_ip": http_request.get("clientIp"),
        "country": http_request.get("country"),
        "http_method": http_request.get("httpMethod"),
        "uri": http_request.get("uri"),
        "user_agent": user_agent,
        "action": event.get("action"),
        "source": event.get("httpSourceName"),
        "source_id": event.get("httpSourceId"),
        "terminating_rule_id": event.get("terminatingRuleId"),
        "terminating_rule_type": event.get("terminatingRuleType"),
    }

    # Add matched data if available
    terminating_matches = event.get("terminatingRuleMatchDetails", [])
    if terminating_matches:
        context["matched_data"] = [
            {
                "condition_type": m.get("conditionType"),
                "location": m.get("location"),
                "matched_strings": m.get("matchedData", []),
            }
            for m in terminating_matches
        ]

    return context


def severity(event):
    action = event.get("action", "")
    if action == "ALLOW":
        return "CRITICAL"
    if action == "BLOCK":
        return "HIGH"
    if action == "COUNT":
        return "MEDIUM"
    return "DEFAULT"

Detection rules belong to the projects that publish them and remain under their own licenses. This site indexes and links to them; it claims no rights in them.