Post

Outcast

Outcast

Description

Author: YesWeHack

YesWeHack has provided this CTF challenge, and they state: “This challenge is meant to be run as a black-box environment. The source code is intentionally not provided.”

Light enumeration is permitted for this challenge.

NOTE, the flag for this challenge is not in the standard flag format. The format of the flag is with a flag{} wrapper but with l33tsp3@k! inside the curly braces.

Special thanks to YesWeHack for the sponsorship and support of the NahamCon CTF!

Exploitation

Enumeration

Here is the web page: home

Looking into the source code, we disclosed another endpoint /test notes

Here is what we find inside the test endpoint that was commented out but developer forgot to remove it: discovery

As mentioned, we got access to two methods so far: getusers and getversion. Remembering that we are in the api caller endpoint that is not supposed to be accessible. The version is => v1.3.37 but that lead us no where.

We also found this php code following the Download template code link here below:

link

Here is the code we downloaded:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
<?php

class APICaller {
	private $url =  'http://localhost/api/';
	private $path_tmp = '/tmp/';
	private $id;

	public function __construct($id, $path_tmp = '/tmp/') {
		$this->id = $id;
		$this->path_tmp = $path_tmp;

	}

	public function __call($apiMethod, $data = array()) {
		$url = $this->url . $apiMethod;
		$data['id'] = $this->id;

		foreach ($data as $k => &$v) {
			if ( ($v) && (is_string($v)) && str_starts_with($v, '@') ) {
				$file = substr($v, 1);

				if ( str_starts_with($file, $this->path_tmp) ) {
					$v = file_get_contents($file);
				}
			}
			if (is_array($v) || is_object($v)) {
				$v = json_encode($v);
			}
		}

		// Call the API server using the given configuraions
		$ch = curl_init($url);
		curl_setopt_array($ch, array(
			CURLOPT_POST           => true,
			CURLOPT_POSTFIELDS     => $data,
			CURLOPT_RETURNTRANSFER => true,
			CURLOPT_HTTPHEADER     => array('Accept: application/json'),
		));
		$response = curl_exec($ch);
		$error  = curl_error($ch);
		
		curl_close($ch);

		if (!empty($error)) {
			throw new Exception($error);
		}

		return $response;
	}
}

Code review

Now it’s time to review the code of the API caller. We noticed that the APICaller class is a callable class that can be called using the __call method as the main method. We also noticed that the APICaller class has a url property that is set to http://localhost/api/ and a path_tmp property that is set to /tmp/. Let’s add to that the unsanitized $apiMethod and $data parameter that is passed to the __call method. We also see a dangerous file_get_contents call that is used to read files from the local file system, which arises a security concern since it allows arbitrary file access when the input is not properly sanitized. To add to our findings, in order to trigger the file_get_contents call, we can use the @ symbol to inject a file path that starts with /tmp/. We can then read the contents of the file. Finally, we see a curl call that is used to make a POST request to the API server. We can use this to send a request to the server and extract the response from the server with a file_get_contents call in order to read the files from the local file system.

Login page Enumeration

We find out that we could not send a login request to the server, so looking into the source code of the login page, we find the following:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
            <form class="space-y-6" action="#" method="POST">
                <div>
                    <label for="username" class="block text-sm/6 font-medium text-gray-900">Username</label>
                    <div class="mt-2">
                        <input
                            type="text"
                            name="username"
                            id="username"
                            value=""
                            required
                            class="block w-full rounded-md bg-white px-3 py-1.5 text-base text-gray-900 outline-1 -outline-offset-1 outline-gray-300 placeholder:text-gray-400 focus:outline-2 focus:-outline-offset-2 focus:outline-red-700 sm:text-sm/6">
                    </div>
                </div>

                <div>
                    <div class="flex items-center justify-between">
                        <label for="password" class="block text-sm/6 font-medium text-gray-900">Password</label>
                    </div>
                    <div class="mt-2">
                        <input
                            type="password"
                            name="password"
                            value=""
                            id="password"
                            required class="block w-full rounded-md bg-white px-3 py-1.5 text-base text-gray-900 outline-1 -outline-offset-1 outline-gray-300 placeholder:text-gray-400 focus:outline-2 focus:-outline-offset-2 focus:outline-red-700 sm:text-sm/6">
                    </div>
                </div>

                <div>
                    <button
                        type="submit"
                        class="flex w-full justify-center rounded-md bg-red-500 px-3 py-1.5 text-sm/6 font-semibold text-white shadow-xs hover:bg-red-700 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-red-700">
                        Sign in</button>
                </div>
            </form>

The action is marked as # in the form, so wathever we put in the username and password fields will be sent to the server as a POST request but not the login request.

API caller Exploitation

Now that we can try to use the API and start injecting some input to the server and see what happens, we injected the following: injecting

After some trials and errors, we remembered that we had an /api/ endpoint: api-endpoint

So we discovered a path traversal vulnerability in the $apiMethod parameter that we were using to call the API. vuln1

Here we confirmed the vulnerability by injecting the following: ../api/ endpoint. vuln2

Then we realized from our assumptions about the login page that our request were not being submitted, so we could actually use the API to send a POST request to the server and get the response on the API caller endpoint. This is for a writeup purpose but honestly, I spent hours trying to figure out how to do this and I was not sure if it was even possible. But after some trials and errors, we found that we could use the file_get_contents function to read the contents of the file and send it as a POST request to the server using one of the parameters of the login form. First we needed to confirm if we could access the login page as we did with the /api/ endpoint. login-page

We found that we could access the login page by injecting the following: /login/ endpoint. Now, we can abuse the file_get_contents function to read the contents of the file and send it as a POST request to the server using one of the parameters of the login form.

We injected the following to test => username=@/tmp/../etc/passwd in the parameters of the API caller. It worked as we were able to leak the contents of the /etc/passwd file. leak

Then finally we injected the following payload to get the flag: flag

This was interesting because we had to chain vulnerabilities to get the flag. I really thought this was a clever attack and I was impressed by the challenge as it was ranked as easy. I would rank it as medium if I had to do it again. Because of the time spent figuring it out. Overall I am happy with the challenge and I learned a lot from it.

Here is the result in clear text:

Flag

flag{ch41n1ng_bug$_1s_w0nd3rful!}

Happy hacking ! 😊

This post is licensed under CC BY 4.0 by the author.