Detecting attacks based on TCP Flags (DDOS, SYN Flood, and others)
Sergio Albea
????♂? Threat Hunter-Researcher and User Behaviour Analyst ????♂? / Senior Cloud Security Engineer/Architect
Let's kick off 2025 with these kinds of queries that open the door to so many more—love it! ??
I've been digging into TCP flags for a while because they can reveal a lot about malicious activity based on how they're set. These flags hold key details about network connectivity status, so understanding them is pretty important.
While exploring DeviceNetworkEvents, I noticed that in the AdditionalDetails field, the TCPFlags value appears for inbound connections—but in integer format. That meant I had to convert it to binary manually. Surprisingly, there's no built-in tobinary function in KQL to handle this, which feels like a missed opportunity—maybe something to request in the future?
Since I needed it right away, I went ahead and built a KQL query to convert integers to binary myself:
// Sergio Albea
let number = 42; // Replace with your integer
let binary =
range i from 0 to 31 step 1
| extend bit = toint(floor((number / pow(2, i)), 1) % 2)
| summarize binary = strcat_array(make_list(strcat(bit)), "")
| project binary;
binary
But performance-wise, it was limiting my time range, so I just went ahead and created a list with the integers and binaries, which made things run faster. In short, here are the different TCP flags identified:
0- FIN (Finish) Signals connection termination.
1- SYN (Synchronize) Initiates a new TCP connection.
2- RST (Reset) Abruptly terminates a connection.
3- PSH (Push) Instructs immediate delivery to the application
4- ACK (Acknowledgment) Confirms receipt of data from sender.
5 URG (Urgent) Marks urgent data.
6- ECE (ECN Echo) Signals congestion notification to the sender.
7 - CWR (Congestion Window Reduced) Indicates congestion control adjustment.
Here is the KQL query I developed to extract the bits of the TCP flags based on the remote IP for inbound connection attempts and queries associated to every Flag:
//Sergio Albea
let binnary_codes = externaldata(Integer: string, binary: string)[@"https://raw.githubusercontent.com/Sergio-Albea-Git/Threat-Hunting-KQL-Queries/refs/heads/main/Security-Lists/binary_list.csv"] with (format="csv", ignoreFirstRecord=True);
DeviceNetworkEvents
| extend tags = parse_json( AdditionalFields)
| extend direction = tags["direction"]
| where direction has "In"
| extend Geo_IP = tostring(geo_info_from_ip_address(RemoteIP).country)
| where isnotempty(Geo_IP)
| extend TCPFlags = tostring(tags["Tcp Flags"])
| join kind=leftouter ( binnary_codes) on $left.TCPFlags == $right.Integer
| extend num0 = strcat(substring(binary, 0,1)),num1 = strcat(substring(binary, 1,1)),num2 = strcat(substring(binary, 2,1)),num3 = strcat(substring(binary, 3,1)),num4 = strcat(substring(binary, 4,1)),num5 = strcat(substring(binary, 5,1)),num6 = strcat(substring(binary, 6,1)),num7 = strcat(substring(binary, 7,1))
| summarize make_set(binary),FIN= countif(num7 == 1),SYN= countif(num6 == 1),RST = countif(num5 == 1),PSH = countif(num4 == 1),ACK = countif(num3 == 1),URG = countif(num2 == 1),ECE = countif(num1 == 1),CWR = countif(num0 == 1), count() by RemoteIP, Geo_IP
0- FIN (Finish)
FIN Scan – Attackers send FIN packets to probe open ports and evade firewalls.
//Sergio Albea
let binnary_codes = externaldata(Integer: string, binary: string)[@"https://raw.githubusercontent.com/Sergio-Albea-Git/Threat-Hunting-KQL-Queries/refs/heads/main/Security-Lists/binary_list.csv"] with (format="csv", ignoreFirstRecord=True);
DeviceNetworkEvents
| extend tags = parse_json( AdditionalFields)
| extend direction = tags["direction"]
| where direction has "In"
| extend Geo_IP = tostring(geo_info_from_ip_address(RemoteIP).country)
| where isnotempty( Geo_IP)
| extend TCPFlags = tostring(tags["Tcp Flags"])
| join kind=leftouter ( binnary_codes) on $left.TCPFlags == $right.Integer
| extend num0 = strcat(substring(binary, 0,1)),num1 = strcat(substring(binary, 1,1)),num2 = strcat(substring(binary, 2,1)),num3 = strcat(substring(binary, 3,1)),num4 = strcat(substring(binary, 4,1)),num5 = strcat(substring(binary, 5,1)),num6 = strcat(substring(binary, 6,1)),num7 = strcat(substring(binary, 7,1))
| summarize make_set(binary),FIN= countif(num7 == 1),SYN= countif(num6 == 1),RST = countif(num5 == 1),PSH = countif(num4 == 1),ACK = countif(num3 == 1),URG = countif(num2 == 1),ECE = countif(num1 == 1),CWR = countif(num0 == 1), count() by RemoteIP, Geo_IP
| where FIN > 0
1- SYN (Synchronize)
SYN Flood Attack – Attackers send numerous SYN packets without completing the handshake to exhaust server resources.
领英推荐
//Sergio Albea
let binnary_codes = externaldata(Integer: string, binary: string)[@"https://raw.githubusercontent.com/Sergio-Albea-Git/Threat-Hunting-KQL-Queries/refs/heads/main/Security-Lists/binary_list.csv"] with (format="csv", ignoreFirstRecord=True);
DeviceNetworkEvents
| extend tags = parse_json( AdditionalFields)
| extend direction = tags["direction"]
| where direction has "In"
| extend Geo_IP = tostring(geo_info_from_ip_address(RemoteIP).country)
| where isnotempty( Geo_IP)
| extend TCPFlags = tostring(tags["Tcp Flags"])
| join kind=leftouter ( binnary_codes) on $left.TCPFlags == $right.Integer
| extend num0 = strcat(substring(binary, 0,1)),num1 = strcat(substring(binary, 1,1)),num2 = strcat(substring(binary, 2,1)),num3 = strcat(substring(binary, 3,1)),num4 = strcat(substring(binary, 4,1)),num5 = strcat(substring(binary, 5,1)),num6 = strcat(substring(binary, 6,1)),num7 = strcat(substring(binary, 7,1))
| summarize make_set(binary),FIN= countif(num7 == 1),SYN= countif(num6 == 1),RST = countif(num5 == 1),PSH = countif(num4 == 1),ACK = countif(num3 == 1),URG = countif(num2 == 1),ECE = countif(num1 == 1),CWR = countif(num0 == 1), count() by RemoteIP, Geo_IP
| where SYN > 100 // Adjust threshold based on baseline
2- RST (Reset)
RST Flood Attack – Attackers send excessive RST packets to disrupt ongoing connections.
//Sergio Albea
let binnary_codes = externaldata(Integer: string, binary: string)[@"https://raw.githubusercontent.com/Sergio-Albea-Git/Threat-Hunting-KQL-Queries/refs/heads/main/Security-Lists/binary_list.csv"] with (format="csv", ignoreFirstRecord=True);
DeviceNetworkEvents
| extend tags = parse_json( AdditionalFields)
| extend direction = tags["direction"]
| where direction has "In"
| extend Geo_IP = tostring(geo_info_from_ip_address(RemoteIP).country)
| where isnotempty( Geo_IP)
| extend TCPFlags = tostring(tags["Tcp Flags"])
| join kind=leftouter ( binnary_codes) on $left.TCPFlags == $right.Integer
| extend num0 = strcat(substring(binary, 0,1)),num1 = strcat(substring(binary, 1,1)),num2 = strcat(substring(binary, 2,1)),num3 = strcat(substring(binary, 3,1)),num4 = strcat(substring(binary, 4,1)),num5 = strcat(substring(binary, 5,1)),num6 = strcat(substring(binary, 6,1)),num7 = strcat(substring(binary, 7,1))
| summarize make_set(binary),FIN= countif(num7 == 1),SYN= countif(num6 == 1),RST = countif(num5 == 1),PSH = countif(num4 == 1),ACK = countif(num3 == 1),URG = countif(num2 == 1),ECE = countif(num1 == 1),CWR = countif(num0 == 1), count() by RemoteIP, Geo_IP
| where RST > 0
3- PSH (Push)
Attackers use PSH flags to quickly transmit stolen data.
//Sergio Albea
let binnary_codes = externaldata(Integer: string, binary: string)[@"https://raw.githubusercontent.com/Sergio-Albea-Git/Threat-Hunting-KQL-Queries/refs/heads/main/Security-Lists/binary_list.csv"] with (format="csv", ignoreFirstRecord=True);
DeviceNetworkEvents
| extend tags = parse_json( AdditionalFields)
| extend direction = tags["direction"]
| where direction has "In"
| extend Geo_IP = tostring(geo_info_from_ip_address(RemoteIP).country)
| where isnotempty( Geo_IP)
| extend TCPFlags = tostring(tags["Tcp Flags"])
| join kind=leftouter ( binnary_codes) on $left.TCPFlags == $right.Integer
| extend num0 = strcat(substring(binary, 0,1)),num1 = strcat(substring(binary, 1,1)),num2 = strcat(substring(binary, 2,1)),num3 = strcat(substring(binary, 3,1)),num4 = strcat(substring(binary, 4,1)),num5 = strcat(substring(binary, 5,1)),num6 = strcat(substring(binary, 6,1)),num7 = strcat(substring(binary, 7,1))
| summarize make_set(binary),FIN= countif(num7 == 1),SYN= countif(num6 == 1),RST = countif(num5 == 1),PSH = countif(num4 == 1),ACK = countif(num3 == 1),URG = countif(num2 == 1),ECE = countif(num1 == 1),CWR = countif(num0 == 1), count() by RemoteIP, Geo_IP
| where PSH > 0
4- ACK (Acknowledgment)
ACK Flood Attack – Attackers flood a target with ACK packets to exhaust system resources.
//Sergio Albea
let binnary_codes = externaldata(Integer: string, binary: string)[@"https://raw.githubusercontent.com/Sergio-Albea-Git/Threat-Hunting-KQL-Queries/refs/heads/main/Security-Lists/binary_list.csv"] with (format="csv", ignoreFirstRecord=True);
DeviceNetworkEvents
| extend tags = parse_json( AdditionalFields)
| extend direction = tags["direction"]
| where direction has "In"
| extend Geo_IP = tostring(geo_info_from_ip_address(RemoteIP).country)
| where isnotempty( Geo_IP)
| extend TCPFlags = tostring(tags["Tcp Flags"])
| join kind=leftouter ( binnary_codes) on $left.TCPFlags == $right.Integer
| extend num0 = strcat(substring(binary, 0,1)),num1 = strcat(substring(binary, 1,1)),num2 = strcat(substring(binary, 2,1)),num3 = strcat(substring(binary, 3,1)),num4 = strcat(substring(binary, 4,1)),num5 = strcat(substring(binary, 5,1)),num6 = strcat(substring(binary, 6,1)),num7 = strcat(substring(binary, 7,1))
| summarize make_set(binary),FIN= countif(num7 == 1),SYN= countif(num6 == 1),RST = countif(num5 == 1),PSH = countif(num4 == 1),ACK = countif(num3 == 1),URG = countif(num2 == 1),ECE = countif(num1 == 1),CWR = countif(num0 == 1), count() by RemoteIP, Geo_IP
| where ACK > 100
5 URG (Urgent)
Injection Attacks – Attackers manipulate urgent pointers to inject malicious payloads.
//Sergio Albea
let binnary_codes = externaldata(Integer: string, binary: string)[@"https://raw.githubusercontent.com/Sergio-Albea-Git/Threat-Hunting-KQL-Queries/refs/heads/main/Security-Lists/binary_list.csv"] with (format="csv", ignoreFirstRecord=True);
DeviceNetworkEvents
| extend tags = parse_json( AdditionalFields)
| extend direction = tags["direction"]
| where direction has "In"
| extend Geo_IP = tostring(geo_info_from_ip_address(RemoteIP).country)
| where isnotempty(Geo_IP)
| extend TCPFlags = tostring(tags["Tcp Flags"])
| join kind=leftouter ( binnary_codes) on $left.TCPFlags == $right.Integer
| extend num0 = strcat(substring(binary, 0,1)),num1 = strcat(substring(binary, 1,1)),num2 = strcat(substring(binary, 2,1)),num3 = strcat(substring(binary, 3,1)),num4 = strcat(substring(binary, 4,1)),num5 = strcat(substring(binary, 5,1)),num6 = strcat(substring(binary, 6,1)),num7 = strcat(substring(binary, 7,1))
| summarize make_set(binary),FIN= countif(num7 == 1),SYN= countif(num6 == 1),RST = countif(num5 == 1),PSH = countif(num4 == 1),ACK = countif(num3 == 1),URG = countif(num2 == 1),ECE = countif(num1 == 1),CWR = countif(num0 == 1), count() by RemoteIP, Geo_IP
| where URG > 0
6- ECE (ECN Echo)
ECN-Based Attacks – Attackers abuse ECN to cause unnecessary congestion handling.
//Sergio Albea
let binnary_codes = externaldata(Integer: string, binary: string)[@"https://raw.githubusercontent.com/Sergio-Albea-Git/Threat-Hunting-KQL-Queries/refs/heads/main/Security-Lists/binary_list.csv"] with (format="csv", ignoreFirstRecord=True);
DeviceNetworkEvents
| extend tags = parse_json( AdditionalFields)
| extend direction = tags["direction"]
| where direction has "In"
| extend Geo_IP = tostring(geo_info_from_ip_address(RemoteIP).country)
| where isnotempty(Geo_IP)
| extend TCPFlags = tostring(tags["Tcp Flags"])
| join kind=leftouter ( binnary_codes) on $left.TCPFlags == $right.Integer
| extend num0 = strcat(substring(binary, 0,1)),num1 = strcat(substring(binary, 1,1)),num2 = strcat(substring(binary, 2,1)),num3 = strcat(substring(binary, 3,1)),num4 = strcat(substring(binary, 4,1)),num5 = strcat(substring(binary, 5,1)),num6 = strcat(substring(binary, 6,1)),num7 = strcat(substring(binary, 7,1))
| summarize make_set(binary),FIN= countif(num7 == 1),SYN= countif(num6 == 1),RST = countif(num5 == 1),PSH = countif(num4 == 1),ACK = countif(num3 == 1),URG = countif(num2 == 1),ECE = countif(num1 == 1),CWR = countif(num0 == 1), count() by RemoteIP, Geo_IP
| where ECE > 0
7 - CWR (Congestion Window Reduced)
ECN Flooding Attack – Attackers manipulate congestion control mechanisms to disrupt network performance.
//Sergio Albea
let binnary_codes = externaldata(Integer: string, binary: string)[@"https://raw.githubusercontent.com/Sergio-Albea-Git/Threat-Hunting-KQL-Queries/refs/heads/main/Security-Lists/binary_list.csv"] with (format="csv", ignoreFirstRecord=True);
DeviceNetworkEvents
| extend tags = parse_json( AdditionalFields)
| extend direction = tags["direction"]
| where direction has "In"
| extend Geo_IP = tostring(geo_info_from_ip_address(RemoteIP).country)
| where isnotempty(Geo_IP)
| extend TCPFlags = tostring(tags["Tcp Flags"])
| join kind=leftouter ( binnary_codes) on $left.TCPFlags == $right.Integer
| extend num0 = strcat(substring(binary, 0,1)),num1 = strcat(substring(binary, 1,1)),num2 = strcat(substring(binary, 2,1)),num3 = strcat(substring(binary, 3,1)),num4 = strcat(substring(binary, 4,1)),num5 = strcat(substring(binary, 5,1)),num6 = strcat(substring(binary, 6,1)),num7 = strcat(substring(binary, 7,1))
| summarize make_set(binary),FIN= countif(num7 == 1),SYN= countif(num6 == 1),RST = countif(num5 == 1),PSH = countif(num4 == 1),ACK = countif(num3 == 1),URG = countif(num2 == 1),ECE = countif(num1 == 1),CWR = countif(num0 == 1), count() by RemoteIP, Geo_IP
| where CWR > 0
Conclusion
This use case helps to identify TCP Flags information allowing you to create multiple queries to detect different type of attacks such as DDOS, SYN Flood Attacks, and others. For instance:
I recommend executing various queries to analyse the number of occurrences for each type of TCP flag to gain a comprehensive understanding of their distribution.
Based on my findings and analysis, I strongly suggest reviewing all connection attempts that include CWR, ECE, URG, PSH, and RST TCP flags, as they may indicate unusual or potentially malicious activity.
SOC Analyst
4 周This great work, I would never think about this. Also I believe that you can use Binary and to replicate it. Edit, here is improved version to fix flaws of previous KQL query DeviceNetworkEvents | extend Tcp_Flags_ = toint(coalesce(AdditionalFields.["Tcp Flags"], "0")) | where Tcp_Flags_ <> 0 | extend FIN = iif(binary_and(Tcp_Flags_, binary_shift_left(1, 0)) != 0, 1, 0), SYN = iif(binary_and(Tcp_Flags_, binary_shift_left(1, 1)) != 0, 1, 0), RST = iif(binary_and(Tcp_Flags_, binary_shift_left(1, 2)) != 0, 1, 0), PSH = iif(binary_and(Tcp_Flags_, binary_shift_left(1, 3)) != 0, 1, 0), ACK = iif(binary_and(Tcp_Flags_, binary_shift_left(1, 4)) != 0, 1, 0), URG = iif(binary_and(Tcp_Flags_, binary_shift_left(1, 5)) != 0, 1, 0), ECE = iif(binary_and(Tcp_Flags_, binary_shift_left(1, 6)) != 0, 1, 0), CWR = iif(binary_and(Tcp_Flags_, binary_shift_left(1, 7)) != 0, 1, 0), NS = iif(binary_and(Tcp_Flags_, binary_shift_left(1, 8)) != 0, 1, 0) | extend TCP_Flag_Binary_form = strcat(FIN, SYN, RST, PSH, ACK, URG, ECE, CWR, NS)