SQL Injection 대응 방안
공격이 성공한다면 굉장히 막대한 피해를 입힐 수 있는 SQL 인젝션 공격. 어떻게 대응할 수 있을까?
Prepared Statement
SQLi 대응의 기본이자 정석인 방법이다. Prepared Statement는 원래부터 SQL 인젝션을 막기 위한 시큐어 코딩이 아니었고, 속도를 향상시키기 위해 나온 방법이었다. 일반 Statement는 DB에 요청할 때마다 쿼리를 컴파일하여 새로 생성한 후 실행하는 방식인데 이는 대량의 코드를 전부 컴파일해야 하는 성능상의 문제와 함께 공격자는 이를 이용하여 파라미터에 입력값을 조작하여 원하는 의도대로 실행되게끔 한다는 문제가 있었다.
Prepared Statement는 외부의 입력값을 제외한 나머지 부분을 미리 컴파일하고 입력값만을 매개변수로 바인딩하여 쿼리를 생성한다. 여러 번 실행한다고 해도 전체 코드를 컴파일하는 일이 없어 성능상의 장점도 따르며 사용자의 입력이 질의문에 영향을 주지 않기 때문에 SQLi 공격으로부터 완전히 안전해진다.
$sql = "select * from users where username='$id' and password='$pw'"
->
$stmt=$db->prepare("select * from users where username=? and password=?")
위의 쿼리문은 일반 Statement문이고, 아래 쿼리는 Prepared statement를 적용한 구문이다. 일반 쿼리 같은 경우 사용자의 입력이 그대로 쿼리문에 영향을 주기 때문에 취약점이 생기는 반면, Prepared Statement는 저렇게 사용자의 입력을 받는 부분을 ?기호로 대체하여 나중에 전달받는 입력값만 바인딩한다. 공격을 시도하기 위해서 union이든 select든 사용해봐야 문자열로 처리되니 공격이 원천적으로 막히게 되는 것이다.
그렇다면 Prepared Statement문을 사용하면 SQLi 공격이 전혀 통하지 않을 것 같은데 아직도 이 공격이 되는 이유가 무엇일까?
1. Prepared Statement로 처리하지 못하는 곳이 있음.
prepared statement로 전부 처리되면 좋겠지만 order by 정렬 부분이나 table 이름, column 이름 부분 등에는 prepared 처리가 되지 않으므로 인젝션이 발생할 수 있다.
2. 옛날 라이브러리
게시판 검색 기능 같이 예전 라이브러리 쓰는 경우
3. 잘못된 Prepared Statement 처리
그럼 prepared statement로 처리되지 못하는 부분에 할 수 있는 대응방안은 없을까?
필터링
사용자의 입력을 받는 곳에 SQL 쿼리에 사용되는 특수문자와 select, union, update 등의 문자열을 필터링하는 블랙 리스트 기반의 필터링을 하거나 정해진 목적의 입력만 받도록 하는 화이트 리스트 기반의 필터링을 한다.
보안적으로는 화이트 리스트 기반의 필터링이 더 좋으며 필터링을 SQLi의 메인 대응 방안으로 생각하기보다는 이렇게 보조적으로 활용하는 것이 좋다.
그 외의 방법
- 웹 방화벽(WAF)을 이용한다.
- 입력되는 문자열의 길이를 제한할 수도 있다.
- 웹 애플리케이션이 사용하는 데이터베이스 사용자 권한을 제한한다.