MongoDB Injection에 대해서

Dev Smile·2025년 1월 14일
1

FastAPI

목록 보기
8/10
post-thumbnail

SQL을 공부했을 때 SQL Injection이라는 공격 방법도 같이 배운 적이 있습니다. MongoDB를 사용하는 와중에 문득 MongoDB에서도 이와 비슷한 문제가 발생할 수 있지 않을까 생각이 들어 찾아본 결과, MongoDB에서도 Injection 문제가 존재한다는 것을 알게 되었습니다. 이번 글에서는 MongoDB Injection의 개념, 발생 원인, 예방 방법, 그리고 FastAPI를 활용하여 사례를 작성해보았습니다.


1. MongoDB Injection이란?

MongoDB 인젝션(MongoDB Injection)은 공격자가 악의적인 입력을 통해 MongoDB 쿼리를 조작함으로써, 데이터베이스의 기밀 정보에 접근하거나 데이터를 변조하는 공격 기법입니다. 이는 SQL 인젝션과 유사한 원리를 가지고 있으며, 일반적으로 사용자 입력값이 제대로 검증되지 않을 때 발생합니다.


2. MongoDB Injection의 원리

MongoDB는 JSON과 유사한 BSON(Binary JSON)을 기반으로 데이터를 처리합니다. 클라이언트에서 전달된 JSON 형식의 데이터를 데이터베이스 쿼리에 직접 사용하면, 악성 입력이 쿼리를 변경하거나 조작할 수 있습니다.

1. 취약점이 발생하는 상황

  • 사용자 입력값을 검증하지 않고 쿼리에 직접 포함시키는 경우.
  • 동적 쿼리 작성 시, 입력값을 JSON 객체로 변환할 때 공격 가능성이 존재.
# 취약한 코드 예시
user_input = {"username": "admin", "password": {"$ne": ""}}
db.users.find(user_input)

위 코드에서 {"$ne": ""}는 MongoDB의 $ne 연산자를 활용하여 특정 필드를 "비어 있지 않다"고 평가합니다. 이는 인증 없이도 로그인을 우회할 수 있도록 쿼리를 조작하는 MongoDB 인젝션입니다.


2. 공격 기법의 종류

  1. 논리 연산자를 이용한 공격

    • $or 연산자를 사용하여 인증 우회:
      { "username": "admin", "$or": [ { "password": "" }, { "password": { "$ne": "" } } ] }
      • 결과적으로, password 필드 값이 무엇이든 쿼리가 참이 되어 인증이 우회됩니다.
  2. 비교 연산자를 이용한 공격

    • $gt, $lt, $ne 등의 연산자를 사용해 조건 조작:
      { "username": "admin", "password": { "$ne": "" } }
      • 빈 패스워드를 입력해도 쿼리가 성공적으로 실행됩니다.
  3. 코드 실행을 유발하는 공격

    • $where 연산자를 활용하여 JavaScript 코드를 실행:
      { "$where": "this.username === 'admin' && this.password.length > 0" }
      • JavaScript를 통해 복잡한 조건을 삽입하여 인증을 우회하거나 시스템을 공격할 수 있습니다.

3. 데이터 탈취 예제

  • MongoDB, test_database, user collection에 다음과 같이 데이터를 추가하였습니다.
{
  "_id": {
    "$oid": "67865e57d79949e7cc6c05ec"
  },
  "user": "test_user",
  "password": "test_password"
}
  • 그리고 아래와 같이 받은 데이터를 바로 활용하여 조회하는 FastAPI 코드를 작성했습니다.
from fastapi import FastAPI, Request
from pymongo import MongoClient

app = FastAPI()
client = MongoClient("mongodb://localhost:27017/")
db = client.test_database

@app.post("/login")
async def login(request: Request):
    body = await request.json()
    print("Request body:", body)  # 추가된 로그
    user = body.get("user")
    password = body.get("password")

    # 취약한 코드 예시
    user_record = db.user.find_one({"user": user, "password": password})

    if user_record:
        return {"message": "Login successful"}
    return {"message": "Invalid credentials"}

만약 사용자가 user 필드에 { "$ne": null }을 입력하면, 조건이 항상 참이 되어 인증이 우회될 수 있습니다.


4. MongoDB Injection 예방 방법

(1) 입력 검증

사용자 입력을 항상 검증하고, 허용된 값만 사용하도록 제한합니다.

from fastapi import FastAPI, Request
from pydantic import BaseModel, constr
from pymongo import MongoClient

app = FastAPI()
client = MongoClient("mongodb://localhost:27017/")
db = client.test_database


class LoginRequest(BaseModel):
    user: constr(strip_whitespace=True, min_length=1)
    password: constr(strip_whitespace=True, min_length=1)


@app.post("/login")
async def login(request: LoginRequest):
    sanitized_user = request.user
    sanitized_password = request.password

    # 안전한 코드 예시
    user_record = db.users.find_one({"user": sanitized_user, "password": sanitized_password})

    if user_record:
        return {"message": "Login successful"}
    return {"message": "Invalid credentials"}

(2) 파라미터화된 쿼리 사용

MongoDB에서는 일반적으로 사용자의 입력값을 직접 삽입하지 말고, 파라미터를 통해 안전하게 처리해야 합니다.

(3) ODM(Object Document Mapper) 사용

ODM 라이브러리(Motor, Beanie 등)를 사용하면 쿼리 작업 중 자동으로 Injection 방지가 가능합니다.

(4) 권한 관리 강화

데이터베이스에서 권한 관리를 철저히 하여, 애플리케이션의 취약점이 데이터베이스에 직접 영향을 미치지 않도록 해야 합니다.

(5) 최신 보안 패치 적용

MongoDB의 최신 버전을 사용하고, 보안 패치를 항상 유지합니다.


5. 정리 및 결론

MongoDB Injection은 잘못된 입력 검증에서 발생하는 심각한 보안 문제입니다. FastAPI와 같은 프레임워크를 사용할 때에도 철저한 입력 검증, 파라미터화된 쿼리, 그리고 ODM 사용과 같은 최선의 보안 관행을 준수하여 보안 문제를 겪지 않도록 합시다.

0개의 댓글

관련 채용 정보