반응형

 


1. 문제 설명


 

 

이번 문제도 SQLi 문제이다.

 

문제를 클릭해보자.

 

문제를 클릭하면 다음과 같은 화면을 볼 수 있었다.

 

 

 

이게 뭐지?

 

done! (0/70)??

 

뭔가 시도 횟수를 의미하는것 같았다.

 

역시나 뭐 별 다른 기능이 없어 view-source를 클릭해서 소스코드를 보았다.

 

역시나 소스코드가 길었다.

 

이럴때는 하나하나 분석을 해야한다.

 

$agent=trim(getenv("HTTP_USER_AGENT"));

$ip=$_SERVER['REMOTE_ADDR'];

if(preg_match("/from/i",$agent))
{
  echo("<br>Access Denied!<br><br>");
  echo(htmlspecialchars($agent));
  exit();
}

 

먼저 getenv 함수를 사용해서 HTTP_USER_AGENT 값을 가져온다.

 

가져온 값에서 trim 함수를 이용해서 나온 값을 agent라는 변수에 담는다.

 

그럼 getenv 함수와 trim 함수를 알아보자.

 

 

getenv 함수는 위와과 같이 정의 되어있다.

 

getenv 함수는 환경 변수의 값을 가져오는 함수이다.

 

그리고 인자 값에 가져오고 싶은 환경 변수 값을 적어주는데

 

문제에서는 "HTTP_USER_AGENT"가 인자로 들어가있다.

 

HTTP_USER_AGENT웹 사이트를 접속한 컴퓨터의 웹 브라우저 정보를 의미한다.

 

다음은 trim 함수이다.

 

 

trim 함수는 whitespace공백strip 하는 함수이다.

 

하지만 문장 가운데 존재하는 공백이 아니라 처음 시작과 끝의 공백을 제거하는 함수이다.

 

예를 들어,  __hello__라는 문장이 있다고 가정하자.

 

 

하지만 trim 함수를 사용하면 처음 시작 2개의 공백과 끝의 2개의 공백을 제거하여

 

다음과 같은 문장이 나온다.

 

 

여기까지 정리하자면 접속한 웹 브라우저의 정보를 읽어와 앞 뒤 공백을 제거한다는 의미이다.

 

다음 코드는 $ip=$_SERVER['REMOTE_ADDR'] 인데

 

REMOTE_ADDR는 웹 사이트에 접속한 컴퓨터의 ip 주소를 가져온다.

 

그 다음 preg_match 함수가 또 보인다. (preg_match -> 클릭)

 

regexp 하는 역할을 하는데 agent라는 변수에 들어간 웹 브라우저 정보에서 from을 필터링한다.

 

다음 코드를 보자.

 

$db = dbconnect();

$count_ck = mysqli_fetch_array(mysqli_query($db,"select count(id) from chall8"));

if($count_ck[0] >= 70)
{ 
  mysqli_query($db,"delete from chall8"); 
}

$result = mysqli_query($db,"select id from chall8 where agent='".addslashes($_SERVER['HTTP_USER_AGENT'])."'");

$ck = mysqli_fetch_array($result);

 

웹 브라우저 정보에 "from"이라는 단어가 없으면 dbconnect 함수를 이용해서 db에 접속한다.

 

그 후 다음과 같은 쿼리로 DB에 쿼리를 날린다.

 

말 그대로 chall8이라는 테이블에 id의 갯수가 몇개있는지 묻는 쿼리이다.

 

그 후 countid의 갯수를 count_ck라는 변수에 넣고

 

count_ck70개 이상이면 chall8 테이블을 삭제한다.

 

만약에 70개 미만이면 코드가 밑으로 내려와 DB에 다음과 같은 쿼리로 묻는다.

 

 

위의 쿼리의 의미를 해석 하기 전에 addslashes 함수에 대해서 알아보자.

 

addslashes 함수에 대한 설명은 여기에 자세히 설명이 되어있다. (클릭)

 

$_SERVER['HTTP_USER_AGENT']유저의 브라우저 접속환경을 의미한다.

 

브라우저 접속환경이 왜필요할까?

 

서버의 입장에서는 브라우저별로 스타일을 다르게 보여지게 하거나 필요한 동작이 다를 수 있다.

 

무튼 다시 쿼리문을 정리하자면 다음과 같이 정리할 수 있다.

 

 

그럼 해석 해보자면 chall8이라는 테이블에서 어떤 사용자의 user_agent 값이 있다면

 

user_agent 값을 가지는 id를 보여달라는것이다.

 

만약 나의 idtest이고 user_agent값이 123123이라면

 

chall8이라는 테이블에서 agent의 값이 123123id는 누구냐라는 의미이다.

 

쿼리상 문제가 없으면 id값(test)이 이제 ck라는 변수에 들어가게된다.

 

다음 코드를 보자.

 

if($ck)
{
  echo "hi <b>".htmlentities($ck[0])."</b><p>";
  
  if($ck[0]=="admin")
  {
    mysqli_query($db,"delete from chall8");
    solve(8);
  }
}

if(!$ck)
{
  $q=mysqli_query($db,"insert into chall8(agent,ip,id) values('{$agent}','{$ip}','guest')") or die("query error");
  echo("<br><br>done!  ({$count_ck[0]}/70)");
}

 

ck 변수에 이제 값(id)이 있다면 누구인지 echo 함수를 이용해서 알려주고

 

만약에 admin이라면 chall8이라는 테이블을 삭제 시킨 후

 

solve(8)이라는 함수를 실행 시키면서 문제를 clear 할 수 있다.

 

하지만 ck라는 변수 즉 user_agent로 조회를 했을때 id가 없다면 insert 함수를 이용해서

 

chall8이라는 테이블에 agent, ip, id 값을 각각 넣어준다.

 

여기까지 정리하면 다음과 같다.

 

해당 문제는 idadmin으로 맞춰줘야지만 solve(8) 함수를 실행시킬 수 있다.

 

그렇기 위해서는 adminuser_agent 값을 알아야한다.

 

그 이유는 id를 뽑기 위해서는 밑의 쿼리를 사용해야하기 때문이다.

 

 

하지만 우리는 adminuser_agent 값을 모른다.

 

아니면 chall8이라는 테이블에 admin이라는 id를 가진 값이 없을 수도 있다.

 

따라서 adminuser_agent 값을 알아내던, chall8이라는 테이블에 idadmin을 생성해줘야할것같다.

 


2. 문제 풀이


 

이번 문제를 풀기 위해선 2개의 가정을 생각해보았다.

  1. chall8이라는 테이블에 id가 admin이 있을 경우 -> admin의 user_agent 값을 알아내야함 or 우회
  2. chall8이라는 테이블에 id가 admin이 없을 경우 -> chall8에 id가 admin을 insert 해야함

위의 처럼 정리 해보았다.

 

확률상 1번일 확률은 적다고 생각했다.

 

왜냐하면 php 코를 봤을 때 adminuser_agent를 알아내기 위한 방법이 현재로써는 보이지 않았다.

 

따라서 2번의 가정으로 문제를 풀어봤다.

 

그 이유는 php 코드를 봤을때 아래의 그림처럼 insert문이 존재했기 때문이다.

 

 

그리고 insert에서 사용자의 입력이나 값이 들어가는곳은 2곳이다.

 

agentip이다.

 

ip는 서버에 접속한 클라이언트의 ip를 $ip=$_SERVER['REMOTE_ADDR']로 설정하기 때문에

 

ipSQLi를 하기는 어렵다고 생각하고 agent 변수에 

 

즉, user-agent 값을 수정해서 insert문을 이용해서 id가 'guest'가 아닌 'admin'으로 수정하려고한다.

 

user-agent 값을 수정하기 위해서는 web-proxy를 사용해야한다.

 

이렇게 웹 프록시로 보면 User-Agent에 접속한 브라우저나 환경 값들이 들어가 있다.

 

여기에 SQLi를 수행할것이다.

 

먼저 SQLi가 잘 먹히는지 확인하기 위해

 

프록시를 사용하여 User-Agentguest01이라고 입력하고 서버로 전송해보자.

 

 

 

chall8 테이블에 guest01이라는 user-agent값이 없으니 아래의 SQLiinsert할것이다.

 

그렇다면 다시 guest01를 그대로 user-agent 값에 넣어서 서버로 전송하면 어떻게 될까?

 

chall8guest01이 라는 user-agentinsert 되었기 때문에 "hi guest"라는 문구를 볼 수가 있다.

 

그렇다면 이제 user-agent를 변경해서 insert가 되는것을 확인했으니

 

chall8에 존재하지않는 user-agent 값과 뒤의 'guest' 부분을 'admin'으로 바꿔주고

 

다시 insert 했을때 사용했던 user-agent 값으로 변경하여 서버로 전송하면

 

"hi guest"가 아닌 admin이 될것이다.

 

페이로드는 다음과 같다.

 

이렇게 SQL문을 만들 수 있다.

 

뒤에 #이라고 넣어준 이유는 뒤에 남아 있는 원래의 SQL문을 주석 처리하기 위함이다.

 

 

이렇게 SQL문을 만들었으면 이제 직접 프록시를 이용하여 작성한 페이로드를 넣어보자.

 

 

guest02라는 새로운 user-agent 값과 아이피 주소, 그리고 guest가 아닌 admin을 넣어준다.

 

 

 

그 후 정상적으로 chall8이라는 테이블에 등록되었다는 done!이라는 문구를 볼 수 있다.

 

다시 guest02로 요청을 한다.

 

 

요청을 하게 되면 서버에서 clear 했다는 문구를 볼 수 있다.

 

 

반응형

'웹 해킹 > Webhacking.kr' 카테고리의 다른 글

10. old-10  (0) 2021.11.08
9. old-09  (0) 2021.11.08
7. old-07  (0) 2021.11.04
6. old-06  (0) 2021.11.03
5. old-05  (0) 2021.11.01
반응형

 

문제를 보면 다음과 같이 또 DB 모양이 있다.

 

이번 문제도 SQLi 문제일까? 하는 생각에 문제를 클릭해보자.

 

 

문제를 클릭하면 다음과 같이 LoginJoin 창이 보인다.

 

Login을 눌러보자.

 

 

Login을 눌르면 이렇게 IDPW를 입력하는 창이 나온다..

 

별다른 정보가 없기 때문에 페이지 소스를 보았다.

 

 

페이지 소스 보기를 봐도 별 다른 정보가 없어서 test/test를 입력해보았다.

 

 

 

test/test을 입력하고 login을 클릭한 결과 Wrong password라는 문구와 함께 다시 login 폼이 보인다.

 

IDPW를 알아내기 위해 IDPWSQLi를 수행보았지만 별다른 반응이 없었다.

 

따라서 이번 문제를 풀기 위해서는 계정을 알아야할것같았다.

 

따라서 제일 처음으로 돌아와 Join을 눌러보았다.

 

 

Join을 눌러 보니 Access_Denied이라는 문구와 함께 Join을 할 수 없게 되어있다.

 

페이지 소스에서도 아무런 정보도 찾을 수도 없고, ID, PWSQLi도 안먹히고..

 

그 다음 할 것이 없어 URL 주소를 보았다.

 

로그인 화면의 URL은 다음과 같다.

 

https://webhacking.kr/challenge/web-05/mem/login.php

 

우리는 할 수 있는 시도를 다 해보았고 문제에 힌트가 될 정보가 없기 때문에

 

https://webhacking.kr/challenge/web-05/mem를 입력하여

 

디렉토리 인덱스 취약점이 있는지 살펴보았다. 

 

여기서 말하는 디렉토리 인덱스 취약점을 짧게 정리하면 다음과 같다.


웹 애플리케이션을 사용하고 있는 서버의 미흡한 설정으로 인해 인덱싱 기능이 활성화 되어 있을 경우

공격자가 강제 호출을 통해 서버내의 모든 디렉터리 및 파일에 대해 접근이 가능하며

웹 애플리케이션 및 주요 정보가 노출될수 있는 취약점이다.


https://webhacking.kr/challenge/web-05/mem를 입력하니 디렉토리 인덱스 취약점이 발견되었다.

 

 

여기서 우리가 원하는 페이지인 join.php 페이지를 볼 수 있다.

 

join.php 페이지를 클릭해보자.

 

 

클릭하니 bye라는 문구와 함께 검은색 창만 보인다.

 

뭐지??하고 맨날 보았던 페이지 소스를 보았다.

 

 

script 태그에 이상한 script가 존재하였다.

 

원래의 보통의 script가 아니였기 때문에 이게 힌트일 것 같았다.

 

HTMl 소스코드를 가독성 있게 보면 다음과 같다.

 

<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='>';

...... 생략 ........

</script>
</body>

</html>

 

뭔가 딱 보자마자 바로 느낌이 왔다.

 

치환이다!!

 

예를 들어보자.

 

l='a';

 

말 그대로 l은 a이다. 

 

생략한 코드를 치환하면 다음과 같다.

 

<html>
<title>Challenge 5</title></head><body bgcolor=black><center>
<script>


...... 생략 ........

if(eval(document.cookie).indexOf(oldzombie)==-1) 
{
    alert('bye');
    throw "stop";
}

if(eval(document.'U''R''L').indexOf(mode'='+1)==-1)
{
    alert('access_denied');
    throw "stop";
}

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=20></td></tr>');
    document.write('<tr><td><font color=gray>pass</font></td><td><input type=text name='+pw+'></td></tr>');
    document.write('<tr align=center><td colspan=2><input type=submit></td></tr></form></table>');
}
</script>
</body>
</html>

 

이렇게 위의 코드처럼 치환하면 문제를 풀 수 있을 듯하다.

 

코드를 분석해보자.

 

 

위의 코드는 현재 cookie값 중에 oldzombie 라는 값이 있는지 체크하는것이다.

 

즉, 현재 cookie값 중에 oldzombie가 없다면 bye라는 문구와 함께 alert창을 볼 수 있다.

 

따라서 먼저 이 부분을 우회를 해야할것같다.

 

따라서 cookie 이름은 test, 값은 oldzombie를 설정하는 명령어로 cookie값을 설정해보았다.

 

 

그리고 다시 join.php 페이지에 접속해보았다.

 

 

아까와는 달리 access_denied라는 문구를 볼 수 있다!!

 

그럼 그 아래의 코드 또한 우회 해주면 된다!!

 

document.URL 값은 다음과 같이 'https://webhacking.kr/challenge/web-05/mem/join.php' 값이다.

 

 

근데 위의 소스코드에서는 oldzombie처럼 mode=1이 있는지 확인하는것이다.

 

따라서 https://webhacking.kr/challenge/web-05/mem/join.php/mode=1를 입력해보았다.

 

 

이처럼 Join 할 수 있는 폼이 생겼다.

 

여기서 JoinIDPW를 입력하고 제출 버튼을 눌러보자.

 

 

 

이제 Login.php에 접속해서 가입한 정보로 로그인을 해보자!

 

 

이렇게 로그인은 된다.

 

하지만 admin이 아니라는 문구가 뜬다.

 

따라서 이 문제의 출제 의도는 admin으로 로그인을 해야 한다는 것이다.

 

그럼 다시 Join.php에서 admin으로 가입해보자.

 

 

 

????

 

이미 admin이라는 계정이 존재한다.

 

하지만 이번 문제는 admin으로 로그인을 해야 한다.

 

앞에서 로그인 화면에선 SQLi가 통하지 않았기 때문에 이번 문제는 Join.php에서

 

admin으로 가입을 해야한다.

 

가입할때의 패킷을 보자.

 

다음과 같다.

 

id와 pw라는 파라미터로 입력한 admin123POST 형식으로 서버로 넘어간다.

 

예전에 학교 다닐때 DB에 저장될때 띄어쓰기나 공백이 들어가면 저장되는 형태에 따라서

 

'admin'과 'admin '이 같다는걸 배운적이 있다.

 

따라서 id값에 'admin' 대신 'admin '로 서버로 보내봤다.

 

그 후 로그인을 해보았다.

 

 

기억을 더듬어 문제는 풀었는데 정확하게 기억이 나지 않아서 찾아보았다.

 

https://techblog.woowahan.com/2559/ 여기서 정말 자세히 설명이되어있다.

 


쉽게 정리하자면 mysql DB에서는 'a'와 'a ' 또는 ' a'는 같다라고 인식을 한다.

 

즉, 공백이 있어도 같은 문자나 문자열이라고 인식을 한다는것이다.

 

하지만 이건 DB에 저장될때의 자료형이 CHAR일때만 가능하다.

 

비교하려는 두 문자열의 길이가 다른 경우

 

짧은 쪽에 공백을 이어붙여 길이를 똑같이 만든 다음 비교하기 때문에 발생하는 일이다.

 

이런 식이라면 'admin'와 'admin  '를 비교해도 똑같을 수밖에 없다.


반응형

'웹 해킹 > Webhacking.kr' 카테고리의 다른 글

7. old-07  (0) 2021.11.04
6. old-06  (0) 2021.11.03
4. old-04  (0) 2021.11.01
3. old-03  (0) 2021.10.19
2. old-02  (0) 2021.10.18

+ Recent posts