Forum Discussion
Jeremy_Church_3
Cirrus
Hello Tony,
Thoughts on the XFF header:- Contents of X-Forwarded-For are not guaranteed to be valid or accurate IP addresses.
- Multiple headers are possible in a single request.
- If a single XFF header contains multiple addresses, it will likely be comma-space delimited e.g.
though not guaranteed.1.1.1.1, 2.2.2.2
- It is possible to have a combination of multiple XFF headers with some containing multiple addresses.
- The X-Forwarded-For header can be inserted by anyone with any address they choose. It is important to understand filtering by XFF does not provide security.
If possible, it would be preferable if a custom header colud be added similar to what CDNs do, e.g.
True-Client-IP
, to reduce the chance of anomalous values from occuring.
Parsing IPs from X-Forwarded-For
When using the method:
[getfield [HTTP::header values X-Forwarded-For] " " 1]
which is similar to:
[lindex [HTTP::header values X-Forwarded-For] 0]
you may end up with something like:
1.1.1.1,
which is not a valid IP causing a TCL error.
What I have done in the past
I'll be honest, I'm not 100% clear on your 2nd objective, but here is a possible rule based on my interpretation. This may not be the most efficient rule, but it should help to reduce the number of TCL error you may encounter and maybe point you in the right direction.
when HTTP_REQUEST {
if {[class match [HTTP::uri] starts_with DG-ALLOWED-URI-LIST]} {
this is a secure URI
check the real source IP
set MATCH [class match [IP::client_addr] equals DG-ALLOWED-IP-XFF]
cycle the X-Forwarded-For headers
foreach HDR [HTTP::header values X-Forwarded-For] {
if {$MATCH} { break }
format the X-Forwarded-For header into a valid TCL list
foreach IP [string trim [regsub -all {[,; ]+} $HDR " "]] {
if {[catch {set MATCH [class match $IP equals DG-ALLOWED-IP-XFF]}] != 0} {
log local0.err "[virtual name]: Client [IP::client_addr] \
had an invalid X-Forwarded-For address - \x22${IP}\x22"
set MATCH 0
}
if {$MATCH} { break }
}
}
if {!$MATCH} { HTTP::respond 403 Connection close }
unset MATCH
}
}
Notes
- This rule checks your URI once. The example/objective you provided seems to be performing more than one match so it may need to be modified.
- The outside/first
loop cycles each XFF header individually. Usually there is only 1, but it is possible for multiple headers to exist.foreach
- The inside/second
loop usesforeach
+regsub
to strip any seperator characters resulting in a valid TCL list.string trim
- When matching the address with
, theclass match
command is used to prevent a TCL error and a log message created for later review.catch
- Instead of
, the rule is responding with a 403 Forbidden. Thereject
command works too, but my preference is a 403 response which is effectively the HTTP version of reject.reject
- The logic in this rule can also match the real source IP and won't cycle X-Forwarded-For values if it matches. This may or may not be what you need. If not, the rule would need a
in its place.set MATCH 0
Tony2020
Mar 25, 2017Nimbostratus
Thank you Jeremy!
If I want to integrate this in, how would you add it to this code? So if your XFF IP is in the data group, and you are trying to access this URI "/uri1/uri2/uri3/adminpage", than redirect to "/secret/uri1/uri2/uri3/adminpage/"...
Thank you for your recommendation.
if { ([class match $CHECK_IP eq "DG-ALLOWED-IP-XFF"]) } {
if { [HTTP::uri] eq "*/uri1/uri2/uri3/adminpage*"} {
HTTP::redirect "https://[HTTP::host]/secret/uri1/uri2/uri3/adminpage/" }