일반적으로 웹 애플리케이션은 위와 같이 브라우저 - 서버 - 데이터베이스
로 이루어져 사용자는 서버를 통해 간접적으로 Database와 상호작용한다.
예를 들면, 다음과 같이 사용자가 데이터를 입력하고 submit 버튼을 클릭할 경우, 해당 데이터를 포함한 HTTP 요청(GET)이 서버로 전송된다.
HTTP 요청의 파라미터들은 PHP 스크립트의 요청과 동일한 methods 배열에 저장된다.
PHP 프로그램은 데이터베이스에 쿼리를 전송하기 전에, 데이터베이스 서버와 연결을 생성한다.
또한 쿼리 문자열을 구성하여 데이터베이스에 전송 및 실행한다.
결국 서버는 중간에서 사용자와 데이터 베이스 간 통신을 매개하며, 사용자에게 HTTP 응답 메시지를 전달한다.
SQL Injection Attack은 사용자-서버-데이터베이스
통신 경로의 취약점을 이용하여 데이터베이스에 직접적으로 피해를 줄 수 있다.
사용자가 제공하는 정보는 SQL 문장을 구성하는 요소가 된다. 아래 예시를 살펴보자.
웹 애플리케이션 개발자는 다음과 같이 빈칸에 사용자가 데이터를 입력하도록 유도한다.
개발자의 의도와 달리 사용자가 악의적으로 문장을 구성한 경우를 살펴보자.
SQL에서 #
은 주석을 의미하며, 만약 다음과 같이 사용자 입력에 #
이 포함된다면 이후 문장은 전부 주석으로 처리된다.
이로 인해 비밀번호 일치 여부를 검사하는 쿼리문이 전부 무력화된다. 따라서 입력된 비밀번호와 관계없이 데이터 베이스 상 EID = EID5002
인 직원의 정보에 접근할 수 있다.
꼭 웹 애플리케이션 상 UI에서만 공격을 수행해야 하는 것은 아니다. 터미널상에서 curl
명령어를 활용하여 다음과 같이 SQL Injection을 수행할 수 있다.
curl
명령어를 이용할 경우 반드시 URL enocding을 적용해야 브라우저나 서버가 제대로 해석할 수 있다.
SQL Injection 공격의 근본적인 원인은 데이터와 코드가 혼합된 형태로 작성되었기 때문이다.
Filtering : 사용자로부터 제공된 데이터를 검사하여 코드로 해석될 수 있는 문자는 필터링
Encoding : SQL 인젝션 공격은 특수 문자들이 흔히 사용되므로 인코딩을 통해 특수 문자를 제거한다.
특수 문자를 인코딩할 경우, Parser는 해당 문자를 코드가 아닌 데이터로 처리하게 된다.
PHP의 mysqli
확장 기능에는 mysqli::real_escape_string()
이라는 내장 메소드가 존재하며, 이는 사용자 입력 데이터를 인코딩하는 데 활용된다.
Prepared Statement란, SQL 문장을 미리 준비해두고 필요한 값(파라미터)만 전달받아 쿼리를 실행시키는 방식이다.
1) 일부 값(파라미터)이 비워진 SQL 문장의 템플릿을 먼저 데이터베이스로 전달
2) 데이터베이스는 SQL 템플릿을 파싱, 컴파일, 쿼리 최적화까지 수행한 뒤 실행하지 않고 저장
3) 사용자 데이터를 입력한 경우, 이미 준비된 문장에 데이터를 바인딩(Bind)하여 쿼리를 실행
Prepared Statement는 데이터와 코드를 명확히 분리하여 SQL Inejction을 예방함과 동시에 동일한 SQL 문장을 여러번 사용할 때 성능을 최적화할 수 있다는 장점이 존재한다.
Prepared Statement가 보안을 보장할 수 있는 원리는 다음과 같다.
신뢰성이 보장된 코드는 코드 채널을 통해 전달
사용자 입력 데이터는 신뢰성이 보장되지 않았기 때문에 데이터 채널을 통해 전송
데이터 채널에서 받은 데이터는 파싱되지 않기 때문에(문자열로 취급) 데이터 안에 코드가 숨겨져 있더라도 코드로 취급되지 않아 공격이 실행되지 않는다.
이로 인해 데이터베이스는 코드와 데이터의 경계를 명확히 인식
쿼리 결과를 포함하지 않는 HTTP 응답도 존재한다. 이에 대해서도 공격을 수행할 수 있는데, 이를 Blind SQL Injection이라고 한다.
다음과 같은 상황을 가정해보자
Cookie: TrackingId=u5YD3PapBcR4lN3e7Tj4
SELECT TrackingId FROM TrackedUsers WHERE TrackingId = 'u5YD3PapBcR4lN3e7Tj4'
만약 쿼리 결과가 참이라면, HTTP 응답에 'Welcome'이 포함될 것이며, 쿼리 결과가 거짓이라면 HTTP 응답에는 아무런 반응이 없을 것이다.
즉, 위와 같이 결과가 직접적으로 출력되지 않더라도 서버 응답의 변화를 통해 민감한 정보를 추출할 수 있다.
SQL error를 의도적으로 발생시키는 방법으로, SQL 오류가 발생할 경우 다음과 같은 상황이 존재할 수 있다.
# 예를 들면, 다음과 같이 Username='Administrator'
# 비밀번호 첫 글자가 'm'보다 크면 1/0이 실행되면서 오류가 발생
xyz' AND (
SELECT CASE
WHEN (Username = 'Administrator' AND SUBSTRING(Password, 1, 1) > 'm')
THEN 1/0
ELSE 'a'
END
FROM Users
) = 'a
# 공격 예시
CAST((SELECT example_column FROM example_table) AS int)
# 응답 예시 : 오류 메시지를 발생시켜 그 안에 민감한 데이터가 포함되도록 유도
ERROR: invalid input syntax for type integer: "Example data"
오류 처리를 우아한 방식으로 처리할 경우, 오류 기반 SQL 인젝션은 사용이 불가능하다.
만약 시간 지연이 발생할 경우, HTTP 응답 지연으로 연결되는 상황을 가정해보자.
# 공격 예시 : Password의 첫 글자가 'm'보다 크다면, WAITFOR DELAY 실행
'; IF (SELECT COUNT(Username) FROM Users
WHERE Username = 'Administrator'
AND SUBSTRING(Password, 1, 1) > 'm') = 1
WAITFOR DELAY '0:0:{delay}'--
시간 지연 공격은 응답 속도를 통해 결과를 추론하는 방식으로, 서버를 일부로 몇 초동안 멈추게 하는 등의 방식으로 공격자가 응답이 느린지 빠른지를 보고 조건이 맞는지 확인할 수 있다.