COMP6841 Something Awesome

z5418112 (Sam Zheng)

Insomnia

By: khanhhnahk1

1 hour

Easy
PHP
SSRF

Another PHP challenge this time round. Let's jump right into it.

Reconnaissance

The first thing I noticed with this particular challenge was that the source codebase seems to be massive compared to the other previous challenges.

The source code for the challenge

The source code for the challenge

What on earth - I thought this was meant to be an easy challenge. But as I started to look through the code, I realised a lot of the folders contained little to no code - this was probably a framework.

Reference to framework

Reference to framework

Ah yep - it's a framework known as CodeIgniter. My first thought was that there could be a vulnerability in the framework itself, but continued to look through the codebase during this reconnaissance phase.

The entrypoint.sh contained some interesting information - that there was a basic SQLite database with a table named users containing a single user with the username administator and a random password. There was also no mention of the flag.txt being randomly named and placed somewhere else. There was also a randomly generated JWT_SECRET, stored as an env variable.

Playing around with the website, and in knowing that there are JWTs involved, I checked how these JWTs were being stored - as I suspected, they were being stored in a cookie.

Continuing to snoop around the source code - there's this interesting file called ProfileController.php which contained the following code snippet:

public function index()
{
$token = (string) $_COOKIE["token"] ?? null;
$flag = file_get_contents(APPPATH . "/../flag.txt");
if (isset($token)) {
$key = (string) getenv("JWT_SECRET");
$jwt_decode = JWT::decode($token, new Key($key, "HS256"));
$username = $jwt_decode->username;
if ($username == "administrator") {
return view("ProfilePage", [
"username" => $username,
"content" => $flag,
]);

Well - clearly we need to get admin access to get the flag in this case.

The actual HTML pages were stored in the Views folder, and most of these had little interesting code. They mostly contained the HTML for the pages. Checking the rest of the folders, it looked like the most interesting code was all contained within the Controllers folder.

SQL injection seemed to be a dead end, as the application was using prepared statements. I also tried to see if there was any way to bypass the JWT token, but that seemed to be far too deep of a rabbit hole to look into, surely there was a simpler way.

Exploitation

Taking a closer look at the UserController.php file, we have the main logic for the login and registration of users - I suspected that this would be the place to look for the vulnerability given that our end goal was to get the flag via logging in as the administrator.

$db = db_connect();
$json_data = request()->getJSON(true);
if (!count($json_data) == 2) {
return $this->respond("Please provide username and password", 404);
}
$query = $db->table("users")->getWhere($json_data, 1, 0);
$result = $query->getRowArray();
if (!$result) {
return $this->respond("User not found", 404);
} else {
$key = (string) getenv("JWT_SECRET");
// rest of login logic

Wait a minute - the if condition looks a bit off. It's checking if the count of the JSON data is not equal to 2 - but it seems to be missing some brackets. The placement of the ! next to the count function seems to be a mistake. count($json_data) would of course return a number of the elements in the JSON data, which would be 2 in a normal login scenario. But, because of the ! operator, we get a boolean value out of the LHS, which would never be equal to 2.

Paying attention to the login logic, all we are doing is querying the database based on the fields we provide it. So, if I don't provide a password, then there is no need to match the password field in the database and I could login as any user. With the flaw in the if logic condition, by providing only one key, the if condition would never trigger (in fact, it never does).

Hmm, not quite there. As you can see int he request tab, the password field is still present, just empty. The database query would still be matching against the empty string, which is of course false, so we aren't quite there yet.

No stress, we can just remove the password field from the request and send it manually ourselves, not through the website. I just used Firefox's built in dev tools to spoof a request.

Success!

Success!

Nice - we have a token now. Interestingly, the page didn't redirect me to the profile page where I could grab the flag. Instead, I manually spoofed my own cookie to contain the token for the admin, and navigated to the profile page.

We have the flag :)

We have the flag :)

Reflection and Learnings

Last Writeup

Render Quest

Next Writeup

No Threshold