SQL Injection
SQL Injection은 웹 서버와 데이터베이스의 소통 언어인 SQL 언어에 악성 SQL 문을 주입시켜 데이터베이스 안에 있는 데이터를 탈취할 수 있는 공격입니다
간단한 예시를 들어 설명하자면 우리가 클라이언트를 통해 웹 서버에 로그인 요청을 보낼 때 웹 서버는 요청 정보를 DB랑 소통하기 위해 '무사'의 비밀번호 1234라는 SQL 언어로 요청합니다 그러면 DB는 '무사'의 비밀번호 1234를 찾아 맞으면 웹 서버에게 맞다고 응답하여 클라이언트에 로그인이 정상적으로 작동되게 됩니다 이때 SQL Injection은 '무사'의 비밀번호 1234라는 SQL 언어에 '무사의 비밀번호를 1111로 변경해 줘 그리고 뒤에 문자는 무시해 줘'라는 코드를 주입시킬 수 있습니다
원본 : '무사'의 비밀번호 1234
주입 : '무사의 비밀번호를 1111로 변경해 줘 그리고 뒤에 문자는 무시해 줘'의 비밀번호 1234
결국 '의 비밀번호 1234'는 무시되니 '무사의 비밀번호를 1111로 변경해 줘 그리고 뒤에 문자는 무시해 줘'라는 SQL 문이 작동되게 됩니다 이후 DB에서 무사의 비밀번호가 1111로 변경됩니다 이런 식으로 SQL 문을 주입시켜 데이터를 변경하거나 추출할 수 있습니다
해당 개념을 바탕으로 SQL 언어로 논리적 취약점을 이용한 SQL Injection을 이해해 봅시다
[ 실제 SQL 쿼리 문 ]
select * from login where name='musa' and password = '1234'
//select : 데이터를 조회할 때 사용하는 구문
// * : 모든 정보
// login : 테이블 이름
// where : 조회할 데이터의 조건문으로 뒤에 오는 name은 id이며 이 id가 'musa'이고, password는 '1234' 일 때라는 조건문이다
해석 : login 테이블에 있는 모든 정보에서 name id가 'musa'이고 password가 '1234'인 데이터를 추출한다
SELECT 속성1,속성2 FROM TABLE이름 WHERE 조건문
( 이 구문에 로그인 정보가 맞으면 로그인이 성공하게 됩니다 )
↓ ↓ 💉 SQL Injection 삽입 ↓ ↓
select * from login where name='musa' and password = 'or'1'='1
'or'1'='1은 (1=1) 1과 1이 같으므로 항상 참이 됩니다 결국 이 구문은 항상 참이므로 로그인이 성공하게 됩니다
위 쿼리문처럼 논리적 취약점을 이용해서 musa의 비밀번호가 없어도 로그인에 성공하게 됩니다
💉 Union based SQL Injection
SQL Injection의 종류인 Union based SQL Injection 공격 방법에 대해 설명하겠습니다
먼저 Union은 합치다는 의미인데 SQL 문을 하나로 합쳐 주는 것을 의미합니다
이 Union Injection이 성공하면 원하는 쿼리문을 실행할 수 있습니다 그러나 Union Injection을 성공하기 위해서는 두 테이블에 열(column) 개수와 데이터 형태가 같아야 합니다
①번 쿼리문은 Board라는 테이블에서 입력된 값이 title 혹은 contents 내용과 비교하여 테이블을 조회하는 쿼리문 입니다
②번 쿼리문은 ①번 쿼리문에 입력값을 Union과 함께 열(Column)을 맞춰 ①번 쿼리문에 넣습니다(현재 Union 쿼리문은 id와 password를 요청하는 쿼리문 입니다)
③번 쿼리문은 두 테이블이 합쳐 하나의 테이블로 보이게 됩니다 결국 사용자의 password 데이터가 게시글과 함께 화면에 출력됩니다 (FROM Users -- 구문은 Users 테이블 뒷부분부터 모두 주석 처리)
즉 Union based SQL Injection은 기존에 존재하는 쿼리문에 열(Column)과 데이터 형태가 같은 악성 쿼리문을 넣어 하나의 쿼리문으로 출력한다
💉 Blind based SQL Injection
Blind based SQL Injection은 Boolean based SQL Injection과 Time based SQL Injection으로 나뉘는데 Boolean based SQL Injection부터 설명하겠습니다
💊 Boolean based SQL Injection
Boolean based SQL Injection은 쿼리문을 삽입할 때 참과 거짓의 정보를 구분하기 위해 사용되는 기술입니다 주로 테이블 이름을 알아낼 때 사용됩니다
①번 쿼리문은 Users 테이블에 모든 정보에서 id가 'INPUT1'이고 password가 'INPUT2'인 값을 추출하는 쿼리문 입니다
②번은 테이블 이름을 알아내기 위해 'abc123'이라는 계정을 공격자가 미리 생성하여 아이디 값과 ASCII(SUBSTR(SELECT name From information_schema.tables WHERE table_type=’base table’ limit 0,1)1,1)) > 100 –의 악성 문법을 넣은 쿼리문 입니다
(이 쿼리문은 limit 키워드를 이용하여 하나의 테이블만 조회하게 하고, substr이라는 문자열 자르는 함수를 이용해 문자의 첫 글자를 가져와 아스키코드 값으로 변환하여 비교해 주는 쿼리문 입니다 만약 조회되는 테이블이 musa라면 이 문자에 'm' 자를 아스키코드 값으로 변환하여 100이라는 범위에서 비교하게 됩니다 거짓이면 실패이고, 참이면 다음 'u' 문자도 마찬가지로 100 범위를 조정해서 똑같은 방식으로 테이블 이름을 찾아낼 수 있습니다 여기서 지난 시간에 다룬 이진 탐색 개념을 활용할 수 있습니다) - MySQL 문법일 때 사용이 가능하고 DBMS 종류에 따라 문법이 달라집니다
③번은 ②번 악성 쿼리문을 ①번 쿼리문 'INPUT1' id 위치에 주입시켰습니다
💊 Time based SQL Injection
Time based SQL Injection도 Boolean based SQL Injection과 동일하게 참과 거짓의 응답을 통해 DB의 데이터를 구분할 수 있습니다 해당 기법을 이용하여 DB의 이름의 길이를 알아낼 수 있습니다
①번 쿼리문은 Boolean based SQL Injection과 동일합니다
②번은 공격자가 미리 생성한 'abc123'이라는 계정을 넣어 LENGTH 함수를 이용하여 데이터베이스의 이름의 길이를 반환하게 됩니다 이때 SLEEP() 함수는 프로그램에서 일정 시간 동안 대기할 때 사용되는 함수입니다 즉 해당 데이터베이스의 이름의 길이가 = 1이면 SLEEP() 함수가 작동되는 겁니다
③번은 ②번 악성 쿼리문을 ①번 쿼리문 'INPUT1' id 위치에 주입시켰습니다
💉 Error based SQL Injection
Error based SQL Injection은 SQL 언어의 에러를 이용하여 DB의 데이터를 탈취할 수 있습니다
SQL Injection 개념을 설명할 때 다뤘던 쿼리문과 동일합니다
select * from login where name='musa' and password = '1234'
↓ ↓ 💉 Error based SQL Injection 삽입 ↓↓
select * from login where name='musa' and password = 'or'1'='1
이렇게 다양한 SQL 공격 기법으로 DB에 있는 데이터를 탈취할 수 있었습니다 그러면 어떻게 하면 SQL Injection을 예방할 수 있을까요? 완벽하지는 않지만 사용자의 입력 값에 대한 검사를 할 수 있게 위에서 다룬 공격 키워드를 의미 없는 단어로 치환되게 하는 소스 코드를 추가하면 됩니다
또한 SQL에서 설명했던 Prepared Statement 구문을 사용할 수 있습니다
Prepared Statement 구문은 구문 분석 과정을 최초 1번만 수행하고 이후에는 생략할 수 있기 때문에 입력된 정보는 기계어로 컴파일 되어 쿼리문은 그대로입니다
이런 과정을 통해 Prepared Statement은 SELECT 쿼리에 대입된 값을 SQL로 인식하지 않아 SQL Injection을 예방할 수 있습니다 ( 이미 컴파일 되어 있기 때문에 대입된 값을 문자열로 처리함 )
[ 참고 자료 ]
🎄🎅 𝓜𝓮𝓻𝓻𝔂 𝓒𝓱𝓻𝓲𝓼𝓽𝓶𝓪𝓼 🎅🎄