FlawOpen

flawopen.com/SQL Injection/숫자 ID

숫자 ID에도 문자열 포맷팅이 위험한 이유는 무엇인가요?

참고 페이지
짧은 답변

위험은 값의 실제 의미가 아니라 쿼리를 텍스트 연결로 만든다는 데 있습니다. “항상 숫자”라는 말은 데이터에 대한 가정이지 코드가 보장하는 조건이 아닙니다.

가정이 깨지는 지점

HTTP 매개변수는 항상 문자열로 도착합니다

URL 쿼리 매개변수나 폼 필드는 DB 열이 기대하는 유형과 관계없이 전송될 때 텍스트입니다. 유형을 명시적으로 검증하지 않으면 코드가 받기 전에 프레임워크가 id=1 OR 1=1 같은 입력을 거부하지 않습니다.

나중의 리팩터링이 값의 출처를 조용히 바꿀 수 있습니다

ID가 항상 신뢰할 수 있는 내부 출처에서 와서 지금은 안전한 코드라도, 새 호출자가 사용자 입력을 같은 함수에 전달하면 취약해질 수 있습니다. 취약점은 그 변경을 기다리며 잠복해 있었습니다.

형 변환이 실제 값을 숨길 수 있습니다

특히 동적 타입 언어에서는 로그에 숫자처럼 보이는 값이 추가 문자가 든 문자열일 수 있습니다. 명시적 검증 없이는 기대한 유형과 실제 유형이 같다고 보장할 수 없습니다.

유형 검증 후에도 쿼리를 매개변수화하세요

ID를 애플리케이션이 기대하는 형식에 맞게 검증한 뒤 데이터베이스 드라이버의 매개변수 API로 전달하세요. 검증은 입력 계약을 보호하고, 매개변수화는 입력이 SQL 구문으로 해석되지 않게 합니다. 프로토콜에서 드라이버가 동작하는 방식은 다를 수 있으므로 모든 드라이버가 값을 똑같이 전송한다고 가정하지 마세요. PostgreSQL 매개변수는 테이블이나 열 식별자를 나타낼 수 없습니다. 동적 쿼리 구조는 애플리케이션의 고정 허용 목록에서 선택하세요.

숫자 값을 바인딩하고 의미를 검증하세요

애플리케이션이 해당 형식을 요구한다면 ID가 양의 안전한 정수인지 검증하되, 쿼리 값으로 계속 바인딩하세요. 검증은 애플리케이션의 입력 계약을 보호하고, 매개변수화는 입력이 SQL 구문이 되지 않도록 합니다. PostgreSQL 매개변수는 테이블이나 열 식별자를 대신할 수 없으므로 동적 쿼리 구조는 애플리케이션의 고정 허용 목록에서 선택하세요.

위험 — ID를 SQL에 삽입
const rawId = new URL(request.url).searchParams.get("id");
const result = await pool.query(
  `SELECT id FROM users WHERE id = ${rawId}`
);
안전 — ID를 검증하고 값으로 바인딩
const rawId = new URL(request.url).searchParams.get("id");
if (rawId === null || !/^[0-9]+$/.test(rawId)) {
  return new Response("Invalid id", { status: 400 });
}
const id = Number(rawId);
if (!Number.isSafeInteger(id) || id < 1) {
  return new Response("Invalid id", { status: 400 });
}
const result = await pool.query(
  "SELECT id FROM users WHERE id = $1",
  Array.of(id)
);

참고 자료

함께 보기 SQL 인젝션(CWE-89): 동작 원리와 언어별 방어 방법 — flawopen.com 매개변수화 쿼리란 무엇인가 Prisma $queryRaw는 SQL Injection에 안전한가요? Rails where() 문자열 보간은 안전할까요? Sequelize query()는 SQL Injection에 안전한가요? SQLAlchemy text()는 SQL Injection에 안전한가요? sqlx의 컴파일 시 쿼리 검사가 SQL 인젝션을 막나요?