Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 67 additions & 0 deletions tests/rules/absent-02/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# Suricata-Verify Test: error_or option of absent keyword

## Description

This test validates the `error_or` option of the `absent` keyword functionality in Suricata. The `error_or` option matches when either:
1. A transform (like `from_base64`) fails to process the input data (error condition), OR
2. The transform succeeds and subsequent content patterns match

## Test Cases

### Test 1 (SID 1): Invalid base64 with content
- **Input**: Invalid base64 string containing "fail"
- **Expected**: Alert should fire because base64 decode fails (error condition)
- **Rule**: `http.request_body; from_base64; absent: error_or; content:"fail";`

### Test 2 (SID 2): Valid base64 with matching content
- **Input**: Valid base64 that decodes to text containing "malicious"
- **Expected**: Alert should fire because decode succeeds AND content matches
- **Rule**: `http.request_body; from_base64; absent: error_or; content:"malicious";`

### Test 3 (SID 3): Invalid base64 triggering error
- **Input**: Invalid base64 string containing "error"
- **Expected**: Alert should fire because base64 decode fails
- **Rule**: `http.request_body; from_base64; absent: error_or; content:"error";`

### Test 4 (SID 4): Valid base64 without matching content
- **Input**: Valid base64 that decodes to benign content
- **Expected**: NO alert (decode succeeds but content doesn't match)
- **Rule**: `http.request_body; from_base64; absent: error_or; content:"nomatch";`

## Generating the PCAP

Run the Python script to generate the test PCAP:

```bash
python3 generate-pcap.py
```

This requires the `scapy` Python library:

```bash
pip3 install scapy
```

## Running the Test

This test is designed to work with the suricata-verify framework:

```bash
# From the suricata-verify repository
./run.py /path/to/this/test/directory
```

## Key Behavior Tested

1. **Error Detection**: The `error_or` option correctly identifies when transforms fail
2. **Content Matching**: When transforms succeed, normal content matching proceeds
3. **OR Logic**: The keyword matches if EITHER condition is true (error OR content match)
4. **Non-Match**: The keyword doesn't match when both conditions are false

## Difference from `absent: or_else`

While `absent: or_else` checks if a buffer is NULL (absent) OR matches content, `absent: error_or`
specifically checks for transform **errors** (via the DETECT_CI_FLAGS_ERROR flag) OR content matches. This makes it ideal for detecting:
- Malformed encoded data
- Evasion attempts using invalid encodings
- Protocol violations in encoded fields
99 changes: 99 additions & 0 deletions tests/rules/absent-02/generate-pcap.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
#!/usr/bin/env python3
"""
Generate a PCAP file for testing the error_or option of the absent keyword with base64 transforms.

This creates HTTP POST requests with different base64 payloads:
1. Invalid base64 data (to trigger decode error)
2. Valid base64 containing "malicious"
3. Invalid base64 with "error" as raw text
4. Valid base64 without target content
"""

from scapy.all import *
import base64

def create_http_request(payload):
"""Create an HTTP POST request with the given payload."""
http_request = (
b"POST /upload HTTP/1.1\r\n"
b"Host: example.com\r\n"
b"Content-Length: " + str(len(payload)).encode() + b"\r\n"
b"\r\n" +
payload
)
return http_request

def main():
packets = []

# Common packet parameters - use different ports for each connection
src_ip = "192.168.1.100"
dst_ip = "192.168.1.200"
dst_port = 80

test_cases = [
(b"!!!invalid@base64#data$$$fail", None, "invalid base64"),
(None, b"This is malicious content", "valid base64 with malicious"),
(b"###not!base64@error###", None, "invalid base64 with error"),
(None, b"This is benign content", "valid base64 benign"),
]

for idx, (invalid_payload, valid_payload, desc) in enumerate(test_cases):
src_port = 12345 + idx # Different source port for each connection
seq = 1000
ack = 2000

# SYN
pkt = IP(src=src_ip, dst=dst_ip)/TCP(sport=src_port, dport=dst_port, flags='S', seq=seq)
packets.append(pkt)

# SYN-ACK
pkt = IP(src=dst_ip, dst=src_ip)/TCP(sport=dst_port, dport=src_port, flags='SA', seq=ack, ack=seq+1)
packets.append(pkt)

# ACK
seq += 1
pkt = IP(src=src_ip, dst=dst_ip)/TCP(sport=src_port, dport=dst_port, flags='A', seq=seq, ack=ack+1)
packets.append(pkt)
ack += 1

# Prepare payload
if invalid_payload:
payload = invalid_payload
else:
payload = base64.b64encode(valid_payload)

# HTTP Request
http_req = create_http_request(payload)
pkt = IP(src=src_ip, dst=dst_ip)/TCP(sport=src_port, dport=dst_port, flags='PA', seq=seq, ack=ack)/Raw(load=http_req)
packets.append(pkt)
seq += len(http_req)

# ACK from server
pkt = IP(src=dst_ip, dst=src_ip)/TCP(sport=dst_port, dport=src_port, flags='A', seq=ack, ack=seq)
packets.append(pkt)

# FIN from client
pkt = IP(src=src_ip, dst=dst_ip)/TCP(sport=src_port, dport=dst_port, flags='FA', seq=seq, ack=ack)
packets.append(pkt)
seq += 1

# FIN-ACK from server
pkt = IP(src=dst_ip, dst=src_ip)/TCP(sport=dst_port, dport=src_port, flags='FA', seq=ack, ack=seq)
packets.append(pkt)
ack += 1

# Final ACK from client
pkt = IP(src=src_ip, dst=dst_ip)/TCP(sport=src_port, dport=dst_port, flags='A', seq=seq, ack=ack)
packets.append(pkt)

# Write packets to PCAP file
wrpcap('input.pcap', packets)
print("Generated input.pcap with HTTP test traffic (separate connections)")
print(f"Test 1: {test_cases[0][0]}")
print(f"Test 2: {base64.b64encode(test_cases[1][1])} -> {test_cases[1][1]}")
print(f"Test 3: {test_cases[2][0]}")
print(f"Test 4: {base64.b64encode(test_cases[3][1])} -> {test_cases[3][1]}")

if __name__ == "__main__":
main()
Binary file added tests/rules/absent-02/input.pcap
Binary file not shown.
15 changes: 15 additions & 0 deletions tests/rules/absent-02/test.rules
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Test 1: error_or with invalid base64 - should match due to transform error
alert http any any -> any any (msg:"Test 1: error_or with invalid base64 error"; \
flow:established,to_server; http.request_body; from_base64; absent: error_or; content:"fail"; sid:1;)

# Test 2: error_or with valid base64 and matching content - should match due to content
alert http any any -> any any (msg:"Test 2: error_or with malicious content"; \
flow:established,to_server; http.request_body; from_base64; absent: error_or; content:"malicious"; sid:2;)

# Test 3: error_or with invalid base64 - should match due to transform error
alert http any any -> any any (msg:"Test 3: error_or with error in payload"; \
flow:established,to_server; http.request_body; from_base64; absent: error_or; content:"error"; sid:3;)

# Test 4: error_or with valid base64 but no matching content - should NOT match
alert http any any -> any any (msg:"Test 4: error_or no match"; \
flow:established,to_server; http.request_body; from_base64; absent: error_or; content:"nomatch"; sid:4;)
27 changes: 27 additions & 0 deletions tests/rules/absent-02/test.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
requires:
min-version: 9

checks:
- filter:
count: 2
match:
event_type: alert
alert.signature_id: 1

- filter:
count: 3
match:
event_type: alert
alert.signature_id: 2

- filter:
count: 2
match:
event_type: alert
alert.signature_id: 3

- filter:
count: 2
match:
event_type: alert
alert.signature_id: 4
5 changes: 5 additions & 0 deletions tests/rules/absent-03/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Suricata-Verify Test: error_or option of the absent keyword

## Description

Ensure error conditions are properly detected and logged
3 changes: 3 additions & 0 deletions tests/rules/absent-03/test.rules
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Test 1: error_or with invalid base64 - should match due to transform error
alert http any any -> any any (msg:"Test 1: error_or with invalid base64 error"; \
flow:established,to_server; absent: error_or; content:"fail"; sid:1;)
12 changes: 12 additions & 0 deletions tests/rules/absent-03/test.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
requires:
min-version: 9

pcap: ../absent-02/input.pcap

exit-code: 1

checks:

- shell:
args: grep "no buffer for absent keyword" suricata.log | wc -l | xargs
expect: 1
Loading