概要
本文主要记录[MRCTF2020]Ez_bypass和[网鼎杯 2020 青龙组]AreUSerialz的解题过程以及相关思路
[MRCTF2020]Ez_bypass
打开题目,发现是一道PHP的代码审计题目,发现其中主要使用的是md5
和is_numeric
这两个函数,并且在第7行处使用的是===
,那么这里就不能通过0e来绕过了,需要使用数组或MD5碰撞来绕过
姿势一(数组绕过):
姿势二(MD5碰撞):
$a=M%C9h%FF%0E%E3%5C%20%95r%D4w%7Br%15%87%D3o%A7%B2%1B%DCV%B7J%3D%C0x%3E%7B%95%18%AF%BF%A2%02%A8%28K%F3n%8EKU%B3_Bu%93%D8Igm%A0%D1%D5%5D%83%60%FB_%07%FE%A2
$b=M%C9h%FF%0E%E3%5C%20%95r%D4w%7Br%15%87%D3o%A7%B2%1B%DCV%B7J%3D%C0x%3E%7B%95%18%AF%BF%A2%00%A8%28K%F3n%8EKU%B3_Bu%93%D8Igm%A0%D1U%5D%83%60%FB_%07%FE%A2
进入到下一步,对于is_numeric
函数,其作用是判断变量是否为数字或数字字符串
即要控制passwd为非数字但是==
1234567
姿势一:
在PHP中,当字符串与数字进行弱类型比较时,会将字符串转换为数字进行比较,如果此时的字符串是以数字开头,那么该字符串就转换为字符串中第一个字符之前的数字,例:
<?php
var_dump(false == 0); //true,布尔类型false与数字0的弱类型比较相等
var_dump('a' == 0); //true,由于不是以数字开头,转换数字失败,转换为false,相等
var_dump('a' == 1); //false,由于不是以数字开头,转换数字失败,转换为false,不相等
var_dump('1a' == 1); //true,以数字1开头,转换后其值为1,相等
var_dump('12a' == 1); //false,以数字12开头,转换后其值为12,不相等
?>
那么就可以使用1234567a
来进行绕过
姿势二:
使用空格来进行绕过,在查看is_numeric
函数介绍时,偶然发现下面有介绍更新
那么查看本题的PHP版本,是5.6.4,即以1234567
会返回false
不过看了其他师傅的WP后,发现也可以使用空字符%00
来进行绕过
[网鼎杯 2020 青龙组]AreUSerialz
打开题目,发现是PHP代码审计题目,涉及到了反序列化
<?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!";
$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);
}
}
?>
可以大概分析出相关函数执行的顺序,首先接受一个str参数,将其强转为string类型,然后使用is_valid
函数进行判断,判断通过后就进行反序列化,其中is_valid
函数主要的作用就是判断参数中的每一个字符的ASCII值是否在[32,125]
内,如果是返回true
,否则返回false
(那么其实这里is_valid
函数已经给了出来,不妨拷贝代码到本地运行,方便进行一些调试)
首先我按照平时的思路写序列化字符串,不过发现并没有通过is_valid
函数,后面我才意识到%00
的ASCII值并不在[32,125]
中
然后我查找了一些过滤%00
的绕过方法,发现可以使用\00
来进行替换,同时前面的s
需要改为S
,这样其中的内容就会被当作十六进制
进行解析
通过is_valid
函数后,应该关注到__destruct
函数,其判断了op
的值,并调用了process
函数,在process
函数中,也对op
的值进行判断,当op
的值为2
的时候调用read
函数,read
函数中使用了file_get_contents
函数,参数为filename
,那么这里我们就可以使用伪协议来通过file_get_contents
函数进行文件内容的读取
解码得到flag