sql = "select username from users where username='%s'" %username //사용자 입력 바로 받음. sql injection 가능
conn = getConnection()
curs = conn.cursor(pymysql.cursors.DictCursor)
curs.execute(sql)
rows = curs.fetchall()
conn.close()
if (rows): //결과 직접 출력X
return"True"
else:return"False"
app.run(host='0.0.0.0', port=8000, debug=True) //디버그 모드 설정 -> 에러 메세지 출력
위와 같이 쿼리 결과가 직접적으로 출력되지 않으며 에러 메시지가 노출되는경우
에러 메시지를 통해 주요 정보를 노출시키는 방법들이 존재합니다.
해당 방법들을 사용하여 Error Based SQL Injection을 성공적으로 활용하기 위해서는
Syntax Error와 같이 DBMS에서 해당 쿼리가 실행되기 전에 검증 가능한 에러가 아닌, Runtime 중에 발생하는 에러가 필요합니다.
아래 SQL 쿼리는 MySQL 에서 Error Based SQL Injection을 위해 많이 사용되는 공격 형태 중 하나입니다.
버전 정보 노출--version()
extractvalue 함수란:
extractvalue 함수는 첫번째 인자에 존재하는 xml 데이터에서 두번째 인자의 XPATH 식을 통해 데이터를 추출하는 함수입니다. 하지만 두번째 인자에 올바르지 않은 XPATH 식을 입력하게 되면 올바르지 않은 XPATH 식이라는 에러와 함께 해당 인자가 함께 출력되기 때문에 이를 이용한 공격 기법입니다.
sql = "select username from users where username='%s'" %username //입력값 그대로 출력, sql injection 가능
conn = getConnection()
curs = conn.cursor(pymysql.cursors.DictCursor)
curs.execute(sql)
rows = curs.fetchall()
conn.close()
if(rows[0]['username'] == "admin"): //username이 admin이면 True 아니면 False
return"True"else:return"False"
app.run(host='0.0.0.0', port=8000)
위 코드는 username에 의해 SQL Injection이 발생하며, 데이터베이스의 결과인 username이 admin인 경우에는 사용자에게 "True"가 반환되고, 아닌 경우에는 "False"가 반환됩니다.
users 테이블
UNION 구문을 이용해 "admin" 반환
admin이 있으면 True, not admin은 없으므로 False
IF Statements 및 비교 구문 추가(비밀번호 알아내기)
/?username=' union select if(substr(password,1,1)='A', 'admin', 'not admin') from users where username='admin' -- -==> False //password 첫 문자가 A이면 admin, 아니면 not admin --> True 또는 False 출력
/?username=' union select if(substr(password,1,1)='B', 'admin', 'not admin') from users where username='admin' -- -==> False
...
/?username=' union select if(substr(password,1,1)='P', 'admin', 'not admin') from users where username='admin' -- -==> True //admin의 password의 첫문자는 P
...
/?username=' union select if(substr(password,2,1)='a', 'admin', 'not admin') from users where username='admin' -- -==> True//admin의 password의 두번째 문자는 a
substr은 첫번째 인자의 문자열을 두번째 인자의 index부터 세번째 인자의 길이만큼 반환하는 함수입니다.
즉,substr(password,1,1)실행 시 "admin"의 패스워드 첫번째 문자열인 "P"가 반환되며, 사용자는 각 문자열을 입력하며 비교합니다.
사용자 입력 문자 중 "P"문자 입력 시 조건이 참이되며, 사용자에게 "True"가 출력되어집니다. 이를 통해 사용자는 password의 첫번째 문자열이 "P"임을 알 수 있습니다.
위와 같은 방법으로 index를 변경하며 password 전체를 획득할 수 있습니다.
퀴즈
or "1" : 참
or 1 : 참, --은 주석(" 무시)
비번 알아내기
union select from where 이용
하나씩 다 대입해보기
blind sql injection
우선 로그인은 성공
근데 blind이므로 값을 알 수 없다. 하나씩 대입해보아야한다.
uid과 (upw는 바로 전부 알 수 없기 때문에) substr을 이용하여 한 단어씩 대입해본다. --> 한 쌍을 만든다.
처음은 p
Time Based
Time Based Blind SQL Injection은 시간 지연을 이용해 참/거짓 여부를 판단합니다.
시간지연을 발생시키는 방법으로는 DBMS에서 제공하는 함수를 이용하거나, 무거운 연산과정을 발생시켜 쿼리 처리 시간을 지연시키는 heavy query 등이 존재합니다.
benchmark 함수, heavy query 등과 같이 DBMS에서 기본적으로 제공하는 시간 지연함수가 아닌 경우에는 대상 시스템의 성능, 환경 등에 따라 지연 시간이 다르게 동작할 수도 있습니다.
1=1이 참이면 sleep(1) 거짓이면 0
sleep(1) : 1초동안 중단 --> 시간 지연 발생
시간 지연 발생 X
시간 지연
MySQL
sleep 함수
benchmark 함수 --benchmark(count, expr) -> expr을 count만큼 실행
RANDOMBLOB에 의해 많은 수의 데이터가 생성되며, 변환 과정과 함수를 거치며 시간 지연이 발생한다는 점 등을 이용하여 공격에 활용할 수 있습니다.
Error Based Blind
Error Based Blind SQL Injection은 임의적으로 에러 발생을 일으켜 참/거짓을 판단하는 공격 기법입니다.
앞서 배운 Error Based은 에러 메시지를 통해 데이터가 출력되는 에러를 이용해야 하지만, Error Based Blind 공격 시에는 에러가 발생했다는 여부만을 확인하면 되기 때문에 다른 Runtime Error도 사용가능합니다.
참일때 범위 값을 넘어서 에러 발생
Short-circuit evaluation
로직 연산의 원리를 이용한 방법입니다.
A 식과 B 식이 있을 때 AND 연산은 모두 참이 되어야 결과가 참이 됩니다. 이때 A가 거짓이라면 B는 연산을 하지 않더라도 결과가 거짓이라는 것을 알 수 있습니다. 그렇기 때문에 실제로 B 식을 수행하지 않는 것을 의미합니다.
A AND B
거짓 ? --> B가 참이든 거짓이든 연산값은 거짓
0이므로 SLEEP(10)수행X, 1이면 참이므로 수행됨
위와 같은 원리로 OR 연산에서도 A가 참이라면 B 식의 결과없이도 결과는 참이되기 때문에 B 식이 수행되지 않습니다.
A OR B
참 ? ---> B가 참이든 거짓이든 연산값은 참 (B 수행X)
1=1은 참이므로 9e307*2 수행 X
Blind SQL Injection Tip - 1
ASCII에서 출력 가능한 문자의 범위는 32~126 이며, 총 94개의 문자가 존재합니다. Blind SQL Injection을 통해 문자열을 추출할 때 하나의 문자를 구하기 위해 최악의 경우 94번의 요청을 전송해야하며 비효율적입니다.
Blind SQL Injection을 좀 더 효율적으로 수행하는 방법들이 존재합니다. 대표적인 예시들에 대해 설명하도록 하겠습니다.
Binary Search (이진 검색 알고리즘)
이진 검색 알고리즘은 정렬된 리스트에서 특정한 값을 찾는 알고리즘입니다. 검색 과정을 간단히 설명하면 아래와 같습니다.
범위 내의 중간 값을 지정하고 값을 비교합니다.
0~100 사이의 범위라면 50을 지정하고 찾고자 하는 값과 비교합니다.
지정한 값이 찾고자 하는 값과 비교하고 범위를 조절합니다.50보다 작으면 범위를 0~49으로 조절.
50보다 크다면 범위를 51~100으로 조절.
위 과정을 반복하여 특정한 값을 찾습니다.
해당 과정을 Blind SQL Injection에 적용하면 아래와 같습니다. 데이터베이스에 아래와 같이 데이터가 있는 경우를 가정하고 설명하도록 하겠습니다
데이터베이스 데이터
ord함수 등을 사용해 해당 문자를 int형으로 변경합니다.
password 첫 문자가 int로 75보다 크면 1출력
위 쿼리의 결과를 통해 75보다 큰 수라는 것을 알 수 있습니다.
위 결과가 0이 나온 것을 통해 101보다는 작은 수라는 것을 알 수 있습니다.
87보다는 같거나 작음80임을 알 수 있다.
--> 이진 탐색을 통해 범위를 줄여나가는 방법
bit 연산
ASCII는 7개의 비트를 통해 하나의 문자를 나타냅니다.
위 원리를 적용하여 바이트를 비트로 변환하면 7번의 요청을 통해 한 바이트를 획득할 수 있습니다.
비트는 1과 0으로 이루어져있기 때문에 1에 대해 요청해서 거짓이 나오면 0이라는 것을 알 수 있습니다.
A가 이 값이라고 할 때1은 참 0은 거짓--> 비트는 1과 0으로 구성되므로 값을 알 수 있다.7번째까지 값 알아냄
위와 같이 7번의 요청을 통해 binary값1000001, 즉 "A"라는 것을 알 수 있습니다.
Blind SQL Injection Tip - 2
Blind SQL Injection은 다수의 요청을 통해 결과를 획득하는 공격 기법입니다. 이를 사용자가 직접 입력하는 방식은 물리적으로 한계가 발생할 수 있습니다. 그래서 Blind SQL Injection 공격 수행 시 스크립트를 작성하여 공격을 수행합니다.
오른쪽 탭의 소스 코드는 Python의 requests 모듈을 사용해 작성된 공격 스크립트 예시입니다. 공격 코드에 index와 비교할 데이터를 추가해 전송하는 간단한 코드입니다.
사용자마다 선호하는 언어와 라이브러리 등이 다르기 때문에 대중적으로 많이 사용되는 Python을 예시로 사용하였습니다. Python 이외에도 웹 브라우저에 기본적으로 내장되어 있는 Javascript를 이용하는 등 다양한 언어와 라이브러리 등을 활용하여 해당 예시 코드보다 좀 더 효율적인 공격 코드를 작성해보시길 바랍니다.
위 소스코드를 통해 한 바이트씩 패스워드를 획득할 수 있습니다.
3. SQL DML 구문에 대한 이해
SQL DML
SQL DML (Data manipulation language)은 데이터베이스에서 데이터를 조회하거나, 추가/삭제/수정을 수행하는 구문입니다. 즉, 일반적인 사용자가 입력하는 데이터는 대부분 DML을 통해 처리하게 됩니다.
각 쿼리가 사용되어지는 목적과 형태를 이해하게 되면 SQL Injection 공격을 이해하는데 더 효과적입니다. 또한 각 구문에서 사용자의 입력 데이터가 주로 사용되는 포인트와 구문마다 공격하는 방식에 대해 알아보도록 하겠습니다.
SELECT
데이터를 조회하는 구문입니다.
SELECT 문자열을 시작으로 조회의 결과로 사용될 표현식 또는 컬럼들에 대해 정의합니다. FROM 절에서는 데이터를 조회하기 위한 테이블의 이름을 입력합니다. WHERE 절에서는 해당 테이블내에 조회하는 데이터의 조건을 설정합니다. ORDER BY 절은 쿼리의 결과 값들을 원하는 컬럼을 기준으로 정렬합니다. --순서 정렬
일반적으로 위 두가지 사항 모두 SQL Injection을 통해 기존에 권한이 없는 정보에 접근하는 것이 공통점 입니다.
SQL Injection 취약점은 일반적으로 임의의 SQL 구문을 실행할 수 있도록 하지만, 입력 형식과 필터링 및 데이터베이스 엔진의 종류에 따라 가능한 구문과 공격의 영향이 제한될 수 있습니다. 이를 위해 웹 서비스에 적용되어 있는 방어 기법을 이해하고 우회할 수 있는 방법을 알아보는 것이 좋습니다.
오른쪽 탭에서는 Blackbox 점검 중 SQL Injection 취약점을 발견하고 공격의 목적을 달성하기까지의 과정에 대해 작성한 내용입니다.
1. SQL Injection 취약점 발견
SQL Injection이 발생 여부를 판단하는 방법은 여러가지 방법이 있습니다. 대표적으로 확인하는 방법들은 아래와 같습니다.
HTTP Response Status Code를 통해 오류가 발생하는지 확인
DBMS의 오류 메시지를 통해 취약점 가능성 확인
웹 어플리케이션에서 변조된 SQL구문이 실행된 데이터가 반환되는지 확인
2. 구문 예측 / DBMS 정보 획득
사용자의 입력 데이터를 처리하는 구문을 예측합니다.
--예를 들어 일반적으로 로그인 로직이 처리되는 곳에서는 데이터베이스에서 회원의 정보를 가져오기 위해SELECT구문의 WHERE절에서 사용자의 입력데이터를 처리합니다. 또한 회원 가입 로직이라면 새로운 데이터를 추가하기 위해 INSERT구문의 VALUES절에서 사용자의 입력데이터를 처리할 것입니다.
DBMS의 정보를 획득하여 Exploit 작성을 위한 정보를 획득합니다.
3. Exploit 작성
사용할 공격 기법을 선정합니다. 데이터베이스의 결과가 그대로 노출된다면 union 또는 subquery구문을 통해 직접적으로 정보를 노출할 수 있는 구문을 사용하거나, 정보가 노출되지 않는다면 Blind SQL Injection을 사용합니다.
WAF과 같이 SQL Injection을 방어하는 로직 등이 존재할 경우 우회 가능성을 판단합니다.
4. 정보 탈취 및 수정/삭제
시스템 테이블 등을 이용하여 데이터베이스의 정보 등을 획득하며, 저장된 데이터를 탈취하거나 수정/삭제 하여 공격의 목적을 달성할 수 있습니다.
SQL Injection - DBMS Fingerprinting
BlackBox 점검에서 SQL Injection이 의심되는 Endpoint를 찾았을 때 DBMS의 종류를 파악 후 공격하는 것이 효율적입니다. 공격 대상 DBMS에서만 사용 가능한 함수 등을 통해 SQL Injection 공격을 좀 더 수월하게 수행할 수 있습니다.
BlackBox 환경에서 SQL Injection 취약점 발견 시 상황은 아래와 같습니다.
각 상황별 DBMS를 파악하는 방법
결과 값이 출력될 때
DMBS 별로 다른 환경 변수 값 출력
결과 값이 출력되지 않지만 에러가 출력될 때
에러 메세지를 통해 DBMS 파악
DBMS별로 에러 메세지 형식이 다르다
True / False를 확인 가능한 경우
Blind로 함수, 조건문을 사용해 테스트
Time Based결과 값이 출력되지 않을 때sleep 함수 실행되는지
MySQL, PostgreSQL, MSSQL, SQLite별로 version, error, Blind, Time Based함수와 그 결과값 다를 수 있다.
System Tables
DBMS마다 데이터베이스의 정보를 담고 있는 시스템 테이블이 있습니다.
DB설정, 계정 정보 외에도 데이터베이스/테이블/컬럼 정보, 현재 실행되고 있는 쿼리 정보 등 다양한 정보들을 담고 있습니다.
이러한 시스템 테이블에 담긴 정보를 통해 SQL Injection 취약점을 좀 더 효율적으로 공격할 수 있습니다.
다양한 상황에서 활용 가능하지만 대표적인 활용 상황으로 아래와 같습니다.
조건
게시판 서비스에서 boards 테이블에서SELECT구문을 통해 조회 시 SQL Injection이 발생