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·어노테이션 검사를 추가하고 정렬 옵션이 고정된 조각만 선택하는지 테스트하세요.