HeroCTF v7-WEB

Revoked

进去就是注册登录登出路由

看源码 主要是下面这几个路由

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@app.route("/employees", methods=["GET"])
@token_required
def employees():
query = request.args.get("query", "")
conn = get_db_connection()
cursor = conn.cursor()
cursor.execute(
f"SELECT id, name, email, position FROM employees WHERE name LIKE '%{query}%'"
)
results = cursor.fetchall()
conn.close()
print(request.username)
return render_template("employees.html", username=request.username, employees=results, query=query)


@app.route("/admin", methods=["GET"])
@token_required
def admin():
is_admin = getattr(request, "is_admin", None)
if is_admin:
return render_template("admin.html", username=request.username, flag=FLAG)

flash("You don't have the permission to access this area", "error")
return redirect("/employees")

在/employee存在sql注入 union联合注入 看源码可以知道数据库的结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
def init_db():
conn = sqlite3.connect("database.db")
cursor = conn.cursor()
cursor.execute("""DROP TABLE IF EXISTS employees;""")
cursor.execute("""DROP TABLE IF EXISTS revoked_tokens;""")
cursor.execute("""DROP TABLE IF EXISTS users;""")
cursor.execute("""CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT UNIQUE NOT NULL,
is_admin BOOL NOT NULL,
password_hash TEXT NOT NULL)""")
cursor.execute("""CREATE TABLE IF NOT EXISTS revoked_tokens (
id INTEGER PRIMARY KEY AUTOINCREMENT,
token TEXT NOT NULL)""")
cursor.execute("""CREATE TABLE IF NOT EXISTS employees (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
email TEXT UNIQUE NOT NULL,
position TEXT NOT NULL,
phone TEXT NOT NULL,
location TEXT NOT NULL)""")
conn.commit()
conn.close()

所以我们sql注入的语句写成

1’ union select 1,id,3,username from users–+

这样就可以查询到我们敏感信息 比如admin的password_hash 我们要拿到flag就需要以admin的身份访问/admin

那么这里有两种做法 官方给的做法 是使用已知hash爆破明文密码用rockyou (?)

这道题其实还有两种解法 我放在revenge一起写

Revoked Revenge

1’ union select 1,id,3,token from revoked_tokens–+

可以看到被撤销的token 想办法让这个token有效

solution1

修改 Base64 的文本表示形式,特别是那些不包含有效数据的部分,不会让我们的token失效并且我们还可以通过admin的验证

原理参考文章:

https://security.stackexchange.com/questions/272746/jwt-able-to-change-signature-and-its-still-verified

https://github.com/jwtk/jjwt/#base64-changing-characters

1
2
3
4
5
6
7
8
9
10
11
1. 签名验证只关心字节数组
加密操作(如签名验证)始终在原始二进制数据(字节数组)上执行。
Base64 只是一种将该字节数组转换为文本格式以便在 JSON 或 HTTP 中传输的编码方式。
只要解码器从修改后的 Base64 字符串中得到了完全相同的原始字节数组,验证就会通过。

2. Base64 存在“冗余”字符 (填充/不关心位)
Base64 将 3 个字节编码成 4 个字符。 当原始签名长度不是 3 的倍数时(这是 RSA 签名等常见情况),最后一个 Base64 块是不完整的。
在这个不完整的块中,最后一个(或倒数第二个)Base64 字符的部分位不代表原始数据,而是填充或“不关心”(don't care)的值。
您可以更改这个字符,只要更改后的字符仍然保持那些“不关心”位,而不影响那些重要的、代表原始签名的位。
结果: 不同的 Base64 字符串可以解码出相同的原始字节数组。
示例: 更改一个字符为另一个具有相同前两位(有效数据位)的字符(例如 'A' 改为 'P'),不会改变解码后的签名。

得到的admin的jwt

1
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwiaXNfYWRtaW4iOjEsImlzc3VlZCI6MTc2NDY2NjY4NS4zMjk5MTgxfQ.T_VUBWxtF0krNyTFYX7mgteeTfGcgqh-jVuhs8VMunY
值 (十进制) 6 位 (二进制) 有效位 (前 4 位) 最后一个 2 位 (不关心) Base64URL 字符
24 $011000_2$ $0110_2$ $00_2$ Y
25 $011001_2$ $0110_2$ $01_2$ Z
26 $011010_2$ $0110_2$ $10_2$ a
27 $011011_2$ $0110_2$ $11_2$ b

改为

1
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwiaXNfYWRtaW4iOjEsImlzc3VlZCI6MTc2NDY2NjY4NS4zMjk5MTgxfQ.T_VUBWxtF0krNyTFYX7mgteeTfGcgqh-jVuhs8VMunZ

solution2

这里是因为处理输入的JWT时,对字符串形式和解码字节形式的处理不一致

这里我们给我们得到的jwt末尾加一个=就可以绕过revoked_tokens的绕过

因为这里判断是不是revoked_token是和数据库里的revoked_token字符串比较

我们加一个=填充可以绕过这个比较并且被解析成正确的jwt

得到

1
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwiaXNfYWRtaW4iOjEsImlzc3VlZCI6MTc2NDY2NjY4NS4zMjk5MTgxfQ.T_VUBWxtF0krNyTFYX7mgteeTfGcgqh-jVuhs8VMunY

改为

1
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwiaXNfYWRtaW4iOjEsImlzc3VlZCI6MTc2NDY2NjY4NS4zMjk5MTgxfQ.T_VUBWxtF0krNyTFYX7mgteeTfGcgqh-jVuhs8VMunY=

image-20251202172432287

Tomwhat

进去就是tomwaht的面板 虽然我们不知道帐号密码

run.sh里面也写了

1
2
3
4
files="$(grep -Rl 'RemoteCIDRValve' $CATALINA_HOME/conf $CATALINA_HOME/webapps || true)" && \
for f in $files; do \
sed -i 's/allow="127.0.0.0\/8,::1\/128"/allow="0.0.0.0\/0,::\/0"/' "$f"; \
done

这道题考点是tomwhat面板可以泄露session并且这里三个路由时共享session会话数据的

admin要求username为darth_sidious

但是/light路由严禁输入darth_sidious 那么这样就可以刚刚我说的面板可以泄露session并且这里三个路由时共享session会话数据的点

参考文章https://www.freebuf.com/articles/web/247253.html

/examples/servlets/servlet/SessionExample 可以生成根目录的session

image-20251202174101279

这样就可以得到username为darth_sidious 的会话数据 直接访问/dark/admin 就可以看到flag了