Foundry
보안
기초
핵심

SQL 인젝션과 파라미터 바인딩

문자열 조합 한 줄이 DB 전체를 내준다

SQL 인젝션과 파라미터 바인딩

무엇이 뚫리는가

  • 입력을 SQL 문자열에 이어 붙이면 값이 아니라 구문으로 해석된다
  • OR 1=1 한 조각으로 WHERE 조건이 무력화되고 테이블 전체가 응답에 실린다
  • UNION SELECT로 다른 테이블(비밀번호 해시, 결제 정보)까지 끌어올 수 있다

방어 우선순위

순서방법비고
1파라미터 바인딩(PreparedStatement)값과 구문을 분리
2ORM, 쿼리빌더의 바인딩 APIJPA, MyBatis #{}
3화이트리스트 검증바인딩 불가한 자리 전용
4최소 권한 DB 계정뚫려도 피해 범위 축소

실무 포인트

  • 바인딩이 안 되는 자리가 있다: 테이블과 컬럼명, ORDER BY 컬럼은 값이 아니라 식별자다 → 화이트리스트로만 처리
  • MyBatis ${}는 문자열 치환, #{}가 바인딩이다. 코드 리뷰에서 ${} 검색은 필수
  • 이스케이프 함수를 직접 만들지 않는다. 인코딩, 주석, 다중바이트 우회를 못 막는다
  • DB 에러 메시지를 그대로 응답에 노출하면 스키마와 드라이버 정보가 유출된다
면접에서 이렇게 나옵니다

Q.레거시 코드에서 문자열로 조합된 쿼리를 발견했다. 어떤 순서로 조치하겠습니까?

전부 고치기 전에 피해 범위를 좁히는 조치를 먼저 넣습니다.

순서조치이유
1사용자 입력이 닿는 경로부터 찾는다내부 상수만 조합한 쿼리는 급하지 않다
2DB 계정 권한을 줄인다뚫려도 읽기만, 필요한 테이블만
3오류 메시지를 감춘다쿼리와 스키마 노출을 막는다
4위험한 경로를 바인딩으로 바꾼다값 자리를 완전히 막는다
5식별자 자리는 허용 목록으로컬럼명과 정렬 방향은 바인딩이 안 된다
6정적 분석을 CI 에 넣는다재발을 막는다

1번을 먼저 하는 이유는 레거시가 크면 전수 수정에 시간이 걸리고, 그동안 노출이 계속되기 때문입니다. 2번과 3번은 코드를 거의 안 건드리고 당일 적용할 수 있습니다.

흔한 실수: 전부 한 번에 바꾸겠다고 답하는 것. 규모가 크면 배포 위험이 커지고 그 사이 방치됩니다. 완화와 수정을 나누어 말하는 것이 실무 판단입니다.

Q.정렬 컬럼을 사용자가 고르는 목록 API는 왜 파라미터 바인딩만으로 안전해지지 않나요?

바인딩은 값 자리에만 쓸 수 있고 정렬 대상은 값이 아니라 쿼리 구조의 일부이기 때문입니다.

SELECT * FROM products WHERE category = ?   -- 값. 바인딩 된다
ORDER BY ?                                   -- 식별자. 되지 않는다

DB는 실행 계획을 세우기 전에 어떤 컬럼으로 정렬할지 알아야 합니다. 값은 나중에 채워도 되지만 구조는 미리 확정돼야 합니다.

그래서 허용 목록으로 처리합니다.

목록내용
허용 컬럼name, price, created 를 각각 실제 컬럼명에 대응시킨다
허용 방향asc 와 desc 만 받아 ASC, DESC 로 바꾼다
목록에 없으면기본값으로 대체하거나 400 으로 거부한다

흔한 실수: 컬럼명을 이스케이프하거나 특수문자만 걸러 통과시키는 것. 우회 가능성이 남습니다. 입력을 통과시키지 않고 미리 정한 값으로 바꿔치기하는 것이 안전합니다.

Q.ORM을 쓰면 SQL 인젝션이 원천 차단된다고 볼 수 있나요?

아닙니다. ORM은 기본 경로를 안전하게 만들어 주지만 빠져나가는 통로가 있습니다.

통로
원시 쿼리createNativeQuery, raw, $queryRaw 로 문자열을 조합
식별자 지정정렬 컬럼, 테이블명을 동적으로 넣는 API
조건 조립 문법일부 ORM 의 where 문자열 표현식
함수와 연산자 이름집계 함수나 연산자를 문자열로 받는 경우

ORM 을 쓰는 팀에서 사고가 나는 자리는 대개 성능 튜닝이나 복잡한 통계 쿼리 때문에 원시 쿼리로 내려간 곳입니다.

흔한 실수: ORM 도입을 대책으로 답하고 끝내는 것. 원시 쿼리 사용 지점을 목록으로 관리하고 그 부분만 정적 분석이나 리뷰로 지키는 것이 실제 대책입니다. 그리고 ORM 은 인젝션과 별개로 접근 제어를 해주지 않습니다.

Q.인젝션이 이미 발생했다고 가정할 때, 피해를 줄이기 위한 설계 장치는 무엇이 있나요?

방어가 뚫린 뒤에도 피해를 줄이는 장치를 미리 둡니다.

장치줄이는 피해
DB 계정 권한 최소화읽기 전용 계정이면 데이터 변조와 삭제를 막는다
테이블과 컬럼 단위 권한결제 정보 테이블에 접근 자체를 막는다
민감 컬럼 암호화덤프가 나가도 평문을 얻지 못한다
쿼리 로그와 이상 탐지평소와 다른 대량 조회를 잡는다
응답 크기 제한과 호출 한도한 번에 전체를 빼가는 것을 늦춘다
오류 메시지 은닉스키마 파악을 어렵게 한다

핵심 발상은 뚫린다는 가정 아래 설계하는 것입니다. 애플리케이션이 필요한 최소 권한만 가지면 인젝션의 결과가 "그 계정이 할 수 있는 일"로 제한됩니다.

흔한 실수: 방어 하나로 끝났다고 보는 것. 바인딩을 했더라도 라이브러리 취약점이나 놓친 경로가 있을 수 있어, 피해 축소 장치가 함께 있어야 합니다.

먼저 스스로 답해보고 아래 답변과 견줘보세요. 막히는 부분은 문제로 확인할 수 있어요.

읽었으면 문제로 확인해보세요

보안 문제를 풀면 틀린 문제가 자동으로 노트에 쌓입니다. 가입 없이 5문제를 먼저 풀어볼 수도 있어요.