HackTheBox DutyBound Writeup | SQL Injection to Root (HTB)

Hack The Box DutyBound Achievement Banner

October 25, 2026

10 min read
Medium Linux HackTheBox Season 12 SQL Injection PostgreSQL
Attack Chain
BCBP Boarding Pass → SQL Injection → PostgreSQL RCE → X11 Desktop Pivot → Kiosk User → duty-rates.csv → systemd Path Unit → eval Injection → Root

HTB DutyBound is a Medium-rated Linux machine from Hack The Box Season 12 that demonstrates how several seemingly unrelated security weaknesses can combine into a complete system compromise.

The initial foothold comes from a boarding-pass verification endpoint that accepts a structured Bar Coded Boarding Pass (BCBP). Although the application validates mandatory boarding-pass fields, attacker-controlled airline-use data reaches an SQL query constructed through unsafe string interpolation.

By exploiting this weakness, we discover an overprivileged PostgreSQL role capable of executing operating-system commands. Access to a live X11 desktop session then enables a pivot into another user's environment.

Finally, a writable CSV file monitored by a privileged systemd service creates a path to root through unsafe shell evaluation.

What Makes DutyBound Interesting: The machine requires us to follow multiple trust boundaries, from structured web input and database permissions to desktop session isolation and privileged background services.

Want more detailed walkthroughs, writeups, and cybersecurity content?

01. External Reconnaissance

The first step was to identify the exposed network services. A full TCP scan revealed three open ports:

Port Service Observation
22/tcp OpenSSH 8.9p1 SSH service
80/tcp nginx Redirects to HTTPS
443/tcp nginx 1.18.0 Main web application

The following Nmap command performs a full TCP port scan:

nmap -Pn -n -p- --min-rate 1000 -T4 10.129.19.28

Visiting the web application revealed AirSnacks, a self-service checkout application.

The application's main functionality included browsing products, managing a shopping cart, and completing a checkout process that involved boarding-pass verification.

The most interesting discovery came from the frontend JavaScript file:

/static/app.js

Inside this file, we discovered a request to the boarding-pass verification endpoint:

postJSON("/checkout/boarding-pass/verify", { raw: "" });

This endpoint accepts raw boarding-pass data and processes it through the application's backend.

Key Insight: The verification endpoint accepts structured BCBP input rather than arbitrary text. Successful verification also triggers an audit logging operation, making the input parsing and database-writing logic particularly interesting.

02. Constructing a Valid Boarding Pass

BCBP stands for Bar Coded Boarding Pass. It is a structured format used to encode passenger and flight information within boarding passes.

The verification endpoint validates multiple mandatory fields before accepting the supplied data.

During testing, the application's error messages revealed several important requirements:

  • The boarding-pass input must begin with M.
  • The seat field must follow a specific format.
  • Required conditional sections must be present.
  • The conditional section length must be represented correctly.

The basic structure of the expected input is:

Fixed BCBP fields
        +
Two-character hexadecimal conditional length
        +
Conditional section

After constructing an acceptable boarding pass, the application returned HTTP status 200.

The successful response included an audit reference, destination information, and an eligibility token.

This confirmed that the submitted boarding pass passed the application's validation process.

However, successful validation alone did not demonstrate SQL injection. We still needed to determine whether attacker-controlled fields were handled safely during subsequent database operations.

Important Lesson: A useful vulnerability test must reach the relevant backend code path. If input is rejected by a parser before reaching the database, the result tells us very little about the security of the database interaction.

03. SQL Injection in Airline-Use Data

The decisive vulnerability was found in the processing of the BCBP conditional section.

After accepting the validated boarding-pass prefix, the application also processed additional airline-use data.

Testing this field with a single quotation mark caused the application to return a PostgreSQL error.

This strongly suggested that user-controlled data was being inserted into an SQL statement without proper parameterization.

An error-based SQL expression then caused PostgreSQL to attempt an integer conversion of the current database username.

The resulting error revealed the application database role:

airsnacks_app

Further database enumeration revealed several interesting tables:

  • bp_scan_audit
  • duty_rate
  • store_config

The audit table also contained a kiosk identifier:

dutyfree-kiosk7

This connected the AirSnacks web application to the underlying duty-free kiosk environment.

The most significant finding concerned the PostgreSQL role's privileges.

Critical Finding: The PostgreSQL application role was a member of pg_execute_server_program. This privilege allows server-side program execution through PostgreSQL features such as COPY ... TO PROGRAM.

This means that the SQL injection was not limited to reading or modifying database records.

Because the application role possessed excessive privileges, the SQL injection could be extended into operating-system command execution under the PostgreSQL service account.

The root cause was eventually traced to the application's audit logging functionality.

The function record_export_evidence constructed an SQL INSERT statement using Python string interpolation with the % operator.

The attacker-controlled airline_use value was inserted into a quoted SQL value before the statement was executed.

This allowed SQL syntax supplied through the boarding-pass data to change the intended query structure.

Three weaknesses combined to make this possible:

  1. Insufficient validation across trust boundaries.
  2. Unsafe SQL statement construction.
  3. Excessive database privileges.

Security Lesson: Even a relatively straightforward SQL injection can become a host-level compromise when the database account has permissions to execute operating-system programs.

Enjoying this writeup? Get the full cybersecurity learning experience with CyberMaster.

04. From the Database Process to the Kiosk User

The command execution capability obtained through PostgreSQL ran under the operating-system account postgres.

After establishing this foothold, we began examining running processes and locally accessible resources.

Process enumeration revealed that another account, dutyfree-kiosk, was already logged into an Openbox desktop session on X display :0.

The desktop environment was running Firefox in kiosk mode.

Further inspection revealed that the Openbox configuration file contained a keyboard shortcut for launching a terminal:

/etc/xdg/openbox/rc.xml

The configuration mapped the following keyboard combination to launching xterm:

Ctrl + Alt + T

The environment also included Python Xlib and the XTEST extension.

These components can generate synthetic keyboard events through an accessible X11 display.

By interacting with the active desktop session, we triggered the keyboard shortcut and opened a terminal under the logged-in kiosk user's context.

This provided command execution as dutyfree-kiosk.

The user flag was then recovered from:

/home/dutyfree-kiosk/user.txt

Key Insight: X11 uses a security model in which clients with access to a display may observe or influence activity within the session. Allowing a database service account to access an interactive user's display can undermine process and user isolation.

05. Finding a Writable Root-Managed Input File

After gaining access as dutyfree-kiosk, we examined the account's group memberships and filesystem permissions.

The kiosk user belonged to the pos-ops group.

Further enumeration revealed a duty-rate CSV file located at:

/opt/vrs/data/duty-rates.csv

The file was owned by root:pos-ops and was group-writable.

Its parent directory was also writable by the same group.

This meant the kiosk user could modify data consumed by another component of the system.

Further investigation revealed an active systemd path unit:

vrs-duty-apply.path

This unit monitored the duty-rate CSV file for changes.

When the file was modified, systemd automatically triggered:

vrs-duty-apply.service

The service executed the following shell script with root privileges:

/opt/vrs/bin/apply-duty-rates.sh

This created a dangerous trust relationship: a lower-privileged account could modify a file automatically processed by a root-owned service.

Examining the shell script revealed the decisive vulnerability.

The script read fields from the CSV file and constructed an assignment using the following expression:

eval "${tax_class}=${duty_pct}"

Although the script validated some numeric values, it failed to safely handle the first field.

Because tax_class originated from a writable CSV file, attacker-controlled content could be interpreted as shell syntax.

The Vulnerability: The privileged script used eval on data derived from a lower-privileged user's file. This converted untrusted input into executable shell code.

06. Root Compromise

With control over the watched CSV file and an understanding of the vulnerable script, the final privilege escalation became possible.

After modifying the CSV file, the systemd path unit detected the change and activated the associated service.

Because the service ran as root, the script processed the attacker-controlled field with elevated privileges.

The unsafe eval expression interpreted the supplied data as shell code before the remaining validation and database import operations completed.

This provided the final privilege escalation to root.

root.txt: DutyBound fully compromised

Unlike many Linux privilege escalation challenges, the final vulnerability did not depend on a conventional SUID executable or an obvious sudo misconfiguration.

Instead, it resulted from the interaction of several components:

  1. A lower-privileged user with group-based write access.
  2. A writable CSV file consumed by a privileged process.
  3. A systemd path unit that automatically triggered a service.
  4. A shell script running with root privileges.
  5. Incomplete input validation.
  6. Unsafe use of the shell's eval functionality.

Key Insight: Privileged background services are particularly dangerous when they automatically process files writable by lower-privileged accounts. Input validation must be enforced before data reaches any operation capable of interpreting it as code.

07. Complete Attack-Chain Summary

AirSnacks Web Application
        |
        v
BCBP Verification Endpoint
        |
        v
SQL Injection in Audit Logging
        |
        v
PostgreSQL Server-Program Privilege
        |
        v
Shell as postgres
        |
        v
Access to Active X11 Desktop
        |
        v
Shell as dutyfree-kiosk
        |
        v
Write Access to duty-rates.csv
        |
        v
systemd Path-Triggered Root Service
        |
        v
Unsafe Shell eval
        |
        v
Root Access

The most important feature of this attack chain is that each vulnerability exposes another trust boundary.

The SQL injection provided access to the database host. Excessive PostgreSQL privileges extended the attack to operating-system command execution.

The database process could interact with a live desktop session, enabling access to the kiosk user's environment.

Finally, the kiosk user could modify a file consumed by a root-level service, which interpreted that data as shell code.

08. Defensive Recommendations

1. Use Parameterized SQL. Never construct SQL statements by interpolating untrusted values. Use parameter placeholders and pass values separately through the database driver's parameter-binding API.

2. Minimize PostgreSQL Privileges. Avoid granting pg_execute_server_program to application roles. Separate ordinary application database access from privileged administrative operations.

3. Isolate Graphical Sessions. A database service should not be able to control an interactive user's desktop. Prevent unrelated service accounts from accessing display sockets and authentication material.

4. Never Evaluate Untrusted Data as Shell Code. Validate input against a strict allowlist and parse fields as data. Use explicit mappings instead of dynamically evaluated shell assignments.

5. Review File and Directory Permissions. Files consumed by privileged services should not be writable by untrusted accounts. Review ownership, group memberships, directory permissions, and file replacement possibilities.

6. Harden systemd Services. Apply filesystem restrictions and appropriate service sandboxing. Avoid running shell interpreters with elevated privileges when safer implementations are available.

7. Improve Error Handling and Audit Logging. Detailed database errors should not be returned to users. Logging functions must follow the same input validation and SQL parameterization requirements as other application components.

09. Key Takeaways

  • Structured input validation does not guarantee security when later processing stages handle user-controlled fields unsafely.
  • SQL injection can become a host-compromise vulnerability when the application database role has excessive privileges.
  • Database processes should not have unnecessary access to active interactive desktop sessions.
  • Writable files become dangerous when privileged services automatically trust and process their contents.
  • A systemd path unit can automatically trigger privileged execution when a monitored file changes.
  • Shell eval introduces serious security risks when its input originates from lower-trust sources.
  • Privilege escalation frequently results from multiple overlooked trust relationships rather than a single obvious misconfiguration.

10. Conclusion

HTB DutyBound is an excellent example of how application-layer and operating-system weaknesses can combine into a complete system compromise.

The initial vulnerability was unsafe SQL construction in the boarding-pass audit logging process. Although the application performed structured BCBP validation, attacker-controlled airline-use data reached the database through an unsafe query.

Excessive PostgreSQL privileges extended the impact of the SQL injection to command execution on the underlying Linux host.

Access to an active X11 desktop session then enabled a pivot into the dutyfree-kiosk user's environment.

Finally, the kiosk user's ability to modify a root-managed duty-rate CSV file, combined with a systemd path-triggered service and unsafe shell evaluation, enabled the final privilege escalation.

For defenders, the priority is to break this chain at multiple points. Parameterized SQL, reduced database privileges, proper desktop session isolation, restricted file permissions, and safe processing of untrusted data would each reduce the attack surface.

For penetration testers, DutyBound is a reminder to examine the entire application and host environment. Obtaining an initial shell is often only the beginning. Understanding how services, files, users, and privileged processes interact can reveal the most significant vulnerabilities.

For more detailed walkthroughs, writeups, and cybersecurity content

Download the CyberMaster app and support the work.

HTB DutyBound Writeup · Medium · Season 12 · Linux

Kebal Bhandari

As an experienced Fullstack Senior Software Developer with a strong background in IT administration and a growing interest in cybersecurity, I am eager to learn more about the complexities of online security, risk management, and data analytics.

Post a Comment

Express your opinion

Previous Post Next Post

Download Cyber Master

Download Cyber Master