Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Mitigating C-Segment IP-Based Malicious Traffic: 4 Defensive Strategies with ModSecurity

Tech May 9 4

Web application defenses like Nginx’s HttpLimitReqModule, Apache’s mod_evasive, and OWASP Core Rules effectively block single-IP malicious activity such as CC attacks or scraping by enforcing per-IP request limits. However, attackers often circumvent these measures by leveraging entire C-segment IP ranges—renting blocks from ISPs to distribute requests across hundreds of IPs, keeping per-IP request rates below threshold triggers. This renders standard per-IP defenses ineffective.

To address this gap, we outline four defensive strategies to mitigate C-segment-based malicious traffic, including their design logic, implemantation rules, and tradeoffs. Strategy 1 is theoretically the most efficient but cannot be used due to inherent ModSecurity limitations; we also detail the troubleshooting process that uncovered this constraint. For low-traffic sites, Strategy 2 is recommended. High-traffic sites should adopt Strategy 3 or 4, with Strategy 4 being preferred if in-house development resources are available.

Preconfiguration Steps

Before implementing any strategy, define core defense thresholds using OWASP Core Rules settings. Locate and uncomment the following two rules in crs-setup.conf: (Note: Enabling these rules activates built-in per-IP DDoS protection. Since our C-segment defenses will also cover per-IP scenarios, you can disable the separate OWASP per-IP DDoS rules to reduce rule overhead—rename REQUEST-912-DOS-PROTECTION.conf to REQUEST-912-DOS-PROTECTION.conf.bak or remove it.)

SecAction \
'id:900260,\
phase:1,\
nolog,\
pass,\
t:none,\
setvar:"tx.excluded_static_types=/.jpg/ /.jpeg/ /.png/ /.gif/ /.js/ /.css/ /.ico/ /.svg/ /.webp/"'

SecAction \
'id:900700,\
phase:1,\
nolog,\
pass,\
t:none,\
setvar:"tx.detection_interval=30",\
setvar:"tx.request_threshold=5",\
setvar:"tx.block_duration=600"'

Rule 900260 defines static file extensions that are excluded from request counting (since these are unlikely to be part of targeted scraping/attacks). Rule 900700 sets threshold values: if a C-segment exceeds tx.request_threshold requests within tx.detection_interval seconds, it will be blocked for tx.block_duration seconds. Adjust these values based on your site’s normal traffic patterns.


Strategy 1: Direct C-Segment Request Counting (Unavailable)

Defensive Logic

  1. Extract the C-segment prefix from the client’s IP address using regular expressions (e.g., 192.168.11.2 becomes "192.168.11.").
  2. Create a global variable named after the C-segment prefix to track total requests from that range, with a TTL matching tx.detection_interval to reset counts after the window expires.
  3. On each request: if the C-segment’s request count is below the threshold, increment the count; if above, block the request and extend the variable’s TTL to tx.block_duration to auto-unblock after the penalty period.

Implementation Rules

# Extract C-segment prefix from client IP and store in ip.c_segment_prefix
SecRule REMOTE_ADDR "^((25[0-5]|2[0-4]\\d|[01]?\\d\\d?)\\.){3}" "id:20000,nolog,pass,phase:1,capture,setvar:ip.c_segment_prefix=%{TX.0}"

# Block requests if C-segment exceeds threshold, extend block duration
SecRule GLOBAL:%{ip.c_segment_prefix} "@ge %{tx.request_threshold}" "id:20001,drop,log,phase:1,expirevar:global.%{ip.c_segment_prefix}=%{tx.block_duration}"

# Increment C-segment count only for non-static requests
SecRule REQUEST_BASENAME ".*?(\\.[a-z0-9]{1,10})?$" "id:20002,phase:5,t:none,t:lowercase,nolog,pass,capture,setvar:tx.req_extension=/%{TX.1}/,chain"
SecRule TX:REQ_EXTENSION "!@within %{tx.excluded_static_types}" "setvar:global.%{ip.c_segment_prefix}=+1,expirevar:global.%{ip.c_segment_prefix}=%{tx.detection_interval}"

Tradeoffs

Pros: Minimal rule overhead, no third-party tools required—purely ModSecurity-based.
Cons: Fully non-functional due to ModSecurity’s variable resolution limitations. The %{ip.c_segment_prefix} placeholder in GLOBAL:%{ip.c_segment_prefix} is not resolved to the actual prefix value; instead, ModSecurity looks for a global variable named literal string %{ip.c_segment_prefix}, which does not exist. Attempts to work around this by assigning the global variable value to a local IP-set variable (e.g., setvar:ip.c_segment_count=%{global.%{ip.c_segment_prefix}}) also fail, as ModSecurity only resolves nested %{} placeholders partially, resulting in invalid values. Source code modifications would be required to fix this, making this strategy unviable without custom ModSecurity builds.


Strategy 2: ModSecurity-Only C-Segment Blocking (For Low Concurrency)

Defensive Logic

Similar to Strategy 1, but avoids nested variable resolution by storing C-segment counts in global variables with explicit names (e.g., global.c_segment_192.168.11.). Use a per-IP variable to track if the client’s C-segment is blocked, and block requests once the threshold is exceeded. Since counting is done in phase 5 (post-request), we add a check in phase 1 to block subsequent requests from the same C-segment.

Implementation Rules

# Extract C-segment prefix from client IP
SecRule REMOTE_ADDR "^((25[0-5]|2[0-4]\\d|[01]?\\d\\d?)\\.){3}" "id:20000,nolog,pass,phase:1,capture,setvar:ip.c_segment_prefix=%{TX.0}"

# Block requests if C-segment is flagged as malicious, log only once per block period
SecRule IP:SEGMENT_BLOCKED "@eq 1" "id:20001,phase:1,drop,log,chain,msg:'Malicious traffic detected from C-segment %{ip.c_segment_prefix}0/24'"
SecRule &IP:BLOCK_LOGGED "@eq 0" "setvar:ip.block_logged=1,expirevar:ip.block_logged=%{tx.block_duration}"

# Block subsequent requests without logging to avoid log spam
SecRule IP:SEGMENT_BLOCKED "@eq 1" "id:20004,phase:1,drop,nolog"

# Increment C-segment count for non-static requests
SecRule REQUEST_BASENAME ".*?(\\.[a-z0-9]{1,10})?$" "phase:5,id:20002,t:none,t:lowercase,nolog,pass,capture,setvar:tx.req_extension=/%{TX.1}/,chain"  
SecRule TX:REQ_EXTENSION "!@within %{tx.excluded_static_types}" "setvar:'global.c_segment_%{ip.c_segment_prefix}=+1',expirevar:global.c_segment_%{ip.c_segment_prefix}=%{tx.detection_interval}"

# Check for C-segments exceeding threshold, flag them as blocked
SecRule GLOBAL:/^c_segment_/ "@ge %{tx.request_threshold}" "phase:5,id:20003,pass,nolog,chain"  
SecRule MATCHED_VAR_NAME "((25[0-5]|2[0-4]\\d|[01]?\\d\\d?)\\.){3}" "capture,setvar:tx.target_segment=%{TX.0},chain,expirevar:global.c_segment_%{TX.0}=%{tx.block_duration}" 
SecRule TX:TARGET_SEGMENT "@streq %{ip.c_segment_prefix}" "setvar:ip.segment_blocked=1,expirevar:ip.segment_blocked=%{tx.block_duration}"

Tradeoffs

Pros: No external tools required, fully ModSecurity-based.
Cons:

  • Increased server resource usage due to global variable iteration on each request (even though it’s done in phase 5).
  • First request from a new IP in a blocked C-segment will not be blocked (since the flag is set post-request). To fix this, move the iteration to phase 1, but this will degrade response times for high-concurrency sites.
  • Per-IP variables mean switching clients from the same IP will allow one unblocked request before the flag is re-applied.

Strategy 3: ModSecurity + Lua + ipset (For High Concurrency)

Defensive Logic

Optimize Strategy 2 by offloading blocking to ipset (a kernel-level IP blocking tool) instead of relying on ModSecurity variables. When a C-segment exceeds the threshold, delete the ModSecurity count variable and use a Lua script to add the C-segment to an ipset list, which is enforced via iptables. This reduces ongoing resource usage since ipset blocks requests before they reach ModSecurity.

Implementation Rules

# Extract C-segment prefix from client IP
SecRule REMOTE_ADDR "^((25[0-5]|2[0-4]\\d|[01]?\\d\\d?)\\.){3}" "id:20000,nolog,pass,phase:1,capture,setvar:ip.c_segment_prefix=%{TX.0}"

# Increment C-segment count for non-static requests
SecRule REQUEST_BASENAME ".*?(\\.[a-z0-9]{1,10})?$" "phase:5,id:20002,t:none,t:lowercase,nolog,pass,capture,setvar:tx.req_extension=/%{TX.1}/,chain"  
SecRule TX:REQ_EXTENSION "!@within %{tx.excluded_static_types}" "setvar:'global.c_segment_%{ip.c_segment_prefix}=+1',expirevar:global.c_segment_%{ip.c_segment_prefix}=%{tx.detection_interval}"

# Detect threshold breaches, delete count variable, and trigger ipset block via Lua
SecRule GLOBAL:/^c_segment_/ "@ge %{tx.request_threshold}" "phase:5,id:20003,pass,log,capture,msg:'Malicious traffic detected from C-segment %{TX.0}0/24',chain" 
SecRule MATCHED_VAR_NAME "((25[0-5]|2[0-4]\\d|[01]?\\d\\d?)\\.){3}" "capture,setvar:!global.c_segment_%{TX.0},exec:/tmp/c_segment_block.lua"

Lua Script (/tmp/c_segment_block.lua)

function main()
    local client_ip = m.getvar("REMOTE_ADDR")
    local ip_parts = {}
    local separator = '%.'
    string.gsub(client_ip, '[^'..separator..']+', function(part) table.insert(ip_parts, part) end)
    if #ip_parts == 4 then
        local c_segment_cidr = ip_parts[1].."."..ip_parts[2].."."..ip_parts[3]..".0/24"
        os.execute("sudo ipset add c_segment_blocklist "..c_segment_cidr)
    end
    return nil
end

Prerequisites

  1. Install ipset:
    yum install ipset-service
    systemctl enable --now ipset
    
  2. Create the ipset list with auto-expiry:
    ipset create c_segment_blocklist hash:ip timeout 3600
    ipset save > /etc/sysconfig/ipset
    
  3. Add an iptables rule to block traffic from the list:
    iptables -A INPUT -m set --match-set c_segment_blocklist src -p tcp --dport 80 -j DROP
    service iptables save
    
  4. Grant the web server user (e.g., daemon or www) passwordless sudo access to ipset:
    chmod 640 /etc/sudoers
    vi /etc/sudoers
    # Add below root ALL=(ALL) ALL:
    daemon    ALL=(ALL)       NOPASSWD: /usr/sbin/ipset
    chmod 440 /etc/sudoers
    

Tradeoffs

Pros: Reduces ModSecurity resource usage by offloading blocking to kernel-level tools, eliminating ongoing variable iteration after a segment is blocked.
Cons: Requires installing ipset and granting elevated privileges to the web server user, which introduces potential security risks if the server is compromised.


Strategy 4: ModSecurity + Lua + Local API (Most Secure for High Concurrency)

Defensive Logic

Enhance Strategy 3 by replacing direct ipset calls with a local, authenticated HTTP API. This avoids granting sudo access to the web server user. The API runs with root privileges and only accepts requests from localhost with a valid auth token, then executes ipset commands on behalf of ModSecurity.

Implementation Rules

# Extract C-segment prefix from client IP
SecRule REMOTE_ADDR "^((25[0-5]|2[0-4]\\d|[01]?\\d\\d?)\\.){3}" "id:20000,nolog,pass,phase:1,capture,setvar:ip.c_segment_prefix=%{TX.0}"

# Increment C-segment count for non-static requests
SecRule REQUEST_BASENAME ".*?(\\.[a-z0-9]{1,10})?$" "phase:5,id:20002,t:none,t:lowercase,nolog,pass,capture,setvar:tx.req_extension=/%{TX.1}/,chain"  
SecRule TX:REQ_EXTENSION "!@within %{tx.excluded_static_types}" "setvar:'global.c_segment_%{ip.c_segment_prefix}=+1',expirevar:global.c_segment_%{ip.c_segment_prefix}=%{tx.detection_interval}"

# Detect threshold breaches, delete count variable, and trigger API call via Lua
SecRule GLOBAL:/^c_segment_/ "@ge %{tx.request_threshold}" "phase:5,id:20003,pass,log,capture,msg:'Malicious traffic detected from C-segment %{TX.0}0/24',chain" 
SecRule MATCHED_VAR_NAME "((25[0-5]|2[0-4]\\d|[01]?\\d\\d?)\\.){3}" "capture,setvar:!global.c_segment_%{TX.0},exec:/tmp/c_segment_block_api.lua"

Lua Script (/tmp/c_segment_block_api.lua)

function main()
    local client_ip = m.getvar("REMOTE_ADDR")
    local ip_parts = {}
    local separator = '%.'
    string.gsub(client_ip, '[^'..separator..']+', function(part) table.insert(ip_parts, part) end)
    if #ip_parts == 4 then
        local c_segment_cidr = ip_parts[1].."."..ip_parts[2].."."..ip_parts[3]..".0/24"
        local auth_token = "your_secure_auth_token_here"
        os.execute("curl -s http://localhost:8080/block-c-segment?cidr="..c_segment_cidr.."&auth="..auth_token)
    end
    return nil
end

Prerequisites

  1. Set up the local API (example with PHP):
    <?php
    // /var/www/block-api/forbidden.php
    $valid_token = "your_secure_auth_token_here";
    $allowed_ip = "127.0.0.1";
    
    if ($_SERVER['REMOTE_ADDR'] !== $allowed_ip || $_GET['auth'] !== $valid_token || !isset($_GET['cidr'])) {
        http_response_code(403);
        exit;
    }
    
    $cidr = escapeshellarg($_GET['cidr']);
    exec("sudo ipset add c_segment_blocklist $cidr");
    http_response_code(200);
    ?>
    
  2. Configure a local web server (e.g., Nginx) to serve the API on port 8080, only accessible from localhost.
  3. Follow the ipset setup steps from Strategy 3, but grant sudo access only to the API server user (not the web server user).

Tradeoffs

Pros: Highest security posture—avoids granting elevated privileges to the main web server. The API can be extended with features like block logging, manual unblocking, and threshold adjustment.
Cons: Requires development and maintenance of a custom local API, which adds operational overhead.


Additional Notes

  1. Place all custom rules at the end of REQUEST-901-INITIALIZATION.conf to ensure they load after OWASP base rules.
  2. Adjust rule IDs if they conflict with existing custom rules.
  3. Insure SecCollectionTimeout in crs-setup.conf is set to a value greater than or equal to tx.block_duration.
  4. All strategies are incompatible with ModSecurity 3.x and newer, as they rely on the expirevar directive, which is not supported in these versions.

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.