FlawOpen

flawopen.com/SQL Injection in PHP/Doctrine ORM

Doctrine ORM은 SQL 인젝션에 안전한가요?

Reference page
짧은 답변

매개변수를 바인딩한 DQL은 안전합니다. 문자열 연결로 DQL이나 네이티브 SQL을 만들면 일반 raw query와 같은 위험이 생깁니다.

취약 — 입력이 DQL 구문이 됨
$em->createQuery(
  "SELECT u FROM User u "
  . "WHERE u.id = $userId"
);
안전 — 신뢰된 표현식만 사용
$qb->select('u')
  ->from('User', 'u')
  ->where('u.id = :id')
  ->setParameter('id', $userId);

바인딩된 값은 쿼리 구조를 보호하지 않습니다

setParameter()는 DQL 값을 바인딩하고 Doctrine은 이를 쿼리 문자열과 분리된 SQL 매개변수로 변환합니다. createQuery()에 입력을 연결하면 보호를 우회합니다. 네이티브 SQL도 마찬가지입니다. Connection::executeQuery()는 placeholder와 매개변수 배열을 사용할 때 안전하며, 입력을 SQL 문자열에 삽입하면 안전하지 않습니다.

매개변수는 식별자를 대체하지 않습니다

Placeholder는 값만 나타내며 DQL 식별자, SQL 열/테이블명, 연산자 또는 정렬 방향을 대신하지 않습니다. ORM 보안 문서는 QueryBuilder 표현식 메서드에 사용자 입력을 전달하면 DQL 인젝션이 가능하다고 경고합니다. 표현식은 신뢰된 코드에 두고 정렬 선택은 엄격한 허용 목록으로 매핑하세요.

취약 — 입력이 DQL 구문이 됨
$qb->orderBy(
  'u.' . $request->query->get('sort'),
  $request->query->get('direction')
);
안전 — 신뢰된 표현식만 사용
$fields = [
    'name' => 'u.name',
    'created' => 'u.createdAt',
];
$directions = ['asc' => 'ASC', 'desc' => 'DESC'];

$key = (string) $request->query->get('sort', 'name');
$sort = $fields[$key] ?? $fields['name'];
$requestedDirection = strtolower(
    (string) $request->query->get('direction', 'asc')
);
$direction = $directions[$requestedDirection] ?? 'ASC';

$qb->orderBy($sort, $direction);

코드베이스 점검 방법

rg -n -g '*.php' 'createQuery\(|orderBy\(|addOrderBy\(|executeQuery\(' .
각 결과에서 문자열 연결/보간, 표현식 API에 전달되는 사용자 입력, 고정 허용 목록 밖의 식별자를 확인하세요. 검색 결과는 검토 후보이지 악용 가능성의 증거는 아닙니다.

출처 및 참고 자료

함께 보기 SQL 인젝션(CWE-89): 동작 원리와 언어별 방어 방법 — flawopen.com EF Core FromSqlRaw: 안전한 SQL 매개변수화 FastAPI SQL 인젝션: Pydantic이 막는 것과 막지 못하는 것 GORM Raw SQL: Go SQL 인젝션 방지 따옴표 이스케이프만으로 SQL Injection을 막을 수 있을까? ORM만 쓰면 SQL 인젝션을 막기에 충분한가 Knex.js는 SQL Injection에 안전한가요?