New BIG-IP versions. A few have reported in the previous article that BIG-IP versions 14 and 15 caused validation errors in the iRule code. These are addressed here.
Salesforce.com extended Lee Brotherston's original work into a project called JA3, and open-sourced it. JA3 included some excellent updates, including support for TLS1.3 and GREASE protocol extensions, plus integrations with tools like Zeek/Bro and a bunch of other open source and commercial products. But most important, Lee's version included a data set of fingerprint-to-user-agent matches that hasn't really been maintained for a while. JA3, however, offers a much larger, and more current, crowd-sourced data set.
TL;DR
TLS fingerprinting is a methodology for uniquely identifying a client (user-agent) by virtue of examining a TLS Client Hello message for patterns that are particular to that user-agent. In any given TLS Client Hello message, the client will send a TLS version, a number of supported ciphers, a number of extensions, a number of elliptic curves, and curve point formats, and in a specific order. This pattern (how many elements and what order) has been found to be somewhat unique among the various user-agents (i.e. different browsers running in different OS's, non-browser Internet apps, malware command-and-control agents, etc.). It is possible then, to use this information in a few ways. Here are a few example use cases where I've personally witnessed its use over the last few years:
To attempt positive identification of user agents to control some process flow. For example, in a SSL visibility use case where SSL Orchestrator is deployed to decrypt and inspect outbound traffic, but devices may exist in the environment that aren't controlled by the organization and thus don't have the local issuing CA certificate installed. A version of TLS fingerprinting can be deployed to whitelist the known assets for decryption and inspection. It's important to understand here that TLS fingerprinting isn't (can't be) a 100%-reliable fingerprinting option. Clients can change the type and order of ciphers and extensions they use to mask their signature. But for identification of corporate-owned assets, this method can still be quite effective. It's also been highly useful at identifying malware command-and-control (C2) agents, which is exactly how some of the aforementioned JA3 integrated tools use it.
To uniquely identify a specific client (not the user-agent). As mentioned above, positive identification of a user-agent isn't 100% reliable. But, sometimes just having a hash of these unique signature properties is enough to isolate one potentially malicious client from another. And this, as it turns out, is how some of the other JA3 integrated tools use TLS fingerprint signatures. It doesn't matter if the client changes TLS properties to mask user-agent fingerprinting, as the hash of its current TLS signature is enough to identify and track it through its journey of destruction.
This article provides a few updates
First and foremost, it fixes the iRule validation issues in the original article for BIG-IP 14 and higher.
Our security heroes over in F5 SIRT created a JA3 version of this iRule that returns the JA3 version of the fingerprint hash. I've updated that JA3 code some more to include an option to search the crowd-sourced user-agent data set.
So without further ado...
TLS Fingerprinting Update
I'll ask that you refer to the original article for information on collecting the user-agent data and converting to a data group, as that hasn't changed. Otherwise, the relevant iRule code change is here in the updated Library-Rule:
## Library-Rule
## TLS Fingerprint Procedure #################
##
## Author: Kevin Stewart, Original (12/2016), Update(09/2020)
## Derived from Lee Brotherston's "tls-fingerprinting" project @ https://github.com/LeeBrotherston/tls-fingerprinting
## Purpose: to identify the user agent based on unique characteristics of the TLS ClientHello message
## Input:
## Full TCP payload collected in CLIENT_DATA event of a TLS handshake ClientHello message
## Record length (rlen)
## TLS outer version (outer)
## TLS inner version (inner)
## Client IP
## Server IP
##
## ## Update v2 to remove TCL errors from original code, and add code to return FP string, fp hash, or user-agent lookup
##############################################
proc fingerprintTLS { payload rlen outer inner clientip serverip } {
## user-defined: enable logging
set debug 0
## user-defined: enable
## - fingerprint string return ("fp")
## - fingerprint md5 hash return ("fphash")
## - user-agent lookup result ("ua")
set proc_return "fp"
## The first 43 bytes of a ClientHello message are the record type, TLS versions, some length values and the
## handshake type. We should already know this stuff from the calling iRule. We're also going to be walking the
## packet, so the field_offset variable will be used to track where we are.
set field_offset 43
## The first value in the payload after the offset is the session ID, which may be empty. Grab the session ID length
## value and move the field_offset variable that many bytes forward to skip it.
binary scan ${payload} @${field_offset}c sessID_len
set field_offset [expr {${field_offset} + 1 + ${sessID_len}}]
## The next value in the payload is the ciphersuite list length (how big the ciphersuite list is. We need the binary
## and hex values of this data.
binary scan ${payload} @${field_offset}S cipherList_len
binary scan ${payload} @${field_offset}H4 cipherList_len_hex
set cipherList_len_hex_text ${cipherList_len_hex}
## Now that we have the ciphersuite list length, let's offset the field_offset variable to skip over the length (2) bytes
## and go get the ciphersuite list. Multiple by 2 to get the number of appropriate hex characters.
set field_offset [expr {${field_offset} + 2}]
set cipherList_len_hex [expr {${cipherList_len} * 2}]
binary scan ${payload} @${field_offset}H${cipherList_len_hex} cipherlist
## Next is the compression method length and compression method. First move field_offset to skip past the ciphersuite
## list, then grab the compression method length. Then move field_offset past the length (2) bytes and grab the
## compression method value. Finally, move field_offset past the compression method bytes.
set field_offset [expr {${field_offset} + ${cipherList_len}}]
binary scan ${payload} @${field_offset}c compression_len
#set field_offset [expr {${field_offset} + ${compression_len}}]
set field_offset [expr {${field_offset} + 1}]
binary scan ${payload} @${field_offset}H[expr {${compression_len} * 2}] compression_type
set field_offset [expr {${field_offset} + ${compression_len}}]
## We should be in the extensions section now, so we're going to just run through the remaining data and
## pick out the extensions as we go. But first let's make sure there's more record data left, based on
## the current field_offset vs. rlen.
if { [expr {${field_offset} < ${rlen}}] } {
## There's extension data, so let's go get it. Skip the first 2 bytes that are the extensions length
set field_offset [expr {${field_offset} + 2}]
## Make a variable to store the extension types we find
set extensions_list ""
## Pad rlen by 1 byte
set rlen [expr {${rlen} + 1}]
while { [expr {${field_offset} <= ${rlen}}] } {
## Grab the first 2 bytes to determine the extension type
binary scan ${payload} @${field_offset}H4 ext
## Store the extension in the extensions_list variable
append extensions_list ${ext}
## Increment field_offset past the 2 bytes of the extension type
set field_offset [expr {${field_offset} + 2}]
## Grab the 2 bytes of extension lenth
binary scan ${payload} @${field_offset}S ext_len
## Increment field_offset past the 2 bytes of the extension length
set field_offset [expr {${field_offset} + 2}]
## Look for specific extension types in case these need to increment the field_offset (and because we need their values)
switch $ext {
"000b" {
## ec_point_format - there's another 1 byte after length
## Grab the extension data
binary scan ${payload} @[expr {${field_offset} + 1}]H[expr {(${ext_len} - 1) * 2}] ext_data
set ec_point_format ${ext_data}
}
"000a" {
## elliptic_curves - there's another 2 bytes after length
## Grab the extension data
binary scan ${payload} @[expr {${field_offset} + 2}]H[expr {(${ext_len} - 2) * 2}] ext_data
set elliptic_curves ${ext_data}
}
"000d" {
## sig_alg - there's another 2 bytes after length
## Grab the extension data
binary scan ${payload} @[expr {${field_offset} + 2}]H[expr {(${ext_len} - 2) * 2}] ext_data
set sig_alg ${ext_data}
}
default {
## Grab the otherwise unknown extension data
binary scan ${payload} @${field_offset}H[expr {${ext_len} * 2}] ext_data
}
}
## Increment the field_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}}]
}
}
## Now let's compile all of that data.
set cipl [string toupper ${cipherList_len_hex_text}]
set ciph [string toupper ${cipherlist}]
set coml ${compression_len}
set comp [string toupper ${compression_type}]
if { ( [info exists extensions_list] ) and ( ${extensions_list} ne "" ) } { set exte [string toupper ${extensions_list}] } else { set exte "@@@@" }
if { ( [info exists elliptic_curves] ) and ( ${elliptic_curves} ne "" ) } { set ecur [string toupper ${elliptic_curves}] } else { set ecur "@@@@" }
if { ( [info exists sig_alg] ) and ( ${sig_alg} ne "" ) } { set siga [string toupper ${sig_alg}] } else { set siga "@@@@" }
if { ( [info exists ec_point_format] ) and ( ${ec_point_format} ne "" ) } { set ecfp [string toupper ${ec_point_format}] } else { set ecfp "@@@@" }
## Now let's build the fingerprint string and search the database
set fingerprint_str "${outer}+${inner}+${cipl}+${ciph}+${coml}+${comp}+${exte}+${ecur}+${siga}+${ecfp}"
if { ${debug} } { log local0. "${clientip}-${serverip}: fingerprint_str = ${fingerprint_str}" }
switch ${proc_return} {
"fp" {
return ${fingerprint_str}
}
"fphash" {
binary scan [md5 ${fingerprint_str}] H* fp_digest
return ${fp_digest}
}
"ua" {
## Initialize the match variable
set match ""
if { [class match ${fingerprint_str} equals fingerprint_db] } {
## Direct match
set match [class match -value ${fingerprint_str} equals fingerprint_db]
} elseif { not ( ${ciph} starts_with "C0" ) and not ( ${ciph} starts_with "00" ) } {
## Hmm.. there's no direct match, which could either mean a database entry doesn't exist, or Chrome (and Opera) are adding
## special values to the cipherlist, extensions list and elliptic curves list.
## ex. 9A9A, 5A5A, EAEA, BABA, etc. at the beginning of the cipherlist
## Let's strip out these anomalous values and try the match again.
## Substract 2 bytes from cipherlist length (v2 fix)
set cipl_tmp "set cipl \[format %04x \[expr \{ \[expr 0x$\{cipl\}\] - 2 \}\]\]"
eval ${cipl_tmp}
## Subtract 2 bytes from the front of the cipher list
set ciph [string range ${ciph} 4 end]
## Subtract 2 bytes from the front of the extensions list
set exte [string range ${exte} 4 end]
## There might be an additional random set in the string that needs to be removed (pattern is "(.)A\1A")
## (v2 fix)
regsub {(.)A\\1A} ${exte} "" exte
## Subtract 2 bytes from the front of the elliptic curves list
set ecur [string range ${ecur} 4 end]
## Rebuild the fingerprint string
set fingerprint_str "${outer}+${inner}+${cipl}+${ciph}+${coml}+${comp}+${exte}+${ecur}+${siga}+${ecfp}"
if { [class match ${fingerprint_str} equals fingerprint_db] } {
## Guess match
set match [class match -value ${fingerprint_str} equals fingerprint_db]
} else {
## No match
set match ""
}
}
## Return the matching user agent string
return ${match}
}
}
}
Of particular interest, I've added some control flags at the top of the iRule to control debug logging and what to output:
where "fp" returns the full TLS fingerprint string, "fphash" returns a short md5 hash of that string (see the second use case above), and "ua" attempts to match the fingerprint against the data group (removing the GREASE extensions first). The caller iRule is the same as the original.
JA3 TLS Fingerprint Update
For this exercise, we'll extend upon Aaron Brailsford's JA3 version (F5 SIRT) and add a user-agent lookup. As with the above, I've given you the option to select what to output, so if you only want the hash, then you won't need the data set. But if you do want to lookup the user-agent, you'll first need to acquire the data.
In an empty folder on your local machine (or BIG-IP), download the crowd-sourced JA3 data to a file:
curl -vk https://ja3er.com/getAllUasJson > ja3.db
Then create and run this simple Python script to convert this file to external data group format. It's a lot of data, so you'll want to use an external data group for this.
import json
with open('ja3.db') as data_file:
data = json.load(data_file)
file = open("ja3.dg", "a")
for x in data:
if x['User-Agent']:
#print("%s := %s," % (x['md5'], x['User-Agent']))
file.write("\"%s\" := \"%s\",\n" % (x['md5'].encode('utf-8'), x['User-Agent'].encode('utf-8').strip('\"')))
file.close()
Simply run it like this:
python ja3-converter.py
The script looks for ja3.db and creates a new ja3.dg file. Now go ahead and import this to the BIG-IP. Navigate to System -> File Management -> Data Group File List. then click Import.
File Name: browse to and select the ja3.dg file
Name: enter "ja3_dg"
File Contents: select "String"
Key / Value Pair Separator: select :=
Data Group Name: enter "ja3_dg"
Now create the "Library-Rule" iRule:
## Library-Rule
## JA3 TLS Fingerprint Procedure #################
##
## Author: Kevin Stewart, 09/2020
## Derived from Aaron Brailsford's JA3 version
## Derived from Lee Brotherston's "tls-fingerprinting" project @ https://github.com/LeeBrotherston/tls-fingerprinting
## Based on the TLS Fingerprinting iRule by Kevin Stewart @ https://devcentral.f5.com/s/articles/tls-fingerprinting-a-method-for-identifying-a-tls-client-without-decrypting-24598
## Purpose: to identify the user agent based on unique characteristics of the TLS ClientHello message
## Input:
## Full TCP payload collected in CLIENT_DATA event of a TLS handshake ClientHello message
## Record length (rlen)
## TLS inner version (sslversion)
##
## Update v2 to remove GREASE information and provide method to search a local ja3 user-agent database
##############################################
proc fingerprintTLS { payload rlen sslversion } {
## user-defined: debug logging
set debug 0
## user-defined: enable
## - ja3 fingerprint hash ("fphash")
## - ja3 user-agent lookup result ("ua")
set proc_return "fphash"
## decimal-converted GREASE values - these will be removed from the output
set GREASE_LIST [list 2570 6682 10794 14906 19018 23130 27242 31354 35466 39578 43690 47802 51914 56026 60138 64250]
## The first 43 bytes of a ClientHello message are the record type, TLS versions, some length values and the
## handshake type. We should already know this stuff from the calling iRule. We're also going to be walking the
## packet, so the field_offset variable will be used to track where we are.
set field_offset 43
## The first value in the payload after the offset is the session ID, which may be empty. Grab the session ID length
## value and move the field_offset variable that many bytes forward to skip it.
binary scan ${payload} @${field_offset}c sessID_len
set field_offset [expr {${field_offset} + 1 + ${sessID_len}}]
## The next value in the payload is the ciphersuite list length (how big the ciphersuite list is.
binary scan ${payload} @${field_offset}S cipherList_len
## Now that we have the ciphersuite list length, let's offset the field_offset variable to skip over the length (2) bytes
## and go get the ciphersuite list.
set field_offset [expr {${field_offset} + 2}]
binary scan ${payload} @${field_offset}S[expr {${cipherList_len} / 2}] cipherlist_decimal
## Next is the compression method length and compression method. First move field_offset to skip past the ciphersuite
## list, then grab the compression method length. Then move field_offset past the length (2)
## Finally, move field_offset past the compression method bytes.
set field_offset [expr {${field_offset} + ${cipherList_len}}]
binary scan ${payload} @${field_offset}c compression_len
set field_offset [expr {${field_offset} + 1}]
set field_offset [expr {${field_offset} + ${compression_len}}]
## We should be in the extensions section now, so we're going to just run through the remaining data and
## pick out the extensions as we go. But first let's make sure there's more record data left, based on
## the current field_offset vs. rlen.
if { [expr {${field_offset} < ${rlen}}] } {
## There's extension data, so let's go get it. Skip the first 2 bytes that are the extensions length
set field_offset [expr {${field_offset} + 2}]
## Make a variable to store the extension types we find
set extensions_list ""
## Pad rlen by 1 byte
set rlen [expr {${rlen} + 1}]
while { [expr {${field_offset} <= ${rlen}}] } {
## Grab the first 2 bytes to determine the extension type
binary scan ${payload} @${field_offset}S ext
set ext [expr {$ext & 0xFFFF}]
## Store the extension in the extensions_list variable
lappend extensions_list ${ext}
## Increment field_offset past the 2 bytes of the extension type
set field_offset [expr {${field_offset} + 2}]
## Grab the 2 bytes of extension lenth
binary scan ${payload} @${field_offset}S ext_len
## Increment field_offset past the 2 bytes of the extension length
set field_offset [expr {${field_offset} + 2}]
## Look for specific extension types in case these need to increment the field_offset (and because we need their values)
switch $ext {
"11" {
## ec_point_format - there's another 1 byte after length
## Grab the extension data
binary scan ${payload} @[expr {${field_offset} + 1}]s ext_data
set ec_point_format ${ext_data}
}
"10" {
## elliptic_curves - there's another 2 bytes after length
## Grab the extension data
binary scan ${payload} @[expr {${field_offset} + 2}]S[expr {(${ext_len} - 2) / 2}] ext_data
set elliptic_curves ${ext_data}
}
default {
## Grab the otherwise unknown extension data
binary scan ${payload} @${field_offset}H[expr {${ext_len} * 2}] ext_data
}
}
## Increment the field_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}}]
}
}
## Now let's compile all of that data.
## The cipherlist values need masking with 0xFFFF to return the unsigned integers we need
## v2 update: strip out GREASE values
foreach cipher $cipherlist_decimal {
if { [lsearch -exact ${GREASE_LIST} [expr {$cipher & 0xFFFF}]] == -1 } {
lappend cipd [expr {$cipher & 0xFFFF}]
}
}
set cipd_str [join $cipd "-"]
## v2 update: strip out GREASE values
if { ( [info exists extensions_list] ) and ( ${extensions_list} ne "" ) } {
set exte_tmp [list]
foreach x ${extensions_list} {
if { [lsearch -exact ${GREASE_LIST} ${x}] == -1 } {
lappend exte_tmp ${x}
}
}
set exte [join ${exte_tmp} "-"]
} else {
set exte ""
}
## v2 update: strip out GREASE values
if { ( [info exists elliptic_curves] ) and ( ${elliptic_curves} ne "" ) } {
set ecur_tmp [list]
foreach x ${elliptic_curves} {
if { [lsearch -exact ${GREASE_LIST} ${x}] == -1 } {
lappend ecur_tmp ${x}
}
}
set ecur [join ${ecur_tmp} "-"]
} else {
set ecur ""
}
if { ( [info exists ec_point_format] ) and ( ${ec_point_format} ne "" ) } { set ecfp [join ${ec_point_format} "-"] } else { set ecfp "" }
set ja3_str "${sslversion},${cipd_str},${exte},${ecur},${ecfp}"
binary scan [md5 ${ja3_str}] H* ja3_digest
if { ${debug} } {
log local0. "ja3 string = ${ja3_str}"
log local0. "ja3 digest = ${ja3_digest}"
}
switch ${proc_return} {
"fphash" {
return ${ja3_digest}
}
"ua" {
if { [set ua_match [class match -value ${ja3_digest} eq ja3_dg]] ne "" } {
return ${ua_match}
} else {
return "No Match"
}
}
}
}
Again, I've given you the option to enable/disable debug logging, and select what data to return:
## user-defined: debug logging
set debug 0
## user-defined: enable
## - ja3 fingerprint hash ("fphash")
## - ja3 user-agent lookup result ("ua")
set proc_return "fphash"
where "fphash" is the JA3 fingerprint hash, and "ua" attempts to find the matching user-agent in the data group. The caller iRule is pretty similar to the original:
when CLIENT_ACCEPTED {
## Collect the TCP payload
TCP::collect
}
when CLIENT_DATA {
## Get the TLS packet type and versions
if { ! [info exists rlen] } {
## We actually only need the recort type (rtype), record length (rlen) handshake type (hs_type) and 'inner' SSL version (inner_sslver) here
## But it's easiest to parse them all out of the payload along with the bytes we don't need (outer_sslver & rilen)
binary scan [TCP::payload] cSScH6S rtype outer_sslver rlen hs_type rilen inner_sslver
if { ( ${rtype} == 22 ) and ( ${hs_type} == 1 ) } {
## This is a TLS ClientHello message (22 = TLS handshake, 1 = ClientHello)
## Call the fingerprintTLS proc
set ja3_fingerprint [call Library-Rule::fingerprintTLS [TCP::payload] ${rlen} ${inner_sslver}]
#binary scan [md5 ${ja3_fingerprint}] H* ja3_digest
### Do Something here ###
log local0. "[IP::client_addr]:[TCP::client_port] ja3 ${ja3_fingerprint}"
### Do Something here ###
}
}
# Collect the rest of the record if necessary
if { [TCP::payload length] < $rlen } {
TCP::collect $rlen
}
## Release the paylaod
TCP::release
}
And there you have it. I think you'll find that if you experiment with both of these versions, you'll have more success matching a user-agent to the JA3 data set. Otherwise, using the fingerprint hash (from either version) provides a compelling method to uniquely identify and track clients as a function of threat intelligence.
"}},"componentScriptGroups({\"componentId\":\"custom.widget.Beta_Footer\"})":{"__typename":"ComponentScriptGroups","scriptGroups":{"__typename":"ComponentScriptGroupsDefinition","afterInteractive":{"__typename":"PageScriptGroupDefinition","group":"AFTER_INTERACTIVE","scriptIds":[]},"lazyOnLoad":{"__typename":"PageScriptGroupDefinition","group":"LAZY_ON_LOAD","scriptIds":[]}},"componentScripts":[]},"component({\"componentId\":\"custom.widget.Tag_Manager_Helper\"})":{"__typename":"Component","render({\"context\":{\"component\":{\"entities\":[],\"props\":{}},\"page\":{\"entities\":[\"message:286117\"],\"name\":\"TkbMessagePage\",\"props\":{},\"url\":\"https://community.f5.com/kb/technicalarticles/tls-fingerprinting-update---a-method-for-identifying-a-tls-client-without-decryp/286117\"}}})":{"__typename":"ComponentRenderResult","html":" "}},"componentScriptGroups({\"componentId\":\"custom.widget.Tag_Manager_Helper\"})":{"__typename":"ComponentScriptGroups","scriptGroups":{"__typename":"ComponentScriptGroupsDefinition","afterInteractive":{"__typename":"PageScriptGroupDefinition","group":"AFTER_INTERACTIVE","scriptIds":[]},"lazyOnLoad":{"__typename":"PageScriptGroupDefinition","group":"LAZY_ON_LOAD","scriptIds":[]}},"componentScripts":[]},"component({\"componentId\":\"custom.widget.Consent_Blackbar\"})":{"__typename":"Component","render({\"context\":{\"component\":{\"entities\":[],\"props\":{}},\"page\":{\"entities\":[\"message:286117\"],\"name\":\"TkbMessagePage\",\"props\":{},\"url\":\"https://community.f5.com/kb/technicalarticles/tls-fingerprinting-update---a-method-for-identifying-a-tls-client-without-decryp/286117\"}}})":{"__typename":"ComponentRenderResult","html":""}},"componentScriptGroups({\"componentId\":\"custom.widget.Consent_Blackbar\"})":{"__typename":"ComponentScriptGroups","scriptGroups":{"__typename":"ComponentScriptGroupsDefinition","afterInteractive":{"__typename":"PageScriptGroupDefinition","group":"AFTER_INTERACTIVE","scriptIds":[]},"lazyOnLoad":{"__typename":"PageScriptGroupDefinition","group":"LAZY_ON_LOAD","scriptIds":[]}},"componentScripts":[]},"cachedText({\"lastModified\":\"1744046271000\",\"locale\":\"en-US\",\"namespaces\":[\"shared/client/components/common/QueryHandler\"]})":[{"__ref":"CachedAsset:text:en_US-shared/client/components/common/QueryHandler-1744046271000"}],"cachedText({\"lastModified\":\"1744046271000\",\"locale\":\"en-US\",\"namespaces\":[\"components/community/NavbarDropdownToggle\"]})":[{"__ref":"CachedAsset:text:en_US-components/community/NavbarDropdownToggle-1744046271000"}],"cachedText({\"lastModified\":\"1744046271000\",\"locale\":\"en-US\",\"namespaces\":[\"components/messages/MessageSubject\"]})":[{"__ref":"CachedAsset:text:en_US-components/messages/MessageSubject-1744046271000"}],"cachedText({\"lastModified\":\"1744046271000\",\"locale\":\"en-US\",\"namespaces\":[\"components/messages/MessageBody\"]})":[{"__ref":"CachedAsset:text:en_US-components/messages/MessageBody-1744046271000"}],"cachedText({\"lastModified\":\"1744046271000\",\"locale\":\"en-US\",\"namespaces\":[\"components/messages/MessageCustomFields\"]})":[{"__ref":"CachedAsset:text:en_US-components/messages/MessageCustomFields-1744046271000"}],"cachedText({\"lastModified\":\"1744046271000\",\"locale\":\"en-US\",\"namespaces\":[\"components/messages/MessageRevision\"]})":[{"__ref":"CachedAsset:text:en_US-components/messages/MessageRevision-1744046271000"}],"cachedText({\"lastModified\":\"1744046271000\",\"locale\":\"en-US\",\"namespaces\":[\"components/messages/MessageReplyButton\"]})":[{"__ref":"CachedAsset:text:en_US-components/messages/MessageReplyButton-1744046271000"}],"cachedText({\"lastModified\":\"1744046271000\",\"locale\":\"en-US\",\"namespaces\":[\"components/messages/MessageAuthorBio\"]})":[{"__ref":"CachedAsset:text:en_US-components/messages/MessageAuthorBio-1744046271000"}],"cachedText({\"lastModified\":\"1744046271000\",\"locale\":\"en-US\",\"namespaces\":[\"components/guides/GuideBottomNavigation\"]})":[{"__ref":"CachedAsset:text:en_US-components/guides/GuideBottomNavigation-1744046271000"}],"cachedText({\"lastModified\":\"1744046271000\",\"locale\":\"en-US\",\"namespaces\":[\"components/tags/TagView/TagViewChip\"]})":[{"__ref":"CachedAsset:text:en_US-components/tags/TagView/TagViewChip-1744046271000"}],"cachedText({\"lastModified\":\"1744046271000\",\"locale\":\"en-US\",\"namespaces\":[\"components/users/UserLink\"]})":[{"__ref":"CachedAsset:text:en_US-components/users/UserLink-1744046271000"}],"cachedText({\"lastModified\":\"1744046271000\",\"locale\":\"en-US\",\"namespaces\":[\"shared/client/components/users/UserRank\"]})":[{"__ref":"CachedAsset:text:en_US-shared/client/components/users/UserRank-1744046271000"}],"cachedText({\"lastModified\":\"1744046271000\",\"locale\":\"en-US\",\"namespaces\":[\"components/users/UserRegistrationDate\"]})":[{"__ref":"CachedAsset:text:en_US-components/users/UserRegistrationDate-1744046271000"}],"cachedText({\"lastModified\":\"1744046271000\",\"locale\":\"en-US\",\"namespaces\":[\"components/customComponent/CustomComponent\"]})":[{"__ref":"CachedAsset:text:en_US-components/customComponent/CustomComponent-1744046271000"}],"cachedText({\"lastModified\":\"1744046271000\",\"locale\":\"en-US\",\"namespaces\":[\"shared/client/components/users/UserAvatar\"]})":[{"__ref":"CachedAsset:text:en_US-shared/client/components/users/UserAvatar-1744046271000"}],"cachedText({\"lastModified\":\"1744046271000\",\"locale\":\"en-US\",\"namespaces\":[\"shared/client/components/ranks/UserRankLabel\"]})":[{"__ref":"CachedAsset:text:en_US-shared/client/components/ranks/UserRankLabel-1744046271000"}]},"Theme:customTheme1":{"__typename":"Theme","id":"customTheme1"},"User:user:-1":{"__typename":"User","id":"user:-1","uid":-1,"login":"Former Member","email":"","avatar":null,"rank":null,"kudosWeight":1,"registrationData":{"__typename":"RegistrationData","status":"ANONYMOUS","registrationTime":null,"confirmEmailStatus":false,"registrationAccessLevel":"VIEW","ssoRegistrationFields":[]},"ssoId":null,"profileSettings":{"__typename":"ProfileSettings","dateDisplayStyle":{"__typename":"InheritableStringSettingWithPossibleValues","key":"layout.friendly_dates_enabled","value":"false","localValue":"true","possibleValues":["true","false"]},"dateDisplayFormat":{"__typename":"InheritableStringSetting","key":"layout.format_pattern_date","value":"dd-MMM-yyyy","localValue":"MM-dd-yyyy"},"language":{"__typename":"InheritableStringSettingWithPossibleValues","key":"profile.language","value":"en-US","localValue":null,"possibleValues":["en-US","es-ES"]},"repliesSortOrder":{"__typename":"InheritableStringSettingWithPossibleValues","key":"config.user_replies_sort_order","value":"DEFAULT","localValue":"DEFAULT","possibleValues":["DEFAULT","LIKES","PUBLISH_TIME","REVERSE_PUBLISH_TIME"]}},"deleted":false},"CachedAsset:pages-1744706921165":{"__typename":"CachedAsset","id":"pages-1744706921165","value":[{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"HowDoI.GetInvolved.MvpProgram","type":"COMMUNITY","urlPath":"/c/how-do-i/get-involved/mvp-program","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"BlogViewAllPostsPage","type":"BLOG","urlPath":"/category/:categoryId/blog/:boardId/all-posts/(/:after|/:before)?","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"CasePortalPage","type":"CASE_PORTAL","urlPath":"/caseportal","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"CreateGroupHubPage","type":"GROUP_HUB","urlPath":"/groups/create","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"CaseViewPage","type":"CASE_DETAILS","urlPath":"/case/:caseId/:caseNumber","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"InboxPage","type":"COMMUNITY","urlPath":"/inbox","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"HowDoI.GetInvolved.AdvocacyProgram","type":"COMMUNITY","urlPath":"/c/how-do-i/get-involved/advocacy-program","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"HowDoI.GetHelp.NonCustomer","type":"COMMUNITY","urlPath":"/c/how-do-i/get-help/non-customer","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"HelpFAQPage","type":"COMMUNITY","urlPath":"/help","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"HowDoI.GetHelp.F5Customer","type":"COMMUNITY","urlPath":"/c/how-do-i/get-help/f5-customer","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"IdeaMessagePage","type":"IDEA_POST","urlPath":"/idea/:boardId/:messageSubject/:messageId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"IdeaViewAllIdeasPage","type":"IDEA","urlPath":"/category/:categoryId/ideas/:boardId/all-ideas/(/:after|/:before)?","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"LoginPage","type":"USER","urlPath":"/signin","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"BlogPostPage","type":"BLOG","urlPath":"/category/:categoryId/blogs/:boardId/create","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"HowDoI.GetInvolved","type":"COMMUNITY","urlPath":"/c/how-do-i/get-involved","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"HowDoI.Learn","type":"COMMUNITY","urlPath":"/c/how-do-i/learn","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1739501996000,"localOverride":null,"page":{"id":"Test","type":"CUSTOM","urlPath":"/custom-test-2","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"ThemeEditorPage","type":"COMMUNITY","urlPath":"/designer/themes","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"TkbViewAllArticlesPage","type":"TKB","urlPath":"/category/:categoryId/kb/:boardId/all-articles/(/:after|/:before)?","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"OccasionEditPage","type":"EVENT","urlPath":"/event/:boardId/:messageSubject/:messageId/edit","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"OAuthAuthorizationAllowPage","type":"USER","urlPath":"/auth/authorize/allow","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"PageEditorPage","type":"COMMUNITY","urlPath":"/designer/pages","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"PostPage","type":"COMMUNITY","urlPath":"/category/:categoryId/:boardId/create","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"ForumBoardPage","type":"FORUM","urlPath":"/category/:categoryId/discussions/:boardId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"TkbBoardPage","type":"TKB","urlPath":"/category/:categoryId/kb/:boardId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"EventPostPage","type":"EVENT","urlPath":"/category/:categoryId/events/:boardId/create","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"UserBadgesPage","type":"COMMUNITY","urlPath":"/users/:login/:userId/badges","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"GroupHubMembershipAction","type":"GROUP_HUB","urlPath":"/membership/join/:nodeId/:membershipType","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"MaintenancePage","type":"COMMUNITY","urlPath":"/maintenance","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"IdeaReplyPage","type":"IDEA_REPLY","urlPath":"/idea/:boardId/:messageSubject/:messageId/comments/:replyId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"UserSettingsPage","type":"USER","urlPath":"/mysettings/:userSettingsTab","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"GroupHubsPage","type":"GROUP_HUB","urlPath":"/groups","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"ForumPostPage","type":"FORUM","urlPath":"/category/:categoryId/discussions/:boardId/create","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"OccasionRsvpActionPage","type":"OCCASION","urlPath":"/event/:boardId/:messageSubject/:messageId/rsvp/:responseType","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"VerifyUserEmailPage","type":"USER","urlPath":"/verifyemail/:userId/:verifyEmailToken","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"AllOccasionsPage","type":"OCCASION","urlPath":"/category/:categoryId/events/:boardId/all-events/(/:after|/:before)?","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"EventBoardPage","type":"EVENT","urlPath":"/category/:categoryId/events/:boardId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"TkbReplyPage","type":"TKB_REPLY","urlPath":"/kb/:boardId/:messageSubject/:messageId/comments/:replyId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"IdeaBoardPage","type":"IDEA","urlPath":"/category/:categoryId/ideas/:boardId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"CommunityGuideLinesPage","type":"COMMUNITY","urlPath":"/communityguidelines","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"CaseCreatePage","type":"SALESFORCE_CASE_CREATION","urlPath":"/caseportal/create","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"TkbEditPage","type":"TKB","urlPath":"/kb/:boardId/:messageSubject/:messageId/edit","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"ForgotPasswordPage","type":"USER","urlPath":"/forgotpassword","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"IdeaEditPage","type":"IDEA","urlPath":"/idea/:boardId/:messageSubject/:messageId/edit","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"TagPage","type":"COMMUNITY","urlPath":"/tag/:tagName","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"BlogBoardPage","type":"BLOG","urlPath":"/category/:categoryId/blog/:boardId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"OccasionMessagePage","type":"OCCASION_TOPIC","urlPath":"/event/:boardId/:messageSubject/:messageId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"ManageContentPage","type":"COMMUNITY","urlPath":"/managecontent","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"ClosedMembershipNodeNonMembersPage","type":"GROUP_HUB","urlPath":"/closedgroup/:groupHubId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"HowDoI.GetHelp.Community","type":"COMMUNITY","urlPath":"/c/how-do-i/get-help/community","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"CommunityPage","type":"COMMUNITY","urlPath":"/","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"HowDoI.GetInvolved.ContributeCode","type":"COMMUNITY","urlPath":"/c/how-do-i/get-involved/contribute-code","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"ForumMessagePage","type":"FORUM_TOPIC","urlPath":"/discussions/:boardId/:messageSubject/:messageId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"IdeaPostPage","type":"IDEA","urlPath":"/category/:categoryId/ideas/:boardId/create","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"BlogMessagePage","type":"BLOG_ARTICLE","urlPath":"/blog/:boardId/:messageSubject/:messageId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"RegistrationPage","type":"USER","urlPath":"/register","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"EditGroupHubPage","type":"GROUP_HUB","urlPath":"/group/:groupHubId/edit","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"ForumEditPage","type":"FORUM","urlPath":"/discussions/:boardId/:messageSubject/:messageId/edit","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"ResetPasswordPage","type":"USER","urlPath":"/resetpassword/:userId/:resetPasswordToken","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"TkbMessagePage","type":"TKB_ARTICLE","urlPath":"/kb/:boardId/:messageSubject/:messageId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"HowDoI.Learn.AboutIrules","type":"COMMUNITY","urlPath":"/c/how-do-i/learn/about-irules","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"BlogEditPage","type":"BLOG","urlPath":"/blog/:boardId/:messageSubject/:messageId/edit","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"HowDoI.GetHelp.F5Support","type":"COMMUNITY","urlPath":"/c/how-do-i/get-help/f5-support","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"ManageUsersPage","type":"USER","urlPath":"/users/manage/:tab?/:manageUsersTab?","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"ForumReplyPage","type":"FORUM_REPLY","urlPath":"/discussions/:boardId/:messageSubject/:messageId/replies/:replyId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"PrivacyPolicyPage","type":"COMMUNITY","urlPath":"/privacypolicy","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"NotificationPage","type":"COMMUNITY","urlPath":"/notifications","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"UserPage","type":"USER","urlPath":"/users/:login/:userId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"HealthCheckPage","type":"COMMUNITY","urlPath":"/health","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"OccasionReplyPage","type":"OCCASION_REPLY","urlPath":"/event/:boardId/:messageSubject/:messageId/comments/:replyId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"ManageMembersPage","type":"GROUP_HUB","urlPath":"/group/:groupHubId/manage/:tab?","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"SearchResultsPage","type":"COMMUNITY","urlPath":"/search","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"BlogReplyPage","type":"BLOG_REPLY","urlPath":"/blog/:boardId/:messageSubject/:messageId/replies/:replyId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"GroupHubPage","type":"GROUP_HUB","urlPath":"/group/:groupHubId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"TermsOfServicePage","type":"COMMUNITY","urlPath":"/termsofservice","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"HowDoI.GetHelp","type":"COMMUNITY","urlPath":"/c/how-do-i/get-help","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"HowDoI.GetHelp.SecurityIncident","type":"COMMUNITY","urlPath":"/c/how-do-i/get-help/security-incident","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"CategoryPage","type":"CATEGORY","urlPath":"/category/:categoryId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"ForumViewAllTopicsPage","type":"FORUM","urlPath":"/category/:categoryId/discussions/:boardId/all-topics/(/:after|/:before)?","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"TkbPostPage","type":"TKB","urlPath":"/category/:categoryId/kbs/:boardId/create","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"GroupHubPostPage","type":"GROUP_HUB","urlPath":"/group/:groupHubId/:boardId/create","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1744706921165,"localOverride":null,"page":{"id":"HowDoI","type":"COMMUNITY","urlPath":"/c/how-do-i","__typename":"PageDescriptor"},"__typename":"PageResource"}],"localOverride":false},"CachedAsset:text:en_US-components/context/AppContext/AppContextProvider-0":{"__typename":"CachedAsset","id":"text:en_US-components/context/AppContext/AppContextProvider-0","value":{"noCommunity":"Cannot find community","noUser":"Cannot find current user","noNode":"Cannot find node with id {nodeId}","noMessage":"Cannot find message with id {messageId}","userBanned":"We're sorry, but you have been banned from using this site.","userBannedReason":"You have been banned for the following reason: {reason}"},"localOverride":false},"CachedAsset:text:en_US-shared/client/components/common/Loading/LoadingDot-0":{"__typename":"CachedAsset","id":"text:en_US-shared/client/components/common/Loading/LoadingDot-0","value":{"title":"Loading..."},"localOverride":false},"AssociatedImage:{\"url\":\"https://community.f5.com/t5/s/zihoc95639/images/cmstMjgtQ3U0RXo2\"}":{"__typename":"AssociatedImage","url":"https://community.f5.com/t5/s/zihoc95639/images/cmstMjgtQ3U0RXo2","height":0,"width":0,"mimeType":"image/svg+xml"},"Rank:rank:28":{"__typename":"Rank","id":"rank:28","position":5,"name":"Employee","color":"C20025","icon":{"__ref":"AssociatedImage:{\"url\":\"https://community.f5.com/t5/s/zihoc95639/images/cmstMjgtQ3U0RXo2\"}"},"rankStyle":"OUTLINE"},"User:user:130391":{"__typename":"User","id":"user:130391","uid":130391,"login":"Kevin_Stewart","deleted":false,"avatar":{"__typename":"UserAvatar","url":"https://community.f5.com/t5/s/zihoc95639/images/dS0xMzAzOTEtelZmemp2?image-coordinates=0%2C0%2C500%2C500"},"rank":{"__ref":"Rank:rank:28"},"email":"","messagesCount":5757,"biography":null,"topicsCount":39,"kudosReceivedCount":166,"kudosGivenCount":0,"kudosWeight":1,"registrationData":{"__typename":"RegistrationData","status":null,"registrationTime":"2006-03-16T00:00:00.000-08:00","confirmEmailStatus":null},"followersCount":null,"solutionsCount":29},"Category:category:Articles":{"__typename":"Category","id":"category:Articles","entityType":"CATEGORY","displayId":"Articles","nodeType":"category","depth":1,"title":"Articles","shortTitle":"Articles","parent":{"__ref":"Category:category:top"},"categoryPolicies":{"__typename":"CategoryPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Category:category:top":{"__typename":"Category","id":"category:top","entityType":"CATEGORY","displayId":"top","nodeType":"category","depth":0,"title":"Top","shortTitle":"Top"},"Tkb:board:TechnicalArticles":{"__typename":"Tkb","id":"board:TechnicalArticles","entityType":"TKB","displayId":"TechnicalArticles","nodeType":"board","depth":2,"conversationStyle":"TKB","repliesProperties":{"__typename":"RepliesProperties","sortOrder":"PUBLISH_TIME","repliesFormat":"threaded"},"tagProperties":{"__typename":"TagNodeProperties","tagsEnabled":{"__typename":"PolicyResult","failureReason":null}},"requireTags":true,"tagType":"FREEFORM_AND_PRESET","description":"F5 SMEs share good practice.","title":"Technical Articles","shortTitle":"Technical Articles","parent":{"__ref":"Category:category:Articles"},"ancestors":{"__typename":"CoreNodeConnection","edges":[{"__typename":"CoreNodeEdge","node":{"__ref":"Community:community:zihoc95639"}},{"__typename":"CoreNodeEdge","node":{"__ref":"Category:category:Articles"}}]},"userContext":{"__typename":"NodeUserContext","canAddAttachments":false,"canUpdateNode":false,"canPostMessages":false,"isSubscribed":false},"theme":{"__ref":"Theme:customTheme1"},"boardPolicies":{"__typename":"BoardPolicies","canViewSpamDashBoard":{"__typename":"PolicyResult","failureReason":{"__typename":"FailureReason","message":"error.lithium.policies.feature.moderation_spam.action.access_spam_quarantine.allowed.accessDenied","key":"error.lithium.policies.feature.moderation_spam.action.access_spam_quarantine.allowed.accessDenied","args":[]}},"canArchiveMessage":{"__typename":"PolicyResult","failureReason":{"__typename":"FailureReason","message":"error.lithium.policies.content_archivals.enable_content_archival_settings.accessDenied","key":"error.lithium.policies.content_archivals.enable_content_archival_settings.accessDenied","args":[]}},"canPublishArticleOnCreate":{"__typename":"PolicyResult","failureReason":{"__typename":"FailureReason","message":"error.lithium.policies.forums.policy_can_publish_on_create_workflow_action.accessDenied","key":"error.lithium.policies.forums.policy_can_publish_on_create_workflow_action.accessDenied","args":[]}},"canReadNode":{"__typename":"PolicyResult","failureReason":null}},"isManualSortOrderAvailable":false,"tkbPolicies":{"__typename":"TkbPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"TkbTopicMessage:message:286117":{"__typename":"TkbTopicMessage","uid":286117,"subject":"TLS Fingerprinting Update - a method for identifying a TLS client without decryption","id":"message:286117","revisionNum":1,"repliesCount":0,"author":{"__ref":"User:user:130391"},"depth":0,"hasGivenKudo":false,"helpful":null,"board":{"__ref":"Tkb:board:TechnicalArticles"},"conversation":{"__ref":"Conversation:conversation:286117"},"messagePolicies":{"__typename":"MessagePolicies","canPublishArticleOnEdit":{"__typename":"PolicyResult","failureReason":{"__typename":"FailureReason","message":"error.lithium.policies.forums.policy_can_publish_on_edit_workflow_action.accessDenied","key":"error.lithium.policies.forums.policy_can_publish_on_edit_workflow_action.accessDenied","args":[]}},"canModerateSpamMessage":{"__typename":"PolicyResult","failureReason":{"__typename":"FailureReason","message":"error.lithium.policies.feature.moderation_spam.action.moderate_entity.allowed.accessDenied","key":"error.lithium.policies.feature.moderation_spam.action.moderate_entity.allowed.accessDenied","args":[]}}},"contentWorkflow":{"__typename":"ContentWorkflow","state":"PUBLISH","scheduledPublishTime":null,"scheduledTimezone":null,"userContext":{"__typename":"MessageWorkflowContext","canSubmitForReview":null,"canEdit":false,"canRecall":null,"canSubmitForPublication":null,"canReturnToAuthor":null,"canPublish":null,"canReturnToReview":null,"canSchedule":false},"shortScheduledTimezone":null},"readOnly":false,"editFrozen":false,"moderationData":{"__ref":"ModerationData:moderation_data:286117"},"teaser":"","body":"
New BIG-IP versions. A few have reported in the previous article that BIG-IP versions 14 and 15 caused validation errors in the iRule code. These are addressed here.
\n\n
Salesforce.com extended Lee Brotherston's original work into a project called JA3, and open-sourced it. JA3 included some excellent updates, including support for TLS1.3 and GREASE protocol extensions, plus integrations with tools like Zeek/Bro and a bunch of other open source and commercial products. But most important, Lee's version included a data set of fingerprint-to-user-agent matches that hasn't really been maintained for a while. JA3, however, offers a much larger, and more current, crowd-sourced data set.
\n\n
TL;DR
\n\n
TLS fingerprinting is a methodology for uniquely identifying a client (user-agent) by virtue of examining a TLS Client Hello message for patterns that are particular to that user-agent. In any given TLS Client Hello message, the client will send a TLS version, a number of supported ciphers, a number of extensions, a number of elliptic curves, and curve point formats, and in a specific order. This pattern (how many elements and what order) has been found to be somewhat unique among the various user-agents (i.e. different browsers running in different OS's, non-browser Internet apps, malware command-and-control agents, etc.). It is possible then, to use this information in a few ways. Here are a few example use cases where I've personally witnessed its use over the last few years:
\n\n
To attempt positive identification of user agents to control some process flow. For example, in a SSL visibility use case where SSL Orchestrator is deployed to decrypt and inspect outbound traffic, but devices may exist in the environment that aren't controlled by the organization and thus don't have the local issuing CA certificate installed. A version of TLS fingerprinting can be deployed to whitelist the known assets for decryption and inspection. It's important to understand here that TLS fingerprinting isn't (can't be) a 100%-reliable fingerprinting option. Clients can change the type and order of ciphers and extensions they use to mask their signature. But for identification of corporate-owned assets, this method can still be quite effective. It's also been highly useful at identifying malware command-and-control (C2) agents, which is exactly how some of the aforementioned JA3 integrated tools use it.
\n\n
To uniquely identify a specific client (not the user-agent). As mentioned above, positive identification of a user-agent isn't 100% reliable. But, sometimes just having a hash of these unique signature properties is enough to isolate one potentially malicious client from another. And this, as it turns out, is how some of the other JA3 integrated tools use TLS fingerprint signatures. It doesn't matter if the client changes TLS properties to mask user-agent fingerprinting, as the hash of its current TLS signature is enough to identify and track it through its journey of destruction.
\n\n
This article provides a few updates
\n\n
First and foremost, it fixes the iRule validation issues in the original article for BIG-IP 14 and higher.
\n\n
Our security heroes over in F5 SIRT created a JA3 version of this iRule that returns the JA3 version of the fingerprint hash. I've updated that JA3 code some more to include an option to search the crowd-sourced user-agent data set.
\n\n
So without further ado...
\n\n
TLS Fingerprinting Update
\n\n
I'll ask that you refer to the original article for information on collecting the user-agent data and converting to a data group, as that hasn't changed. Otherwise, the relevant iRule code change is here in the updated Library-Rule:
\n\n
\n## Library-Rule\n\n## TLS Fingerprint Procedure #################\n## \n## Author: Kevin Stewart, Original (12/2016), Update(09/2020)\n## Derived from Lee Brotherston's \"tls-fingerprinting\" project @ https://github.com/LeeBrotherston/tls-fingerprinting\n## Purpose: to identify the user agent based on unique characteristics of the TLS ClientHello message\n## Input: \n## Full TCP payload collected in CLIENT_DATA event of a TLS handshake ClientHello message\n## Record length (rlen)\n## TLS outer version (outer)\n## TLS inner version (inner)\n## Client IP\n## Server IP\n##\n## ## Update v2 to remove TCL errors from original code, and add code to return FP string, fp hash, or user-agent lookup\n##############################################\nproc fingerprintTLS { payload rlen outer inner clientip serverip } {\n \n ## user-defined: enable logging\n set debug 0\n \n ## user-defined: enable \n ## - fingerprint string return (\"fp\")\n ## - fingerprint md5 hash return (\"fphash\")\n ## - user-agent lookup result (\"ua\")\n set proc_return \"fp\"\n\n\n ## The first 43 bytes of a ClientHello message are the record type, TLS versions, some length values and the\n ## handshake type. We should already know this stuff from the calling iRule. We're also going to be walking the\n ## packet, so the field_offset variable will be used to track where we are.\n set field_offset 43\n\n ## The first value in the payload after the offset is the session ID, which may be empty. Grab the session ID length\n ## value and move the field_offset variable that many bytes forward to skip it.\n binary scan ${payload} @${field_offset}c sessID_len\n set field_offset [expr {${field_offset} + 1 + ${sessID_len}}]\n\n ## The next value in the payload is the ciphersuite list length (how big the ciphersuite list is. We need the binary\n ## and hex values of this data.\n binary scan ${payload} @${field_offset}S cipherList_len\n binary scan ${payload} @${field_offset}H4 cipherList_len_hex\n set cipherList_len_hex_text ${cipherList_len_hex}\n\n ## Now that we have the ciphersuite list length, let's offset the field_offset variable to skip over the length (2) bytes\n ## and go get the ciphersuite list. Multiple by 2 to get the number of appropriate hex characters.\n set field_offset [expr {${field_offset} + 2}]\n set cipherList_len_hex [expr {${cipherList_len} * 2}]\n binary scan ${payload} @${field_offset}H${cipherList_len_hex} cipherlist\n\n ## Next is the compression method length and compression method. First move field_offset to skip past the ciphersuite\n ## list, then grab the compression method length. Then move field_offset past the length (2) bytes and grab the \n ## compression method value. Finally, move field_offset past the compression method bytes.\n set field_offset [expr {${field_offset} + ${cipherList_len}}]\n binary scan ${payload} @${field_offset}c compression_len\n #set field_offset [expr {${field_offset} + ${compression_len}}]\n set field_offset [expr {${field_offset} + 1}]\n binary scan ${payload} @${field_offset}H[expr {${compression_len} * 2}] compression_type\n set field_offset [expr {${field_offset} + ${compression_len}}]\n\n ## We should be in the extensions section now, so we're going to just run through the remaining data and\n ## pick out the extensions as we go. But first let's make sure there's more record data left, based on \n ## the current field_offset vs. rlen.\n if { [expr {${field_offset} < ${rlen}}] } {\n ## There's extension data, so let's go get it. Skip the first 2 bytes that are the extensions length\n set field_offset [expr {${field_offset} + 2}]\n\n ## Make a variable to store the extension types we find\n set extensions_list \"\"\n\n ## Pad rlen by 1 byte\n set rlen [expr {${rlen} + 1}]\n\n while { [expr {${field_offset} <= ${rlen}}] } {\n ## Grab the first 2 bytes to determine the extension type\n binary scan ${payload} @${field_offset}H4 ext\n\n ## Store the extension in the extensions_list variable\n append extensions_list ${ext}\n\n ## Increment field_offset past the 2 bytes of the extension type\n set field_offset [expr {${field_offset} + 2}]\n\n ## Grab the 2 bytes of extension lenth\n binary scan ${payload} @${field_offset}S ext_len\n\n ## Increment field_offset past the 2 bytes of the extension length\n set field_offset [expr {${field_offset} + 2}]\n\n ## Look for specific extension types in case these need to increment the field_offset (and because we need their values)\n switch $ext {\n \"000b\" {\n ## ec_point_format - there's another 1 byte after length\n ## Grab the extension data\n binary scan ${payload} @[expr {${field_offset} + 1}]H[expr {(${ext_len} - 1) * 2}] ext_data\n set ec_point_format ${ext_data}\n }\n \"000a\" {\n ## elliptic_curves - there's another 2 bytes after length\n ## Grab the extension data\n binary scan ${payload} @[expr {${field_offset} + 2}]H[expr {(${ext_len} - 2) * 2}] ext_data\n set elliptic_curves ${ext_data}\n }\n \"000d\" {\n ## sig_alg - there's another 2 bytes after length\n ## Grab the extension data\n binary scan ${payload} @[expr {${field_offset} + 2}]H[expr {(${ext_len} - 2) * 2}] ext_data\n set sig_alg ${ext_data}\n }\n default {\n ## Grab the otherwise unknown extension data\n binary scan ${payload} @${field_offset}H[expr {${ext_len} * 2}] ext_data\n }\n }\n\n ## Increment the field_offset past the extension data length. Repeat this loop until we reach rlen (the end of the payload)\n set field_offset [expr {${field_offset} + ${ext_len}}]\n }\n }\n\n ## Now let's compile all of that data.\n set cipl [string toupper ${cipherList_len_hex_text}]\n set ciph [string toupper ${cipherlist}]\n set coml ${compression_len}\n set comp [string toupper ${compression_type}]\n if { ( [info exists extensions_list] ) and ( ${extensions_list} ne \"\" ) } { set exte [string toupper ${extensions_list}] } else { set exte \"@@@@\" }\n if { ( [info exists elliptic_curves] ) and ( ${elliptic_curves} ne \"\" ) } { set ecur [string toupper ${elliptic_curves}] } else { set ecur \"@@@@\" }\n if { ( [info exists sig_alg] ) and ( ${sig_alg} ne \"\" ) } { set siga [string toupper ${sig_alg}] } else { set siga \"@@@@\" }\n if { ( [info exists ec_point_format] ) and ( ${ec_point_format} ne \"\" ) } { set ecfp [string toupper ${ec_point_format}] } else { set ecfp \"@@@@\" }\n\n ## Now let's build the fingerprint string and search the database\n set fingerprint_str \"${outer}+${inner}+${cipl}+${ciph}+${coml}+${comp}+${exte}+${ecur}+${siga}+${ecfp}\"\n if { ${debug} } { log local0. \"${clientip}-${serverip}: fingerprint_str = ${fingerprint_str}\" }\n\n switch ${proc_return} {\n \"fp\" {\n return ${fingerprint_str}\n }\n \"fphash\" {\n binary scan [md5 ${fingerprint_str}] H* fp_digest\n return ${fp_digest}\n }\n \"ua\" {\n ## Initialize the match variable\n set match \"\"\n \n if { [class match ${fingerprint_str} equals fingerprint_db] } {\n ## Direct match\n set match [class match -value ${fingerprint_str} equals fingerprint_db]\n } elseif { not ( ${ciph} starts_with \"C0\" ) and not ( ${ciph} starts_with \"00\" ) } {\n ## Hmm.. there's no direct match, which could either mean a database entry doesn't exist, or Chrome (and Opera) are adding\n ## special values to the cipherlist, extensions list and elliptic curves list.\n ## ex. 9A9A, 5A5A, EAEA, BABA, etc. at the beginning of the cipherlist \n ## Let's strip out these anomalous values and try the match again.\n \n ## Substract 2 bytes from cipherlist length (v2 fix)\n set cipl_tmp \"set cipl \\[format %04x \\[expr \\{ \\[expr 0x$\\{cipl\\}\\] - 2 \\}\\]\\]\"\n eval ${cipl_tmp}\n \n ## Subtract 2 bytes from the front of the cipher list\n set ciph [string range ${ciph} 4 end]\n \n ## Subtract 2 bytes from the front of the extensions list\n set exte [string range ${exte} 4 end]\n ## There might be an additional random set in the string that needs to be removed (pattern is \"(.)A\\1A\")\n ## (v2 fix)\n regsub {(.)A\\\\1A} ${exte} \"\" exte\n \n ## Subtract 2 bytes from the front of the elliptic curves list\n set ecur [string range ${ecur} 4 end]\n \n ## Rebuild the fingerprint string\n set fingerprint_str \"${outer}+${inner}+${cipl}+${ciph}+${coml}+${comp}+${exte}+${ecur}+${siga}+${ecfp}\"\n \n if { [class match ${fingerprint_str} equals fingerprint_db] } {\n ## Guess match\n set match [class match -value ${fingerprint_str} equals fingerprint_db]\n } else {\n ## No match\n set match \"\"\n }\n }\n \n ## Return the matching user agent string\n return ${match}\n }\n }\n}\n
\n\n
\n\n
Of particular interest, I've added some control flags at the top of the iRule to control debug logging and what to output:
where \"fp\" returns the full TLS fingerprint string, \"fphash\" returns a short md5 hash of that string (see the second use case above), and \"ua\" attempts to match the fingerprint against the data group (removing the GREASE extensions first). The caller iRule is the same as the original.
\n\n
JA3 TLS Fingerprint Update
\n\n
For this exercise, we'll extend upon Aaron Brailsford's JA3 version (F5 SIRT) and add a user-agent lookup. As with the above, I've given you the option to select what to output, so if you only want the hash, then you won't need the data set. But if you do want to lookup the user-agent, you'll first need to acquire the data.
\n\n
In an empty folder on your local machine (or BIG-IP), download the crowd-sourced JA3 data to a file:
Then create and run this simple Python script to convert this file to external data group format. It's a lot of data, so you'll want to use an external data group for this.
\n\n
\nimport json\n\nwith open('ja3.db') as data_file:\n data = json.load(data_file)\n\nfile = open(\"ja3.dg\", \"a\")\n\nfor x in data:\n if x['User-Agent']: \n #print(\"%s := %s,\" % (x['md5'], x['User-Agent']))\n file.write(\"\\\"%s\\\" := \\\"%s\\\",\\n\" % (x['md5'].encode('utf-8'), x['User-Agent'].encode('utf-8').strip('\\\"')))\n\nfile.close() \n
\n\n
Simply run it like this:
\n\n
\npython ja3-converter.py\n
\n\n
\n\n
The script looks for ja3.db and creates a new ja3.dg file. Now go ahead and import this to the BIG-IP. Navigate to System -> File Management -> Data Group File List. then click Import.
\n\n
File Name: browse to and select the ja3.dg file
Name: enter \"ja3_dg\"
File Contents: select \"String\"
Key / Value Pair Separator: select :=
Data Group Name: enter \"ja3_dg\"
\n\n
\n\n
Now create the \"Library-Rule\" iRule:
\n\n
\n## Library-Rule\n\n## JA3 TLS Fingerprint Procedure #################\n##\n## Author: Kevin Stewart, 09/2020\n## Derived from Aaron Brailsford's JA3 version\n## Derived from Lee Brotherston's \"tls-fingerprinting\" project @ https://github.com/LeeBrotherston/tls-fingerprinting\n## Based on the TLS Fingerprinting iRule by Kevin Stewart @ https://devcentral.f5.com/s/articles/tls-fingerprinting-a-method-for-identifying-a-tls-client-without-decrypting-24598\n## Purpose: to identify the user agent based on unique characteristics of the TLS ClientHello message\n## Input:\n## Full TCP payload collected in CLIENT_DATA event of a TLS handshake ClientHello message\n## Record length (rlen)\n## TLS inner version (sslversion)\n##\n## Update v2 to remove GREASE information and provide method to search a local ja3 user-agent database\n##############################################\nproc fingerprintTLS { payload rlen sslversion } {\n\n ## user-defined: debug logging\n set debug 0\n \n ## user-defined: enable\n ## - ja3 fingerprint hash (\"fphash\")\n ## - ja3 user-agent lookup result (\"ua\")\n set proc_return \"fphash\" \n\n\n ## decimal-converted GREASE values - these will be removed from the output\n set GREASE_LIST [list 2570 6682 10794 14906 19018 23130 27242 31354 35466 39578 43690 47802 51914 56026 60138 64250]\n\n ## The first 43 bytes of a ClientHello message are the record type, TLS versions, some length values and the\n ## handshake type. We should already know this stuff from the calling iRule. We're also going to be walking the\n ## packet, so the field_offset variable will be used to track where we are.\n set field_offset 43\n\n ## The first value in the payload after the offset is the session ID, which may be empty. Grab the session ID length\n ## value and move the field_offset variable that many bytes forward to skip it.\n binary scan ${payload} @${field_offset}c sessID_len\n set field_offset [expr {${field_offset} + 1 + ${sessID_len}}]\n\n ## The next value in the payload is the ciphersuite list length (how big the ciphersuite list is.\n binary scan ${payload} @${field_offset}S cipherList_len\n\n ## Now that we have the ciphersuite list length, let's offset the field_offset variable to skip over the length (2) bytes\n ## and go get the ciphersuite list.\n set field_offset [expr {${field_offset} + 2}]\n binary scan ${payload} @${field_offset}S[expr {${cipherList_len} / 2}] cipherlist_decimal\n\n ## Next is the compression method length and compression method. First move field_offset to skip past the ciphersuite\n ## list, then grab the compression method length. Then move field_offset past the length (2)\n ## Finally, move field_offset past the compression method bytes.\n set field_offset [expr {${field_offset} + ${cipherList_len}}]\n binary scan ${payload} @${field_offset}c compression_len\n set field_offset [expr {${field_offset} + 1}]\n set field_offset [expr {${field_offset} + ${compression_len}}]\n\n ## We should be in the extensions section now, so we're going to just run through the remaining data and\n ## pick out the extensions as we go. But first let's make sure there's more record data left, based on\n ## the current field_offset vs. rlen.\n if { [expr {${field_offset} < ${rlen}}] } {\n ## There's extension data, so let's go get it. Skip the first 2 bytes that are the extensions length\n set field_offset [expr {${field_offset} + 2}]\n\n ## Make a variable to store the extension types we find\n set extensions_list \"\"\n\n ## Pad rlen by 1 byte\n set rlen [expr {${rlen} + 1}]\n\n while { [expr {${field_offset} <= ${rlen}}] } {\n ## Grab the first 2 bytes to determine the extension type\n binary scan ${payload} @${field_offset}S ext\n set ext [expr {$ext & 0xFFFF}]\n\n ## Store the extension in the extensions_list variable\n lappend extensions_list ${ext}\n\n ## Increment field_offset past the 2 bytes of the extension type\n set field_offset [expr {${field_offset} + 2}]\n\n ## Grab the 2 bytes of extension lenth\n binary scan ${payload} @${field_offset}S ext_len\n\n ## Increment field_offset past the 2 bytes of the extension length\n set field_offset [expr {${field_offset} + 2}]\n\n ## Look for specific extension types in case these need to increment the field_offset (and because we need their values)\n switch $ext {\n \"11\" {\n ## ec_point_format - there's another 1 byte after length\n ## Grab the extension data\n binary scan ${payload} @[expr {${field_offset} + 1}]s ext_data\n set ec_point_format ${ext_data}\n }\n \"10\" {\n ## elliptic_curves - there's another 2 bytes after length\n ## Grab the extension data\n binary scan ${payload} @[expr {${field_offset} + 2}]S[expr {(${ext_len} - 2) / 2}] ext_data\n set elliptic_curves ${ext_data}\n }\n default {\n ## Grab the otherwise unknown extension data\n binary scan ${payload} @${field_offset}H[expr {${ext_len} * 2}] ext_data\n }\n }\n\n ## Increment the field_offset past the extension data length. Repeat this loop until we reach rlen (the end of the payload)\n set field_offset [expr {${field_offset} + ${ext_len}}]\n }\n}\n\n ## Now let's compile all of that data.\n ## The cipherlist values need masking with 0xFFFF to return the unsigned integers we need\n ## v2 update: strip out GREASE values\n foreach cipher $cipherlist_decimal {\n if { [lsearch -exact ${GREASE_LIST} [expr {$cipher & 0xFFFF}]] == -1 } {\n lappend cipd [expr {$cipher & 0xFFFF}]\n }\n }\n\n set cipd_str [join $cipd \"-\"]\n\n ## v2 update: strip out GREASE values\n if { ( [info exists extensions_list] ) and ( ${extensions_list} ne \"\" ) } {\n set exte_tmp [list]\n foreach x ${extensions_list} {\n if { [lsearch -exact ${GREASE_LIST} ${x}] == -1 } {\n lappend exte_tmp ${x}\n }\n }\n set exte [join ${exte_tmp} \"-\"] \n } else { \n set exte \"\" \n }\n\n ## v2 update: strip out GREASE values\n if { ( [info exists elliptic_curves] ) and ( ${elliptic_curves} ne \"\" ) } { \n set ecur_tmp [list]\n foreach x ${elliptic_curves} {\n if { [lsearch -exact ${GREASE_LIST} ${x}] == -1 } {\n lappend ecur_tmp ${x}\n }\n }\n set ecur [join ${ecur_tmp} \"-\"] \n } else { \n set ecur \"\" \n }\n\n if { ( [info exists ec_point_format] ) and ( ${ec_point_format} ne \"\" ) } { set ecfp [join ${ec_point_format} \"-\"] } else { set ecfp \"\" }\n\n set ja3_str \"${sslversion},${cipd_str},${exte},${ecur},${ecfp}\"\n binary scan [md5 ${ja3_str}] H* ja3_digest\n if { ${debug} } { \n log local0. \"ja3 string = ${ja3_str}\"\n log local0. \"ja3 digest = ${ja3_digest}\"\n }\n\n switch ${proc_return} {\n \"fphash\" {\n return ${ja3_digest} \n }\n \"ua\" {\n if { [set ua_match [class match -value ${ja3_digest} eq ja3_dg]] ne \"\" } {\n return ${ua_match}\n } else {\n return \"No Match\"\n } \n }\n }\n}\n
\n\n
\n\n
Again, I've given you the option to enable/disable debug logging, and select what data to return:
\n\n
\n ## user-defined: debug logging\n set debug 0\n \n ## user-defined: enable\n ## - ja3 fingerprint hash (\"fphash\")\n ## - ja3 user-agent lookup result (\"ua\")\n set proc_return \"fphash\" \n
\n\n
\n\n
where \"fphash\" is the JA3 fingerprint hash, and \"ua\" attempts to find the matching user-agent in the data group. The caller iRule is pretty similar to the original:
\n\n
\nwhen CLIENT_ACCEPTED {\n ## Collect the TCP payload\n TCP::collect\n}\nwhen CLIENT_DATA {\n ## Get the TLS packet type and versions\n if { ! [info exists rlen] } {\n ## We actually only need the recort type (rtype), record length (rlen) handshake type (hs_type) and 'inner' SSL version (inner_sslver) here\n ## But it's easiest to parse them all out of the payload along with the bytes we don't need (outer_sslver & rilen)\n binary scan [TCP::payload] cSScH6S rtype outer_sslver rlen hs_type rilen inner_sslver\n\n if { ( ${rtype} == 22 ) and ( ${hs_type} == 1 ) } {\n ## This is a TLS ClientHello message (22 = TLS handshake, 1 = ClientHello)\n\n ## Call the fingerprintTLS proc\n set ja3_fingerprint [call Library-Rule::fingerprintTLS [TCP::payload] ${rlen} ${inner_sslver}]\n #binary scan [md5 ${ja3_fingerprint}] H* ja3_digest\n\n ### Do Something here ###\n log local0. \"[IP::client_addr]:[TCP::client_port] ja3 ${ja3_fingerprint}\"\n ### Do Something here ###\n\n }\n }\n\n # Collect the rest of the record if necessary\n if { [TCP::payload length] < $rlen } {\n TCP::collect $rlen\n }\n\n ## Release the paylaod\n TCP::release\n}\n
\n\n
\n\n
And there you have it. I think you'll find that if you experiment with both of these versions, you'll have more success matching a user-agent to the JA3 data set. Otherwise, using the fingerprint hash (from either version) provides a compelling method to uniquely identify and track clients as a function of threat intelligence.
New BIG-IP versions. A few have reported in the previous article that BIG-IP versions 14 and 15 caused validation errors in the iRule code. These are addressed here.
\n\n
Salesforce.com extended Lee Brotherston's original work into a project called JA3, and open-sourced it. JA3 included some excellent updates, including support for TLS1.3 and GREASE protocol extensions, plus integrations with tools like Zeek/Bro and a bunch of other open source and commercial products. But most important, Lee's version included a data set of fingerprint-to-user-agent matches that hasn't really been maintained for a while. JA3, however, offers a much larger, and more current, crowd-sourced data set.
\n\n
TL;DR
\n\n
TLS fingerprinting is a methodology for uniquely identifying a client (user-agent) by virtue of examining a TLS Client Hello message for patterns that are particular to that user-agent. In any given TLS Client Hello message, the client will send a TLS version, a number of supported ciphers, a number of extensions, a number of elliptic curves, and curve point formats, and in a specific order. This pattern (how many elements and what order) has been found to be somewhat unique among the various user-agents (i.e. different browsers running in different OS's, non-browser Internet apps, malware command-and-control agents, etc.). It is possible then, to use this information in a few ways. Here are a few example use cases where I've personally witnessed its use over the last few years:
\n\n
To attempt positive identification of user agents to control some process flow. For example, in a SSL visibility use case where SSL Orchestrator is deployed to decrypt and inspect outbound traffic, but devices may exist in the environment that aren't controlled by the organization and thus don't have the local issuing CA certificate installed. A version of TLS fingerprinting can be deployed to whitelist the known assets for decryption and inspection. It's important to understand here that TLS fingerprinting isn't (can't be) a 100%-reliable fingerprinting option. Clients can change the type and order of ciphers and extensions they use to mask their signature. But for identification of corporate-owned assets, this method can still be quite effective. It's also been highly useful at identifying malware command-and-control (C2) agents, which is exactly how some of the aforementioned JA3 integrated tools use it.
\n\n
To uniquely identify a specific client (not the user-agent). As mentioned above, positive identification of a user-agent isn't 100% reliable. But, sometimes just having a hash of these unique signature properties is enough to isolate one potentially malicious client from another. And this, as it turns out, is how some of the other JA3 integrated tools use TLS fingerprint signatures. It doesn't matter if the client changes TLS properties to mask user-agent fingerprinting, as the hash of its current TLS signature is enough to identify and track it through its journey of destruction.
\n\n
This article provides a few updates
\n\n
First and foremost, it fixes the iRule validation issues in the original article for BIG-IP 14 and higher.
\n\n
Our security heroes over in F5 SIRT created a JA3 version of this iRule that returns the JA3 version of the fingerprint hash. I've updated that JA3 code some more to include an option to search the crowd-sourced user-agent data set.
\n\n
So without further ado...
\n\n
TLS Fingerprinting Update
\n\n
I'll ask that you refer to the original article for information on collecting the user-agent data and converting to a data group, as that hasn't changed. Otherwise, the relevant iRule code change is here in the updated Library-Rule:
\n\n
\n## Library-Rule\n\n## TLS Fingerprint Procedure #################\n## \n## Author: Kevin Stewart, Original (12/2016), Update(09/2020)\n## Derived from Lee Brotherston's \"tls-fingerprinting\" project @ https://github.com/LeeBrotherston/tls-fingerprinting\n## Purpose: to identify the user agent based on unique characteristics of the TLS ClientHello message\n## Input: \n## Full TCP payload collected in CLIENT_DATA event of a TLS handshake ClientHello message\n## Record length (rlen)\n## TLS outer version (outer)\n## TLS inner version (inner)\n## Client IP\n## Server IP\n##\n## ## Update v2 to remove TCL errors from original code, and add code to return FP string, fp hash, or user-agent lookup\n##############################################\nproc fingerprintTLS { payload rlen outer inner clientip serverip } {\n \n ## user-defined: enable logging\n set debug 0\n \n ## user-defined: enable \n ## - fingerprint string return (\"fp\")\n ## - fingerprint md5 hash return (\"fphash\")\n ## - user-agent lookup result (\"ua\")\n set proc_return \"fp\"\n\n\n ## The first 43 bytes of a ClientHello message are the record type, TLS versions, some length values and the\n ## handshake type. We should already know this stuff from the calling iRule. We're also going to be walking the\n ## packet, so the field_offset variable will be used to track where we are.\n set field_offset 43\n\n ## The first value in the payload after the offset is the session ID, which may be empty. Grab the session ID length\n ## value and move the field_offset variable that many bytes forward to skip it.\n binary scan ${payload} @${field_offset}c sessID_len\n set field_offset [expr {${field_offset} + 1 + ${sessID_len}}]\n\n ## The next value in the payload is the ciphersuite list length (how big the ciphersuite list is. We need the binary\n ## and hex values of this data.\n binary scan ${payload} @${field_offset}S cipherList_len\n binary scan ${payload} @${field_offset}H4 cipherList_len_hex\n set cipherList_len_hex_text ${cipherList_len_hex}\n\n ## Now that we have the ciphersuite list length, let's offset the field_offset variable to skip over the length (2) bytes\n ## and go get the ciphersuite list. Multiple by 2 to get the number of appropriate hex characters.\n set field_offset [expr {${field_offset} + 2}]\n set cipherList_len_hex [expr {${cipherList_len} * 2}]\n binary scan ${payload} @${field_offset}H${cipherList_len_hex} cipherlist\n\n ## Next is the compression method length and compression method. First move field_offset to skip past the ciphersuite\n ## list, then grab the compression method length. Then move field_offset past the length (2) bytes and grab the \n ## compression method value. Finally, move field_offset past the compression method bytes.\n set field_offset [expr {${field_offset} + ${cipherList_len}}]\n binary scan ${payload} @${field_offset}c compression_len\n #set field_offset [expr {${field_offset} + ${compression_len}}]\n set field_offset [expr {${field_offset} + 1}]\n binary scan ${payload} @${field_offset}H[expr {${compression_len} * 2}] compression_type\n set field_offset [expr {${field_offset} + ${compression_len}}]\n\n ## We should be in the extensions section now, so we're going to just run through the remaining data and\n ## pick out the extensions as we go. But first let's make sure there's more record data left, based on \n ## the current field_offset vs. rlen.\n if { [expr {${field_offset} < ${rlen}}] } {\n ## There's extension data, so let's go get it. Skip the first 2 bytes that are the extensions length\n set field_offset [expr {${field_offset} + 2}]\n\n ## Make a variable to store the extension types we find\n set extensions_list \"\"\n\n ## Pad rlen by 1 byte\n set rlen [expr {${rlen} + 1}]\n\n while { [expr {${field_offset} <= ${rlen}}] } {\n ## Grab the first 2 bytes to determine the extension type\n binary scan ${payload} @${field_offset}H4 ext\n\n ## Store the extension in the extensions_list variable\n append extensions_list ${ext}\n\n ## Increment field_offset past the 2 bytes of the extension type\n set field_offset [expr {${field_offset} + 2}]\n\n ## Grab the 2 bytes of extension lenth\n binary scan ${payload} @${field_offset}S ext_len\n\n ## Increment field_offset past the 2 bytes of the extension length\n set field_offset [expr {${field_offset} + 2}]\n\n ## Look for specific extension types in case these need to increment the field_offset (and because we need their values)\n switch $ext {\n \"000b\" {\n ## ec_point_format - there's another 1 byte after length\n ## Grab the extension data\n binary scan ${payload} @[expr {${field_offset} + 1}]H[expr {(${ext_len} - 1) * 2}] ext_data\n set ec_point_format ${ext_data}\n }\n \"000a\" {\n ## elliptic_curves - there's another 2 bytes after length\n ## Grab the extension data\n binary scan ${payload} @[expr {${field_offset} + 2}]H[expr {(${ext_len} - 2) * 2}] ext_data\n set elliptic_curves ${ext_data}\n }\n \"000d\" {\n ## sig_alg - there's another 2 bytes after length\n ## Grab the extension data\n binary scan ${payload} @[expr {${field_offset} + 2}]H[expr {(${ext_len} - 2) * 2}] ext_data\n set sig_alg ${ext_data}\n }\n default {\n ## Grab the otherwise unknown extension data\n binary scan ${payload} @${field_offset}H[expr {${ext_len} * 2}] ext_data\n }\n }\n\n ## Increment the field_offset past the extension data length. Repeat this loop until we reach rlen (the end of the payload)\n set field_offset [expr {${field_offset} + ${ext_len}}]\n }\n }\n\n ## Now let's compile all of that data.\n set cipl [string toupper ${cipherList_len_hex_text}]\n set ciph [string toupper ${cipherlist}]\n set coml ${compression_len}\n set comp [string toupper ${compression_type}]\n if { ( [info exists extensions_list] ) and ( ${extensions_list} ne \"\" ) } { set exte [string toupper ${extensions_list}] } else { set exte \"@@@@\" }\n if { ( [info exists elliptic_curves] ) and ( ${elliptic_curves} ne \"\" ) } { set ecur [string toupper ${elliptic_curves}] } else { set ecur \"@@@@\" }\n if { ( [info exists sig_alg] ) and ( ${sig_alg} ne \"\" ) } { set siga [string toupper ${sig_alg}] } else { set siga \"@@@@\" }\n if { ( [info exists ec_point_format] ) and ( ${ec_point_format} ne \"\" ) } { set ecfp [string toupper ${ec_point_format}] } else { set ecfp \"@@@@\" }\n\n ## Now let's build the fingerprint string and search the database\n set fingerprint_str \"${outer}+${inner}+${cipl}+${ciph}+${coml}+${comp}+${exte}+${ecur}+${siga}+${ecfp}\"\n if { ${debug} } { log local0. \"${clientip}-${serverip}: fingerprint_str = ${fingerprint_str}\" }\n\n switch ${proc_return} {\n \"fp\" {\n return ${fingerprint_str}\n }\n \"fphash\" {\n binary scan [md5 ${fingerprint_str}] H* fp_digest\n return ${fp_digest}\n }\n \"ua\" {\n ## Initialize the match variable\n set match \"\"\n \n if { [class match ${fingerprint_str} equals fingerprint_db] } {\n ## Direct match\n set match [class match -value ${fingerprint_str} equals fingerprint_db]\n } elseif { not ( ${ciph} starts_with \"C0\" ) and not ( ${ciph} starts_with \"00\" ) } {\n ## Hmm.. there's no direct match, which could either mean a database entry doesn't exist, or Chrome (and Opera) are adding\n ## special values to the cipherlist, extensions list and elliptic curves list.\n ## ex. 9A9A, 5A5A, EAEA, BABA, etc. at the beginning of the cipherlist \n ## Let's strip out these anomalous values and try the match again.\n \n ## Substract 2 bytes from cipherlist length (v2 fix)\n set cipl_tmp \"set cipl \\[format %04x \\[expr \\{ \\[expr 0x$\\{cipl\\}\\] - 2 \\}\\]\\]\"\n eval ${cipl_tmp}\n \n ## Subtract 2 bytes from the front of the cipher list\n set ciph [string range ${ciph} 4 end]\n \n ## Subtract 2 bytes from the front of the extensions list\n set exte [string range ${exte} 4 end]\n ## There might be an additional random set in the string that needs to be removed (pattern is \"(.)A\\1A\")\n ## (v2 fix)\n regsub {(.)A\\\\1A} ${exte} \"\" exte\n \n ## Subtract 2 bytes from the front of the elliptic curves list\n set ecur [string range ${ecur} 4 end]\n \n ## Rebuild the fingerprint string\n set fingerprint_str \"${outer}+${inner}+${cipl}+${ciph}+${coml}+${comp}+${exte}+${ecur}+${siga}+${ecfp}\"\n \n if { [class match ${fingerprint_str} equals fingerprint_db] } {\n ## Guess match\n set match [class match -value ${fingerprint_str} equals fingerprint_db]\n } else {\n ## No match\n set match \"\"\n }\n }\n \n ## Return the matching user agent string\n return ${match}\n }\n }\n}\n
\n\n
\n\n
Of particular interest, I've added some control flags at the top of the iRule to control debug logging and what to output:
where \"fp\" returns the full TLS fingerprint string, \"fphash\" returns a short md5 hash of that string (see the second use case above), and \"ua\" attempts to match the fingerprint against the data group (removing the GREASE extensions first). The caller iRule is the same as the original.
\n\n
JA3 TLS Fingerprint Update
\n\n
For this exercise, we'll extend upon Aaron Brailsford's JA3 version (F5 SIRT) and add a user-agent lookup. As with the above, I've given you the option to select what to output, so if you only want the hash, then you won't need the data set. But if you do want to lookup the user-agent, you'll first need to acquire the data.
\n\n
In an empty folder on your local machine (or BIG-IP), download the crowd-sourced JA3 data to a file:
Then create and run this simple Python script to convert this file to external data group format. It's a lot of data, so you'll want to use an external data group for this.
\n\n
\nimport json\n\nwith open('ja3.db') as data_file:\n data = json.load(data_file)\n\nfile = open(\"ja3.dg\", \"a\")\n\nfor x in data:\n if x['User-Agent']: \n #print(\"%s := %s,\" % (x['md5'], x['User-Agent']))\n file.write(\"\\\"%s\\\" := \\\"%s\\\",\\n\" % (x['md5'].encode('utf-8'), x['User-Agent'].encode('utf-8').strip('\\\"')))\n\nfile.close() \n
\n\n
Simply run it like this:
\n\n
\npython ja3-converter.py\n
\n\n
\n\n
The script looks for ja3.db and creates a new ja3.dg file. Now go ahead and import this to the BIG-IP. Navigate to System -> File Management -> Data Group File List. then click Import.
\n\n
File Name: browse to and select the ja3.dg file
Name: enter \"ja3_dg\"
File Contents: select \"String\"
Key / Value Pair Separator: select :=
Data Group Name: enter \"ja3_dg\"
\n\n
\n\n
Now create the \"Library-Rule\" iRule:
\n\n
\n## Library-Rule\n\n## JA3 TLS Fingerprint Procedure #################\n##\n## Author: Kevin Stewart, 09/2020\n## Derived from Aaron Brailsford's JA3 version\n## Derived from Lee Brotherston's \"tls-fingerprinting\" project @ https://github.com/LeeBrotherston/tls-fingerprinting\n## Based on the TLS Fingerprinting iRule by Kevin Stewart @ https://devcentral.f5.com/s/articles/tls-fingerprinting-a-method-for-identifying-a-tls-client-without-decrypting-24598\n## Purpose: to identify the user agent based on unique characteristics of the TLS ClientHello message\n## Input:\n## Full TCP payload collected in CLIENT_DATA event of a TLS handshake ClientHello message\n## Record length (rlen)\n## TLS inner version (sslversion)\n##\n## Update v2 to remove GREASE information and provide method to search a local ja3 user-agent database\n##############################################\nproc fingerprintTLS { payload rlen sslversion } {\n\n ## user-defined: debug logging\n set debug 0\n \n ## user-defined: enable\n ## - ja3 fingerprint hash (\"fphash\")\n ## - ja3 user-agent lookup result (\"ua\")\n set proc_return \"fphash\" \n\n\n ## decimal-converted GREASE values - these will be removed from the output\n set GREASE_LIST [list 2570 6682 10794 14906 19018 23130 27242 31354 35466 39578 43690 47802 51914 56026 60138 64250]\n\n ## The first 43 bytes of a ClientHello message are the record type, TLS versions, some length values and the\n ## handshake type. We should already know this stuff from the calling iRule. We're also going to be walking the\n ## packet, so the field_offset variable will be used to track where we are.\n set field_offset 43\n\n ## The first value in the payload after the offset is the session ID, which may be empty. Grab the session ID length\n ## value and move the field_offset variable that many bytes forward to skip it.\n binary scan ${payload} @${field_offset}c sessID_len\n set field_offset [expr {${field_offset} + 1 + ${sessID_len}}]\n\n ## The next value in the payload is the ciphersuite list length (how big the ciphersuite list is.\n binary scan ${payload} @${field_offset}S cipherList_len\n\n ## Now that we have the ciphersuite list length, let's offset the field_offset variable to skip over the length (2) bytes\n ## and go get the ciphersuite list.\n set field_offset [expr {${field_offset} + 2}]\n binary scan ${payload} @${field_offset}S[expr {${cipherList_len} / 2}] cipherlist_decimal\n\n ## Next is the compression method length and compression method. First move field_offset to skip past the ciphersuite\n ## list, then grab the compression method length. Then move field_offset past the length (2)\n ## Finally, move field_offset past the compression method bytes.\n set field_offset [expr {${field_offset} + ${cipherList_len}}]\n binary scan ${payload} @${field_offset}c compression_len\n set field_offset [expr {${field_offset} + 1}]\n set field_offset [expr {${field_offset} + ${compression_len}}]\n\n ## We should be in the extensions section now, so we're going to just run through the remaining data and\n ## pick out the extensions as we go. But first let's make sure there's more record data left, based on\n ## the current field_offset vs. rlen.\n if { [expr {${field_offset} < ${rlen}}] } {\n ## There's extension data, so let's go get it. Skip the first 2 bytes that are the extensions length\n set field_offset [expr {${field_offset} + 2}]\n\n ## Make a variable to store the extension types we find\n set extensions_list \"\"\n\n ## Pad rlen by 1 byte\n set rlen [expr {${rlen} + 1}]\n\n while { [expr {${field_offset} <= ${rlen}}] } {\n ## Grab the first 2 bytes to determine the extension type\n binary scan ${payload} @${field_offset}S ext\n set ext [expr {$ext & 0xFFFF}]\n\n ## Store the extension in the extensions_list variable\n lappend extensions_list ${ext}\n\n ## Increment field_offset past the 2 bytes of the extension type\n set field_offset [expr {${field_offset} + 2}]\n\n ## Grab the 2 bytes of extension lenth\n binary scan ${payload} @${field_offset}S ext_len\n\n ## Increment field_offset past the 2 bytes of the extension length\n set field_offset [expr {${field_offset} + 2}]\n\n ## Look for specific extension types in case these need to increment the field_offset (and because we need their values)\n switch $ext {\n \"11\" {\n ## ec_point_format - there's another 1 byte after length\n ## Grab the extension data\n binary scan ${payload} @[expr {${field_offset} + 1}]s ext_data\n set ec_point_format ${ext_data}\n }\n \"10\" {\n ## elliptic_curves - there's another 2 bytes after length\n ## Grab the extension data\n binary scan ${payload} @[expr {${field_offset} + 2}]S[expr {(${ext_len} - 2) / 2}] ext_data\n set elliptic_curves ${ext_data}\n }\n default {\n ## Grab the otherwise unknown extension data\n binary scan ${payload} @${field_offset}H[expr {${ext_len} * 2}] ext_data\n }\n }\n\n ## Increment the field_offset past the extension data length. Repeat this loop until we reach rlen (the end of the payload)\n set field_offset [expr {${field_offset} + ${ext_len}}]\n }\n}\n\n ## Now let's compile all of that data.\n ## The cipherlist values need masking with 0xFFFF to return the unsigned integers we need\n ## v2 update: strip out GREASE values\n foreach cipher $cipherlist_decimal {\n if { [lsearch -exact ${GREASE_LIST} [expr {$cipher & 0xFFFF}]] == -1 } {\n lappend cipd [expr {$cipher & 0xFFFF}]\n }\n }\n\n set cipd_str [join $cipd \"-\"]\n\n ## v2 update: strip out GREASE values\n if { ( [info exists extensions_list] ) and ( ${extensions_list} ne \"\" ) } {\n set exte_tmp [list]\n foreach x ${extensions_list} {\n if { [lsearch -exact ${GREASE_LIST} ${x}] == -1 } {\n lappend exte_tmp ${x}\n }\n }\n set exte [join ${exte_tmp} \"-\"] \n } else { \n set exte \"\" \n }\n\n ## v2 update: strip out GREASE values\n if { ( [info exists elliptic_curves] ) and ( ${elliptic_curves} ne \"\" ) } { \n set ecur_tmp [list]\n foreach x ${elliptic_curves} {\n if { [lsearch -exact ${GREASE_LIST} ${x}] == -1 } {\n lappend ecur_tmp ${x}\n }\n }\n set ecur [join ${ecur_tmp} \"-\"] \n } else { \n set ecur \"\" \n }\n\n if { ( [info exists ec_point_format] ) and ( ${ec_point_format} ne \"\" ) } { set ecfp [join ${ec_point_format} \"-\"] } else { set ecfp \"\" }\n\n set ja3_str \"${sslversion},${cipd_str},${exte},${ecur},${ecfp}\"\n binary scan [md5 ${ja3_str}] H* ja3_digest\n if { ${debug} } { \n log local0. \"ja3 string = ${ja3_str}\"\n log local0. \"ja3 digest = ${ja3_digest}\"\n }\n\n switch ${proc_return} {\n \"fphash\" {\n return ${ja3_digest} \n }\n \"ua\" {\n if { [set ua_match [class match -value ${ja3_digest} eq ja3_dg]] ne \"\" } {\n return ${ua_match}\n } else {\n return \"No Match\"\n } \n }\n }\n}\n
\n\n
\n\n
Again, I've given you the option to enable/disable debug logging, and select what data to return:
\n\n
\n ## user-defined: debug logging\n set debug 0\n \n ## user-defined: enable\n ## - ja3 fingerprint hash (\"fphash\")\n ## - ja3 user-agent lookup result (\"ua\")\n set proc_return \"fphash\" \n
\n\n
\n\n
where \"fphash\" is the JA3 fingerprint hash, and \"ua\" attempts to find the matching user-agent in the data group. The caller iRule is pretty similar to the original:
\n\n
\nwhen CLIENT_ACCEPTED {\n ## Collect the TCP payload\n TCP::collect\n}\nwhen CLIENT_DATA {\n ## Get the TLS packet type and versions\n if { ! [info exists rlen] } {\n ## We actually only need the recort type (rtype), record length (rlen) handshake type (hs_type) and 'inner' SSL version (inner_sslver) here\n ## But it's easiest to parse them all out of the payload along with the bytes we don't need (outer_sslver & rilen)\n binary scan [TCP::payload] cSScH6S rtype outer_sslver rlen hs_type rilen inner_sslver\n\n if { ( ${rtype} == 22 ) and ( ${hs_type} == 1 ) } {\n ## This is a TLS ClientHello message (22 = TLS handshake, 1 = ClientHello)\n\n ## Call the fingerprintTLS proc\n set ja3_fingerprint [call Library-Rule::fingerprintTLS [TCP::payload] ${rlen} ${inner_sslver}]\n #binary scan [md5 ${ja3_fingerprint}] H* ja3_digest\n\n ### Do Something here ###\n log local0. \"[IP::client_addr]:[TCP::client_port] ja3 ${ja3_fingerprint}\"\n ### Do Something here ###\n\n }\n }\n\n # Collect the rest of the record if necessary\n if { [TCP::payload length] < $rlen } {\n TCP::collect $rlen\n }\n\n ## Release the paylaod\n TCP::release\n}\n
\n\n
\n\n
And there you have it. I think you'll find that if you experiment with both of these versions, you'll have more success matching a user-agent to the JA3 data set. Otherwise, using the fingerprint hash (from either version) provides a compelling method to uniquely identify and track clients as a function of threat intelligence.
\n\n
Thanks
\n\n
-- Kevin
\n\n
","kudosSumWeight":0,"postTime":"2020-10-05T10:56:12.000-07:00","images":{"__typename":"AssociatedImageConnection","edges":[],"totalCount":0,"pageInfo":{"__typename":"PageInfo","hasNextPage":false,"endCursor":null,"hasPreviousPage":false,"startCursor":null}},"attachments":{"__typename":"AttachmentConnection","pageInfo":{"__typename":"PageInfo","hasNextPage":false,"endCursor":null,"hasPreviousPage":false,"startCursor":null},"edges":[]},"tags":{"__typename":"TagConnection","pageInfo":{"__typename":"PageInfo","hasNextPage":false,"endCursor":null,"hasPreviousPage":false,"startCursor":null},"edges":[{"__typename":"TagEdge","cursor":"MjUuM3wyLjF8b3wxMHxfTlZffDE","node":{"__typename":"Tag","id":"tag:application delivery","text":"application delivery","time":"2021-06-30T01:48:44.000-07:00","lastActivityTime":null,"messagesCount":null,"followersCount":null}},{"__typename":"TagEdge","cursor":"MjUuM3wyLjF8b3wxMHxfTlZffDI","node":{"__typename":"Tag","id":"tag:BIG-IP","text":"BIG-IP","time":"2022-01-24T02:29:45.031-08:00","lastActivityTime":null,"messagesCount":null,"followersCount":null}},{"__typename":"TagEdge","cursor":"MjUuM3wyLjF8b3wxMHxfTlZffDM","node":{"__typename":"Tag","id":"tag:iRules","text":"iRules","time":"2022-01-24T02:29:45.106-08:00","lastActivityTime":null,"messagesCount":null,"followersCount":null}},{"__typename":"TagEdge","cursor":"MjUuM3wyLjF8b3wxMHxfTlZffDQ","node":{"__typename":"Tag","id":"tag:security","text":"security","time":"2009-07-03T08:19:36.000-07:00","lastActivityTime":null,"messagesCount":null,"followersCount":null}},{"__typename":"TagEdge","cursor":"MjUuM3wyLjF8b3wxMHxfTlZffDU","node":{"__typename":"Tag","id":"tag:ssl","text":"ssl","time":"2022-01-24T02:29:52.690-08:00","lastActivityTime":null,"messagesCount":null,"followersCount":null}},{"__typename":"TagEdge","cursor":"MjUuM3wyLjF8b3wxMHxfTlZffDY","node":{"__typename":"Tag","id":"tag:tls","text":"tls","time":"2022-01-24T02:29:53.590-08:00","lastActivityTime":null,"messagesCount":null,"followersCount":null}}]},"timeToRead":13,"rawTeaser":"","introduction":"","currentRevision":{"__ref":"Revision:revision:286117_1"},"latestVersion":{"__typename":"FriendlyVersion","major":"1","minor":"0"},"metrics":{"__typename":"MessageMetrics","views":2050},"visibilityScope":"PUBLIC","canonicalUrl":null,"seoTitle":null,"seoDescription":null,"placeholder":false,"originalMessageForPlaceholder":null,"contributors":{"__typename":"UserConnection","edges":[]},"nonCoAuthorContributors":{"__typename":"UserConnection","edges":[]},"coAuthors":{"__typename":"UserConnection","edges":[{"__typename":"UserEdge","node":{"__ref":"User:user:130391"}}]},"tkbMessagePolicies":{"__typename":"TkbMessagePolicies","canDoAuthoringActionsOnTkb":{"__typename":"PolicyResult","failureReason":{"__typename":"FailureReason","message":"error.lithium.policies.tkb.policy_can_do_authoring_action.accessDenied","key":"error.lithium.policies.tkb.policy_can_do_authoring_action.accessDenied","args":[]}}},"archivalData":null,"replies":{"__typename":"MessageConnection","edges":[],"pageInfo":{"__typename":"PageInfo","hasNextPage":false,"endCursor":null,"hasPreviousPage":false,"startCursor":null}},"customFields":[],"revisions({\"constraints\":{\"isPublished\":{\"eq\":true}},\"first\":1})":{"__typename":"RevisionConnection","totalCount":1}},"Conversation:conversation:286117":{"__typename":"Conversation","id":"conversation:286117","solved":false,"topic":{"__ref":"TkbTopicMessage:message:286117"},"lastPostingActivityTime":"2020-10-05T10:56:12.000-07:00","lastPostTime":"2020-10-05T10:56:12.000-07:00","unreadReplyCount":0,"isSubscribed":false},"ModerationData:moderation_data:286117":{"__typename":"ModerationData","id":"moderation_data:286117","status":"APPROVED","rejectReason":null,"isReportedAbuse":false,"rejectUser":null,"rejectTime":null,"rejectActorType":null},"Revision:revision:286117_1":{"__typename":"Revision","id":"revision:286117_1","lastEditTime":"2020-10-05T10:56:12.000-07:00"},"CachedAsset:theme:customTheme1-1744706920729":{"__typename":"CachedAsset","id":"theme:customTheme1-1744706920729","value":{"id":"customTheme1","animation":{"fast":"150ms","normal":"250ms","slow":"500ms","slowest":"750ms","function":"cubic-bezier(0.07, 0.91, 0.51, 1)","__typename":"AnimationThemeSettings"},"avatar":{"borderRadius":"50%","collections":["custom"],"__typename":"AvatarThemeSettings"},"basics":{"browserIcon":{"imageAssetName":"JimmyPackets-512-1702592938213.png","imageLastModified":"1702592945815","__typename":"ThemeAsset"},"customerLogo":{"imageAssetName":"f5_logo_fix-1704824537976.svg","imageLastModified":"1704824540697","__typename":"ThemeAsset"},"maximumWidthOfPageContent":"1600px","oneColumnNarrowWidth":"800px","gridGutterWidthMd":"30px","gridGutterWidthXs":"10px","pageWidthStyle":"WIDTH_OF_PAGE_CONTENT","__typename":"BasicsThemeSettings"},"buttons":{"borderRadiusSm":"5px","borderRadius":"5px","borderRadiusLg":"5px","paddingY":"5px","paddingYLg":"7px","paddingYHero":"var(--lia-bs-btn-padding-y-lg)","paddingX":"12px","paddingXLg":"14px","paddingXHero":"42px","fontStyle":"NORMAL","fontWeight":"400","textTransform":"NONE","disabledOpacity":0.5,"primaryTextColor":"var(--lia-bs-white)","primaryTextHoverColor":"var(--lia-bs-white)","primaryTextActiveColor":"var(--lia-bs-white)","primaryBgColor":"var(--lia-bs-primary)","primaryBgHoverColor":"hsl(var(--lia-bs-primary-h), var(--lia-bs-primary-s), calc(var(--lia-bs-primary-l) * 0.85))","primaryBgActiveColor":"hsl(var(--lia-bs-primary-h), var(--lia-bs-primary-s), calc(var(--lia-bs-primary-l) * 0.7))","primaryBorder":"1px solid transparent","primaryBorderHover":"1px solid transparent","primaryBorderActive":"1px solid transparent","primaryBorderFocus":"1px solid var(--lia-bs-white)","primaryBoxShadowFocus":"0 0 0 1px var(--lia-bs-primary), 0 0 0 4px hsla(var(--lia-bs-primary-h), var(--lia-bs-primary-s), var(--lia-bs-primary-l), 0.2)","secondaryTextColor":"var(--lia-bs-gray-900)","secondaryTextHoverColor":"hsl(var(--lia-bs-gray-900-h), var(--lia-bs-gray-900-s), calc(var(--lia-bs-gray-900-l) * 0.95))","secondaryTextActiveColor":"hsl(var(--lia-bs-gray-900-h), var(--lia-bs-gray-900-s), calc(var(--lia-bs-gray-900-l) * 0.9))","secondaryBgColor":"var(--lia-bs-gray-400)","secondaryBgHoverColor":"hsl(var(--lia-bs-gray-400-h), var(--lia-bs-gray-400-s), calc(var(--lia-bs-gray-400-l) * 0.96))","secondaryBgActiveColor":"hsl(var(--lia-bs-gray-400-h), var(--lia-bs-gray-400-s), calc(var(--lia-bs-gray-400-l) * 0.92))","secondaryBorder":"1px solid transparent","secondaryBorderHover":"1px solid transparent","secondaryBorderActive":"1px solid transparent","secondaryBorderFocus":"1px solid transparent","secondaryBoxShadowFocus":"0 0 0 1px var(--lia-bs-primary), 0 0 0 4px hsla(var(--lia-bs-primary-h), var(--lia-bs-primary-s), var(--lia-bs-primary-l), 0.2)","tertiaryTextColor":"var(--lia-bs-gray-900)","tertiaryTextHoverColor":"hsl(var(--lia-bs-gray-900-h), var(--lia-bs-gray-900-s), calc(var(--lia-bs-gray-900-l) * 0.95))","tertiaryTextActiveColor":"hsl(var(--lia-bs-gray-900-h), var(--lia-bs-gray-900-s), calc(var(--lia-bs-gray-900-l) * 0.9))","tertiaryBgColor":"transparent","tertiaryBgHoverColor":"transparent","tertiaryBgActiveColor":"hsla(var(--lia-bs-black-h), var(--lia-bs-black-s), var(--lia-bs-black-l), 0.04)","tertiaryBorder":"1px solid transparent","tertiaryBorderHover":"1px solid hsla(var(--lia-bs-black-h), var(--lia-bs-black-s), var(--lia-bs-black-l), 0.08)","tertiaryBorderActive":"1px solid transparent","tertiaryBorderFocus":"1px solid transparent","tertiaryBoxShadowFocus":"0 0 0 1px var(--lia-bs-primary), 0 0 0 4px hsla(var(--lia-bs-primary-h), var(--lia-bs-primary-s), var(--lia-bs-primary-l), 0.2)","destructiveTextColor":"var(--lia-bs-danger)","destructiveTextHoverColor":"hsl(var(--lia-bs-danger-h), var(--lia-bs-danger-s), calc(var(--lia-bs-danger-l) * 0.95))","destructiveTextActiveColor":"hsl(var(--lia-bs-danger-h), var(--lia-bs-danger-s), calc(var(--lia-bs-danger-l) * 0.9))","destructiveBgColor":"var(--lia-bs-gray-300)","destructiveBgHoverColor":"hsl(var(--lia-bs-gray-300-h), var(--lia-bs-gray-300-s), calc(var(--lia-bs-gray-300-l) * 0.96))","destructiveBgActiveColor":"hsl(var(--lia-bs-gray-300-h), var(--lia-bs-gray-300-s), calc(var(--lia-bs-gray-300-l) * 0.92))","destructiveBorder":"1px solid transparent","destructiveBorderHover":"1px solid transparent","destructiveBorderActive":"1px solid transparent","destructiveBorderFocus":"1px solid transparent","destructiveBoxShadowFocus":"0 0 0 1px var(--lia-bs-primary), 0 0 0 4px hsla(var(--lia-bs-primary-h), var(--lia-bs-primary-s), var(--lia-bs-primary-l), 0.2)","__typename":"ButtonsThemeSettings"},"border":{"color":"hsla(var(--lia-bs-black-h), var(--lia-bs-black-s), var(--lia-bs-black-l), 0.08)","mainContent":"NONE","sideContent":"NONE","radiusSm":"3px","radius":"5px","radiusLg":"9px","radius50":"100vw","__typename":"BorderThemeSettings"},"boxShadow":{"xs":"0 0 0 1px hsla(var(--lia-bs-gray-900-h), var(--lia-bs-gray-900-s), var(--lia-bs-gray-900-l), 0.08), 0 3px 0 -1px hsla(var(--lia-bs-gray-900-h), var(--lia-bs-gray-900-s), var(--lia-bs-gray-900-l), 0.08)","sm":"0 2px 4px hsla(var(--lia-bs-gray-900-h), var(--lia-bs-gray-900-s), var(--lia-bs-gray-900-l), 0.06)","md":"0 5px 15px hsla(var(--lia-bs-gray-900-h), var(--lia-bs-gray-900-s), var(--lia-bs-gray-900-l), 0.15)","lg":"0 10px 30px hsla(var(--lia-bs-gray-900-h), var(--lia-bs-gray-900-s), var(--lia-bs-gray-900-l), 0.15)","__typename":"BoxShadowThemeSettings"},"cards":{"bgColor":"var(--lia-panel-bg-color)","borderRadius":"var(--lia-panel-border-radius)","boxShadow":"var(--lia-box-shadow-xs)","__typename":"CardsThemeSettings"},"chip":{"maxWidth":"300px","height":"30px","__typename":"ChipThemeSettings"},"coreTypes":{"defaultMessageLinkColor":"var(--lia-bs-primary)","defaultMessageLinkDecoration":"none","defaultMessageLinkFontStyle":"NORMAL","defaultMessageLinkFontWeight":"400","defaultMessageFontStyle":"NORMAL","defaultMessageFontWeight":"400","defaultMessageFontFamily":"var(--lia-bs-font-family-base)","forumColor":"#0C5C8D","forumFontFamily":"var(--lia-bs-font-family-base)","forumFontWeight":"var(--lia-default-message-font-weight)","forumLineHeight":"var(--lia-bs-line-height-base)","forumFontStyle":"var(--lia-default-message-font-style)","forumMessageLinkColor":"var(--lia-default-message-link-color)","forumMessageLinkDecoration":"var(--lia-default-message-link-decoration)","forumMessageLinkFontStyle":"var(--lia-default-message-link-font-style)","forumMessageLinkFontWeight":"var(--lia-default-message-link-font-weight)","forumSolvedColor":"#62C026","blogColor":"#730015","blogFontFamily":"var(--lia-bs-font-family-base)","blogFontWeight":"var(--lia-default-message-font-weight)","blogLineHeight":"1.75","blogFontStyle":"var(--lia-default-message-font-style)","blogMessageLinkColor":"var(--lia-default-message-link-color)","blogMessageLinkDecoration":"var(--lia-default-message-link-decoration)","blogMessageLinkFontStyle":"var(--lia-default-message-link-font-style)","blogMessageLinkFontWeight":"var(--lia-default-message-link-font-weight)","tkbColor":"#C20025","tkbFontFamily":"var(--lia-bs-font-family-base)","tkbFontWeight":"var(--lia-default-message-font-weight)","tkbLineHeight":"1.75","tkbFontStyle":"var(--lia-default-message-font-style)","tkbMessageLinkColor":"var(--lia-default-message-link-color)","tkbMessageLinkDecoration":"var(--lia-default-message-link-decoration)","tkbMessageLinkFontStyle":"var(--lia-default-message-link-font-style)","tkbMessageLinkFontWeight":"var(--lia-default-message-link-font-weight)","qandaColor":"#4099E2","qandaFontFamily":"var(--lia-bs-font-family-base)","qandaFontWeight":"var(--lia-default-message-font-weight)","qandaLineHeight":"var(--lia-bs-line-height-base)","qandaFontStyle":"var(--lia-default-message-link-font-style)","qandaMessageLinkColor":"var(--lia-default-message-link-color)","qandaMessageLinkDecoration":"var(--lia-default-message-link-decoration)","qandaMessageLinkFontStyle":"var(--lia-default-message-link-font-style)","qandaMessageLinkFontWeight":"var(--lia-default-message-link-font-weight)","qandaSolvedColor":"#3FA023","ideaColor":"#F3704B","ideaFontFamily":"var(--lia-bs-font-family-base)","ideaFontWeight":"var(--lia-default-message-font-weight)","ideaLineHeight":"var(--lia-bs-line-height-base)","ideaFontStyle":"var(--lia-default-message-font-style)","ideaMessageLinkColor":"var(--lia-default-message-link-color)","ideaMessageLinkDecoration":"var(--lia-default-message-link-decoration)","ideaMessageLinkFontStyle":"var(--lia-default-message-link-font-style)","ideaMessageLinkFontWeight":"var(--lia-default-message-link-font-weight)","contestColor":"#FCC845","contestFontFamily":"var(--lia-bs-font-family-base)","contestFontWeight":"var(--lia-default-message-font-weight)","contestLineHeight":"var(--lia-bs-line-height-base)","contestFontStyle":"var(--lia-default-message-link-font-style)","contestMessageLinkColor":"var(--lia-default-message-link-color)","contestMessageLinkDecoration":"var(--lia-default-message-link-decoration)","contestMessageLinkFontStyle":"ITALIC","contestMessageLinkFontWeight":"var(--lia-default-message-link-font-weight)","occasionColor":"#EE4B5B","occasionFontFamily":"var(--lia-bs-font-family-base)","occasionFontWeight":"var(--lia-default-message-font-weight)","occasionLineHeight":"var(--lia-bs-line-height-base)","occasionFontStyle":"var(--lia-default-message-font-style)","occasionMessageLinkColor":"var(--lia-default-message-link-color)","occasionMessageLinkDecoration":"var(--lia-default-message-link-decoration)","occasionMessageLinkFontStyle":"var(--lia-default-message-link-font-style)","occasionMessageLinkFontWeight":"var(--lia-default-message-link-font-weight)","grouphubColor":"#491B62","categoryColor":"#949494","communityColor":"#FFFFFF","productColor":"#949494","__typename":"CoreTypesThemeSettings"},"colors":{"black":"#000000","white":"#FFFFFF","gray100":"#F7F7F7","gray200":"#F7F7F7","gray300":"#E8E8E8","gray400":"#D9D9D9","gray500":"#CCCCCC","gray600":"#949494","gray700":"#707070","gray800":"#545454","gray900":"#333333","dark":"#545454","light":"#F7F7F7","primary":"#0C5C8D","secondary":"#333333","bodyText":"#222222","bodyBg":"#F5F5F5","info":"#1D9CD3","success":"#62C026","warning":"#FFD651","danger":"#C20025","alertSystem":"#FF6600","textMuted":"#707070","highlight":"#FFFCAD","outline":"var(--lia-bs-primary)","custom":["#C20025","#081B85","#009639","#B3C6D7","#7CC0EB","#F29A36"],"__typename":"ColorsThemeSettings"},"divider":{"size":"3px","marginLeft":"4px","marginRight":"4px","borderRadius":"50%","bgColor":"var(--lia-bs-gray-600)","bgColorActive":"var(--lia-bs-gray-600)","__typename":"DividerThemeSettings"},"dropdown":{"fontSize":"var(--lia-bs-font-size-sm)","borderColor":"var(--lia-bs-border-color)","borderRadius":"var(--lia-bs-border-radius-sm)","dividerBg":"var(--lia-bs-gray-300)","itemPaddingY":"5px","itemPaddingX":"20px","headerColor":"var(--lia-bs-gray-700)","__typename":"DropdownThemeSettings"},"email":{"link":{"color":"#0069D4","hoverColor":"#0061c2","decoration":"none","hoverDecoration":"underline","__typename":"EmailLinkSettings"},"border":{"color":"#e4e4e4","__typename":"EmailBorderSettings"},"buttons":{"borderRadiusLg":"5px","paddingXLg":"16px","paddingYLg":"7px","fontWeight":"700","primaryTextColor":"#ffffff","primaryTextHoverColor":"#ffffff","primaryBgColor":"#0069D4","primaryBgHoverColor":"#005cb8","primaryBorder":"1px solid transparent","primaryBorderHover":"1px solid transparent","__typename":"EmailButtonsSettings"},"panel":{"borderRadius":"5px","borderColor":"#e4e4e4","__typename":"EmailPanelSettings"},"__typename":"EmailThemeSettings"},"emoji":{"skinToneDefault":"#ffcd43","skinToneLight":"#fae3c5","skinToneMediumLight":"#e2cfa5","skinToneMedium":"#daa478","skinToneMediumDark":"#a78058","skinToneDark":"#5e4d43","__typename":"EmojiThemeSettings"},"heading":{"color":"var(--lia-bs-body-color)","fontFamily":"Inter","fontStyle":"NORMAL","fontWeight":"600","h1FontSize":"30px","h2FontSize":"25px","h3FontSize":"20px","h4FontSize":"18px","h5FontSize":"16px","h6FontSize":"16px","lineHeight":"1.2","subHeaderFontSize":"11px","subHeaderFontWeight":"500","h1LetterSpacing":"normal","h2LetterSpacing":"normal","h3LetterSpacing":"normal","h4LetterSpacing":"normal","h5LetterSpacing":"normal","h6LetterSpacing":"normal","subHeaderLetterSpacing":"2px","h1FontWeight":"var(--lia-bs-headings-font-weight)","h2FontWeight":"var(--lia-bs-headings-font-weight)","h3FontWeight":"var(--lia-bs-headings-font-weight)","h4FontWeight":"var(--lia-bs-headings-font-weight)","h5FontWeight":"var(--lia-bs-headings-font-weight)","h6FontWeight":"var(--lia-bs-headings-font-weight)","__typename":"HeadingThemeSettings"},"icons":{"size10":"10px","size12":"12px","size14":"14px","size16":"16px","size20":"20px","size24":"24px","size30":"30px","size40":"40px","size50":"50px","size60":"60px","size80":"80px","size120":"120px","size160":"160px","__typename":"IconsThemeSettings"},"imagePreview":{"bgColor":"var(--lia-bs-gray-900)","titleColor":"var(--lia-bs-white)","controlColor":"var(--lia-bs-white)","controlBgColor":"var(--lia-bs-gray-800)","__typename":"ImagePreviewThemeSettings"},"input":{"borderColor":"var(--lia-bs-gray-600)","disabledColor":"var(--lia-bs-gray-600)","focusBorderColor":"var(--lia-bs-primary)","labelMarginBottom":"10px","btnFontSize":"var(--lia-bs-font-size-sm)","focusBoxShadow":"0 0 0 3px hsla(var(--lia-bs-primary-h), var(--lia-bs-primary-s), var(--lia-bs-primary-l), 0.2)","checkLabelMarginBottom":"2px","checkboxBorderRadius":"3px","borderRadiusSm":"var(--lia-bs-border-radius-sm)","borderRadius":"var(--lia-bs-border-radius)","borderRadiusLg":"var(--lia-bs-border-radius-lg)","formTextMarginTop":"4px","textAreaBorderRadius":"var(--lia-bs-border-radius)","activeFillColor":"var(--lia-bs-primary)","__typename":"InputThemeSettings"},"loading":{"dotDarkColor":"hsla(var(--lia-bs-black-h), var(--lia-bs-black-s), var(--lia-bs-black-l), 0.2)","dotLightColor":"hsla(var(--lia-bs-white-h), var(--lia-bs-white-s), var(--lia-bs-white-l), 0.5)","barDarkColor":"hsla(var(--lia-bs-black-h), var(--lia-bs-black-s), var(--lia-bs-black-l), 0.06)","barLightColor":"hsla(var(--lia-bs-white-h), var(--lia-bs-white-s), var(--lia-bs-white-l), 0.4)","__typename":"LoadingThemeSettings"},"link":{"color":"var(--lia-bs-primary)","hoverColor":"hsl(var(--lia-bs-primary-h), var(--lia-bs-primary-s), calc(var(--lia-bs-primary-l) - 10%))","decoration":"none","hoverDecoration":"underline","__typename":"LinkThemeSettings"},"listGroup":{"itemPaddingY":"15px","itemPaddingX":"15px","borderColor":"var(--lia-bs-gray-300)","__typename":"ListGroupThemeSettings"},"modal":{"contentTextColor":"var(--lia-bs-body-color)","contentBg":"var(--lia-bs-white)","backgroundBg":"var(--lia-bs-black)","smSize":"440px","mdSize":"760px","lgSize":"1080px","backdropOpacity":0.3,"contentBoxShadowXs":"var(--lia-bs-box-shadow-sm)","contentBoxShadow":"var(--lia-bs-box-shadow)","headerFontWeight":"700","__typename":"ModalThemeSettings"},"navbar":{"position":"FIXED","background":{"attachment":null,"clip":null,"color":"var(--lia-bs-white)","imageAssetName":null,"imageLastModified":"0","origin":null,"position":"CENTER_CENTER","repeat":"NO_REPEAT","size":"COVER","__typename":"BackgroundProps"},"backgroundOpacity":0.8,"paddingTop":"15px","paddingBottom":"15px","borderBottom":"1px solid var(--lia-bs-border-color)","boxShadow":"var(--lia-bs-box-shadow-sm)","brandMarginRight":"30px","brandMarginRightSm":"10px","brandLogoHeight":"30px","linkGap":"10px","linkJustifyContent":"flex-start","linkPaddingY":"5px","linkPaddingX":"10px","linkDropdownPaddingY":"9px","linkDropdownPaddingX":"var(--lia-nav-link-px)","linkColor":"var(--lia-bs-body-color)","linkHoverColor":"var(--lia-bs-primary)","linkFontSize":"var(--lia-bs-font-size-sm)","linkFontStyle":"NORMAL","linkFontWeight":"400","linkTextTransform":"NONE","linkLetterSpacing":"normal","linkBorderRadius":"var(--lia-bs-border-radius-sm)","linkBgColor":"transparent","linkBgHoverColor":"transparent","linkBorder":"none","linkBorderHover":"none","linkBoxShadow":"none","linkBoxShadowHover":"none","linkTextBorderBottom":"none","linkTextBorderBottomHover":"none","dropdownPaddingTop":"10px","dropdownPaddingBottom":"15px","dropdownPaddingX":"10px","dropdownMenuOffset":"2px","dropdownDividerMarginTop":"10px","dropdownDividerMarginBottom":"10px","dropdownBorderColor":"hsla(var(--lia-bs-black-h), var(--lia-bs-black-s), var(--lia-bs-black-l), 0.08)","controllerBgHoverColor":"hsla(var(--lia-bs-black-h), var(--lia-bs-black-s), var(--lia-bs-black-l), 0.1)","controllerIconColor":"var(--lia-bs-body-color)","controllerIconHoverColor":"var(--lia-bs-body-color)","controllerTextColor":"var(--lia-nav-controller-icon-color)","controllerTextHoverColor":"var(--lia-nav-controller-icon-hover-color)","controllerHighlightColor":"hsla(30, 100%, 50%)","controllerHighlightTextColor":"var(--lia-yiq-light)","controllerBorderRadius":"var(--lia-border-radius-50)","hamburgerColor":"var(--lia-nav-controller-icon-color)","hamburgerHoverColor":"var(--lia-nav-controller-icon-color)","hamburgerBgColor":"transparent","hamburgerBgHoverColor":"transparent","hamburgerBorder":"none","hamburgerBorderHover":"none","collapseMenuMarginLeft":"20px","collapseMenuDividerBg":"var(--lia-nav-link-color)","collapseMenuDividerOpacity":0.16,"__typename":"NavbarThemeSettings"},"pager":{"textColor":"var(--lia-bs-link-color)","textFontWeight":"var(--lia-font-weight-md)","textFontSize":"var(--lia-bs-font-size-sm)","__typename":"PagerThemeSettings"},"panel":{"bgColor":"var(--lia-bs-white)","borderRadius":"var(--lia-bs-border-radius)","borderColor":"var(--lia-bs-border-color)","boxShadow":"none","__typename":"PanelThemeSettings"},"popover":{"arrowHeight":"8px","arrowWidth":"16px","maxWidth":"300px","minWidth":"100px","headerBg":"var(--lia-bs-white)","borderColor":"var(--lia-bs-border-color)","borderRadius":"var(--lia-bs-border-radius)","boxShadow":"0 0.5rem 1rem hsla(var(--lia-bs-black-h), var(--lia-bs-black-s), var(--lia-bs-black-l), 0.15)","__typename":"PopoverThemeSettings"},"prism":{"color":"#000000","bgColor":"#f5f2f0","fontFamily":"var(--font-family-monospace)","fontSize":"var(--lia-bs-font-size-base)","fontWeightBold":"var(--lia-bs-font-weight-bold)","fontStyleItalic":"italic","tabSize":2,"highlightColor":"#b3d4fc","commentColor":"#62707e","punctuationColor":"#6f6f6f","namespaceOpacity":"0.7","propColor":"#990055","selectorColor":"#517a00","operatorColor":"#906736","operatorBgColor":"hsla(0, 0%, 100%, 0.5)","keywordColor":"#0076a9","functionColor":"#d3284b","variableColor":"#c14700","__typename":"PrismThemeSettings"},"rte":{"bgColor":"var(--lia-bs-white)","borderRadius":"var(--lia-panel-border-radius)","boxShadow":" var(--lia-panel-box-shadow)","customColor1":"#bfedd2","customColor2":"#fbeeb8","customColor3":"#f8cac6","customColor4":"#eccafa","customColor5":"#c2e0f4","customColor6":"#2dc26b","customColor7":"#f1c40f","customColor8":"#e03e2d","customColor9":"#b96ad9","customColor10":"#3598db","customColor11":"#169179","customColor12":"#e67e23","customColor13":"#ba372a","customColor14":"#843fa1","customColor15":"#236fa1","customColor16":"#ecf0f1","customColor17":"#ced4d9","customColor18":"#95a5a6","customColor19":"#7e8c8d","customColor20":"#34495e","customColor21":"#000000","customColor22":"#ffffff","defaultMessageHeaderMarginTop":"14px","defaultMessageHeaderMarginBottom":"10px","defaultMessageItemMarginTop":"0","defaultMessageItemMarginBottom":"10px","diffAddedColor":"hsla(170, 53%, 51%, 0.4)","diffChangedColor":"hsla(43, 97%, 63%, 0.4)","diffNoneColor":"hsla(0, 0%, 80%, 0.4)","diffRemovedColor":"hsla(9, 74%, 47%, 0.4)","specialMessageHeaderMarginTop":"14px","specialMessageHeaderMarginBottom":"10px","specialMessageItemMarginTop":"0","specialMessageItemMarginBottom":"10px","__typename":"RteThemeSettings"},"tags":{"bgColor":"var(--lia-bs-gray-200)","bgHoverColor":"var(--lia-bs-gray-400)","borderRadius":"var(--lia-bs-border-radius-sm)","color":"var(--lia-bs-body-color)","hoverColor":"var(--lia-bs-body-color)","fontWeight":"var(--lia-font-weight-md)","fontSize":"var(--lia-font-size-xxs)","textTransform":"UPPERCASE","letterSpacing":"0.5px","__typename":"TagsThemeSettings"},"toasts":{"borderRadius":"var(--lia-bs-border-radius)","paddingX":"12px","__typename":"ToastsThemeSettings"},"typography":{"fontFamilyBase":"Atkinson Hyperlegible","fontStyleBase":"NORMAL","fontWeightBase":"400","fontWeightLight":"300","fontWeightNormal":"400","fontWeightMd":"500","fontWeightBold":"700","letterSpacingSm":"normal","letterSpacingXs":"normal","lineHeightBase":"1.3","fontSizeBase":"15px","fontSizeXxs":"11px","fontSizeXs":"12px","fontSizeSm":"13px","fontSizeLg":"20px","fontSizeXl":"24px","smallFontSize":"14px","customFonts":[],"__typename":"TypographyThemeSettings"},"unstyledListItem":{"marginBottomSm":"5px","marginBottomMd":"10px","marginBottomLg":"15px","marginBottomXl":"20px","marginBottomXxl":"25px","__typename":"UnstyledListItemThemeSettings"},"yiq":{"light":"#ffffff","dark":"#000000","__typename":"YiqThemeSettings"},"colorLightness":{"primaryDark":0.36,"primaryLight":0.74,"primaryLighter":0.89,"primaryLightest":0.95,"infoDark":0.39,"infoLight":0.72,"infoLighter":0.85,"infoLightest":0.93,"successDark":0.24,"successLight":0.62,"successLighter":0.8,"successLightest":0.91,"warningDark":0.39,"warningLight":0.68,"warningLighter":0.84,"warningLightest":0.93,"dangerDark":0.41,"dangerLight":0.72,"dangerLighter":0.89,"dangerLightest":0.95,"__typename":"ColorLightnessThemeSettings"},"localOverride":false,"__typename":"Theme"},"localOverride":false},"CachedAsset:text:en_US-shared/client/components/common/Loading/LoadingDot-1744046271000":{"__typename":"CachedAsset","id":"text:en_US-shared/client/components/common/Loading/LoadingDot-1744046271000","value":{"title":"Loading..."},"localOverride":false},"CachedAsset:quilt:f5.prod:pages/kbs/TkbMessagePage:board:TechnicalArticles-1744706918897":{"__typename":"CachedAsset","id":"quilt:f5.prod:pages/kbs/TkbMessagePage:board:TechnicalArticles-1744706918897","value":{"id":"TkbMessagePage","container":{"id":"Common","headerProps":{"backgroundImageProps":null,"backgroundColor":null,"addComponents":null,"removeComponents":["community.widget.bannerWidget"],"componentOrder":null,"__typename":"QuiltContainerSectionProps"},"headerComponentProps":{"community.widget.breadcrumbWidget":{"disableLastCrumbForDesktop":false}},"footerProps":null,"footerComponentProps":null,"items":[{"id":"message-list","layout":"MAIN_SIDE","bgColor":"transparent","showTitle":true,"showDescription":true,"textPosition":"CENTER","textColor":"var(--lia-bs-body-color)","sectionEditLevel":null,"bgImage":null,"disableSpacing":null,"edgeToEdgeDisplay":null,"fullHeight":null,"showBorder":null,"__typename":"MainSideQuiltSection","columnMap":{"main":[{"id":"tkbs.widget.tkbArticleWidget","className":"lia-tkb-container","props":{"contributorListType":"panel","showHelpfulness":false,"showTimestamp":true,"showGuideNavigationSection":true,"showVersion":true,"lazyLoad":false,"editLevel":"CONFIGURE"},"__typename":"QuiltComponent"}],"side":[{"id":"featuredWidgets.widget.featuredContentWidget","className":null,"props":{"instanceId":"featuredWidgets.widget.featuredContentWidget-1702666556326","layoutProps":{"layout":"card","layoutOptions":{"useRepliesCount":false,"useAuthorRank":false,"useTimeToRead":true,"useKudosCount":false,"useViewCount":true,"usePreviewMedia":true,"useBody":false,"useCenteredCardContent":false,"useTags":true,"useTimestamp":false,"useBoardLink":true,"useAuthorLink":false,"useSolvedBadge":true}},"titleSrOnly":false,"showPager":true,"pageSize":3,"lazyLoad":true},"__typename":"QuiltComponent"},{"id":"messages.widget.relatedContentWidget","className":null,"props":{"hideIfEmpty":true,"enablePagination":true,"useTitle":true,"listVariant":{"type":"listGroup"},"pageSize":3,"style":"list","pagerVariant":{"type":"loadMore"},"viewVariant":{"type":"inline","props":{"useRepliesCount":true,"useMedia":true,"useAuthorRank":false,"useNode":true,"useTimeToRead":true,"useSpoilerFreeBody":true,"useKudosCount":true,"useNodeLink":true,"useViewCount":true,"usePreviewMedia":false,"useBody":false,"timeStampType":"postTime","useTags":true,"clampSubjectLines":2,"useBoardIcon":false,"useMessageTimeLink":true,"clampBodyLines":3,"useTextBody":true,"useSolvedBadge":true,"useAvatar":true,"useAuthorLogin":true,"useUnreadCount":true}},"lazyLoad":true,"panelType":"divider"},"__typename":"QuiltComponent"}],"__typename":"MainSideSectionColumns"}}],"__typename":"QuiltContainer"},"__typename":"Quilt","localOverride":false},"localOverride":false},"CachedAsset:text:en_US-components/common/EmailVerification-1744046271000":{"__typename":"CachedAsset","id":"text:en_US-components/common/EmailVerification-1744046271000","value":{"email.verification.title":"Email Verification Required","email.verification.message.update.email":"To participate in the community, you must first verify your email address. The verification email was sent to {email}. To change your email, visit My Settings.","email.verification.message.resend.email":"To participate in the community, you must first verify your email address. The verification email was sent to {email}. Resend email."},"localOverride":false},"CachedAsset:text:en_US-pages/kbs/TkbMessagePage-1744046271000":{"__typename":"CachedAsset","id":"text:en_US-pages/kbs/TkbMessagePage-1744046271000","value":{"title":"{contextMessageSubject} | {communityTitle}","errorMissing":"This article cannot be found","name":"TKB Message Page","section.message-list.title":"","archivedMessageTitle":"This Content Has Been Archived","section.erPqcf.title":"","section.erPqcf.description":"","section.message-list.description":""},"localOverride":false},"CachedAsset:quiltWrapper:f5.prod:Common:1744732624144":{"__typename":"CachedAsset","id":"quiltWrapper:f5.prod:Common:1744732624144","value":{"id":"Common","header":{"backgroundImageProps":{"assetName":"header.jpg","backgroundSize":"COVER","backgroundRepeat":"NO_REPEAT","backgroundPosition":"LEFT_CENTER","lastModified":"1702932449000","__typename":"BackgroundImageProps"},"backgroundColor":"transparent","items":[{"id":"custom.widget.Beta_MetaNav","props":{"widgetVisibility":"signedInOrAnonymous","useTitle":true,"useBackground":false,"title":"","lazyLoad":false},"__typename":"QuiltComponent"},{"id":"community.widget.navbarWidget","props":{"showUserName":false,"showRegisterLink":true,"style":{"boxShadow":"var(--lia-bs-box-shadow-sm)","linkFontWeight":"700","controllerHighlightColor":"hsla(30, 100%, 50%)","dropdownDividerMarginBottom":"10px","hamburgerBorderHover":"none","linkFontSize":"15px","linkBoxShadowHover":"none","backgroundOpacity":0.4,"controllerBorderRadius":"var(--lia-border-radius-50)","hamburgerBgColor":"transparent","linkTextBorderBottom":"none","hamburgerColor":"var(--lia-nav-controller-icon-color)","brandLogoHeight":"48px","linkLetterSpacing":"normal","linkBgHoverColor":"transparent","collapseMenuDividerOpacity":0.16,"paddingBottom":"10px","dropdownPaddingBottom":"15px","dropdownMenuOffset":"2px","hamburgerBgHoverColor":"transparent","borderBottom":"0","hamburgerBorder":"none","dropdownPaddingX":"10px","brandMarginRightSm":"10px","linkBoxShadow":"none","linkJustifyContent":"center","linkColor":"var(--lia-bs-primary)","collapseMenuDividerBg":"var(--lia-nav-link-color)","dropdownPaddingTop":"10px","controllerHighlightTextColor":"var(--lia-yiq-dark)","background":{"imageAssetName":"","color":"var(--lia-bs-white)","size":"COVER","repeat":"NO_REPEAT","position":"CENTER_CENTER","imageLastModified":""},"linkBorderRadius":"var(--lia-bs-border-radius-sm)","linkHoverColor":"var(--lia-bs-primary)","position":"FIXED","linkBorder":"none","linkTextBorderBottomHover":"2px solid #0C5C8D","brandMarginRight":"30px","hamburgerHoverColor":"var(--lia-nav-controller-icon-color)","linkBorderHover":"none","collapseMenuMarginLeft":"20px","linkFontStyle":"NORMAL","linkPaddingX":"10px","paddingTop":"10px","linkPaddingY":"5px","linkTextTransform":"NONE","dropdownBorderColor":"hsla(var(--lia-bs-black-h), var(--lia-bs-black-s), var(--lia-bs-black-l), 0.08)","controllerBgHoverColor":"hsla(var(--lia-bs-black-h), var(--lia-bs-black-s), var(--lia-bs-black-l), 0.1)","linkDropdownPaddingX":"var(--lia-nav-link-px)","linkBgColor":"transparent","linkDropdownPaddingY":"9px","controllerIconColor":"#0C5C8D","dropdownDividerMarginTop":"10px","linkGap":"10px","controllerIconHoverColor":"#0C5C8D"},"links":{"sideLinks":[],"mainLinks":[{"children":[{"linkType":"INTERNAL","id":"migrated-link-1","params":{"boardId":"TechnicalForum","categoryId":"Forums"},"routeName":"ForumBoardPage"},{"linkType":"INTERNAL","id":"migrated-link-2","params":{"boardId":"WaterCooler","categoryId":"Forums"},"routeName":"ForumBoardPage"}],"linkType":"INTERNAL","id":"migrated-link-0","params":{"categoryId":"Forums"},"routeName":"CategoryPage"},{"children":[{"linkType":"INTERNAL","id":"migrated-link-4","params":{"boardId":"codeshare","categoryId":"CrowdSRC"},"routeName":"TkbBoardPage"},{"linkType":"INTERNAL","id":"migrated-link-5","params":{"boardId":"communityarticles","categoryId":"CrowdSRC"},"routeName":"TkbBoardPage"}],"linkType":"INTERNAL","id":"migrated-link-3","params":{"categoryId":"CrowdSRC"},"routeName":"CategoryPage"},{"children":[{"linkType":"INTERNAL","id":"migrated-link-7","params":{"boardId":"TechnicalArticles","categoryId":"Articles"},"routeName":"TkbBoardPage"},{"linkType":"INTERNAL","id":"article-series","params":{"boardId":"article-series","categoryId":"Articles"},"routeName":"TkbBoardPage"},{"linkType":"INTERNAL","id":"security-insights","params":{"boardId":"security-insights","categoryId":"Articles"},"routeName":"TkbBoardPage"},{"linkType":"INTERNAL","id":"migrated-link-8","params":{"boardId":"DevCentralNews","categoryId":"Articles"},"routeName":"TkbBoardPage"}],"linkType":"INTERNAL","id":"migrated-link-6","params":{"categoryId":"Articles"},"routeName":"CategoryPage"},{"children":[{"linkType":"INTERNAL","id":"migrated-link-10","params":{"categoryId":"CommunityGroups"},"routeName":"CategoryPage"},{"linkType":"INTERNAL","id":"migrated-link-11","params":{"categoryId":"F5-Groups"},"routeName":"CategoryPage"}],"linkType":"INTERNAL","id":"migrated-link-9","params":{"categoryId":"GroupsCategory"},"routeName":"CategoryPage"},{"children":[],"linkType":"INTERNAL","id":"migrated-link-12","params":{"boardId":"Events","categoryId":"top"},"routeName":"EventBoardPage"},{"children":[],"linkType":"INTERNAL","id":"migrated-link-13","params":{"boardId":"Suggestions","categoryId":"top"},"routeName":"IdeaBoardPage"},{"children":[],"linkType":"EXTERNAL","id":"Common-external-link","url":"https://community.f5.com/c/how-do-i","target":"SELF"}]},"className":"QuiltComponent_lia-component-edit-mode__lQ9Z6","showSearchIcon":false},"__typename":"QuiltComponent"},{"id":"community.widget.bannerWidget","props":{"backgroundColor":"transparent","visualEffects":{"showBottomBorder":false},"backgroundImageProps":{"backgroundSize":"COVER","backgroundPosition":"CENTER_CENTER","backgroundRepeat":"NO_REPEAT"},"fontColor":"#222222"},"__typename":"QuiltComponent"},{"id":"community.widget.breadcrumbWidget","props":{"backgroundColor":"var(--lia-bs-primary)","linkHighlightColor":"#FFFFFF","visualEffects":{"showBottomBorder":false},"backgroundOpacity":60,"linkTextColor":"#FFFFFF"},"__typename":"QuiltComponent"}],"__typename":"QuiltWrapperSection"},"footer":{"backgroundImageProps":{"assetName":null,"backgroundSize":"COVER","backgroundRepeat":"NO_REPEAT","backgroundPosition":"CENTER_CENTER","lastModified":null,"__typename":"BackgroundImageProps"},"backgroundColor":"var(--lia-bs-body-color)","items":[{"id":"custom.widget.Beta_Footer","props":{"widgetVisibility":"signedInOrAnonymous","useTitle":true,"useBackground":false,"title":"","lazyLoad":false},"__typename":"QuiltComponent"},{"id":"custom.widget.Tag_Manager_Helper","props":{"widgetVisibility":"signedInOrAnonymous","useTitle":true,"useBackground":false,"title":"","lazyLoad":false},"__typename":"QuiltComponent"},{"id":"custom.widget.Consent_Blackbar","props":{"widgetVisibility":"signedInOrAnonymous","useTitle":true,"useBackground":false,"title":"","lazyLoad":false},"__typename":"QuiltComponent"}],"__typename":"QuiltWrapperSection"},"__typename":"QuiltWrapper","localOverride":false},"localOverride":false},"CachedAsset:text:en_US-components/common/ActionFeedback-1744046271000":{"__typename":"CachedAsset","id":"text:en_US-components/common/ActionFeedback-1744046271000","value":{"joinedGroupHub.title":"Welcome","joinedGroupHub.message":"You are now a member of this group and are subscribed to updates.","groupHubInviteNotFound.title":"Invitation Not Found","groupHubInviteNotFound.message":"Sorry, we could not find your invitation to the group. The owner may have canceled the invite.","groupHubNotFound.title":"Group Not Found","groupHubNotFound.message":"The grouphub you tried to join does not exist. It may have been deleted.","existingGroupHubMember.title":"Already Joined","existingGroupHubMember.message":"You are already a member of this group.","accountLocked.title":"Account Locked","accountLocked.message":"Your account has been locked due to multiple failed attempts. Try again in {lockoutTime} minutes.","editedGroupHub.title":"Changes Saved","editedGroupHub.message":"Your group has been updated.","leftGroupHub.title":"Goodbye","leftGroupHub.message":"You are no longer a member of this group and will not receive future updates.","deletedGroupHub.title":"Deleted","deletedGroupHub.message":"The group has been deleted.","groupHubCreated.title":"Group Created","groupHubCreated.message":"{groupHubName} is ready to use","accountClosed.title":"Account Closed","accountClosed.message":"The account has been closed and you will now be redirected to the homepage","resetTokenExpired.title":"Reset Password Link has Expired","resetTokenExpired.message":"Try resetting your password again","invalidUrl.title":"Invalid URL","invalidUrl.message":"The URL you're using is not recognized. Verify your URL and try again.","accountClosedForUser.title":"Account Closed","accountClosedForUser.message":"{userName}'s account is closed","inviteTokenInvalid.title":"Invitation Invalid","inviteTokenInvalid.message":"Your invitation to the community has been canceled or expired.","inviteTokenError.title":"Invitation Verification Failed","inviteTokenError.message":"The url you are utilizing is not recognized. Verify your URL and try again","pageNotFound.title":"Access Denied","pageNotFound.message":"You do not have access to this area of the community or it doesn't exist","eventAttending.title":"Responded as Attending","eventAttending.message":"You'll be notified when there's new activity and reminded as the event approaches","eventInterested.title":"Responded as Interested","eventInterested.message":"You'll be notified when there's new activity and reminded as the event approaches","eventNotFound.title":"Event Not Found","eventNotFound.message":"The event you tried to respond to does not exist.","redirectToRelatedPage.title":"Showing Related Content","redirectToRelatedPageForBaseUsers.title":"Showing Related Content","redirectToRelatedPageForBaseUsers.message":"The content you are trying to access is archived","redirectToRelatedPage.message":"The content you are trying to access is archived","relatedUrl.archivalLink.flyoutMessage":"The content you are trying to access is archived View Archived Content"},"localOverride":false},"CachedAsset:component:custom.widget.Beta_MetaNav-en-us-1744706939108":{"__typename":"CachedAsset","id":"component:custom.widget.Beta_MetaNav-en-us-1744706939108","value":{"component":{"id":"custom.widget.Beta_MetaNav","template":{"id":"Beta_MetaNav","markupLanguage":"HANDLEBARS","style":null,"texts":{},"defaults":{"config":{"applicablePages":[],"description":"MetaNav menu at the top of every page.","fetchedContent":null,"__typename":"ComponentConfiguration"},"props":[],"__typename":"ComponentProperties"},"components":[{"id":"custom.widget.Beta_MetaNav","form":null,"config":null,"props":[],"__typename":"Component"}],"grouping":"CUSTOM","__typename":"ComponentTemplate"},"properties":{"config":{"applicablePages":[],"description":"MetaNav menu at the top of every page.","fetchedContent":null,"__typename":"ComponentConfiguration"},"props":[],"__typename":"ComponentProperties"},"form":null,"__typename":"Component","localOverride":false},"globalCss":null,"form":null},"localOverride":false},"CachedAsset:component:custom.widget.Beta_Footer-en-us-1744706939108":{"__typename":"CachedAsset","id":"component:custom.widget.Beta_Footer-en-us-1744706939108","value":{"component":{"id":"custom.widget.Beta_Footer","template":{"id":"Beta_Footer","markupLanguage":"HANDLEBARS","style":null,"texts":{},"defaults":{"config":{"applicablePages":[],"description":"DevCentral´s custom footer.","fetchedContent":null,"__typename":"ComponentConfiguration"},"props":[],"__typename":"ComponentProperties"},"components":[{"id":"custom.widget.Beta_Footer","form":null,"config":null,"props":[],"__typename":"Component"}],"grouping":"CUSTOM","__typename":"ComponentTemplate"},"properties":{"config":{"applicablePages":[],"description":"DevCentral´s custom footer.","fetchedContent":null,"__typename":"ComponentConfiguration"},"props":[],"__typename":"ComponentProperties"},"form":null,"__typename":"Component","localOverride":false},"globalCss":null,"form":null},"localOverride":false},"CachedAsset:component:custom.widget.Tag_Manager_Helper-en-us-1744706939108":{"__typename":"CachedAsset","id":"component:custom.widget.Tag_Manager_Helper-en-us-1744706939108","value":{"component":{"id":"custom.widget.Tag_Manager_Helper","template":{"id":"Tag_Manager_Helper","markupLanguage":"HANDLEBARS","style":null,"texts":{},"defaults":{"config":{"applicablePages":[],"description":"Helper widget to inject Tag Manager scripts into head element","fetchedContent":null,"__typename":"ComponentConfiguration"},"props":[],"__typename":"ComponentProperties"},"components":[{"id":"custom.widget.Tag_Manager_Helper","form":null,"config":null,"props":[],"__typename":"Component"}],"grouping":"CUSTOM","__typename":"ComponentTemplate"},"properties":{"config":{"applicablePages":[],"description":"Helper widget to inject Tag Manager scripts into head element","fetchedContent":null,"__typename":"ComponentConfiguration"},"props":[],"__typename":"ComponentProperties"},"form":null,"__typename":"Component","localOverride":false},"globalCss":null,"form":null},"localOverride":false},"CachedAsset:component:custom.widget.Consent_Blackbar-en-us-1744706939108":{"__typename":"CachedAsset","id":"component:custom.widget.Consent_Blackbar-en-us-1744706939108","value":{"component":{"id":"custom.widget.Consent_Blackbar","template":{"id":"Consent_Blackbar","markupLanguage":"HTML","style":null,"texts":{},"defaults":{"config":{"applicablePages":[],"description":"","fetchedContent":null,"__typename":"ComponentConfiguration"},"props":[],"__typename":"ComponentProperties"},"components":[{"id":"custom.widget.Consent_Blackbar","form":null,"config":null,"props":[],"__typename":"Component"}],"grouping":"TEXTHTML","__typename":"ComponentTemplate"},"properties":{"config":{"applicablePages":[],"description":"","fetchedContent":null,"__typename":"ComponentConfiguration"},"props":[],"__typename":"ComponentProperties"},"form":null,"__typename":"Component","localOverride":false},"globalCss":null,"form":null},"localOverride":false},"CachedAsset:text:en_US-components/community/Breadcrumb-1744046271000":{"__typename":"CachedAsset","id":"text:en_US-components/community/Breadcrumb-1744046271000","value":{"navLabel":"Breadcrumbs","dropdown":"Additional parent page navigation"},"localOverride":false},"CachedAsset:text:en_US-components/messages/MessageBanner-1744046271000":{"__typename":"CachedAsset","id":"text:en_US-components/messages/MessageBanner-1744046271000","value":{"messageMarkedAsSpam":"This post has been marked as spam","messageMarkedAsSpam@board:TKB":"This article has been marked as spam","messageMarkedAsSpam@board:BLOG":"This post has been marked as spam","messageMarkedAsSpam@board:FORUM":"This discussion has been marked as spam","messageMarkedAsSpam@board:OCCASION":"This event has been marked as spam","messageMarkedAsSpam@board:IDEA":"This idea has been marked as spam","manageSpam":"Manage Spam","messageMarkedAsAbuse":"This post has been marked as abuse","messageMarkedAsAbuse@board:TKB":"This article has been marked as abuse","messageMarkedAsAbuse@board:BLOG":"This post has been marked as abuse","messageMarkedAsAbuse@board:FORUM":"This discussion has been marked as abuse","messageMarkedAsAbuse@board:OCCASION":"This event has been marked as abuse","messageMarkedAsAbuse@board:IDEA":"This idea has been marked as abuse","preModCommentAuthorText":"This comment will be published as soon as it is approved","preModCommentModeratorText":"This comment is awaiting moderation","messageMarkedAsOther":"This post has been rejected due to other reasons","messageMarkedAsOther@board:TKB":"This article has been rejected due to other reasons","messageMarkedAsOther@board:BLOG":"This post has been rejected due to other reasons","messageMarkedAsOther@board:FORUM":"This discussion has been rejected due to other reasons","messageMarkedAsOther@board:OCCASION":"This event has been rejected due to other reasons","messageMarkedAsOther@board:IDEA":"This idea has been rejected due to other reasons","messageArchived":"This post was archived on {date}","relatedUrl":"View Related Content","relatedContentText":"Showing related content","archivedContentLink":"View Archived Content"},"localOverride":false},"CachedAsset:text:en_US-components/tkbs/TkbArticleWidget-1744046271000":{"__typename":"CachedAsset","id":"text:en_US-components/tkbs/TkbArticleWidget-1744046271000","value":{},"localOverride":false},"Category:category:Forums":{"__typename":"Category","id":"category:Forums","categoryPolicies":{"__typename":"CategoryPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Forum:board:TechnicalForum":{"__typename":"Forum","id":"board:TechnicalForum","forumPolicies":{"__typename":"ForumPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}},"boardPolicies":{"__typename":"BoardPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Forum:board:WaterCooler":{"__typename":"Forum","id":"board:WaterCooler","forumPolicies":{"__typename":"ForumPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}},"boardPolicies":{"__typename":"BoardPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Tkb:board:DevCentralNews":{"__typename":"Tkb","id":"board:DevCentralNews","tkbPolicies":{"__typename":"TkbPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}},"boardPolicies":{"__typename":"BoardPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Category:category:GroupsCategory":{"__typename":"Category","id":"category:GroupsCategory","categoryPolicies":{"__typename":"CategoryPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Category:category:F5-Groups":{"__typename":"Category","id":"category:F5-Groups","categoryPolicies":{"__typename":"CategoryPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Category:category:CommunityGroups":{"__typename":"Category","id":"category:CommunityGroups","categoryPolicies":{"__typename":"CategoryPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Occasion:board:Events":{"__typename":"Occasion","id":"board:Events","boardPolicies":{"__typename":"BoardPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}},"occasionPolicies":{"__typename":"OccasionPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Idea:board:Suggestions":{"__typename":"Idea","id":"board:Suggestions","boardPolicies":{"__typename":"BoardPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}},"ideaPolicies":{"__typename":"IdeaPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Category:category:CrowdSRC":{"__typename":"Category","id":"category:CrowdSRC","categoryPolicies":{"__typename":"CategoryPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Tkb:board:codeshare":{"__typename":"Tkb","id":"board:codeshare","tkbPolicies":{"__typename":"TkbPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}},"boardPolicies":{"__typename":"BoardPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Tkb:board:communityarticles":{"__typename":"Tkb","id":"board:communityarticles","tkbPolicies":{"__typename":"TkbPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}},"boardPolicies":{"__typename":"BoardPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Tkb:board:security-insights":{"__typename":"Tkb","id":"board:security-insights","tkbPolicies":{"__typename":"TkbPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}},"boardPolicies":{"__typename":"BoardPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Tkb:board:article-series":{"__typename":"Tkb","id":"board:article-series","tkbPolicies":{"__typename":"TkbPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}},"boardPolicies":{"__typename":"BoardPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"QueryVariables:TopicReplyList:message:286117:1":{"__typename":"QueryVariables","id":"TopicReplyList:message:286117:1","value":{"id":"message:286117","first":10,"sorts":{"postTime":{"direction":"ASC"}},"repliesFirst":3,"repliesFirstDepthThree":1,"repliesSorts":{"postTime":{"direction":"ASC"}},"useAvatar":true,"useAuthorLogin":true,"useAuthorRank":true,"useBody":true,"useKudosCount":true,"useTimeToRead":false,"useMedia":false,"useReadOnlyIcon":false,"useRepliesCount":true,"useSearchSnippet":false,"useAcceptedSolutionButton":false,"useSolvedBadge":false,"useAttachments":false,"attachmentsFirst":5,"useTags":true,"useNodeAncestors":false,"useUserHoverCard":false,"useNodeHoverCard":false,"useModerationStatus":true,"usePreviewSubjectModal":false,"useMessageStatus":true}},"ROOT_MUTATION":{"__typename":"Mutation"},"CachedAsset:text:en_US-components/community/Navbar-1744046271000":{"__typename":"CachedAsset","id":"text:en_US-components/community/Navbar-1744046271000","value":{"community":"Community Home","inbox":"Inbox","manageContent":"Manage Content","tos":"Terms of Service","forgotPassword":"Forgot Password","themeEditor":"Theme Editor","edit":"Edit Navigation Bar","skipContent":"Skip to content","migrated-link-9":"Groups","migrated-link-7":"Technical Articles","migrated-link-8":"DevCentral News","migrated-link-1":"Technical Forum","migrated-link-10":"Community Groups","migrated-link-2":"Water Cooler","migrated-link-11":"F5 Groups","Common-external-link":"How Do I...?","migrated-link-0":"Forums","article-series":"Article Series","migrated-link-5":"Community Articles","migrated-link-6":"Articles","security-insights":"Security Insights","migrated-link-3":"CrowdSRC","migrated-link-4":"CodeShare","migrated-link-12":"Events","migrated-link-13":"Suggestions"},"localOverride":false},"CachedAsset:text:en_US-components/community/NavbarHamburgerDropdown-1744046271000":{"__typename":"CachedAsset","id":"text:en_US-components/community/NavbarHamburgerDropdown-1744046271000","value":{"hamburgerLabel":"Side Menu"},"localOverride":false},"CachedAsset:text:en_US-components/community/BrandLogo-1744046271000":{"__typename":"CachedAsset","id":"text:en_US-components/community/BrandLogo-1744046271000","value":{"logoAlt":"Khoros","themeLogoAlt":"Brand Logo"},"localOverride":false},"CachedAsset:text:en_US-components/community/NavbarTextLinks-1744046271000":{"__typename":"CachedAsset","id":"text:en_US-components/community/NavbarTextLinks-1744046271000","value":{"more":"More"},"localOverride":false},"CachedAsset:text:en_US-components/authentication/AuthenticationLink-1744046271000":{"__typename":"CachedAsset","id":"text:en_US-components/authentication/AuthenticationLink-1744046271000","value":{"title.login":"Sign In","title.registration":"Register","title.forgotPassword":"Forgot Password","title.multiAuthLogin":"Sign In"},"localOverride":false},"CachedAsset:text:en_US-components/nodes/NodeLink-1744046271000":{"__typename":"CachedAsset","id":"text:en_US-components/nodes/NodeLink-1744046271000","value":{"place":"Place {name}"},"localOverride":false},"CachedAsset:text:en_US-components/messages/MessageView/MessageViewStandard-1744046271000":{"__typename":"CachedAsset","id":"text:en_US-components/messages/MessageView/MessageViewStandard-1744046271000","value":{"anonymous":"Anonymous","author":"{messageAuthorLogin}","authorBy":"{messageAuthorLogin}","board":"{messageBoardTitle}","replyToUser":" to {parentAuthor}","showMoreReplies":"Show More","replyText":"Reply","repliesText":"Replies","markedAsSolved":"Marked as Solution","movedMessagePlaceholder.BLOG":"{count, plural, =0 {This comment has been} other {These comments have been} }","movedMessagePlaceholder.TKB":"{count, plural, =0 {This comment has been} other {These comments have been} }","movedMessagePlaceholder.FORUM":"{count, plural, =0 {This reply has been} other {These replies have been} }","movedMessagePlaceholder.IDEA":"{count, plural, =0 {This comment has been} other {These comments have been} }","movedMessagePlaceholder.OCCASION":"{count, plural, =0 {This comment has been} other {These comments have been} }","movedMessagePlaceholderUrlText":"moved.","messageStatus":"Status: ","statusChanged":"Status changed: {previousStatus} to {currentStatus}","statusAdded":"Status added: {status}","statusRemoved":"Status removed: {status}","labelExpand":"expand replies","labelCollapse":"collapse replies","unhelpfulReason.reason1":"Content is outdated","unhelpfulReason.reason2":"Article is missing information","unhelpfulReason.reason3":"Content is for a different Product","unhelpfulReason.reason4":"Doesn't match what I was searching for"},"localOverride":false},"CachedAsset:text:en_US-components/messages/ThreadedReplyList-1744046271000":{"__typename":"CachedAsset","id":"text:en_US-components/messages/ThreadedReplyList-1744046271000","value":{"title":"{count, plural, one{# Reply} other{# Replies}}","title@board:BLOG":"{count, plural, one{# Comment} other{# Comments}}","title@board:TKB":"{count, plural, one{# Comment} other{# Comments}}","title@board:IDEA":"{count, plural, one{# Comment} other{# Comments}}","title@board:OCCASION":"{count, plural, one{# Comment} other{# Comments}}","noRepliesTitle":"No Replies","noRepliesTitle@board:BLOG":"No Comments","noRepliesTitle@board:TKB":"No Comments","noRepliesTitle@board:IDEA":"No Comments","noRepliesTitle@board:OCCASION":"No Comments","noRepliesDescription":"Be the first to reply","noRepliesDescription@board:BLOG":"Be the first to comment","noRepliesDescription@board:TKB":"Be the first to comment","noRepliesDescription@board:IDEA":"Be the first to comment","noRepliesDescription@board:OCCASION":"Be the first to comment","messageReadOnlyAlert:BLOG":"Comments have been turned off for this post","messageReadOnlyAlert:TKB":"Comments have been turned off for this article","messageReadOnlyAlert:IDEA":"Comments have been turned off for this idea","messageReadOnlyAlert:FORUM":"Replies have been turned off for this discussion","messageReadOnlyAlert:OCCASION":"Comments have been turned off for this event"},"localOverride":false},"CachedAsset:text:en_US-components/messages/MessageReplyCallToAction-1744046271000":{"__typename":"CachedAsset","id":"text:en_US-components/messages/MessageReplyCallToAction-1744046271000","value":{"leaveReply":"Leave a reply...","leaveReply@board:BLOG@message:root":"Leave a comment...","leaveReply@board:TKB@message:root":"Leave a comment...","leaveReply@board:IDEA@message:root":"Leave a comment...","leaveReply@board:OCCASION@message:root":"Leave a comment...","repliesTurnedOff.FORUM":"Replies are turned off for this topic","repliesTurnedOff.BLOG":"Comments are turned off for this topic","repliesTurnedOff.TKB":"Comments are turned off for this topic","repliesTurnedOff.IDEA":"Comments are turned off for this topic","repliesTurnedOff.OCCASION":"Comments are turned off for this topic","infoText":"Stop poking me!"},"localOverride":false},"CachedAsset:text:en_US-shared/client/components/common/QueryHandler-1744046271000":{"__typename":"CachedAsset","id":"text:en_US-shared/client/components/common/QueryHandler-1744046271000","value":{"title":"Query Handler"},"localOverride":false},"CachedAsset:text:en_US-components/community/NavbarDropdownToggle-1744046271000":{"__typename":"CachedAsset","id":"text:en_US-components/community/NavbarDropdownToggle-1744046271000","value":{"ariaLabelClosed":"Press the down arrow to open the menu"},"localOverride":false},"CachedAsset:text:en_US-components/messages/MessageSubject-1744046271000":{"__typename":"CachedAsset","id":"text:en_US-components/messages/MessageSubject-1744046271000","value":{"noSubject":"(no subject)"},"localOverride":false},"CachedAsset:text:en_US-components/messages/MessageBody-1744046271000":{"__typename":"CachedAsset","id":"text:en_US-components/messages/MessageBody-1744046271000","value":{"showMessageBody":"Show More","mentionsErrorTitle":"{mentionsType, select, board {Board} user {User} message {Message} other {}} No Longer Available","mentionsErrorMessage":"The {mentionsType} you are trying to view has been removed from the community.","videoProcessing":"Video is being processed. Please try again in a few minutes.","bannerTitle":"Video provider requires cookies to play the video. Accept to continue or {url} it directly on the provider's site.","buttonTitle":"Accept","urlText":"watch"},"localOverride":false},"CachedAsset:text:en_US-components/messages/MessageCustomFields-1744046271000":{"__typename":"CachedAsset","id":"text:en_US-components/messages/MessageCustomFields-1744046271000","value":{"CustomField.default.label":"Value of {name}"},"localOverride":false},"CachedAsset:text:en_US-components/messages/MessageRevision-1744046271000":{"__typename":"CachedAsset","id":"text:en_US-components/messages/MessageRevision-1744046271000","value":{"lastUpdatedDatePublished":"{publishCount, plural, one{Published} other{Updated}} {date}","lastUpdatedDateDraft":"Created {date}","version":"Version {major}.{minor}"},"localOverride":false},"CachedAsset:text:en_US-components/messages/MessageReplyButton-1744046271000":{"__typename":"CachedAsset","id":"text:en_US-components/messages/MessageReplyButton-1744046271000","value":{"repliesCount":"{count}","title":"Reply","title@board:BLOG@message:root":"Comment","title@board:TKB@message:root":"Comment","title@board:IDEA@message:root":"Comment","title@board:OCCASION@message:root":"Comment"},"localOverride":false},"CachedAsset:text:en_US-components/messages/MessageAuthorBio-1744046271000":{"__typename":"CachedAsset","id":"text:en_US-components/messages/MessageAuthorBio-1744046271000","value":{"sendMessage":"Send Message","actionMessage":"Follow this blog board to get notified when there's new activity","coAuthor":"CO-PUBLISHER","contributor":"CONTRIBUTOR","userProfile":"View Profile","iconlink":"Go to {name} {type}"},"localOverride":false},"CachedAsset:text:en_US-components/guides/GuideBottomNavigation-1744046271000":{"__typename":"CachedAsset","id":"text:en_US-components/guides/GuideBottomNavigation-1744046271000","value":{"nav.label":"Previous/Next Page","nav.previous":"Previous","nav.next":"Next"},"localOverride":false},"CachedAsset:text:en_US-components/tags/TagView/TagViewChip-1744046271000":{"__typename":"CachedAsset","id":"text:en_US-components/tags/TagView/TagViewChip-1744046271000","value":{"tagLabelName":"Tag name {tagName}"},"localOverride":false},"CachedAsset:text:en_US-components/users/UserLink-1744046271000":{"__typename":"CachedAsset","id":"text:en_US-components/users/UserLink-1744046271000","value":{"authorName":"View Profile: {author}","anonymous":"Anonymous"},"localOverride":false},"CachedAsset:text:en_US-shared/client/components/users/UserRank-1744046271000":{"__typename":"CachedAsset","id":"text:en_US-shared/client/components/users/UserRank-1744046271000","value":{"rankName":"{rankName}","userRank":"Author rank {rankName}"},"localOverride":false},"CachedAsset:text:en_US-components/users/UserRegistrationDate-1744046271000":{"__typename":"CachedAsset","id":"text:en_US-components/users/UserRegistrationDate-1744046271000","value":{"noPrefix":"{date}","withPrefix":"Joined {date}"},"localOverride":false},"CachedAsset:text:en_US-components/customComponent/CustomComponent-1744046271000":{"__typename":"CachedAsset","id":"text:en_US-components/customComponent/CustomComponent-1744046271000","value":{"errorMessage":"Error rendering component id: {customComponentId}","bannerTitle":"Video provider requires cookies to play the video. Accept to continue or {url} it directly on the provider's site.","buttonTitle":"Accept","urlText":"watch"},"localOverride":false},"CachedAsset:text:en_US-shared/client/components/users/UserAvatar-1744046271000":{"__typename":"CachedAsset","id":"text:en_US-shared/client/components/users/UserAvatar-1744046271000","value":{"altText":"{login}'s avatar","altTextGeneric":"User's avatar"},"localOverride":false},"CachedAsset:text:en_US-shared/client/components/ranks/UserRankLabel-1744046271000":{"__typename":"CachedAsset","id":"text:en_US-shared/client/components/ranks/UserRankLabel-1744046271000","value":{"altTitle":"Icon for {rankName} rank"},"localOverride":false}}}},"page":"/kbs/TkbMessagePage/TkbMessagePage","query":{"boardId":"technicalarticles","messageSubject":"tls-fingerprinting-update---a-method-for-identifying-a-tls-client-without-decryp","messageId":"286117"},"buildId":"FP3zsFp6DJl70wDujOvtu","runtimeConfig":{"buildInformationVisible":false,"logLevelApp":"info","logLevelMetrics":"info","openTelemetryClientEnabled":false,"openTelemetryConfigName":"f5","openTelemetryServiceVersion":"25.3.0","openTelemetryUniverse":"prod","openTelemetryCollector":"http://localhost:4318","openTelemetryRouteChangeAllowedTime":"5000","apolloDevToolsEnabled":false,"inboxMuteWipFeatureEnabled":false},"isFallback":false,"isExperimentalCompile":false,"dynamicIds":["./components/customComponent/CustomComponent/CustomComponent.tsx","./components/community/Navbar/NavbarWidget.tsx","./components/community/Breadcrumb/BreadcrumbWidget.tsx","./components/tkbs/TkbArticleWidget/TkbArticleWidget.tsx","./components/messages/MessageView/MessageViewStandard/MessageViewStandard.tsx","./components/messages/ThreadedReplyList/ThreadedReplyList.tsx","./components/customComponent/CustomComponentContent/TemplateContent.tsx","../shared/client/components/common/List/UnwrappedList/UnwrappedList.tsx","./components/tags/TagView/TagView.tsx","./components/tags/TagView/TagViewChip/TagViewChip.tsx","./components/customComponent/CustomComponentContent/HtmlContent.tsx","./components/customComponent/CustomComponentContent/CustomComponentScripts.tsx"],"appGip":true,"scriptLoader":[]}