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();
점프투파이썬 - 정규 표현식 시작하기
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> HINT : base64 </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
를 각각user
와password
쿠키 값에 넣는다.
두 번째 <?>
태그 안의 내용은 다음과 같다.
user
쿠키 값이 존재할 경우$decode_id
와$decode_pw
에user
와password
쿠키 값을 각각 넣는다.$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창을 이용해, 출력된 값을 각각 user
와 password
쿠키 값으로 설정한다.
document.cookie=“user = 20times-encoded-admin”
document.cookie=“password = 20times-encoded-admin”
설정 후 페이지를 새로고침하면 admin으로 로그인 성공(!)
그런데…
사실 내가 ;
를 이용해서 user와 password 쿠키 값을 한꺼번에 설정하려고 해봤는데…
안되더라…
document.cookie로 cookie를 출력했을 때,
;
로 여러 개의 쿠키를 구분하여 한꺼번에 출력해주길래…
쿠키 값을 설정할 때도 그렇게 되지 않을까 해서 해봤는데… 안됨… 또오륵…
여러 개의 쿠키를 한 번에 설정하는 방법은 없을까?
궁금증을 탐구해보자(!)
Cannot set multiple cookies by document.cookie (?)
질문의 제목처럼 javascript set multiple cookies by document.cookie 라고 검색해 보았다.
역시! 나와 같은 질문을 한 사람이 있었다 ㅎㅎ
- 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를 어떻게 설정하고, 내용을 확인하는지 전반적으로 잘 설명되어 있다.
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에서 쿠키 처리하기
그렇다면, ;
를 올바르게 사용해 보기로 했다 ^^
Cookie Test 1
현재 설정된 쿠키의 값은 다음과 같다.
“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 쿠키의 속성이 변경될 줄 알았는데 아니었다(!)
Cookie Test 2
쿠키 속성 중 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 헤더를 꼭 추가해주어야 한다고 함(!)
그렇다면… Mail 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 도구를 이용했다.
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의 사용법을 간단히 아래 그림과 함께 첨부한다(!)
- 요청 방식을 POST로 설정한다.
- 요청을 보낼 웹페이지 주소를 입력한다.
- Header 표시 방식을 Form에서 Raw로 바꾸어 설정한다.
- Header 칸에, 복사한 Request Header 내용을 그래도 붙여넣기 한다.
- 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
는 안 적어두셨나 봅니다 ㅠㅠ
아니면… 혹 bcc
가 cc
와 다른 큰 차이점이 있어서… 정답 형식으로 인정하지 않은… 뭐 그런 건 아니겠죠? 혹 그런 게 있다면 누가 좀 알려주세요…
흠냐… 사실 문제를 풀면서 소소하게 생기는 궁금증에 대해 생각해보면…
모든 궁금증이, 나의 검색 욕구를 자극하는 것도 아니고…
궁금한 것을 검색했을 때 명확한 답을 항상 얻을 수 있는 것도 아니라서…
(스스로의 검색능력을 탓해야 하는 것일 수 있음 크흡)
이 알쓸웹잡
의 콘텐츠 고갈이 걱정인 1人입니다… ‘ㅅ’a
그래서 언젠가…
저의 검색 능력으로 못 찾겠는 내용은 여기에다 도리어 질문으로 던지기도 하고(!)
저의 검색 욕구를 자극할 만한 웹 관련 궁긍증을 추천받아 볼까…(?)
고민중입니다 ㅋㅋㅋ
훗날 고민을 실천해 볼 수 있길 바라며…
다음 편에서 또 만나요! 안녕!!