Forum Discussion
Host Header Validation
Greetings,
I have been tasked with crafting an iRule to validate the host header of incoming packets to a given virtual server. I have tried a few different irules to attempt this an yet I am told that the security team is able to modify the host header of the packet and still get around the iRule's checks. Would someone be able to look over the iRule's and tell me what I may be missing/doing wrong?
when HTTP_REQUEST if { [HTTP::host] equals "xyz.com"}{ pool /Common/xyz_pool }else{ reject } }
(this one below is more an attempt to force the correct one than validate whats coming in)
when HTTP_REQUEST { if {[HTTP::header exists Host]}{ HTTP::Header replace Host xyz.com }else{ HTTP::header replace Host xyz.com } }
A data-group is a convenience rather than a necessity.
I tried the following on 11.5.4:
when HTTP_REQUEST { if { [HTTP::host] ne "xyz.com" } { reject } }
I tried all of the following combinations:
- HTTP/1.1 Host header xyz.com;
- HTTP/1.1 Host header foo.com;
- HTTP/1.0 no Host header;
- HTTP/1.1 Host header xyz.com followed by HTTP/1.0 no Host header;
- HTTP/1.1 Host header xyz.com followed by HTTP/1.1 Host header foo.com
Case 1: allowed;
Case 2: rejected (i.e., TCP RST);
Case 3: rejected;
Case 4: allowed then rejected;
Case 5: allowed then rejected.
Incidentally, if a pool is assigned to the VS, then the else clause isn't needed (it's the default anyway).
Having said all of that, your issue may be relate to this note, found in the reject explanation:
Subsequent code in the current event in the current iRule or other iRules on the VS are still executed prior to the reset being sent.
I recommend putting a return after the reject.
- ekaleidoCirrus
You need an datagroup that is essentially a whitelist. Host headers are user submitted, so the user can manipulate what is sent in. Try something like this..
datagroup would be a string type and would contain something like this:
www.example.com := www.example.com_pool
when HTTP_REQUEST { set hostpool [class match -value [string tolower [HTTP::host]] equals datagroup_name if { $hostpool ne "" } { pool $hostpool HTTP::header replace Host [HTTP::host] } else { discard } }
- VernonWellsEmployee
A data-group is a convenience rather than a necessity.
I tried the following on 11.5.4:
when HTTP_REQUEST { if { [HTTP::host] ne "xyz.com" } { reject } }
I tried all of the following combinations:
- HTTP/1.1 Host header xyz.com;
- HTTP/1.1 Host header foo.com;
- HTTP/1.0 no Host header;
- HTTP/1.1 Host header xyz.com followed by HTTP/1.0 no Host header;
- HTTP/1.1 Host header xyz.com followed by HTTP/1.1 Host header foo.com
Case 1: allowed;
Case 2: rejected (i.e., TCP RST);
Case 3: rejected;
Case 4: allowed then rejected;
Case 5: allowed then rejected.
Incidentally, if a pool is assigned to the VS, then the else clause isn't needed (it's the default anyway).
Having said all of that, your issue may be relate to this note, found in the reject explanation:
Subsequent code in the current event in the current iRule or other iRules on the VS are still executed prior to the reset being sent.
I recommend putting a return after the reject.
- Chronos_258816Nimbostratus
Good to know that it should be working as intended as per your examples above. I have placed the return after the reject and hope that takes care of the issue. If not, I need to see just how the exploits are being done.
- ekaleidoCirrus
Curious because I'm not near a VE to test on, but is the following case sensitive:
[HTTP::host] ne "xyz.com"
As in, could Xyz.com create a different behavior?
- VernonWellsEmployee
Naturally, but actually, that snippet is "more secure", in the sense that it is stricter. That is, if the Host header is Xyz.com (which, by RFC, means exactly the same thing as xyz.com), then it will be rejected. To allow any case (which, again, would be the RFC conforming choice):
if { [string tolower [HTTP::host]] ne "xyz.com" } { reject }
- Vijay_ECirrus
Are you using OneConnect ? By default F5 makes load balancing decision based on the very 1st HTTP request and once a pool member is selected, subsequent HTTP requests within the same TCP connection is sent to the same pool member. You can force the F5 to make a load balancing decision per HTTP Request instead of the default per TCP connection by using OneConnect.
- VernonWellsEmployee
HTTP_REQUEST should fire on each received HTTP Request message (strictly speaking, once the HTTP Request headers are fully received) regardless of the OneConnect setting. To demonstrate this, I use the following rule:
when HTTP_REQUEST { log local0. "Received Host header: [HTTP::host]" }
On a VS without OneConnect, I do the following:
- Single request for Host = aaa.com in single connection; then
- Request for bbb.com, then ccc.com, then ddd.com in a single connection.
This is what the log looks like after these two connections, which collectively include four requests:
Rule /Common/rule-validate-host : Received Host header: aaa.com Rule /Common/rule-validate-host : Received Host header: bbb.com Rule /Common/rule-validate-host : Received Host header: ccc.com Rule /Common/rule-validate-host : Received Host header: ddd.com
Then I apply OneConnect to the same VS and repeat the tests. The logs:
Rule /Common/rule-validate-host : Received Host header: aaa.com Rule /Common/rule-validate-host : Received Host header: bbb.com Rule /Common/rule-validate-host : Received Host header: ccc.com Rule /Common/rule-validate-host : Received Host header: ddd.com
The long-and-short: HTTP_REQUEST fires on each Request message regardless of Keep-Alive and OneConnect.
Recent Discussions
Related Content
* Getting Started on DevCentral
* Community Guidelines
* Community Terms of Use / EULA
* Community Ranking Explained
* Community Resources
* Contact the DevCentral Team
* Update MFA on account.f5.com