FlawOpen

flawopen.com/Java의 SQL Injection/MyBatis

MyBatis ${}와 #{}: SQL Injection 방지

참고 페이지
간단한 답변

#{value}를 PreparedStatement의 보호된 값 칸으로 생각하세요. DB는 이를 데이터로 취급합니다. ${value}는 준비 전에 SQL에 텍스트를 복사합니다.

바인딩 값과 원시 SQL 텍스트

위험 — ${} 원시 텍스트
<select id="getUser">
  SELECT * FROM users
  WHERE id = ${id}
</select>
// Raw substitution: the SQL text changes
바인딩 값 — #{}
<select id="getUser">
  SELECT * FROM users
  WHERE id = #{id}
</select>
// MyBatis creates a PreparedStatement parameter

MyBatis에 두 문법이 있는 이유

#{}는 JDBC PreparedStatement 매개변수로 값을 바인딩합니다. 매개변수는 테이블·열 이름이나 정렬 방향 같은 SQL 구문을 대신하지 못합니다. ${}는 텍스트를 직접 치환하며 이스케이프하지 않습니다. 정적 SQL이나 <choose>를 우선하고, 필요한 경우 전체 식별자를 서버 허용 목록에서 선택하세요. 요청 문자열을 그대로 넣지 마세요.

동적 식별자가 필요한 경우

전체 SQL 식별자 또는 조각을 허용 목록으로 제한

바인딩 값은 식별자를 대체하지 않습니다. 정렬 선택을 서버의 고정 SQL 조각에 매핑하거나 <choose>를 사용하세요. 클라이언트의 열·테이블·ASC/DESC를 ${}에 직접 넣지 마세요. 이스케이프만으로 임의 SQL이 안전해지지 않습니다.

MyBatis mapper 감사 방법

rg -n -F '${' --glob '*.xml' --glob '*.java' .
XML과 어노테이션의 모든 결과를 검토하고 값의 출처를 추적하세요. 값에는 #{}를, ${}에는 유한한 서버 허용 목록을 사용하세요. 검색은 구문만 찾으며 간접 SQL 생성을 놓칠 수 있습니다.

자주 묻는 질문

어노테이션 기반 mapper에도 적용되나요?

네. 같은 규칙이 적용됩니다. @Select의 ${column}은 원시 텍스트를 삽입합니다. 값은 #{value}로 바인딩하고 식별자는 허용 목록으로 제한하세요.

${} 검색만으로 안전성을 증명할 수 있나요?

아니요. 결과를 검토하고 다른 곳에서 조립되는 SQL도 확인하세요. XML·어노테이션 검사를 추가하고 정렬 옵션이 고정된 조각만 선택하는지 테스트하세요.

참고 자료

함께 보기 SQL 인젝션(CWE-89): 동작 원리와 언어별 방어 방법 — flawopen.com MySQLi와 PDO: 안전한 Prepared Statement NHibernate는 SQL Injection을 막을 수 있나요? MongoDB NoSQL Injection: 안전한 쿼리 필터 숫자 ID도 문자열 포맷팅으로 SQL Injection이 발생할 수 있나요? 매개변수화 쿼리란 무엇인가 Prisma $queryRaw는 SQL Injection에 안전한가요?