알쓸Web잡 CH.02 - 웹해킹쩜케알 prob 1/6/47

By choirish | July 20, 2018

(난) 알고싶지만 (너)에겐 쓸데없는 Web 잡동사니 CH.02


안녕하세요, choirish 입니다 :)

알쓸웹잡 두 번째 시간(!)
오늘도, webhacking.kr 문제를 풀면서 궁금했던 것들에 대해 썰을 풀어보겠습니다.




오늘의 탐구 Challenge

  • Challenge 1
  • Challenge 6
  • Challenge 47




Challenge 1



index.phps를 클릭하면 php 소스코드를 볼 수 있다.

<?
if(!$_COOKIE[user_lv])
{
SetCookie("user_lv","1");
echo("<meta http-equiv=refresh content=0>");
}
?>
<html>
<head>
<title>Challenge 1</title>
</head>
<body bgcolor=black>
<center>
<br><br><br><br><br>
<font color=white>
---------------------<br>
<?

$password="????";

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

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

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

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

?>
<br>
<pre>
<a onclick=location.href='index.phps'>----- index.phps -----</a>
</body>
</html>


user_lv 쿠키 값이 없으면 user_lv 쿠키 값이 1로 설정된다.

user_lv 값이 있을 경우, 다음과 같이 3번의 필터링을 거친다.


# "숫자" "," "." 를 제외한 문자는 쓸 수 없다.
if(eregi("[^0-9,.]",$_COOKIE[user_lv])) $_COOKIE[user_lv]=1;

# 6보다 작은 숫자여야 한다.
if($_COOKIE[user_lv]>=6) $_COOKIE[user_lv]=1;

# 5보다 큰 숫자여야 한다.
if($_COOKIE[user_lv]>5) @solve();


php - eregi()

점프투파이썬 - 정규 표현식 시작하기
regex 101 - Online regular expressions tester


필터링을 통과하기 위해서는 user_lv 쿠키 값으로 5와 6사이의 숫자를 설정해야 한다…

처음엔 읭? 했는데… 필터링 조건을 곱씹다보니 생각이 났다 ㅎ
개발자 도구의 Console 창을 이용해 user_lv 값을 5.5로 세팅하면 성!공!




문제는 해결하였고…

다음 문제로 넘어가기 전에, 나는 이 문제를 가장 처음 풀기 시작했을 때부터 궁금했던 것을 짚고 넘어가려고 한다.

이 문제는 하이퍼링크를 통해 index.phps 소스코드를 제공한다.

여기서 phps 확장자는 무엇일까? php 확장자랑 다른 무언가가 있는 걸까?
한번 알아보자 :)



phps 는 무엇 (?)


예상했던 대로(?!) PHPS 확장자 파일은 PHP Source와 관련된 파일이라고 한다.

일반적으로 php 파일은 웹서버를 통해 해석되어 실행되고 그 결과를 클라이언트에게 전달하기 때문에 서버에서 처리되는 php 코드를 일부러 보여주는 게 아니라면 우리가 php 코드를 볼 기회는 없다.


그런데, phps 확장자가 존재함으로써(!)
phps 코드를 보여주도록 설정이 되어 있는 서버에 한해, 웹페이지에 보여지는 .php 파일 확장자를 .phps 로 바꾸어 php 소스 코드를 확인할 수 있다. 특별히 php syntax도 적용되어 보여준다!

사실 phps 확장자는 왜 만들어지게 되었을까(?) php 파일이 만들어졌을 때부터 함께 만들어졌을까(?) 궁금하긴 한데… 검색해보았을 때는 그런 생성 이유까지 나오지는 않았다…

이런 내용을 어떻게 검색하면 잘 나오는지, 이런 내용(탄생 behind story!!)이 공식적으로 기록된 곳이 있는지 아시는 분은 꼭 조언 바랍니다(!!!)

무튼… 나의 상상으로는, phps도 개발자도구처럼…
개발자를 위한 용도로.. 서버 측의 php 코드를 볼 수 있도록 만들어두지 않았을까 싶기도 하고…

무튼… 이렇게 웹해킹 관련 사이트에서 문제 소스를 공개하는 데에 잘 활용할 수 있으니, 잘 만들었다고 볼 수 있겠다(!) ㅋㅋ


filext.com - .PHPS File
StackOverflow - What is the file extension .PHPS? And what is it used for?




Challenge 6



해당 페이지의 소스코드는 다음과 같다.

<?php 
if(!$_COOKIE[user]) 
{ 
    $val_id="guest"; 
    $val_pw="123qwe"; 

    for($i=0;$i<20;$i++) 
    { 
        $val_id=base64_encode($val_id); 
        $val_pw=base64_encode($val_pw); 

    } 

    $val_id=str_replace("1","!",$val_id); 
    $val_id=str_replace("2","@",$val_id); 
    $val_id=str_replace("3","$",$val_id); 
    $val_id=str_replace("4","^",$val_id); 
    $val_id=str_replace("5","&",$val_id); 
    $val_id=str_replace("6","*",$val_id); 
    $val_id=str_replace("7","(",$val_id); 
    $val_id=str_replace("8",")",$val_id); 

    $val_pw=str_replace("1","!",$val_pw); 
    $val_pw=str_replace("2","@",$val_pw); 
    $val_pw=str_replace("3","$",$val_pw); 
    $val_pw=str_replace("4","^",$val_pw); 
    $val_pw=str_replace("5","&",$val_pw); 
    $val_pw=str_replace("6","*",$val_pw); 
    $val_pw=str_replace("7","(",$val_pw); 
    $val_pw=str_replace("8",")",$val_pw); 

    Setcookie("user",$val_id); 
    Setcookie("password",$val_pw); 

    echo("<meta http-equiv=refresh content=0>"); 
} 
?> 

<html> 
<head> 
<title>Challenge 6</title> 
<style type="text/css"> 
body { background:black; color:white; font-size:10pt; } 
</style> 
</head> 
<body> 

<? 

$decode_id=$_COOKIE[user]; 
$decode_pw=$_COOKIE[password]; 

$decode_id=str_replace("!","1",$decode_id); 
$decode_id=str_replace("@","2",$decode_id); 
$decode_id=str_replace("$","3",$decode_id); 
$decode_id=str_replace("^","4",$decode_id); 
$decode_id=str_replace("&","5",$decode_id); 
$decode_id=str_replace("*","6",$decode_id); 
$decode_id=str_replace("(","7",$decode_id); 
$decode_id=str_replace(")","8",$decode_id); 

$decode_pw=str_replace("!","1",$decode_pw); 
$decode_pw=str_replace("@","2",$decode_pw); 
$decode_pw=str_replace("$","3",$decode_pw); 
$decode_pw=str_replace("^","4",$decode_pw); 
$decode_pw=str_replace("&","5",$decode_pw); 
$decode_pw=str_replace("*","6",$decode_pw); 
$decode_pw=str_replace("(","7",$decode_pw); 
$decode_pw=str_replace(")","8",$decode_pw); 


for($i=0;$i<20;$i++) 
{ 
    $decode_id=base64_decode($decode_id); 
    $decode_pw=base64_decode($decode_pw); 
} 

echo("<font style=background:silver;color:black>&nbsp;&nbsp;HINT : base64&nbsp;&nbsp;</font><hr><a href=index.phps style=color:yellow;>index.phps</a><br><br>"); 
echo("ID : $decode_id<br>PW : $decode_pw<hr>"); 

if($decode_id=="admin" && $decode_pw=="admin") 
{ 
    @solve(6,100); 
} 


?> 

</body> 
</html> 


첫 번째 <?> 태그 안의 내용은 다음과 같다.

  • user 쿠키 값이 없을 경우
  • $val_id$val_pw를 각각 설정하고 그 값을 20번 반복하여 base64 방식으로 인코딩한다.
  • 인코딩 이후 특수 문자가 있을 경우 숫자로 변환한다.
  • $val_id$val_pw를 각각 userpassword 쿠키 값에 넣는다.

두 번째 <?> 태그 안의 내용은 다음과 같다.

  • user 쿠키 값이 존재할 경우
  • $decode_id$decode_pwuserpassword 쿠키 값을 각각 넣는다.
  • $decode_id$decode_pw 값에 특수 문자가 있을 경우 숫자로 변환한다.
  • 두 변수의 값을 각각 20번 반복하여 base64 방식으로 디코딩한다.
  • 디코딩 이후 $decode_id$decode_pw의 값이 “admin”과 일치할 경우 문제를 solve 한 것으로 처리한다.

여기에서 핵심 부분은 두 개의 <?> 태그 안에 있는 for loop의 내용이다.

for($i=0;$i<20;$i++) 
{ 
    $val_id=base64_encode($val_id); 
    $val_pw=base64_encode($val_pw); 
} 
for($i=0;$i<20;$i++) 
{ 
    $decode_id=base64_decode($decode_id); 
    $decode_pw=base64_decode($decode_pw); 
} 


두 개의 <?> 태그 내용을 정리하면,
해당 페이지의 쿠키 값은 특정 사용자의 id와 pw를 base64로 20번 인코딩한 값으로 구성된다.

나는 admin인 척 해야하니까, “admin”을 base64로 20번 인코딩한 값user, password 쿠키 값으로 설정하면 검사 루틴을 통과하고 문제를 해결 할 수 있다.

그렇다면, 개발자 도구의 Console 창을 이용해 쿠키 값을 만들어보자(!)

Console 창에서는 javascript를 임의로 실행한 결과를 확인할 수 있다.
javascript에서 base64 인코딩/디코딩하는 함수(method)는 다음과 같다.

  • atob() : base64 decode
  • btoa() : base64 encode


사실 atob() 하면 a를 base64로 만든다는 건가(?) 생각되서 base64로 encode하는 함수일 거 같은데… 반대다…ㅋ

MDN web docs - Base64 encoding and decoding
w3schools - Window atob() Method


Console 창에서 직접 테스트 해보았다.




오 엄청 간단하게 encode/decode 할 수 있어서 좋다.

그럼 이제 20번 encode 하는 함수를 간단히 작성하여 실행하고 쿠키 값을 얻어보자(!)

function encode20()
{
    a = "admin";

    for($i=0;$i<20;$i++) 
    { 
        a = btoa(a);
    } 

    console.log(a);
}




20번 인코딩해서 엄청 길다..ㅎ
Console창을 이용해, 출력된 값을 각각 userpassword 쿠키 값으로 설정한다.

document.cookie=“user = 20times-encoded-admin”
document.cookie=“password = 20times-encoded-admin”

설정 후 페이지를 새로고침하면 admin으로 로그인 성공(!)

그런데…
사실 내가 ;를 이용해서 user와 password 쿠키 값을 한꺼번에 설정하려고 해봤는데…
안되더라…




document.cookie로 cookie를 출력했을 때,
;로 여러 개의 쿠키를 구분하여 한꺼번에 출력해주길래…

쿠키 값을 설정할 때도 그렇게 되지 않을까 해서 해봤는데… 안됨… 또오륵…

여러 개의 쿠키를 한 번에 설정하는 방법은 없을까?
궁금증을 탐구해보자(!)





질문의 제목처럼 javascript set multiple cookies by document.cookie 라고 검색해 보았다.

StackOverflow - Setting multiple cookies in Javascript


역시! 나와 같은 질문을 한 사람이 있었다 ㅎㅎ

  • I’m trying to set multiple cookies in document.cookie, but unfortunately only one is getting added.

여기에 첫 번째 답변자가 말해준다..
여러 개의 쿠키 값을 설정하려면 document.cookie = "name=value"를 여러번 반복하라고.. ^^

  • Adding a cookie is performed via document.cookie = “name=value”.
    To add multiple keys, you should perform multiple assigments.

그리고 두 번째 답변자가 명확하게 말해준다..!
document.cookie를 여러번 반복하여 설정하라고..^^
그리고, ; 구분자는 여러 개의 쿠키 값을 설정하기 위한 것이 아니라, 해당 쿠키의 추가 정보를 명시하기 위함이라고!

  • Cookies are key value pairs (with some optional additional info added on, like the expiry date). To set more than one, you just set document.cookie more than once. The ; separator is used to specify the additional info, not to add more different cookies.

오호(!) 여러 개의 쿠키를 한번에 설정하는 방법은 찾지 못했지만,
;의 용도를 정확히 알게 되었으니 큰 수확이다 :-D

MDN web docs를 보면 cookie를 어떻게 설정하고, 내용을 확인하는지 전반적으로 잘 설명되어 있다.

MDN web docs - document.cookie


Write a new cookie 항목에서, document.cookie를 이용하여 쿠키 값을 설정할 때 ;이 어떻게 사용되는지 자세히 말해주고 있다(!)

하나의 cookie key에는 다음과 같은 여러 가지 속성이 있는데, ;를 이용하여 여러 가지 속성 값을 추가로 설정할 수 있다.

  • path : 절대 경로. 정의하지 않은 경우, 현재 페이지 경로로 설정.
  • domain : 정의하지 않은 경우, 현재 페이지 주소로 설정.
  • max-age
  • expires
  • secure : https 프로토콜에서만 설정 가능


WIKIPEDIA - HTTP cookie
searcher.tistory.com - COOKIE 개념
neokido.tistory.com - Javascript에서 쿠키 처리하기


그렇다면, ;를 올바르게 사용해 보기로 했다 ^^





현재 설정된 쿠키의 값은 다음과 같다.

“password=efgh; user=abcd; PHPSESSID=935bb28f06c5aa6bb4df8ddd986e6a5f”

EditThisCookie 도구를 이용하여 user 쿠키 값을 확인해보면, path 속성 값이 challenge/web/web-06이다.




user 쿠키의 path 속성 값을 /로 설정해보자.

document.cookie = “user=abcd; path=/”




그랬더니, cookie의 key-value는 같은데, path 속성 값이 다른 user 쿠키가 하나 더 생겼다….
흠… 기존의 user 쿠키의 속성이 변경될 줄 알았는데 아니었다(!)







쿠키 속성 중 secure는 https 프로토콜에서만 설정할 수 있다기에 www.naver.com 페이지에서 테스해보았다 :)

(사실.. webhacking.kr에서 secure 속성 설정하려고 했더니 안되길래 설명을 다시 읽고 알게되었다^^)




EditThisCookie 도구로 확인해보니 test 쿠키의 value, path, secure 값이 잘 설정된 것을 확인할 수 있다.






Challenge 47



해당 페이지의 소스코드(index.phps)는 다음과 같다.

<html>
<head>
<title>Challenge 47</title>
</head>
<body>
Mail Header injection
<pre>
<form method=post action=index.php>
<font size=2>Mail</font> : <input type=text name=email size=50 style=border:0 maxlength=50><input type=submit>
</form>

<?

if($_POST[email])
{

$pass="????";

$header="From: $_POST[email]\r\n";

mail("admin@webhacking.kr","readme","password is $pass",$header);


echo("<script>alert('Done');</script><meta http-equiv=refresh content=1>");
}
?>

</pre>

<!-- index.phps -->

</body>
</html>


Mail Header Injection 이라고 딱 되어있다(!)

첫 번째 <input> 태그를 보면,
해당 태그의 name 속성 값은 email이고, 내가 입력한 값이 email의 내용으로 들어간다.

<?> 태그 안의 내용을 보면,
내가 입력한 내용(email)이 POST 방식으로 전달되어, $header 변수의 “보낸 사람(From)” 항목에 들어간다.

그리고 $header는 mail( ) 함수의 4번째 인자로 사용된다.

mail 함수의 쓰임새를 알아보자(!)

  • bool mail ( string $to , string $subject , string $message [, string $additional_headers [, string $additional_parameters ]] )

mail 함수의 4번째 인자 $additional_headers는 선택 사항으로,
From, Cc, Bcc와 같은 헤더를 추가하기 위해 사용되고, 각 추가 헤더는 \r\n로 구분해야 한다.

$additional_header가 선택 사항이기는 하지만,
메일을 보낼 경우에는 From 헤더를 꼭 추가해주어야 한다고 함(!)

php - mail()
w3schools - PHP mail() Function


그렇다면… Mail Header Injection을 검색해보자(!)

acunetix.com - What is Email Header Injection?


간단히 정리하면, Mail Header Injection이란,
사용자의 입력이 메일 헤더 값에 영향을 줄 때, 개행문자 \r\n을 이용하여 의도하지 않은 헤더(ex: cc, bcc)를 추가할 수 있는 공격을 말한다.

  • CC(carbon copy) : 참조
  • BCC(blind carbon copy) : 숨은 참조

accunetix.com에 소개된 예시가, 47번 문제의 소스와 매칭이 잘 돼서
여기에 소개된 대로 개행문자 \n을 이용하여 bcc(숨은참조) 헤더를 추가해보기로 하였다.

POST 방식으로 원하는 값을 전달하기 위해, Restlet Client 도구를 이용했다.

Chrome Web Store - Restlet Client


Restlet을 통해 특정 사이트에 웹 요청을 보낼 때에는 Request Header 내용이 필요하므로, 문제 페이지에서 개발자 도구의 Network 탭을 열고 index.php의 Request Header 내용 전체를 긁어 복사한다.




※ Request Header 복사 시 주의 사항(!)

  • 47번 문제 페이지를 열고 아무것도 안한 상태의 Request Header에는 Content-Type 헤더가 없다(!)
  • 문제 페이지에서 input 칸에 아무거나(aaaa)라도 입력한 후에, Network 창에 기록되는 내용의 Request Header를 긁어와야 함(!)
  • 그래야 Content-Type이 포함된 헤더를 가져올 수 있다.
  • Content-Type 헤더가 정의되어 있지 않을 경우 해당 페이지에 요청을 보내도, input을 입력한 것으로 인식하지 못하는 것 같다(!)
  • 이처럼 요청을 보낼 때 필수적으로 필요한 헤더가 있기 마련인데… (나는) 이것을 시행착오를 통해 알 수 있었다… restlet에서 특정 헤더를 지우고 요청을 보냈을 때, 원하는 응답이 오지 않는 것을 통해 이를 확인할 수 있다(!)

Restlet의 사용법을 간단히 아래 그림과 함께 첨부한다(!)




  1. 요청 방식을 POST로 설정한다.
  2. 요청을 보낼 웹페이지 주소를 입력한다.
  3. Header 표시 방식을 Form에서 Raw로 바꾸어 설정한다.
  4. Header 칸에, 복사한 Request Header 내용을 그래도 붙여넣기 한다.
  5. Body 칸에, POST 방식으로 전달할 email 값의 내용을 적는다.

이렇게 요청을 내용을 빠짐 없이 채워넣은 뒤 오른쪽 상단의 Send 버튼을 누르면(?)

스크롤을 아래로 내려서 해당 웹페이지의 응답을 확인할 수 있다(!)




Response의 Body 형태를 preview로 바꾸어 보면, 실제 웹 페이지에서 나타나는 화면을 확인할 수 있다. (한글깨짐주의)




이렇게 %0a를 통해 줄바꿈을 하고 cc 헤더를 추가하면 Mail Header Injection을 성공할 수 있다.
body 칸에 %0a 대신에 실제로 줄바꿈(enter)을 하고 아랫줄에 cc 헤더 내용을 적어도 된다.


그런데… 이렇게 말하면 쉽게 한 번에 성공한 거 같지만 사실은…

앞서 문제를 풀기 전에 “accunetix.com를 참고하여 개행문자 \n로 bcc(숨은참조) 헤더를 추가해보자”고 했던 것을 기억하는가(?)

그렇다… 사실 처음에는 cc 대신에 bcc로 이렇게 저렇게 다해봤는데 실패하고…
(좌절)*1000000 하다가…
메일 헤더 다시 검색해보고 cc로 하고 성공…. ㅂㄷㅂㄷ….

하드 코딩해놓으셨다고 하더니… bcc는 안 적어두셨나 봅니다 ㅠㅠ

아니면… 혹 bcccc와 다른 큰 차이점이 있어서… 정답 형식으로 인정하지 않은… 뭐 그런 건 아니겠죠? 혹 그런 게 있다면 누가 좀 알려주세요…

Dan’s Mail Format Site - Headers: From / To / CC / BCC




흠냐… 사실 문제를 풀면서 소소하게 생기는 궁금증에 대해 생각해보면…

모든 궁금증이, 나의 검색 욕구를 자극하는 것도 아니고…

궁금한 것을 검색했을 때 명확한 답을 항상 얻을 수 있는 것도 아니라서…
(스스로의 검색능력을 탓해야 하는 것일 수 있음 크흡)

알쓸웹잡의 콘텐츠 고갈이 걱정인 1人입니다… ‘ㅅ’a

그래서 언젠가…

저의 검색 능력으로 못 찾겠는 내용은 여기에다 도리어 질문으로 던지기도 하고(!)
저의 검색 욕구를 자극할 만한 웹 관련 궁긍증을 추천받아 볼까…(?)

고민중입니다 ㅋㅋㅋ

훗날 고민을 실천해 볼 수 있길 바라며…
다음 편에서 또 만나요! 안녕!!

comments powered by Disqus