[그림 01] level10의 시작화면


level10을 들어가면 [그림 01]과 같이 시작화면에는 몇 가지 문자와 O로 보이는 문자가 있습니다.

'O'를 클릭해보면 한 칸씩 옆으로 이동하는 것을 확인할 수 있는데, 이를 F12(개발자 도구)로 확인해보면,

[그림 02]와 같이 확인할 수 있습니다.


[그림 02] hackme 영역


hackme 영역은 id="hackme" style="......"로 이루어진 영역인데, O를 클릭할 때마다 this.style.posLeft가 한 칸씩 옆으로 이동하는 것을 알 수 있습니다.

그렇다면 posLeft가 800이 될 때 this.href='?go=' 인 것으로 보아 아마 posLeft를 800까지 이동시켜야 하는 것을 의미하는가 봅니다.

우리는 800번의 클릭을 하는 것보다 값을 조정해보도록 합시다.


[그림 03] posLeft를 799까지 옮긴 모습


F12(개발자 도구)에서 hackme.style.posLeft = 799로 하게 되면 'O'는 799 좌표로 이동하게 됩니다.

800으로 이동시켰지만 href가 동작하지 않는 것으로 보아 799번째에서 클릭하면 동작하는 방식인 것 같습니다.

799로 옮긴 후 O를 클릭하면 링크가 클릭되며, Congratulation이 뜨게 됩니다.


<참고>


this.style.posLeft가 움직이는 것은 id가 hackme인 곳을 this로 가리키게 됩니다.

id="hackme" 안에서 style의 값을 this.style.posLeft라는 것은 hackme.style.posLeft와 동일하다는 것을 의미합니다.


'WARGAMES > webhacking.kr - old' 카테고리의 다른 글

Webhacking.kr_No.16(100) - old  (0) 2016.06.28
Webhacking.kr_No.15(50) - old  (0) 2016.06.28
Webhacking.kr_No.14(100) - old  (0) 2016.06.28
Webhacking.kr_No.12(250) - old  (0) 2016.06.28
Webhacking.kr_No.06(100) - old  (0) 2016.06.28
Webhacking.kr_No.05(300) - old  (0) 2016.06.26
Webhacking.kr_No.04(150) - old  (0) 2016.06.22
Webhacking.kr_No.03(350) - old  (0) 2016.06.16

[그림 01] Challenge 06번 시작 화면


ID와 PW가 보입니다. index.phps는 링크입니다.

index.phps 즉, index.php의 소스를 보여주는 곳입니다. 들어가봅시다.


소스를 들어가면 아래와 같이 나옵니다.▼

[그림 02] PHP소스 요약


소스를 간단히 해석해보면

01. id, pw에 있는 값을 20번 base64로 인코딩합니다.

02. 1,2,3,4,5,6,7,8을 각각 !,@,$,^,&,*,(,)로 바꿉니다.

03. 쿠키에 user, pasword를 추가하고 20번 인코딩 된 값을 각각 입력합니다.

04. 만약 user, password라는 쿠키값에 있는 !,@,$,^,&,*,(,)을 1,2,3,4,5,6,7,8로 replace하고 이를 20번 디코딩한 쿠키값(user, password)이 모두 admin이면 solve()라는 함수가 실행됩니다.


즉, user와 password 쿠키값에 admin을 20번 인코딩하고 replace까지 한 값을 입력하면 됩니다.


[그림 03] Chrome 브라우저 콘솔에서 user, password를 입력



참고 소스 (Python) - admin 문자열을 20번 인코딩 및 replace함 :

import base64

string_admin='admin'

for i in range(0,20):
       string_admin = base64.standard_b64encode(string_admin)

string_admin = string_admin.replace('1','!')
string_admin = string_admin.replace('2','@')
string_admin = string_admin.replace('3','$')
string_admin = string_admin.replace('4','^')
string_admin = string_admin.replace('5','&')
string_admin = string_admin.replace('6','*')
string_admin = string_admin.replace('7','(')
string_admin = string_admin.replace('8',')')

f = open('string_admin.txt', 'w')

print >> f, 'Set Cookie user value and password value : '
print >> f, string_admin


'WARGAMES > webhacking.kr - old' 카테고리의 다른 글

Webhacking.kr_No.15(50) - old  (0) 2016.06.28
Webhacking.kr_No.14(100) - old  (0) 2016.06.28
Webhacking.kr_No.12(250) - old  (0) 2016.06.28
Webhacking.kr_No.10(250) - old  (0) 2016.06.28
Webhacking.kr_No.05(300) - old  (0) 2016.06.26
Webhacking.kr_No.04(150) - old  (0) 2016.06.22
Webhacking.kr_No.03(350) - old  (0) 2016.06.16
Webhacking.kr_No.01(200) - old  (0) 2016.06.16


[그림 01] 시작 화면


[그림 01]과 같은 화면을 시작 화면으로 볼 수 있을 것입니다.

[그림 02]는 login, [그림 03]은 join을 눌렀을 때의 상태를 보여줍니다.

[그림 02] join을 눌렀을 때의 화면

[그림 03] join을 눌렀을 때의 화면


[그림 03]처럼 join을 눌렀을 때의 화면이 나오는 이유는 Javascript를 이용하여 그냥 alert('Access_Denied')로 돼 있어서 입니다.

즉, link가 따로 없습니다.

그렇다면 다른 방향으로 접근해야 합니다.

이 사이트에서 각 문제는 하위 디렉토리로 구성되는 것을 익히 아실거라 생각합니다. [그림 04]를 참고해주십시오.

[그림 04] 디렉토리 구성을 알려주는 주소


[Directory, web]->[Directory, web-05]->[Directory, mem]->[File, login.php]입니다.

web이라는 디렉토리 하위의 web-05 하위의 mem 하위의 login.php라는 PHP파일을 브라우저를 통해 보여주는 형식입니다.

그럼 우리는 login을 들어가면 login.php이니, join은 혹시 join.php가 아닐까 하는 느낌을 받을 수 있지 않을까 합니다.


이제 login.php를 바꿔 join.php로 들어가봅니다.


join.php로 들어가면 다음과 같이 난독화가 되어있는 것을 알 수 있습니다.


Challenge5


이와 같이 난독화 되어 있는 코드를 python 코드로 간단하게 작성하여 디코드해보았습니다.

아래와 같이 난독화가 풀어지는 것을 확인할 수 있습니다.


l1'b'1'b'1'b'1'b'1'b'1'b'1'b'1'b'1'b'1l='o'+'l'+'d'+'z'+'o'+'m'+'b'+'i'+'e'

l08l='d'+'o'+'c'+'u'+'m'+'e'+'n'+'t'+.+'c'+'o'+'o'+'k'+'i'+'e'
if(eval(l08l).indexOf(l1'b'1'b'1'b'1'b'1'b'1'b'1'b'1'b'1'b'1l)==-1)
    {
        bye;
    }
if(eval('d'+'o'+'c'+'u'+'m'+'e'+'n'+'t'+.+'U'+'R'+'L').indexOf('m'+'o'+'d'+'e'+'='+1)==-1)
    {
    alert('access_denied')
    history.go(-1);
    }
else
    {
    document.write('Join

'); document.write('.

.

.

.

.

'); document.write('

'); document.write(''); document.write(''); document.write('
id
pass
'); }

이 코드를 보니 document.write로 되어 있는 부분은 화면에 다른 소스를 입력하기 위한 창으로 볼 수 있고, 그 위에는 그 화면으로 접근하기 위한 방법으로 알 수 있습니다.

대충 요약하면 조건 두 가지가 나옵니다.▼


조건 1. 쿠키에 oldzombie 라는 이 있어야 합니다.

조건 2. URL에 ?mode=1이 있어야 합니다.


[그림 05] 완성된 화면


id에 admin이라고 입력하면 'admin is already exist'라는 문자가 뜨고, 나머지는 'Done! 뭐시기 뭐시기'가 뜹니다.

그런데, admin 이외의 다른 계정을 계속 생성해도 Done이 되는 걸 보니 DB에 INSERT가 허용되는 것으로 확인할 수 있습니다.


admin을 어떻게 덮어쓰기 위해서는 DataBase에서는 삭제되고, 쿼리는 admin이 아니게 전송할 수 있는 문자열이 필요합니다.


SQL에서는 trim()이라는 함수가 존재합니다. 구문에서 띄어쓰기를 삭제하는 함수입니다.

예를 들면 id 컬럼에 'admin   1', 'admin        1'이라는 값을 입력하여 차례대로 생성해보면,

1. admin1이 생성되었습니다.

2. admin1이 이미 있습니다.(중복 에러)

라고 뜹니다.


띄어쓰기는 삭제되는 것을 볼 수 있습니다.


id에 'admin '을 입력하고 원하는 비밀번호를 입력하면 Done이 뜨고, login.php로 들어가 로그인을 하게 되면 성공입니다.




참고 소스(Python) - 난독화를 대충이라도 디코딩 및 프린트 하기:

string_a = """

lIllIllIllIllIllIllIllIllIllIl=lllllllllllllll+llllllllllll+llll+llllllllllllllllllllllllll+lllllllllllllll+lllllllllllll+ll+lllllllll+lllll

lIIIIIIIIIIIIIIIIIIl=llll+lllllllllllllll+lll+lllllllllllllllllllll+lllllllllllll+lllll+llllllllllllll+llllllllllllllllllll+li+lll+lllllllllllllll+lllllllllllllll+lllllllllll+lllllllll+lllll
if(eval(lIIIIIIIIIIIIIIIIIIl).indexOf(lIllIllIllIllIllIllIllIllIllIl)==-1)
    {
        bye;
    }
if(eval(llll+lllllllllllllll+lll+lllllllllllllllllllll+lllllllllllll+lllll+llllllllllllll+llllllllllllllllllll+li+'U'+'R'+'L').indexOf(lllllllllllll+lllllllllllllll+llll+lllll+'='+I)==-1)
    {
    alert('access_denied')
    history.go(-1);
    }
else
    {
    document.write('Join

'); document.write('.

.

.

.

.

'); document.write('

'); document.write(''); document.write(''); document.write('
id
pass
'); }""" for i in range(26,1,-1): string_a = string_a.replace('l'*i,"'"+chr(ord('z')-(26-i))+"'") for i in range(10,0,-1): if i == 10: string_a = string_a.replace('I'*i,'0') else: string_a = string_a.replace('I'*i,str(i)) string_a = string_a.replace('li', '.') string_a = string_a.replace('ii', '<') string_a = string_a.replace('iii', '>') print string_a


'WARGAMES > webhacking.kr - old' 카테고리의 다른 글

Webhacking.kr_No.15(50) - old  (0) 2016.06.28
Webhacking.kr_No.14(100) - old  (0) 2016.06.28
Webhacking.kr_No.12(250) - old  (0) 2016.06.28
Webhacking.kr_No.10(250) - old  (0) 2016.06.28
Webhacking.kr_No.06(100) - old  (0) 2016.06.28
Webhacking.kr_No.04(150) - old  (0) 2016.06.22
Webhacking.kr_No.03(350) - old  (0) 2016.06.16
Webhacking.kr_No.01(200) - old  (0) 2016.06.16

 

 개편된 문제


<?php
  include "../../config.php";
  if($_GET['view-source'] == 1) view_source();
?><html>
<head>
<title>Challenge 4</title>
<style type="text/css">
body { background:black; color:white; font-size:9pt; }
table { color:white; font-size:10pt; }
</style>
</head>
<body><br><br>
<center>
<?php
  sleep(1); // anti brute force
  if((isset($_SESSION['chall4'])) && ($_POST['key'] == $_SESSION['chall4'])) solve(4);
  $hash = rand(10000000,99999999)."salt_for_you";
  $_SESSION['chall4'] = $hash;
  for($i=0;$i<500;$i++) $hash = sha1($hash);
?><br>
<form method=post>
<table border=0 align=center cellpadding=10>
<tr><td colspan=3 style=background:silver;color:green;><b><?=$hash?></b></td></tr>
<tr align=center><td>Password</td><td><input name=key type=text size=30></td><td><input type=submit></td></tr>
</table>
</form>
<a href=?view-source=1>[view-source]</a>
</center>
</body>
</html>


1. rand(10000000,99999999)로 나온 값과 "salt_for_you"라는 값을 받아 $hash에 저장합니다.

2. 이후 $hash 값을 sha1 해쉬값으로 500회 반복하여 만듭니다.

3. 세션의 chall4의 값과 만들어진 hash 값이 같으면 문제가 풀립니다.


위의 순서는 코드와는 조금 상이하나, 전체적인 흐름은 맞을 겁니다.


원래는 Rainbow Table을 만들어서 해당하는 값을 찾아내는 Dictionary Bruteforce 공격을 수행하는 것이 정석이지만.

저는 multiprocessing을 이용하여 해당 세션이 만료되기 전에 계산을 하는 python 소스로 공격을 수행하였습니다.


사진에서 나오는 해쉬값을 받아서 계속해서 비교하는 소스입니다.


단, 해당 소스를 실행하기 위해서는 Memory 오류가 날 수 있으니 반드시 Python3 64bit로 실행해주시기 바랍니다.

또한 메모리에 대략 8GB 정도 올라가기 때문에 가능하면 16GB 이상에서 돌릴 것을 당부드립니다. ㅎㅎ

#!/bin/python3
import hashlib
from   multiprocessing import Pool

def sha1_500(string):
    answer = "Your_Hash_Value"
    b  = string
    for i in range(500):
        string = hashlib.sha1(string.encode()).hexdigest()
    if answer == string:
        return b
    else:
        return 'a'


def main():
    answer = "Your_Hash_Value"
    ps_cnt = 8*1
    count  = 0
    pool   = Pool(processes=ps_cnt)

    data   = pool.map(sha1_500, [str(s)+"salt_for_you" for s in range(10000000,99999999)])
    data.sort()

    print("[+] Find Result : ", data[-1])
    print("[+] Find Result : ", data[0] )


if __name__ == "__main__": 
    main()    


위의 소스로는 시간이 꽤 걸릴 것입니다.

만약 rainbow table을 미리 생성하는 소스를 작성하여 실행한 후, 이를 통해 데이터를 쭉 비교하는 것과 비슷한 시간이 걸릴 것으로 예상됩니다.




 

 개편되기 전 문제


[그림 01] level4 시작화면


[그림 01]에서 나타난 위의 문자열을 Decrypt 해야할 것만 같습니다.


맨 뒤에 == 이 있고, 영어 대/소문자, 숫자로 된 문자열을 보아하니 BASE64가 틀림이 없습니다.

BASE64로 디코드해보면, c4033bff94b567a190e33faa551f411caef444f2(40바이트)로 디코딩 됩니다.

40바이트의 해시가 어떤 게 있는가 함을 보니 SHA1이 있습니다.

SHA1으로 c4033bff94b567a190e33faa551f411caef444f2를 디코드 해보면, a94a8fe5ccb19ba61c4c0873d391e987982fbbd3(40바이트)로 디코드 됩니다.

이것도 40바이트로 나오는 걸 보니 SHA1으로 다시 한 번 디코드 해야 할 것 같습니다.

a94a8fe5ccb19ba61c4c0873d391e987982fbbd3를 SHA1으로 디코드하면, test라는 값을 얻을 수 있습니다.


<<해쉬가 이루어지는 과정>>

01. YzQwMzNiZmY5NGI1NjdhMTkwZTMzZmFhNTUxZjQxMWNhZWY0NDRmMg==

02. c4033bff94b567a190e33faa551f411caef444f2

03. a94a8fe5ccb19ba61c4c0873d391e987982fbbd3

04. test



답 : test


처음 SHA1으로 디코딩 했을 때 답이 안 나오는 걸 보니 이게 SHA1이 아닌가 했지만, 다시 40바이트로 나오는 걸 보고 재 디코드 해보았습니다.

하마터면 삽질을 할 뻔 했습니다. 시간을 효율적으로!!

'WARGAMES > webhacking.kr - old' 카테고리의 다른 글

Webhacking.kr_No.15(50) - old  (0) 2016.06.28
Webhacking.kr_No.14(100) - old  (0) 2016.06.28
Webhacking.kr_No.12(250) - old  (0) 2016.06.28
Webhacking.kr_No.10(250) - old  (0) 2016.06.28
Webhacking.kr_No.06(100) - old  (0) 2016.06.28
Webhacking.kr_No.05(300) - old  (0) 2016.06.26
Webhacking.kr_No.03(350) - old  (0) 2016.06.16
Webhacking.kr_No.01(200) - old  (0) 2016.06.16

[그림 01] level3 시작화면


level3의 시작은 [그림 01]과 같습니다.

index.php에서는 이와 같은 퍼즐을 보여줍니다.

퍼즐의 의미를 먼저 파악하는 것이 우선일 것 같습니다.

의미는 다음과 같습니다.

가로 세로, 각 숫자만큼의 네모(색칠된 네모)가 있어야 합니다.

퍼즐을 풀게 되면 [그림 02]와 같은 모습이 됩니다.


[그림 02] 퍼즐이 해결된 모습


[그림 02]와 같이 만든 이후 gogo를 클릭하면 다음 화면으로 넘어갈 수 있습니다.

[그림 03] SQL 인젝션을 요구하는 듯한 입력창


네, 그렇습니다. 저 빈 칸으로 되어 있는 부분이 굉장히 눈에 들어옵니다.

마치 SQL 인젝션을 해달라고 도발하는 듯한 빈 칸입니다. 건방집니다. 파헤칩시다.

[그림 04] 아무 값이나 넣었을 때의 모습


일단 111이라는 아무런 값이나 넣어보니 다음과 같은 name, answer, ip가 나옵니다.

이 때 우리가 111을 넣었을 때 어떻게 서버로 전달이 되는지 파악해보는 것이 필수입니다.


[그림 05] 서버로 전송되는 쿼리에 대해


GET 형태로는 http://webhacking.kr/challange/web/web-03/index.php?_1=1&_2=0 뭐시기 뭐시기 .... _25=1&_answer=1010100000011100101011111을 넘기고 있는 것을 볼 수 있고,

POST로는 뒤의 answer=1010100000011100101011111가 넘겨지는 것을 볼 수 있습니다.

DB에서 Select를 할 때 어떻게 되는지는 모르지만 가설을 세워봅시다.


만약

SELECT 뭐시기

FROM 뭐시기

WHERE id=' 입력값 ' and answer=1010100000011100101011111

라면 WHERE의 조건문을 TRUE로 만들어줘야 할 것입니다.


[그림 06] 필터링 되는 값


아마 [그림 06]에는 다 표현하지 못했지만, 따옴표, 역슬래쉬 등이 필터링 되는 것을 확인할 수 있다.

따옴표가 필터링이 된다는 것은 name : 으로 된 부분에서는 SQL 인젝션이 막혀있다는 것입니다.

그렇다면 다른 곳에서 WHERE의 TRUE를 만들어야 할 것입니다.


POST로 넘어가는 answer에서 그 해답을 찾아보도록 합시다.


조건문 가설 : WHERE id=' 입력값 ' and answer=1010100000011100101011111

WHERE를 TRUE로 만들기 위해서는 WHERE (뭐시기 뭐시기) or (TRUE)를 해버리면

WHERE ((뭐시기 뭐시기) or (TRUE))가 되어 결론, TRUE가 됩니다.

WHERE id=' 입력값 ' and answer=1010100000011100101011111 or 1 을 하면 TRUE가 되지 않을까 합니다.


answer에 들어가는 값은 int값이므로 따옴표가 없습니다.


POST의 answer 값에 or를 이용하여 쿼리를 날리면 'no hack'을 반환합니다.

int라는 가정으로 || 로 or를 대신하여 값을 보냅니다.


하지만 answer=1010100000011100101011111 || 1를 해도 아무런 변화도 없습니다.

id 값이 NULL이면 처음 창으로 돌아가게 됩니다.

따라서 '&id=아무런 값'도 함께 POST로 넘겨주면 해답이 나오게 됩니다.


[그림 07] 해답











'WARGAMES > webhacking.kr - old' 카테고리의 다른 글

Webhacking.kr_No.15(50) - old  (0) 2016.06.28
Webhacking.kr_No.14(100) - old  (0) 2016.06.28
Webhacking.kr_No.12(250) - old  (0) 2016.06.28
Webhacking.kr_No.10(250) - old  (0) 2016.06.28
Webhacking.kr_No.06(100) - old  (0) 2016.06.28
Webhacking.kr_No.05(300) - old  (0) 2016.06.26
Webhacking.kr_No.04(150) - old  (0) 2016.06.22
Webhacking.kr_No.01(200) - old  (0) 2016.06.16

[그림 01] level1 시작화면


level1의 시작은 [그림 01]과 같습니다.

index.phps는 index.php라는 php파일의 소스를 볼 수 있는 설정입니다..

원래는 php소스를 client가 볼 수 없도록 설정해야 하지만, php의 낮은 버전에서는 phps를 지원하는 경우가 있습니다.

이는 구버전의 php의 취약점이지요.


index.php라고 되어 있는 부분을 index.phps라고 바꿔주거나 index.phps를 클릭하면 [그림 02]와 같은 화면으로 넘어가게 됩니다.

[그림 02] index.phps


00. if(!$_COOKIE[user_lv]){SetCookie("user_lv", "1"); echo("<meta ...")}

 처음 쿠키를 설정할 때, user_lv에는 1이라는 숫자를 입력

01. $password="????";

 password라는 변수에 "????"라는 문자열 입력

02. if(eregi("[^0-9,.]",$_COOKIE[user_lv])) $_COOKIE[user_lv]=1;

 eregi() 함수는 검색대상 문자열(string)에서 정규표현식으로 나타낸 패턴과 일치하면 True, 일치하지 않으면 False를 반환

 eregi() 함수는 대소문자 구분이 없음

 ereg() 함수는 대소문자 구분이 있음

 만약 쿠키 값 중, 'user_lv' 이라는 변수가 0~9 그리고 .(온점)일 경우, user_lv이란 변수에 1이라는 숫자를 입력

03. if($_COOKIE[user_lv]>=6) $_COOKIE[user_lv]=1;

 만약 쿠키 값 중 'user_lv'이 숫자 6보다 크면 1로 다시 초기화

04. if($_COOKIE[user_lv]>5 @solve();

 만약 쿠키 값 중 'user_lv'이 숫자 5보다 크면 solve()라는 함수를 실행

05.echo("<br>level : $_COOKIE[user_lv]");

 [그림 01]의 level : 1이라는 곳의 숫자 1을 user_lv에 입력된 숫자로 바꿈


위의 내용을 해석하면, 쿠키 값 중 user_lv을 6보다 작고, 5보다 크게 만들어서 solve()라는 함수를 실행해보는 것이 해답일 것으로 생각됩니다.

이제 cookie값을 확인하는 법을 알아봅시다.

cookie값을 확인하는 방법은 cooxie라는 툴을 설치하여 cookie를 확인하거나 javascript를 사용하여 확인하는 방법입니다.


 명령어 : javascript:alert(document.cookie)


위의 명령어를 주소창에 입력하면 [그림 03]과 같이 나옵니다.


[그림 03] javascript 명령어를 통한 cookie 값 확인


[그림 03]을 보면 user_lv=1과 같은 것을 확인할 수 있습니다.

앞서 [그림 02]에서와 같이 user_lv을 통한 solve()함수를 실행하는 것으로 짐작할 수 있습니다.

조건은 6 > user_lv > 5입니다. 그렇다면 아래의 [그림 04]와 같이 user_lv의 쿠키 값을 변경해볼 수 있겠지요.

[그림 04] 쿠키 값, user_lv에 5.1이라는 값을 삽입


[그림 04]의 명령어를 통해 user_lv에 5.1을 저장하면 쿠키 값이 1에서 5.1(6보다 작고 5보다 큰 것)을 입력하면 solve()를 실행할 수 있게 됩니다.


다시 index.php로 들어가면 해결됩니다!







'WARGAMES > webhacking.kr - old' 카테고리의 다른 글

Webhacking.kr_No.15(50) - old  (0) 2016.06.28
Webhacking.kr_No.14(100) - old  (0) 2016.06.28
Webhacking.kr_No.12(250) - old  (0) 2016.06.28
Webhacking.kr_No.10(250) - old  (0) 2016.06.28
Webhacking.kr_No.06(100) - old  (0) 2016.06.28
Webhacking.kr_No.05(300) - old  (0) 2016.06.26
Webhacking.kr_No.04(150) - old  (0) 2016.06.22
Webhacking.kr_No.03(350) - old  (0) 2016.06.16

+ Recent posts