IronHold
The source leaked. Read it like an attacker, chain the flaws, and shell the door-control server. - by empanadaL0ver
The following post by 0xb0b is licensed under CC BY 4.0
Scenario
IronHold is retiring its inmate-management platform. Somewhere in the handover, a developer pushed the complete repository to a public mirror and then left the company. Facility security wants a straight answer before the system goes dark for good: if that repository is out there, how far could someone actually get?
We start with nothing but what leaked: the full, unredacted source, and a live copy of the application still running on the network. No credentials, no map, no walkthrough. The code tells us what the developers got wrong; the running instance tells us if we're right.
Get all four and Ironhold's last system goes down the same way it went up: on its own mistakes.
Download the source archive attached to this task and start reading. The lab machine is reachable at http://<IP>:8080.
Summary
Summary
In IronHold, we start with a leaked source repository and a live instance on port 8080. A source code review revealed that DataAccessConfig.java exposes hardcoded database credentials, DataSeeder.java seeds four staff accounts and hints that a flag sits in case file IA-2024-007, and controller review turns up three flaws: string-concatenated SQL in InmateController.search(), a mass-assignment vulnerability in ProfileController.update() that lets any user set their own role, and unsafe ObjectInputStream.readObject()deserialization in ImportExportController.importData().
Logging in with a seeded account gets us the first flag on the dashboard. A UNION-based SQL injection against the search endpoint enumerates information_schema.tables and pulls the case_files record for IA-2024-007, yielding the second flag. Abusing the mass-assignment flaw by ntercepting a profile update and adding role=warden, promotes us to warden and unlocks the third flag at /admin/control. Finally we send a serialized batch to /admin/import using ysoserial CommonsCollections6, confirm code execution via a curl callback, then land a base64-wrapped reverse shell as appuser to grab the final flag at /opt/ironhold/flag.txt.
Recon
We use rustscan -b 500 -a 10.113.191.174 --top -- -sC -sV -Pn to enumerate all TCP ports on the target machine, piping the discovered results into Nmap, which runs default NSE scripts -sC, service and version detection -sV, and treats the host as online without ICMP echo -Pn.
A batch size of 500 trades speed for stability, the default 1500 balances both, while much larger sizes increase throughput but risk missed responses and instability.
We confirm the service is running on port 8080.

We visit the site and have a staff log in portal in front of us.

Source Code Analysis
We'll continue with a source code analysis and take a look at each individual class, especially the controllers. We'll start with the configuration files. From DataAccessConfig.java, we can extract the database connection details and find hard-coded credentials for the database user; we can also see that an H2 database is being used.

The DataSeeder.java file is particularly interesting. It populates the app initially. From this file, we can extract, among other things, the default credentials for four users.

We also see that the second flag is located in case file with the case_number IA-2024-007.

There is an SQL injection vulnerability in the InmateController.search() method.
The search endpoint builds its query by directly concatenating the unsanitized q request parameter into a SQL string, rather than binding it as a parameter. Because q is inserted inline inside the string literal rather than passed as a bind variable, any single quote or SQL syntax the user supplies is parsed as part of the query itself rather than as literal data.

The POST /profile/update binds the request body directly onto the Staff object via @ModelAttribute, and the handler copies role from that binding onto the current user's own record with no authorization check. Any authenticated staff member can therefore submit role=WARDEN in the POST body and grant themselves that privilege level.

Furthermore there is an insecure deserialization on ImportExportController.importData().
The POST /admin/import endpoint receives and base64-decodes the raw request body and passes it directly to ObjectInputStream.readObject()with no type filtering, and the classpath includes commons-collections 3.2.1, a version with a publicly known exploitable gadget chain. This gives an unauthenticated user a path to remote code execution.


Our attack path, as identified through source code analysis, is as follows. We first attempt to gain access to the internal dashboard using hard-coded credentials found and then escalate our privileges by exploiting the mass assignment vulnerability via the profile update. From there, we should be able to access the admin functions, which we will then use to achieve remote code execution by exploiting the insecure deserialization vulnerability via the import feature.
Once we have access as any user, we try to leverage the SQL injection vulnerability in the inmate search to retrieve the hidden case file.
Access as j.reyes
We use the hard coded credentials we found from seed/DataSeeder.java and login.

We are being redirected to the dashboard and get the first flag.

With authentication we are able to visit the inmates endpoint and have access to the search functionlity which we discoverd to be vulnerable to SQL injeciton.

We try a simple UNION injection and are successful.

From there we query for the available tables.

We recall the case number containing the second flag:

Next, we query for the summary of the case 7IA-2024-007 and retrieve the second flag.

Access as Warden
Next, we head to the profile endpoint to update our profile and abuse the mass assignment vulnerability to escalate our privilges to become a warden.


We catch an update request using Burp Suite and redirect the request to the repeater module.

We add the parameter role=warden and send the request.

We reload the page and see that we have become a warden.

If we access the admin control panel now, we can see that we have access to the third flag and can retrieve it.

Shell as appuser
Next, we visit the import endpoint we identified to be vulnerable to insecure deserialization. From there we want to achive remote code execution.

On the import page, we find the following note. This means we could start a bulk import directly using curl.
But first we test the import with a simple cURL command. We will use the present serialized data from /admin/export. That way we can make sure our curl command is working correctly.
First, we request the already serialized data from /admin/export and save the result to a variable.
Next, we try to resend it and see that the Batch got accepted. Our request to import the data is working correctly.

Next, we want to leverage ysoserial to get remote code execution. We prepare a docker container to launch the recent release of ysoserial to craft a payload.

From the pom.xml file, we saw that commons-collections is being used and that the import provides a sink for insecure deserialization.
First we want to simply try each CommonsCollections option with a simple payload to see if one of those options work. More complex commands need work arounds, but for now we just want to proof our remote code execution. For this we will try to connect to our web server with a simple curl command.
We spin up a python web server.
Prepare a payload with CommonsCollections6....

... and import the data.

We receive a connection back. The option CommonCollections6 is working and we can execute simple commands.

Now we want to get a reverse shell. For this we follow the following resource to execute more complex commands:
Regarding command execution payloads failure while providing
Runtime.getRuntime().exec()multiple commands, we should be using this website for building our payload, which will be divided into different key-surrounded commands who are supported by bash
We do not need the website to craft a payload, but can be reached via the Wayback-Machine: https://web.archive.org/web/20220126205656/https://www.jackson-t.ca/runtime-exec-payloads.html
We'll adapt the following payload:
We prepare a reverse shell and encode it in base64 and replace it with the one from the resource.
Next, we prepare and run the ysoserial command.

We upload the resulting payload...

... and receive a connection back. We are appuser.

We find the final flag at /opt/ironhold/flag.txt.

Last updated