2023. 12. 16. 02:01ㆍnormaltic 취업반 5기/과제
SQL Injection이란?
SQL Injection이란 웹 사이트의 보안 취약점을 이용하여 특정 SQL 쿼리문을 삽입하여 공격자에게 필요한 DB 정보를 추출하거나 인증 우회 등의 공격을 하는 해킹 기법이다. 주로 클라이언트가 입력하는 값에 대한 필터링 기능이 제대로 갖춰지지 않은 경우 발생한다. SQL Injection 공격은 쉬운 난이도에 비해 그 피해는 상당히 효과적이므로 주의해야 한다.
SQL Injection으로 가능한 공격으로는 다음과 같다.
- Authentication Bypass (인증 우회)
- DB Access (데이터베이스 접근)
- Content Change (콘텐츠 변경)
SQL Injection 조건
SQL Injection 공격이 이루어지는 원리에 대해 설명하기 위해서는 먼저 SQL Injection 공격이 가능한 조건에 대해 알아야 한다.
SQL Injection 공격이 가능한 조건은 다음과 같다.
- 웹 애플리케이션이 DB와 연동되어있어야 한다.
- 사용자가 입력한 내용이 SQL 질의문의 일부분으로 사용되어야 한다.
SQL Injection 공격은 웹 애플리케이션이 DB와 연동되어있어야 가능하며 사용자가 입력한 내용이 SQL 질의문의 일부분으로 사용되어야 한다. SQL 질의문을 사용하여 DB 데이터를 조회하는 흐름에 대해서 알고 있다면 위 조건들이 어느 정도 이해가 될 것이다.
DB 데이터 조회 흐름은 다음과 같다.
- 사용자가 데이터를 입력
- 입력 데이터는 DB 언어인 SQL 질의문에 삽입
- 완성된 SQL 질의문으로 DB 데이터 조회
- 조회된 데이터 반환
1) 사용자가 데이터를 입력
사용자는 필요한 정보를 조회하기 위해 데이터를 입력한다. 이는 꼭 필요한 정보를 조회한다라는 과정뿐 아니라 로그인 시 식별/인증 과정 등 SQL 질의문이 사용되는 모든 부분에서 일어나는 일련의 과정에 해당한다.
2) 입력 데이터는 DB 언어인 SQL 질의문에 삽입
입력된 데이터는 서버에서 DB에 전송하기 위해 SQL 질의문의 특정 위치에 삽입된다.
3) 완성된 SQL 질의문으로 DB 데이터 조회
완성된 SQL 질의문을 DB에 전달한다. DB는 전달받은 SQL 질의문에 적합한 데이터를 조회한다.
4) 조회된 데이터 반환
DB에서 조회된 데이터는 서버에 반환되고 서버는 그 데이터를 사용자에게 출력해 준다.
SQL Injection 원리
SQL Injection 공격은 위에 설명한 DB 데이터 흐름 중에 1번, 2번 과정에서 수행 가능하다. 공격자는 2번 과정에서 사용되는 SQL 질의문이 어떻게 구성되어 있는지 추측을 하는 과정을 거치게 된다. SQL 질의문이 어떤 형태로 구성되어 있는지는 사용되는 기능에 따라 다르기 때문에 공격자는 자신이 공격을 수행하는 위치를 인지하고 추측해야 한다. 만약 웹 개발을 했던 경험이 있다면 이 과정에 큰 어려움은 없을 것이다.
로그인 페이지를 예시로 들어보자. 로그인 페이지에서는 입력되는 데이터인 ID와 Password로 식별/인증 과정이 진행된다.
위 SQL 질의문은 식별/인증 과정에서 사용하는 SQL 질의문 중 하나를 예시로 든 것이다. 1번 과정에서 입력한 데이터는 2번 과정에서 사용하는 SQL 질의문의 일부분으로서 작용된다. 로그인 페이지에서는 주로 인증 우회 공격과 DB 데이터 추출 공격이 가능하다. 그렇다면 로그인 페이지에서 SQL Injection으로 인증 우회하는 공격을 진행할 때 사용되는 SQL 질의문이 어떤 것이 있는지 알아보자.
1) 입력 ID' OR '1'='1
인증 우회에 가장 많이 쓰이는 구문 중 하나이다. 아이디는 알지만 비밀번호는 모르는 경우 사용된다.
- 아이디 입력창에 [ 아이디' OR '1' = '1 ]을 입력한다.
- 논리연산자 우선순위에 의해 OR 연산자보다 AND 연산자가 우선적으로 연산된다.
- '1' = '1'의 결과는 참이지만 비밀번호는 모르기에 PASS='비밀번호'는 거짓이 된다. 따라서 AND 연산 결과는 거짓이 된다.
- 아이디는 DB에 저장되어 있는 정보와 일치하므로 ID = '아이디'의 결과는 참이므로 OR 연산 결과는 참이 된다.
- 따라서 ID = '아이디'인 계정으로 로그인된다.
해당 구문은 논리연산자 AND와 OR의 우선순위를 이용하여 비밀번호에 대한 일치는 필요 없이 아이디만 알아도 로그인이 가능하도록 만들어준다. 이러한 유형은 OR 뒤에 나오는 구문의 결과가 TRUE이기만 하면 되기 때문에 꼭 '1' = '1일 필요는 없다.
2) [아이디]' #
이번 구문도 마찬가지로 아이디는 알고 있지만 비밀번호를 모르는 경우 사용된다.
- 아이디 입력창에 [ 아이디' #]을 입력한다.
- #기호로 인해 #기호 뒤의 구문은 모두 주석처리되어 비밀번호 일치 코드는 무시된다.
- 아이디는 DB에 저장되어 있는 정보와 일치하므로 ID = '아이디'의 결과는 참이 된다.
- 따라서 ID = '아이디'인 계정으로 로그인된다.
해당 구문은 주석으로 비밀번호 일치 코드를 무시하게 만들어 아이디만 일치하면 로그인이 가능하게 만드는 구문이다. 주석 기호는 MySQL은 '#', Oracle은 '--'으로 DB마다 다르기 때문에 공격 대상의 DB의 종류가 무엇인지 파악한다거나 DB종류마다 다른 주석 기호를 모두 사용하여 공격을 시도해 볼 수 있다.
3) 1' OR '1' = '1' #
이번 구문은 아이디도 비밀번호도 모르는 경우 사용된다.
- 아이디 입력창에 [ ' OR '1' = '1' # ]을 입력한다.
- #기호로 인해 #기호 뒤는 주석 처리되어 비밀번호 일치 코드는 무시된다.
- ID = '1' OR '1' = '1' 구문은 항상 참이기 때문에 DB에 저장되어 있는 모든 계정 정보를 조회한다.
- 모든 계정 정보를 조회하기 때문에 DB에 저장돼 있는 순서대로 조회가 된다.
- 이때 가장 위에 있는 계정으로 로그인된다.
해당 구문은 아이디도 비밀번호도 모를 때 공격 대상의 웹 애플리케이션의 인증 우회가 필요한 경우 사용된다. 주석으로 비밀번호 일치 코드를 무시하게 만들고 아이디에 해당하는 논리 연산 결과를 항상 참으로 만들어 DB에 저장되어 있는 모든 계정 정보를 조회하게 만든다. 이때 DB에 가장 먼저 저장된 계정으로 로그인이 된다. 일반적으로 DB에 가장 먼저 저장되어 있는 계정은 'admin'이라는 관리자 계정일 확률이 높으므로 주의해야 한다.
이렇듯 입력 데이터에 악의적인 SQL 질의문을 삽입하게 되면 삽입된 SQL 질의문은 기존에 존재하는 SQL 질의문의 구성을 변경시킴으로써 제 기능을 발휘하지 못하게 하고 공격자가 원하는 SQL 질의문에 대한 데이터를 조회하도록 만든다.
SQL Injection 공격 종류
SQL Injection 공격은 데이터가 출력되는 방식에 따라 사용 가능한 공격이 달라진다. 대표적으로 3가지 종류가 존재하며 이는 다음과 같다.
- Union Based SQL Injection : 게시판 같이 데이터가 출력되는 곳에서 사용이 가능하다.
- Error Based SQL Injection : 에러 메시지가 출력되는 곳에서 사용이 가능하다.
- Blind SQL Injection : 데이터가 출력되지 않아 Error Based/Union Based SQL Injection 공격이 불가능한 곳에서 사용이 가능하다.
Union Based SQL Injection
Union Based SQL Injection 공격은 SQL Injection 공격 중 한 종류로서 SQL 문법 중 하나인 Union 문법을 사용하여 게시판 같이 SQL 질의문 결과가 출력되는 곳에서 사용 가능한 공격이다. 이를 통해 공격대상의 DB 데이터를 추출하는 공격을 진행하여 피해를 입힐 수 있다. 그렇다면 먼저 Union 문법에 대해 간단히 알아보자.
1) Union
Union이란 2개 이상의 select문을 하나의 select문으로 합치는 문법이다. 이때 생기는 중복된 값들은 하나만 남기고 모두 제거한다. Union 문법을 쓰기 위해서는 여러 조건들이 존재하며 이를 지켜야 사용가능하다. 조건들은 다음과 같다.
- 컬럼명이 동일해야 한다.
- 컬럼별 데이터 타입이 동일해야 한다.
- 출력할 컬럼의 개수가 동일해야 한다.
- 만약 컬럼명이 동일하지 않는 경우 AS를 통해 동일하게 맞춰준다.
2) Union 예시
TABLE_1과 TABLE_2라는 이름을 가진 두 테이블이 존재하며 두 테이블의 컬럼명과 컬럼 수는 같으므로 Union 문법을 사용하기 위한 조건을 만족한다. 이제 이를 사용하기 위해 SQL 질의문을 입력한다.
Union 질의문 결과는 먼저 입력된 SELECT문 데이터 결과 밑으로 합쳐진다. 만약 중복된 값이 존재하면 가장 먼저 조회된 값만 남기고 나머지는 제거하여 합친다.
Union Based SQL Injection을 이용한 DB 추출 공격
Union Based SQL Injection을 이용한 DB 추출 공격은 아래의 과정에 따라 진행한다.
- SQL Injection 작동 여부
- COLUMN 개수 찾기
- 출력되는 COLUMN 위치 찾기
- DB 이름 찾기
- TABLE 이름 찾기
- COLUMN 이름 찾기
- DATA 추출
[1] SQL Injection 작동 여부
가장 먼저 수행되어야 할 작업이다. SQL Injection 공격을 하려면 우선 질의문을 삽입했을 때 정상적으로 작동해야 한다. 질의문으로 항등원을 삽입하여 정상 작동되면 해당 포인트는 SQL Injection 공격이 유효하다는 뜻이다.
해당 공격은 위 사진의 실습용 웹 어플리케이션을 활용할 것이다. 웹 페이지는 게임의 이름을 입력했을 때 그 게임의 이름과 점수, 제작사의 정보를 출력한다. 조회에 사용되는 질의문은 select * from game where name like '%게임이름%'으로 조회하고 싶은 게임의 이름에 포함된 문자가 입력되면 조건에 맞는 모든 데이터를 출력해 준다. 그렇다면 조건에 맞게 질의문을 삽입해 보자.
입력창에 over%' and '1%'='1을 입력했더니 정상적인 결과가 출력됐다. 항등원이란 연산 결과가 자기 자신이 나오는 걸 의미한다. 이는 and 연산자를 사용하고 뒤에 오는 피연산자를 문법에 맞게 항상 참인 결과를 나타내는 구문으로 만들어 앞의 결과가 출력되도록 한다. 그 결과 질의문을 삽입했을 때와 삽입하지 않았을 때 같은 결과가 출력이 되면 이는 SQL Injection 공격이 유효하다는 것을 의미한다.
=> 예시 : %' and '1%'='1 (이는 예시이므로 사용되는 질의문에 맞게 문법을 변경해주어야 한다.)
[2] COLUMN 개수 찾기
Union 문법을 사용하기 위해서는 질의문에 입력된 컬럼의 개수를 알아야한다. 컬럼의 개수를 알기 위해서는 'ORDER BY' 문법을 사용하면 되는데 이는 ORDER BY 문법의 오류가 발생하는 원리를 이용한다. 그렇다면 간단하게 ORDER BY 문법이 어떤 건지 알아보자.
ORDER BY 문법은 데이터를 정렬하여 조회해 준다. 이때 기준은 ORDER BY 뒤에 나오는 '컬럼명 혹은 컬럼이 적혀진 순서'가 정렬 기준이 된다. 예를 들어 위 질의문에서 DATE를 기준으로 데이터를 조회하고 싶다 하면 ORDER BY 뒤에 'DATE' 혹은 '4'를 입력하면 조회 시 DATE 컬럼을 기준으로 오름차순 정렬을 하여 출력한다. 정렬 방법은 기본값으로 오름차순 정렬이며 내림차순 정렬로 출력하고 싶다면 뒤에 'DESC'를 추가적으로 입력해주면 된다.(오름차순 정렬은 ASC)
그렇다면 ORDER BY 문법이 어떻게 해야 오류가 발생하는지 알아보자. ORDER BY 문법 사용 시 뒤에 입력하는 정렬 기준이 되는 컬럼명 혹은 컬럼 순서를 입력했을 때 존재하지 않으면 오류가 발생한다. 컬럼명은 알 수 없으므로 컬럼 순서를 1부터 차례대로 입력함으로써 오류가 발생하는 순번을 통해 해당 질의문에 컬럼이 몇 개 입력되어 있는지 간접적으로 알 수 있다.
=> 예시 : %' order by 4 #
[3] 출력되는 COLUMN 위치 찾기
전단계에서 컬럼의 개수를 찾고 난 후 Union 문법 사용 시 어떤 컬럼에 데이터가 출력되는지 찾아야한다.
어느 컬럼에 데이터가 출력되는지 알려면 위 사진에 나와있는 질의문처럼 입력하면 된다. union select를 통해 데이터를 추가하여 추가 데이터가 어떤 컬럼으로 나오는지 알 수 있다. 해당 질의문의 결과로 보건대 첫 번째 컬럼은 출력이 되지 않는 것을 알 수 있다. 그렇다면 나중에 데이터 출력 시 2,3,4번 자리에 입력하면 된다.
=> 예시 : %' union select 1,2,3,4 #
[4] DB 이름 찾기
현재 사용 중인 DB 이름은 'select database()' 질의문을 사용하면 알 수 있다. 해당 질의문을 전단계에 사용한 질의문에 대입하면 그 결과를 출력할 수 있다. 이전 단계들로부터 첫 번째 컬럼은 데이터를 출력하지 않으므로 2,3,4번 중 한 자리에 입력한다.
두 번째 컬럼 자리에 'database()'을 입력 후 조회하게 되면 'name' 컬럼에 현재 사용 중인 DB 이름이 출력된다. 사용중인 DB 이름은 'segfault_sql'이란 것을 알 수 있다.
=> 예시 : %' union select 1,database(),3,4 #
[5] TABLE 이름 찾기
현재 사용 중인 DB 이름을 찾았다면 이제 그 DB에 존재하는 테이블의 이름을 알아야 한다. 이를 알기 위해 DB의 메타 정보를 모아둔 DB인 information_schema DB에 존재한다. information_schema DB에는 COLUMNS, TABLES, SCHEMATA 등 여러 가지 테이블이 존재한다. 전단계에서 찾은 DB의 테이블 이름을 알기 위해서는 다음과 같이 질의문을 입력하면 된다.
'DB 이름'이 적혀있는 위치에 전단계에서 찾은 DB 이름인 'segfault_sql'을 입력하면 해당 DB에 존재하는 테이블 이름이 모두 출력된다. 그렇다면 해당 질의문을 union select 질의문에 대입하여 그 결과를 출력해 보자.
출력 결과 'segfault_sql' DB에는 'game, member, secret, secret_member' 테이블들이 존재하는 것을 확인할 수 있다.
=> 예시 %' union select 1,table_name,3,4 from information_schema.tables where table_schema = 'segfault_sql' #
[6] COLUMN 이름 찾기
전단계에서 찾은 각테이블을 구성하는 컬럼 이름을 찾는 방법은 테이블 이름을 찾는 방법과 동일하다.
'TABLE 이름'이 적혀있는 위치에 전단계에서 찾은 TABLE 이름 중 하나인 'game'을 입력하면 해당 TABLE에 존재하는 컬럼 이름이 모두 출력된다. 그렇다면 해당 질의문을 union select 질의문에 대입하여 그 결과를 출력해 보자.
출력 결과 'game' TABLE에는 'idx, name, score, production' 컬럼들이 존재하는 것을 확인할 수 있다.
=> 예시
%' union select 1,column_name,3,4 from information_schema.columns where table_name = 'game' #
[7] DATA 추출
이제 데이터 조회를 위한 모든 정보를 수집하였으니 이를 이용하여 데이터를 추출하는 일만 남았다.
출력 결과 'game' TABLE에는 3개의 데이터가 존재하는 것을 확인할 수 있다.
=> 예시 : %' union select * from game #
Error Based SQL Injection
Error Based SQL Injection 공격은 SQL Injection 공격 중 한 종류로서 SQL 질의문에 문제가 있을 때 출력되는 에러 메시지를 이용한다. SQL 질의문이 에러가 발생하는 원인에는 여러 가지가 존재하며 그중에서도 특정 에러를 유발하는 SQL 질의문을 사용해야 한다.
SQL 질의문에서 유발할 수 있는 에러는 두 가지가 존재한다.
- 문법 에러 : 문법 에러는 질의문 자체가 문법적으로 문제가 있어 실행 자체가 안 되는 에러다. 그렇기에 해당 에러를 발생시켜 출력되는 에러 메시지는 이용 가치가 없다.
- 로직 에러 : 로직 에러는 질의문으로서는 문제가 없어 실행은 되나 그 안에 문제가 되는 부분을 에러 메시지로 출력한다. 해당 에러를 유발함으로써 Error Based SQL Injection 공격을 실행할 수 있다.
결과적으로 Error Based SQL Injection 공격을 실행하기 위해서는 SELECT 질의문이 우선적으로 실행되어야 하기 때문에 에러가 발생해도 질의문이 실행되는 로직 에러를 SQL 질의문으로 유발시켜야 한다.
Error Based SQL Injection Payload
Error Based SQL Injection Payload는 다음과 같은 순서로 작성해나간다. SQL 질의문의 'normaltic'은 공격대상 DB에 저장되어있는 ID 중 하나로 해당 위치에 삽입되는 문자열은 공격대상 DB에 저장되어있는 ID 중 하나여야 한다.
- normaltic' and '1'='1
- normaltic' and extractvalue('1', ':test') and '1'='1
- normaltic' and extractvalue('1', concat(0x3a,(select 'test'))) and '1'='1
1) normaltic' and '1'='1
SQL Injection 하기 위한 기본적인 형태로서 공격대상의 질의문 형태에 맞춰 작성한다. 이때 해당 질의문의 결과값은 상관없이 SQL Injection이 정상 작동하는지 확인한다.
2) normaltic' and extractvalue('1',':test') and '1'='1
공격대상이 사용하고 있는 DB에 맞춰 로직 에러를 유발하는 함수를 입력한다. 예시로는 MySQL의 extractvalue함수를 사용하였다.
* extractvalue('xml 글자', 'xml 표현식')
extractvalue 함수는 xml로 표현된 문자 내에서 특정 문자를 나타낸다. 예를 들어 '<a>abc<b>kkk</b></a>'라는 xml 문자가 존재할 때 'abc'라는 문자열을 나타내고 싶다 하면 '/a' 으로 xml 문자 내 위치를 표현한다. 함수로 표현하면 다음과 같다.
- extractvalue('<a>abc<b>kkk</b></a>','/a') = abc
- extractvalue('<a>abc<b>kkk</b></a>','/a/b') = kkk
extractvalue 함수는 로직 에러를 유발하는 함수로서 'xml 표현식' 인자에 쓰이는 특수문자(/,@ 등) 대신 다른 특수문자를 사용하게 되면 에러 메시지를 출력하게 된다. 이 방식으로 'xml 표현식' 인자에 '특수문자+select 질의문'을 입력하여 질의문이 실행되도록 한다.
3) normaltic' and extractvalue('1', concat(0x3a,(select 'test'))) and '1'='1
SQL Injection 공격은 반복적인 작업이다. 그렇기에 Payload를 Format화 시켜 반복 작업에 들이는 수고를 덜어줘야 한다. Payload에서 계속해서 값이 바뀌는 부분은 extractvalue 함수의 'xml 표현식' 인자 위치이다. 해당 위치에서는 데이터 추출을 위한 select 질의문의 내용이 지속적으로 변경되기 때문에 concat 함수를 사용하여 특수문자(:)와 select 질의문의 위치를 구분 지어준다. 이때 특수문자는 8진수를 사용하여 표현한다. 이는 특수문자가 필터링되어 Payload가 작동하지 않음을 배제하기 위함이다.
Error Based SQL Injection을 이용한 DB 추출 공격
Error Based SQL Injection을 이용한 DB 추출 공격은 아래의 과정에 따라 진행한다.
- SQL Injection 작동 여부
- 로직 에러 유발 함수 찾기
- 공격 Format 만들기
- DB 이름 찾기
- TABLE 이름 찾기
- COLUMN 이름 찾기
- DATA 추출
[1] SQL Injection 작동 여부
Error Based SQL Injection 공격에 사용될 실습용 웹 어플리케이션으로서 ID 중복 검사 기능을 가진 프로그램이다. ID를 입력하면 해당 ID가 DB에 존재하는지 확인하여 존재여부에 대한 결과를 출력한다.
SQL Injection이 작동하는지 확인하기 위해 해당 기능에 쓰인 질의문에 맞춰 항등원의 역할을 하는 질의문을 입력한다.
입력 결과 정상적으로 작동함으로써 SQL Injection 공격이 가능한 것을 확인하였다.
[2] 로직 에러 유발 함수 찾기
로직 에러 유발 함수로는 MySQL의 extractvalue 함수를 사용하도록 한다.
[3] 공격 Format 만들기
Error Based SQL Injection 공격에 쓰이는 Format을 만들어 반복 작업을 하기 쉽도록 한다.
normaltic' and extractvalue('1', concat(0x3a,(__'입력할 질의문'__))) and '1'='1
extractvalue 함수 뒤 문장은 주석으로 처리할 수도 있으나 DB마다 쓰이는 주석이 다르기 때문에 어떤 DB가 쓰여도 Format의 형태가 변하지 않도록 and '1'='1을 입력해 준다.
[4] DB 이름 찾기
이 단계부터는 다른 SQL Injection 공격들과 같다. 만들어진 Format에 알맞은 질의문을 입력하여 데이터를 추출한다.
DB의 이름을 찾는 질의문은 select database()로 Format에 넣어 삽입한 결과, 'errSqli'라는 DB를 찾아냈다.
[5] TABLE 이름 찾기
Error Based SQL Injection 공격으로는 출력되는 데이터가 1개뿐이기에 limit 문법을 사용하여 데이터를 하나하나 출력해봐야 한다. 그 결과, 'flagTable, member'이라는 두 테이블 이름을 찾아냈다.
테이블 데이터가 총 두 개였으므로 3행에 존재하는 데이터는 존재하지 않는다. 그래서 3행에 존재하는 데이터를 찾으려고 했을 때는 그 결과로 false를 반환한다.
[6] COLUMN 이름 찾기
총 두 개의 테이블을 찾았으므로 각 테이블마다 컬럼을 찾아준다.
1. flagTable
flagTable 테이블에서는 'idx, flag'라는 두 컬럼을 찾아냈다.
2. member
member 테이블에서는 'id, pass, email, info'라는 네 컬럼을 찾아냈다.
[7] DATA 추출
데이터 추출에 필요한 모든 정보를 찾아냈으니 이제 본격적으로 데이터를 추출해 보도록 하자.
1. flagTable
flagTable 테이블 내에는 하나의 데이터만 존재하였다. 그 데이터는 문제의 플레그를 나타낸다.
2. member
member 테이블에는 id 컬럼으로 데이터를 추출해 본 결과 'normaltic, dol'라는 두 데이터만 존재하였다.
Blind SQL Injection
Blind SQL Injection 공격은 SQL Injection 공격 중 한 종류로서 질의문의 결과가 참 혹은 거짓으로 출력이 되는 경우에 사용 가능하다. 사실 Blind SQL Injection는 다른 SQL Injection 공격을 사용할 수 없는 상황일 때 최후의 방법으로 사용이 된다. 공격의 이름 대로 눈먼 장님같이 문자 하나하나 대조하면서 공격을 진행한다.
Blind SQL Injection Payload
Blind SQL Injection Payload는 다음과 같은 순서로 작성해 나간다.
- normaltic' and '1'='1
- normaltic' and ('1'='1') and '1'='1
- normaltic' and ((select 'test')='test') and '1'='1
- normaltic' and (substr('test',1,1)='t') and '1'='1
- normaltic' and (ascii(substr('test',1,1)) > 0) and '1'='1
1) normaltic' and '1'='1
SQL Injection 하기 위한 기본적인 형태로서 공격대상의 질의문 형태에 맞춰 작성한다. 이때 해당 형태에서의 결과값은 항상 참이어야 한다.
2) normaltic' and ('1'='1') and '1'='1
질의문이 삽입될 위치에 조건문 하나를 삽입하여 정상 작동하는지 테스트한다. Payload의 결과값은 해당 위치에 삽입될 조건문의 결과에 따라 달라진다.
3) normaltic' and ((select 'test') = 'test') and '1'='1
select 질의문이 정상 작동하는지 조건문에 삽입하여 테스트한다.
4) normaltic' and (substr('test',1,1)='t') and '1'='1
substr 함수를 사용함으로써 반복 작업을 좀 더 편리하게 만들어준다. Payload는 빨간색 숫자를 계속 변경하여 'test' 해당 위치에 맞는 문자일 시 그 결과값으로 참을 반환한다.
* substr('대상 문자열', '시작 위치', '시작 위치에서부터 n번째 위치까지')
substr 함수는 인자로 입력되는 문자열에서 원하는 위치의 문자를 반환해 주는 함수다.
- substr('normaltic',1,1) = 'n' => 'normaltic' 문자열의 첫 번째 위치에서부터 하나의 문자를 반환
- substr('normaltic',3,2) = 'rm' => 'normaltic' 문자열의 세 번째 위치에서부터 두 문자를 반환
5) normaltic' and (ascii(substr('test',1,1)) > 0) and '1'='1
Payload의 형태를 반복 작업의 피로를 덜어주기 위해 문자를 숫자 형태로 바꿔주는 작업을 진행한다. 이때 문자에 대응하는 숫자는 아스키코드로 표현이 가능하며 문자를 아스키 코드로 바꿔주는 함수인 ascii 함수를 사용한다.
* ascii('문자')
ascii 함수는 인자로 입력되는 문자에 대응하는 아스키코드를 결과값으로 반환해 주는 함수다.
- ascii('a') = 97
- ascii('X') = 88
Blind SQL Injection을 이용한 DB 추출 공격
Blind SQL Injection을 이용한 DB 추출 공격은 아래의 과정에 따라 진행한다.
- SQL Injection 작동 여부
- SELECT 문구 사용 가능한지 확인
- 공격 Format 만들기
- DB 이름 찾기
- TABLE 이름 찾기
- COLUMN 이름 찾기
- DATA 추출
[1] SQL Injection 작동 여부
Blind SQL Injection 공격에 사용될 실습용 웹 어플리케이션으로서 ID 중복 검사 기능을 가진 프로그램이다. ID를 입력하면 해당 ID가 DB에 존재하는지 확인하여 존재여부에 대한 결과를 출력한다.
SQL Injection이 작동하는지 확인하기 위해 해당 기능에 쓰인 질의문에 맞춰 항등원의 역할을 하는 질의문을 입력한다.
입력 결과 정상적으로 작동함으로써 SQL Injection 공격이 가능한 것을 확인하였다.
[2] SELECT 문구 사용 가능한지 확인
질의문 삽입 시 select 구문이 적용되는지 테스트한 결과 정상 작동됨을 확인하였다.
[3] 공격 Format 만들기
Blind SQL Injection 공격에 쓰이는 Format을 만들어 반복 작업을 하기 쉽도록 한다.
normaltic' and (ascii(substr((__'입력할 질의문'__),1,1)) > 0) and '1'='1
[4] DB 이름 찾기
DB의 이름을 찾는 질의문은 select database()로 Format에 넣어 삽입한 결과, 'blindSqli'라는 DB를 찾아냈다.
[5] TABLE 이름 찾기
테이블은 'flagTable, member' 총 두 테이블을 찾아냈으나 flagTable 테이블에 한해서만 공격을 진행한다.
[6] COLUMN 이름 찾기
flagTable에서 'idx, flag' 총 두 컬럼을 찾아냈으나 flag 컬럼에 한해서만 공격을 진행한다.
[7] DATA 추출
flagTable 테이블 내에는 하나의 데이터만 존재하였다. 그 데이터는 문제의 플레그를 나타낸다.
SQL Injection 공격이 가능하려면?
SQL Injection 공격은 주로 사용자가 입력을 할 수 있는 입력창에서 발생한다. 그러나 입력창이 아니더라도 SQL Injection 공격은 DB와 관련된 곳이라면 어디든지 발생할 수 있다는 것을 인지해야 한다.
SQL Injection 공격이란 공격자의 입맛에 맞게 SQL 질의문을 삽입함으로써 공격대상의 DB에 존재하는 데이터를 추출하는 것으로 목표로 한다. 그렇다는 것은 SQL Injection 공격은 DB와 연결되어 있으며 SQL 질의문이 사용되는 위치라면 어디든지 가능하다는 의미이다.
*SQL Injection 공격이 발생 가능한 조건
- DB와 연결되어있어야 한다.
- SQL 질의문을 사용하여야 한다.
- SQL Injection 공격 시 참과 거짓에 결과를 구분할 수 있어야 한다.
SQL Injection 공격이 가능한 여러 Case들
SQL Injection 공격은 사용자 입력창뿐만 아니라 다른 위치에서도 가능하다. 다른 위치에서의 SQL Injection 공격을 수행하기 위해 웹 프록시 툴 중 하나인 Burp Suite을 사용하기로 한다.
[1] Cookie에서의 SQL Injection
첫 번째 Case는 Cookie에서의 SQL Injection이다. 웹 페이지 중에서 사용자의 개인정보를 확인하는 '마이페이지'라는 곳이 존재한다. 웹 개발을 하다 보면 마이페이지는 사용자의 개인 정보 조회 시 사용자 측에서 웹 페이지 요청 시 함께 전달하는 SESSION ID로 사용자를 식별한 후 해당 사용자의 개인 정보를 DB에서 조회하여 웹 페이지에 출력한다는 것을 알 수 있다. (지금 사용 중인 웹 페이지는 해당 Case가 존재한다라는 것을 알려주기 위해 의도적으로 user라는 쿠키명에 담겨있는 쿠키값인 사용자 계정을 참고하여 개인정보를 조회하도록 구성되어 있다.)
user라는 쿠키의 쿠키값에 SQL Injection 공격을 시도한 결과 성공적으로 수행된 것을 확인할 수 있다. 그렇다면 DB에 존재하지 않는 계정이 삽입되면(거짓 결과를 유발하는 값) 어떤 정보를 응답하는지 알아보자.
user 쿠키의 쿠키값으로 and '1'='2라는 질의문을 삽입한 결과 정보가 출력되는 세 부분 중 가운데 부분의 'Nothing Here...' 문자열이 출력되지 않았다. 이를 통해 SQL Injection 공격으로 참과 거짓에 대한 결과를 분별 가능함으로써 해당 부분에서 Blind SQL Injection 공격을 수행할 수 있다.
[2] 질의문에 삽입되는 위치가 컬럼명 혹은 테이블명인 경우
두 번째 Case는 질의문에 삽입되는 위치가 컬럼명 혹은 테이블명인 경우이다. 이번 Case는 게시판 페이지에서 게시글 조회 시 입력 문자열을 포함하는 곳을 전달하는 파라미터에 SQL Injection 공격이 가능하다. 게시글 조회 시 사용되는 질의문은 일반적으로 'SELECT * FROM TABLE WHERE COLUMN LIKE '%조회 문자열%'으로 구성되며 COLUMN 위치에 '작성자, 제목, 내용'이라는 데이터를 조건에 맞춰 선택한다. 해당 웹 페이지에서는 option_val라는 파라미터에 COLUMN(작성자, 제목, 내용)에 해당하는 데이터를 담아 전달하며 서버 측에서는 전달받은 데이터를 기준으로 게시글을 출력한다.
컬럼명 혹은 테이블명에서의 SQL Injection 공격을 수행하기 위해서는 생각을 달리 해야한다. 지금까지는 SQL 질의문이 삽입되는 위치가 컬럼명 혹은 테이블명 다음에 위치하기 때문에 '입력 문자열+삽입 문자열' 형태로 구성하여 SQL Injection을 수행했지만 이번에는 '삽입 문자열+입력 문자열'처럼 구성해야 SQL 문법이 망가지지 않는다. 그렇다면 DB에 존재하지 않는 컬럼명이 삽입되면 어떤 정보를 응답하는지 알아보자.
option_val 파라미터에 '1'='2' and username 질의문을 삽입한 결과 어떤 게시글도 출력되지 않았다. 이를 통해 SQL Injection 공격으로 참과 거짓에 대한 결과를 구분 가능함으로써 해당 부분에서 Blind SQL Injection 공격을 수행할 수 있다.
[3] 질의문에 삽입되는 위치가 ORDER BY 구문인 경우
세 번째 Case는 질의문에 삽입되는 위치가 ORDER BY 구문인 경우이다. ORDER BY 구문은 데이터를 정렬할 때 사용하는 구문으로서 게시판 페이지에서 제목, 내용, 그리고 작성일 등을 기준으로 게시글을 정렬하여 나열하는 용도로 쓰인다. 위 사진에서 웹 페이지를 요청할 때 sort 파라미터가 같이 전달되며 sort = 'title'이면 게시글의 제목을 기준으로 게시글을 정렬하여 나열하고 sort = 'content'이면 게시글의 내용을 기준으로 게시글을 정렬하여 나열한다. ORDER BY 구문은 ORDER BY '컬럼명 혹은 숫자 1,2,3...'으로 이루어져 있으며 쓰인 컬럼명을 기준으로 정렬되며 기본값은 오름차순 정렬이다.(숫자는 select 구문에 쓰인 컬럼들의 순서를 나타낸다.)
그렇다면 ORDER BY 구문에서는 어떻게 SQL Injection이 이루어지는지 알아보자.
ORDER BY 구문에서는 주로 CASE WHEN 구문으로 SQL Injection 공격을 수행한다. 우선 CASE WHEN 구문에 대해 간단히 설명하자면 SQL에서의 IF문이라고 생각하면 된다. 'CASE WHEN (조건) THEN (값 1) ELSE (값 2) END'으로 쓰이며 조건으로 출력되는 값이 TRUE이면 값 1을 출력하고 FALSE면 값 2를 출력하는 방식이다. 이러한 방식을 해당 웹 페이지에 적용하여 sort 파라미터에 'sort = CASE WHEN (1=1) THEN (title) ELSE (content) END' 이러한 질의문을 삽입할 수 있다. 조건의 결과가 참이면 title을 출력하므로 게시글은 제목을 기준으로 정렬 후 나열되며 '1=1' 대신 '1=2'를 입력하게 되면 조건의 결과가 거짓이므로 content를 출력하여 게시글을 내용 기준으로 정렬 후 나열되는 것으로 참과 거짓의 결과를 구분할 수 있게 된다.
그러나 위에 쓰인 방법으로 참과 거짓을 판단하게 되면 가끔가다 실수를 할 수 있게 된다. 그래서 확실하게 참과 거짓을 판단하는 구문으로 'CASE WHEN (1=1) THEN 1 ELSE (select 1 union select 2) END'이러한 형태를 사용하기도 한다. 해당 구문은 ORDER BY 구문에 입력되는 값이 1개여야만 하는 것에 착안한 방법으로서 만약 출력되는 값이 적합하지 않다면 에러를 유발한다. 해당 원리를 이용하여 거짓의 결과로 출력되는 구문인 'select 1 union select 2'를 사용함으로써 출력 결과로 두 개의 행을 출력시킨다. 그렇게 되면 ORDER BY 구문은 두 행 중 어느 것도 선택하지 못해 에러를 유발하여 게시글을 출력하지 않는 현상을 야기한다. 이를 통해 SQL Injection 공격으로 참과 거짓에 대한 결과를 구분 가능함으로써 해당 부분에서 Blind SQL Injection 공격을 수행할 수 있다.
SQL Injection 공격 대안
SQL Injection 공격은 가장 난이도가 쉬우면서도 공격의 효율이 좋은 공격이다. 그러나 이러한 SQL Injection 공격은 현재까지 알려진 방법 중 하나인 Prepared Statement으로 쉽게 방어할 수 있다.
* Prepared Statement란?
Prepared Statement는 SQL 질의문을 미리 컴파일하여 DB에 영향을 주는 부하를 줄이기 위해서 개발되었다. 그러나 Prepared Statement를 사용하다 보니 SQL Injection 공격을 방지해 주는 효과를 발견하게 되면서 SQL Injection 공격을 방어하는 방법 중 하나로 알려지게 되었다.
Prepared Statement는 'select * from table1 parms1 = value1 and parms2 = value2'라는 질의문에서 value1과 value2 위치를 '?'로 대체하고 나머지 부분을 미리 컴파일해 둔다. 그렇게 되면 value1과 value2로 삽입되는 문자열을 하나의 데이터로 받아들이게 된다. 이러한 효과로 인해 value1과 value2로 삽입되는 질의문이 기존의 질의문을 변조시킬 수 없게 됨으로써 SQL Injection 공격을 방어할 수 있게 된 것이다.
그러나 Prepared Statement라는 방법이 존재함에도 불구하고 SQL Injection 공격이 가능한 경우가 존재한다.
- Prepared Statement를 제대로 사용하지 못한 경우 : 해당 경우에는 Prepared Statement 방법을 적용을 시키긴 했지만 어설프게 적용시킴으로써 SQL Injection 공격이 가능한 경우다.
- Prepared Statement를 적용시키지 못하는 경우 : 해당 경우에는 Prepared Statement를 적용시키지 못하는 구문에서 SQL Injection 공격이 발생하는 경우다. Prepared Statement를 적용시키지 못하는 위치는 ORDER BY 구문, TABLE 이름, COLUMN 이름 위치가 해당된다.
1번의 경우에는 해결할 수 있는 여지가 존재하지만 2번의 경우에는 Prepared Statement 자체를 사용하지 못하기 때문에 다른 방법으로 대체해야 한다. Prepared Statement 대신 SQL Injection을 방어할 방법으로 사용되는 기법은 화이트 리스트 기반 필터링 방법이 존재한다. 화이트 리스트 기반 필터링은 대상 위치에 개발자가 입력한 단어들만 입력할 수 있는 방법으로 블랙리스트 기반 필터링 방법보다 자주 사용된다.
'normaltic 취업반 5기 > 과제' 카테고리의 다른 글
[normaltic 취업반 5기] 2023-12-13 8주차 과제 : 3번 과제 SQL Injection Advanced 3,4 (2) | 2023.12.20 |
---|---|
[normaltic 취업반 5기] 2023-12-13 8주차 과제 : 3번 과제 SQL Injection Advanced 1,2 (2) | 2023.12.20 |
[normaltic 취업반 5기] 2023-12-06 7주차 과제 : SQL 6번 문제 (0) | 2023.12.07 |
[normaltic 취업반 5기] 2023-12-06 7주차 과제 : SQL 3, 4, 5번 문제 (0) | 2023.12.07 |
[normaltic 취업반 5기] 2023-12-02 6주차 과제 : 3번 과제 (2) | 2023.12.02 |