Skip to content

Using iRules with Access workflow #1

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
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
42 changes: 42 additions & 0 deletions Access/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# F5 iRules for JA4+ Network Fingerprinting within Access flow

F5 iRules for generating JA4+ fingerprints. Currently, only JA4, JA4S, JA4T, JA4L, and JA4H fingerprint iRules are provided. More JA4+ fingerprint iRules *MAY* be added in the future.
The using those fingerprints within Access flow whether to restrict or query ja4db for matching items.

> [!WARNING]
>DISCLAIMER: These iRules are provided as-is with no guarantee of performance or functionality. Use at your own risk.
>These iRules have been tested on F5 BIGIPs running TMOS versions 16.1 and 17.1.


## What is JA4+ Network Fingerprinting?

From the [FoxIO JA4+ Repo](https://github.com/FoxIO-LLC/ja4):
>JA4+ is a suite of network fingerprinting methods that are easy to use and easy to share. These methods are both human >and machine readable to facilitate more effective threat-hunting and analysis. The use-cases for these fingerprints >include scanning for threat actors, malware detection, session hijacking prevention, compliance automation, location >tracking, DDoS detection, grouping of threat actors, reverse shell detection, and many more.

Please read this blog post for more details: [JA4+ Network Fingerprinting](https://medium.com/foxio/ja4-network-fingerprinting-9376fe9ca637)

To understand how to read JA4+ fingerprints, see [Technical Details](https://github.com/FoxIO-LLC/ja4/blob/main/technical_details/README.md)

## JA4+ Licensing

> [!IMPORTANT]
>**JA4 TLS Client Fingerprinting is licensed under BSD 3-Clause**
>
>_Copyright (c) 2024, FoxIO_
>_All rights reserved.
>JA4 TLS Client Fingerprinting is Open-Source, Licensed under BSD 3-Clause.
>For full license text and more details, see the repo root https://github.com/FoxIO-LLC/ja4_
>
>
>**All other JA4+ Fingerprints are under the FoxIO License 1.1**
>
>_Copyright (c) 2024, FoxIO, LLC.
>All rights reserved.
>Licensed under FoxIO License 1.1
>For full license text and more details, see the repo root https://github.com/FoxIO-LLC/ja4_

## How to Use

> Copy the iRules from this folder.
> Add iRule event trigger in your Access Policy, take care of the trigger ID in the iRule.
> In case you try to query external JA4db website, you can utilize HTTP connector to make use of the obtained parameters.
244 changes: 244 additions & 0 deletions Access/ja4-access.irule
Original file line number Diff line number Diff line change
@@ -0,0 +1,244 @@
###############################################################################################
# iRule to calculate JA4 "Client TLS" save in Access variable
# See JA4 spec on GitHub for more details
# https://github.com/FoxIO-LLC/ja4/blob/main/technical_details/JA4.md
#
# Copyright (c) 2024, FoxIO
# All rights reserved.
# JA4 TLS Client Fingerprinting is Open-Source, Licensed under BSD 3-Clause
# For full license text and more details, see the repo root https://github.com/FoxIO-LLC/ja4
###############################################################################################

proc parseClientHello { payload rlen ja4_ver ja4_tprt } {

set ja4_sni "i"
## Define GREASE values so these can be excluded from cipher list
set greaseList "0a0a 1a1a 2a2a 3a3a 4a4a 5a5a 6a6a 7a7a 8a8a 9a9a aaaa baba caca dada eaea fafa"

## HEADERS - SKIP: Already captured in XXX_DATA event (record header, handshake header, server version, server random)
set field_offset 43

## SESSION ID - SKIP
binary scan ${payload} @${field_offset}c sessID_len
set field_offset [expr {${field_offset} + 1 + ${sessID_len}}]

## CLIENT CIPHERS
## Capture cipher list length and incr offset
binary scan ${payload} @${field_offset}S cipherList_len
set field_offset [expr {${field_offset} + 2}]

set cipher_offset 0
set cipher_cnt 0
set cipher_list [list]
while { [expr {${cipher_offset} < ${cipherList_len}}] } {
binary scan ${payload} @${field_offset}H4 cipher_hex
if { [lsearch -sorted -inline $greaseList $cipher_hex] eq "" } {
lappend cipher_list ${cipher_hex}
incr cipher_cnt
}
set cipher_offset [expr {${cipher_offset} + 2}]
set field_offset [expr {${field_offset} + 2}]
}
## Sort cipher_list
set cipher_list [lsort $cipher_list]
## Convert list to comma-separated string
set cipher_str ""
foreach cipher_hex $cipher_list {
append cipher_str "${cipher_hex},"
}
set cipher_str [string trimright ${cipher_str} ","]
## Get truncated hash of cipher list string
binary scan [sha256 ${cipher_str}] H* cipher_hash
set trunc_cipher_hash [string range $cipher_hash 0 11]

## Format cipher count
if { $cipher_cnt > 99 } {
set cipher_cnt 99
}
set ja4_ccnt [format "%02d" $cipher_cnt]

## COMPRESSION METHOD - SKIP
binary scan ${payload} @${field_offset}c compression_len
set field_offset [expr {${field_offset} + 1 + ${compression_len}}]


## EXTENSIONS
set ja4_ecnt 0
set ja4_etype_list [list]
set ja4_alpn "00"
set siga_list ""

## Check if there is more data
if { [expr {${field_offset} < ${rlen}}] } {
## Capture Extensions length and incr offset
binary scan ${payload} @${field_offset}S extList_len
set field_offset [expr {${field_offset} + 2}]

## Pad rlen by 1 byte
set rlen [expr ${rlen} + 1]

## Parse Extensions
while { [expr {${field_offset} <= ${rlen}}] } {
## Capture Ext Type, Incr offset past Ext Type
binary scan ${payload} @${field_offset}H4 ext_hex
set field_offset [expr {${field_offset} + 2}]

## Capture Ext Length, Incr offset past Ext Length
binary scan ${payload} @${field_offset}S ext_len
set field_offset [expr {${field_offset} + 2}]

## Check for GREASE values, if GREASE incr offset by Ext Length
if {[lsearch -sorted -inline $greaseList $ext_hex] ne "" } {
set field_offset [expr {${field_offset} + ${ext_len}}]
continue
} else {
## Check for specific Extension Types
switch $ext_hex {
"0000" {
## SNI (00)
## Set JA4 domain/ip field
set ja4_sni "d"
incr ja4_ecnt
}
"000d" {
## Signature Algorithms (13)
## Capture Signature Algorithms length
binary scan ${payload} @${field_offset}S siga_len
set siga_offset 0
while { [expr {${siga_offset} <= ${siga_len}}] } {
binary scan ${payload} @[expr {${field_offset} + 2 + ${siga_offset}}]H4 siga_hex
if { [lsearch -sorted -inline $greaseList $siga_hex] eq "" } {
append siga_list "${siga_hex},"
}
incr siga_offset 2
}
set siga_list [string trimright ${siga_list} ","]
lappend ja4_etype_list ${ext_hex}
incr ja4_ecnt

}
"0010" {
## ALPN (16)
## Capture APLN length and First ALPN string length
binary scan ${payload} @${field_offset}Sc alpn_len alpn_str_len
## Capture the First APLN string value
binary scan ${payload} @[expr {${field_offset} + 3}]a${alpn_str_len} alpn_str
incr ja4_ecnt
}
"0027" {
## Supported EKT Ciphers (39)
## Set JA4 Transport Protocol as QUIC
set ja4_tprt "q"
lappend ja4_etype_list ${ext_hex}
incr ja4_ecnt
}
"002b" {
## Supported Versions (43)
## Capture Supported Versions length
binary scan ${payload} @${field_offset}c sver_len
set sver_offset 0
set sver_list [list]
while { [expr {${sver_offset} < ${sver_len}}] } {
binary scan ${payload} @[expr {${field_offset} + 1 + ${sver_offset}}]H4 sver_hex
if { [lsearch -sorted -inline $greaseList $sver_hex] eq "" } {
lappend sver_list ${sver_hex}
}
incr sver_offset 2
}
set sver_list [lsort $sver_list]
set ja4_ver [lindex $sver_list end]
lappend ja4_etype_list ${ext_hex}
incr ja4_ecnt
} default {
lappend ja4_etype_list ${ext_hex}
incr ja4_ecnt
}
}

## Incr offset past the extension data length. Repeat this loop until we reach rlen (the end of the payload)
set field_offset [expr {${field_offset} + ${ext_len}}]
}
}
}
## Set JA4 ALPN value
if { [info exist alpn_str] } {
set ja4_alpn "[string index ${alpn_str} 0][string index ${alpn_str} end]"
}

## Format extensions count var
if { $ja4_ecnt > 99 } {
set ja4_ecnt 99
}
set ja4_ecnt [format "%02d" $ja4_ecnt]

## Sort and format extensions type list
set ja4_etype_list [lsort $ja4_etype_list]
set ja4_etype_str ""
foreach ext_type_hex $ja4_etype_list {
append ja4_etype_str "${ext_type_hex},"
}
set ja4_etype_str [string trimright ${ja4_etype_str} ","]
## If present, append signature algorithms list to extensions list
if { ${siga_list} ne ""} {
set ja4_etype_str "${ja4_etype_str}_${siga_list}"
}
## Hash extensions list
binary scan [sha256 ${ja4_etype_str}] H* ja4_ext_hash
set ja4_ext_hash_trunc [string range ${ja4_ext_hash} 0 11]

## Format version
switch $ja4_ver {
0304 { set ja4_ver "13" }
0303 { set ja4_ver "12" }
0302 { set ja4_ver "11" }
0301 { set ja4_ver "10" }
0300 { set ja4_ver "s3" }
0200 { set ja4_ver "s2" }
0100 { set ja4_ver "s1" }
}

##Build JA4 string
set ja4_str "${ja4_tprt}${ja4_ver}${ja4_sni}${ja4_ccnt}${ja4_ecnt}${ja4_alpn}_${trunc_cipher_hash}_${ja4_ext_hash_trunc}"
set ja4_r_str "${ja4_tprt}${ja4_ver}${ja4_sni}${ja4_ccnt}${ja4_ecnt}${ja4_alpn}_${cipher_str}_${ja4_etype_str}"

return "${ja4_str}"
}


when CLIENT_ACCEPTED {
unset -nocomplain rlen
set ja4_tprt "t"
## Collect the TCP payload
TCP::collect
}

when CLIENT_DATA {

## Get the TLS packet type and versions
if { ! [info exists rlen] } {
binary scan [TCP::payload] cH4ScH6H4 rtype proto_ver rlen hs_type rilen server_ver
#log local0. "rtype ${rtype} proto_ver ${proto_ver} rlen ${rlen} hs_type ${hs_type} rilen ${rilen} server_ver ${server_ver}"

if { ( ${rtype} == 22 ) and ( ${hs_type} == 1 ) } {
#log local0. "Found CLIENT_HELLO"
set ja4 [call parseClientHello [TCP::payload] ${rlen} ${server_ver} ${ja4_tprt}]
#log local0. "JA4: '${ja4}'"

}
}

# Collect the rest of the record if necessary
if { [TCP::payload length] < $rlen } {
TCP::collect $rlen
}

## Release the payload
TCP::release
}

when ACCESS_POLICY_AGENT_EVENT {
if { [ACCESS::policy agent_id] eq "JA4FP" } {
ACCESS::session data set session.custom.JA4 $ja4

}
}
110 changes: 110 additions & 0 deletions Access/ja4h-access.irule
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
###################################################################################################################################
# iRule to calculate JA4H "HTTP Request" done after Access policy is evaluated due to the need for HTTP request event to be fired.
# See JA4H spec on GitHub for more details
# https://github.com/FoxIO-LLC/ja4/blob/main/technical_details/JA4H.md
#
# Copyright (c) 2024, FoxIO, LLC.
# All rights reserved.
# Licensed under FoxIO License 1.1
# For full license text and more details, see the repo root https://github.com/FoxIO-LLC/ja4
######################################################################################################################################

when CLIENT_ACCEPTED {
# Use these variables to define the -r (raw) and -o (original) switches defined in the JA4 spec
# 0 = false/disabled (default) and 1 = true/enabled
set ja4h_raw 1
set ja4h_original 0

}

when HTTP_REQUEST priority 10 {
#Collect JA4H "a" values
set me [string range [string tolower [HTTP::method]] 0 1]
set v [string map {"." ""} [HTTP::version]]
set c "n"
set r "n"
if { [HTTP::header exists "cookie"] } {
set c "c"
}
if { [HTTP::header exists "referer"] } {
set r "r"
}
set lang "0000"
if { [set alval [HTTP::header value "accept-language"]] ne "" } {
if { $alval contains ";" } {
set alval [string range $alval 0 [string first ";" $alval]]
}
set alval [string tolower [string range [string map {"-" ""} ${alval}] 0 3]]
set lang [string replace $lang 0 [string length ${alval}] ${alval}]
}

#Collect JA4H "b" values
set hc 0
set hstr ""
foreach hname [HTTP::header names] {
if { ${hname} starts_with "X-JA4" } {
continue
} elseif { ([string tolower ${hname}] eq "cookie") || ([string tolower ${hname}] eq "referer")} {
if { ${ja4h_original} }{
append hstr "${hname},"
}
} else {
incr hc
append hstr "${hname},"
}
}

if { $hc > 99 } {
set hc 99
}
set hc [format "%02d" $hc]
set hstr [string trimright ${hstr} ","]
binary scan [sha256 ${hstr}] H* hhash
set trunc_hhash [string range $hhash 0 11]

#Collect JA4H "c" and "d" values
set cstr ""
set clist [list]
set ckvstr ""
#set ckvlist [list]
foreach cname [HTTP::cookie names] {
lappend clist ${cname}
}
if {${ja4h_original} == 0 }{
set clist [lsort ${clist}]
}
foreach ck ${clist} {
append cstr "${ck},"
append ckvstr "${ck}=[HTTP::cookie value ${ck}],"
}

set cstr [string trimright ${cstr} ","]
set ckvstr [string trimright ${ckvstr} ","]
if { $c eq "c" } {
binary scan [sha256 ${cstr}] H* chash
binary scan [sha256 ${ckvstr}] H* ckvhash
set trunc_chash [string range $chash 0 11]
set trunc_ckvhash [string range $ckvhash 0 11]
} else {
set trunc_chash "000000000000"
set trunc_ckvhash "000000000000"
}

# Generate JA4H fingerprint string
set ja4h_fp "${me}${v}${c}${r}${hc}${lang}_${trunc_hhash}_${trunc_chash}_${trunc_ckvhash}"
HTTP::header insert "X-JA4H" $ja4h_fp

# If enabled, Generate JA4H_r(o) fingerprint string
if { ${ja4h_raw} } {
set ja4hr_fp "${me}${v}${c}${r}${hc}${lang}_${hstr}_${cstr}_${ckvstr}"
set ja4h_xhdr "X-JA4H_r"
if { ${ja4h_original} } {
set ja4h_xhdr "X-JA4H_ro"
}
}
}
when ACCESS_ACL_ALLOWED {
ACCESS::session data set session.custom.JA4h $ja4h_fp


}
Loading