FlawOpen

flawopen.com/참고 자료/MongoDB NoSQL Injection: 입력이 쿼리를 바꾸는 경우

MongoDB NoSQL Injection: 입력이 쿼리를 바꾸는 경우

데이터베이스 보안 가이드

MongoDB는 SQL을 실행하지 않으므로 SQL 문자열 연결이 문제는 아닙니다. 애플리케이션이 스칼라를 기대하는 위치에서 신뢰할 수 없는 중첩 객체를 받아 MongoDB 필터에 전달하면 NoSQL Injection이 발생할 수 있습니다. 이는 애플리케이션 입력 검증 결함이며 MongoDB 서버 CVE가 아닙니다.

쉽게 말해: 값이 규칙으로 바뀝니다

이름을 묻는 양식에서 담당자가 입력란에 보관 지침 전체를 받아들이는 상황을 떠올려 보세요. 데이터베이스는 이름 하나를 비교하는 대신 그 지침을 따릅니다. 기대하는 값의 유형을 검증하고 검색 규칙을 직접 만드세요.

신뢰 경계: 요청 JSON에서 MongoDB 필터까지

쿼리 필터
MongoDB가 필드 비교로 해석하는 BSON 문서이며, 명시된 경우 쿼리 연산자도 포함합니다.
연산자 주입
애플리케이션이 단일 리터럴 값을 기대한 위치에 사용자가 제어하는 구조를 전달하는 문제입니다.
스칼라 검증
입력이 데이터베이스 작업에 영향을 주기 전에 유형, 길이, 허용 형식을 확인합니다.
프로젝션
우발적 데이터 노출을 제한하기 위해 서버가 선택하는 반환 필드 목록입니다.

오류가 발생하는 과정

1

엔드포인트는 스칼라를 기대합니다

API 계약은 username을 문자열로 정의하지만 JSON 파서는 배열, 객체, 불리언, null도 표현할 수 있습니다.

2

애플리케이션이 유형 검증을 생략합니다

핸들러가 파싱된 값을 서버가 만든 필터에 직접 넣으므로 객체는 리터럴 문자열이 아니라 쿼리 구조로 남습니다.

3

MongoDB가 전달된 구조를 평가합니다

중첩 문서에 쿼리 연산자가 있으면 조건이 의도한 정확한 값 비교와 다른 의미가 될 수 있습니다. 영향받는 레코드는 쿼리와 애플리케이션 권한 검사에 따라 달라집니다.

4

응답이 노출 범위를 결정합니다

반환 필드와 레코드 수는 프로젝션, 엔드포인트 동작, DB 계정 권한에 따라 달라집니다. 이것만으로 인증 우회나 전체 DB 접근이 입증되지는 않습니다.

범위와 근거

MongoDB 공식 드라이버 지침은 엔드포인트가 검증되지 않은 JSON을 BSON으로 바꿔 필터로 사용하는 경계를 설명합니다. 이 가이드는 애플리케이션 계층의 결함을 다룹니다. 모든 MongoDB 배포가 취약하다고 주장하거나 서버 CVE를 지정하거나 인증 우회 방법을 제공하지 않습니다.

Node.js의 취약한 패턴과 강화된 패턴

VULNERABLE — UNVALIDATED VALUE ENTERS FILTER
app.post("/users/lookup", async (req, res, next) => {
  try {
    // The route expects a string, but req.body.username is untrusted JSON.
    const user = await users.findOne({ username: req.body.username });
    res.json(user ? { username: user.username } : null);
  } catch (error) {
    next(error);
  }
});
HARDENED — VALIDATE SHAPE, BUILD FILTER, VERIFY HASH
const loginSchema = z.object({
  username: z.string().trim().min(1).max(128),
  password: z.string().min(1).max(1024)
}).strict();

app.post("/login", async (req, res, next) => {
  try {
    const parsed = loginSchema.safeParse(req.body);
    if (!parsed.success) return res.sendStatus(400);

    const { username, password } = parsed.data;
    // The application owns the filter key; input supplies only a string value.
    const user = await users.findOne(
      { username },
      { projection: { _id: 1, username: 1, passwordHash: 1 } }
    );
    const passwordMatches = user &&
      await argon2.verify(user.passwordHash, password);
    if (!passwordMatches) return res.sendStatus(401);

    return res.sendStatus(204); // Session creation is application-specific.
  } catch (error) {
    next(error);
  }
});

강화된 흐름은 데이터베이스에 접근하기 전에 문자열이 아닌 필드를 거부하고, 클라이언트가 보낸 필터 객체를 받지 않으며, 애플리케이션 코드에서 고정된 필터 키를 구성합니다. 선택한 필드만 반환하고 사용자를 조회한 뒤 암호 해시를 검증합니다. 예시에서 z는 Zod, users는 MongoDB 컬렉션, argon2는 설정된 암호 해시 라이브러리입니다. 세션 생성은 의도적으로 예시 범위에서 제외했습니다.

수정 검증 체크리스트

1차 출처

관련 보안 가이드 살펴보기:보안 디렉터리 열기 →
함께 보기 SQL 인젝션(CWE-89): 동작 원리와 언어별 방어 방법 — flawopen.com 숫자 ID도 문자열 포맷팅으로 SQL Injection이 발생할 수 있나요? 매개변수화 쿼리란 무엇인가 Prisma $queryRaw는 SQL Injection에 안전한가요? Rails where() 문자열 보간은 안전할까요? Sequelize query()는 SQL Injection에 안전한가요? SQLAlchemy text()는 SQL Injection에 안전한가요?