Blocking Malware Downloads with AWS Network Firewall (Stateful Suricata Rules)
INTRO
Ran a hands-on lab that simulates a real-world security incident: end users at a
company kept accidentally downloading malware after visiting a specific website.
IT had already identified the malicious URLs. My job was to harden the network
perimeter using AWS Network Firewall so those files could never reach an internal
host again. Here's a full walkthrough of what I did and why each step matters.
SCENARIO
- Company: AnyCompany (lab scenario)
- Role: Security Engineer
- Problem: Users downloading malware from a known-bad site
- Given: Exact URLs hosting the malicious files
- Goal: Block access to those files at the network layer, without touching every single endpoint individually
ARCHITECTURE
- A pre-configured EC2 instance (TestInstance) sits in an isolated perimeter subnet β used purely to simulate an end user's browser/download behavior
- An AWS Network Firewall (LabFirewall) sits between the VPC and the internet
- The firewall is driven by a Firewall Policy (LabFirewallPolicy), which determines stateless vs. stateful packet handling
- A Stateful Rule Group written in Suricata syntax is attached to the policy to do the actual content-based blocking
Flow: TestInstance -> Network Firewall (stateless check -> stateful rule
group) -> Internet -> malicious site (now blocked)
STEP 1: CONFIRM THE PROBLEM IS REAL
Before touching any config, I logged into TestInstance via Systems Manager
Session Manager and reproduced the issue:
wget http://malware.wicar.org/data/js_crypto_miner.html
wget http://malware.wicar.org/data/java_jre17_exec.html
Both returned HTTP 200 OK and downloaded successfully. This confirms the
malicious files are currently reachable and the firewall isn't inspecting
or blocking this traffic yet. Never skip this step in real environments β
you need a documented "before" state to prove the fix worked.
STEP 2: SWITCH THE FIREWALL POLICY TO STATEFUL INSPECTION
By default, a lot of basic firewall configs only do stateless filtering
(rules based on IP/port only, no context, no content awareness). To block
based on URI content, traffic needs to go through the stateful rules engine.
In the Firewall Policy (LabFirewallPolicy):
- Stateless default actions -> Edit
- Fragmented packets: "Use the same actions for all packets"
- Action: "Forward to stateful rule groups"
- Save
Stateless engine = fast, no context, evaluates each packet in isolation
Stateful engine = slower, but understands traffic flow/direction and lets
you write much more precise rules (and log everything)
STEP 3: WRITE A STATEFUL RULE GROUP (SURICATA SYNTAX)
Created a new Stateful Rule Group:
- Rule group type: Stateful
- Format: Suricata compatible rule string
- Evaluation order: Action order
- Name: StatefulRuleGroup
- Capacity: 100
Rules used:
drop http $HOME_NET any -> $EXTERNAL_NET 80 (msg:"MALWARE custom solution"; flow: to_server,established; classtype:trojan-activity; sid:2002001; content:"/data/js_crypto_miner.html"; http_uri; rev:1;)
drop http $HOME_NET any -> $EXTERNAL_NET 80 (msg:"MALWARE custom solution"; flow: to_server,established; classtype:trojan-activity; sid:2002002; content:"/data/java_jre17_exec.html"; http_uri; rev:1;)
What this actually does:
- drop -> silently block the packet (no reset sent back)
- http -> only inspect HTTP traffic
- $HOME_NET -> $EXTERNAL_NET -> traffic leaving your VPC toward the internet
- flow: to_server, established -> only match on established outbound requests
- content: "..."; http_uri -> match specifically on the URI path, not just the domain or IP β this is the key advantage over basic IP/domain blocklists
- sid -> unique rule ID (required by Suricata)
- classtype: trojan-activity -> categorizes the threat for logging/alerting
STEP 4: ATTACH THE RULE GROUP TO THE FIREWALL
- Firewalls -> LabFirewall -> Associated firewall policy -> LabFirewallPolicy
- Stateful rule groups -> Add unmanaged stateful rule groups
- Select StatefulRuleGroup -> Add stateful rule group
This is the step that actually activates the rules β until this point, the
rule group existed but had zero effect on real traffic.
STEP 5: VALIDATE
Back on TestInstance, re-ran the exact same commands:
wget http://malware.wicar.org/data/js_crypto_miner.html
wget http://malware.wicar.org/data/java_jre17_exec.html
This time: "HTTP request sent, awaiting response..." and then it just hangs
(had to Ctrl+C out of it) β no 200 OK, no file downloaded. The firewall is
silently dropping matching requests before they ever complete.
WHY THIS MATTERS (KEY TAKEAWAYS)
- URI-level blocking beats plain IP/domain blocklists. Attackers can move files to a new path on the same domain β a rule matching http_uri content catches the actual malicious payload path, not just the host
- Stateless vs. stateful matters. If your firewall policy defaults to stateless-only, none of your Suricata content rules will ever fire
- This is a form of IDS/IPS at the network edge without needing a separate third-party appliance β useful as one layer in a defense-in-depth strategy
- Always validate with a "before" and "after" test. Screenshots/logs of both states are what actually prove the fix to stakeholders/auditors
TOOLS/SERVICES USED
- AWS Network Firewall
- AWS VPC
- Amazon EC2
- Suricata rule syntax (IDS/IPS)
Top comments (1)
Nice step-by-step lab, especially the before-and-after validation.
One clarification though: those WICAR URLs are controlled browser-exploit test pages, not ordinary real-world malware downloads, so Iβd describe this as validating firewall rule behavior rather than proving broad malware prevention.
Also, the URI rule only blocks those exact paths. If the same content moves to another URI, these rules will not catch it, and HTTPS would require a different inspection strategy. Solid lab overall, but the scope of the protection feels a little overstated.