The X-Forwarded-For (XFF) header is a de-facto standard header for identifying the originating IP address of a client connecting to a web server through an HTTP proxy or a load balancer. When traffic is intercepted between clients and servers, server access logs contain the IP address of the proxy or load balancer only. To see the original IP address of the client, the X-Forwarded-For request header is used. Mozilla

A standardized version of this header is the HTTP Forwarded header.The alternative versions of this header are the X-Forwarded-Host and X-Forwarded-Proto headers.

When building infrastructure for customer environments, it might be important to capture the X-Forwarded-For header if there is an expectation that you would be able to provide real source IP information to the detection team. This is especially true behind load balancers. This post shows how to configure logging for Apache on various distributions. For Microsoft IIS it’s a little more clicky clicky: this one post good. It’s in the logging > “select fields” section and we are adding a custom field:



Why the sudden interest?

There was a Hack the box machine recently that required some modification of the header to gain access to the administrative console. It’s called “Control” and you can find it here. I’ll also freely admit that i haven’t finished this one yet - it’s hard! But i wanted to jot down some notes about XFF since it comes up at work a lot.


We started out in the usual way: nmap -sC -sV

And identified:

root@kali:~# nmap -sC -sV
Starting Nmap 7.80 ( ) at 2020-04-25 14:48 EDT
Nmap scan report for
Host is up (0.10s latency).
Not shown: 997 filtered ports
80/tcp   open  http    Microsoft IIS httpd 10.0
| http-methods: 
|_  Potentially risky methods: TRACE
|_http-server-header: Microsoft-IIS/10.0
|_http-title: Fidelity
135/tcp  open  msrpc   Microsoft Windows RPC
3306/tcp open  mysql?
Service Info: OS: Windows; CPE: cpe:/o:microsoft:windows

Service detection performed. Please report any incorrect results at .
Nmap done: 1 IP address (1 host up) scanned in 34.41 seconds

(i suppose) MySQL is interesting on a Windows box, but attempting to connect with mysql -h didn’t allow any access right away.

The web page on port 80 seemed somewhat interesting, and had login/logout (although they didn’t appear to work right away); this note in the source (ctrl+u) seemed valuable though:


Some more recon on the website with gobuster: gobuster dir -u -x php -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt revealed an admin page (among other things):

Gobuster v3.0.1
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@_FireFart_)
[+] Url:  
[+] Threads:        10
[+] Wordlist:       /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt
[+] Status codes:   200,204,301,302,307,401,403
[+] User Agent:     gobuster/3.0.1
[+] Extensions:     php
[+] Timeout:        10s
2020/04/25 14:59:36 Starting gobuster
/index.php (Status: 200)
/images (Status: 301)
/about.php (Status: 200)
/uploads (Status: 301)
/Images (Status: 301)
/admin.php (Status: 200)
/assets (Status: 301)
/About.php (Status: 200)
/Index.php (Status: 200)
/database.php (Status: 200)

Unfortunately, the admin page responded with:

Access Denied: Header Missing. Please ensure you go through the proxy to access this page

And, that’s where i was stumped for a bit. (Circling around the other potential website approaches and the ports from the original scan).

Eventually after considering that another proxy was probably out of scope of this HTB scenario and the message seemed to be a clue I considered trying to mess with it.

The other clue we have is the machine referenced in the HTML source above.

I checked if that was the magic ticket with burp:


Sweet! That looks good.

Also worth mentioning that it seemed likely that . would not be the actual IP, but just a clue to the subnet, i had planned to use intruder and iterate through the last octet:


Then sort the output by size. You can see that there is a request (number 28) that appears to be much larger than the others. On closer inspection of the response, it appears we are logged in.


Anyway, it looks like the proxy is at and that the X-Forwarded-For header will work for our purpose.

Last thing to do, it to confirm it works using burp’s intercept function to change the request from the browser.


Done! We’re in the admin console. Modifying the X-Forwarded-For header in our HTTP request to appear as though we came through the proxy did the trick.

Anyway, I thought that was cool, and crossed over with work we do on the defensive side to make sure we log these things for our deployments. So, this post is going to be my little repo of X-Forwarded-For notes :)

(I’ll also keep working on this pretty dang hard machine)

Blog Logo

Chad Duffey



Chad Duffey

Blue Team -> Exploit Development & things in-between

Back to Overview