xmi1e-vir.log

[Dreamhack] wargame 'php7cmp4re' write-up 본문

WARGAME/WEB

[Dreamhack] wargame 'php7cmp4re' write-up

eunee22 2025. 9. 23. 18:46
🌱 Biginner
문제링크
php 7.4로 작성된 페이지입니다.
알맞은 Input 값을 입력하고 플래그를 획득하세요.
플래그 형식은 DH{} 입니다.

 

📌문제 파악

index.php

  • input1과 input2를 입력하여 submit하면 check.php로 제출된다.
<div class="container">
      <div class="box">
      <h4>Enter the correct Input.</h4>
        <p>
          <form method="post" action="/check.php">
              <input type="text" placeholder="input1" name="input1">
              <input type="text" placeholder="input2" name="input2">
              <input type="submit" value="제출">
          </form>
        </p>
      </div>

 

  • flag가 존재하는 flag.php로 직접 이동하는 것은 방지되어있다.
<?php
    require_once('flag.php');
    error_reporting(0);
?>

 

check.php

  • 이 코드가 핵심인데, 우리는 input1과 input2에 해당 코드에서 정의 해놓은 규칙에 부합하는 값을 입력해야한다.
  • 정리해보면 다음과 같다.
    1. 두 입력값 모두 빈칸이 아닐것
    2. input1의 길이가 4보다 작을것
    3. input1의 값이 "8", "7.A"보다 작고, "7.9"보다는 클것
    4. input2의 길이가 1과 3사이
    5. input2가 74보다 작고 "74"보다 큼
<div class="container">
<?php
require_once('flag.php');
error_reporting(0);
// POST request
if ($_SERVER["REQUEST_METHOD"] == "POST") {
  $input_1 = $_POST["input1"] ? $_POST["input1"] : "";
  $input_2 = $_POST["input2"] ? $_POST["input2"] : "";
  sleep(1);

  if($input_1 != "" && $input_2 != ""){
    if(strlen($input_1) < 4){
      if($input_1 < "8" && $input_1 < "7.A" && $input_1 > "7.9"){
        if(strlen($input_2) < 3 && strlen($input_2) > 1){
          if($input_2 < 74 && $input_2 > "74"){
            echo "</br></br></br><pre>FLAG\n";
            echo $flag;
            echo "</pre>";
          } else echo "<br><br><br><h4>Good try.</h4>";
        } else echo "<br><br><br><h4>Good try.</h4><br>";
      } else echo "<br><br><br><h4>Try again.</h4><br>";
    } else echo "<br><br><br><h4>Try again.</h4><br>";
  } else{
    echo '<br><br><br><h4>Fill the input box.</h4>';
  }
} else echo "<br><br><br><h3>WHat??!</h3>";
?> 
</div>

 

📌문제 풀이

이 문제에서 주어진 웹사이트의 동작과 조건을 이해하는 것은 어렵지 않았지만, php 관련 지식이 없다보니까 푸는 데에는 굉장히 어려움을 겪었다.

php 숫자형 문자열 (numeric string)

일단 해당 문제의 제목이 php 7.4인 만큼 해당 버전에서만 나타나는 동작, 취약점이 존재할 것이라고 생각했다.

 

문제를 풀기 위해서는 숫자형 문자열에 대해서 알아야한다.
php에서는 문자열이 숫자처럼 해석될 수 있는 경우, 자동으로 숫자형으로 형 변환(type juggling) 이 일어나는 경우가 있다. 이때 해당 문자열을 numeric string (숫자형 문자열) 이라고 부른다. 이러한 문자열은 정수 또는 부동소수점 형식으로 파싱될 수 있는 문자열이다.


아래는 예시이다.

var_dump("123" + 1);         // int(124)
var_dump("10.5" * 2);        // float(21)
var_dump("123abc" + 1);      // int(124) – 앞쪽 숫자만 사용됨
var_dump("abc123" + 1);      // int(1) – 숫자로 변환 실패시 0 처리

 

  • 단, 몇가지 주의점이 존재한다.
  • "123abc"처럼 앞쪽에 숫자가 있고 뒤에 문자가 있으면, 앞부분만 숫자로 파싱된다.
  • 반대로 "abc123"처럼 앞에 문자가 있으면, 전체가 숫자로 변환되지 않아 0으로 간주된다.

이는 == 비교 시에도 영향을 주고, 이는 타입 변환 취약점(Type Juggling Vulnerability)이라는 실제 취약점으로 존재한다.
간단히 말해서 php에서 ==와 같은 느슨한 비교(loose comparison)를 사용할때 두 값의 타입이 다르면 자동으로 type juggling이 시도되기 때문에 이 과정에서 의도치 않은 동등 비교 결과가 발생할 수 있어 보안상 취약점이 된다.
이 문제에서는 해당 취약점이 핵심이 아니기 때문에 참고할만한 블로그를 첨부한다.

 

hy30nq - php 취약점과 예방법
gkdisakdmaqk - php 비교 연산자 취약점(md5 매직해시)
hyunmini - php 의 연산자 취약점

 

php 7.4에서 숫자형 문자열의 기준

php 7.4에서 숫자형 문자열은 is_numeric_string_ex() 함수의 처리 방식에 따라서 판단된다. 해당 함수에서는 정규표현식으로 숫자형 문자열을 판단하는데, 조건 중 하나를 만족하면 숫자형 문자열로 간주어되어 느슨한 비교시 숫자 변환이 이루어진다.

정규표현식의 조건은 아래와 같다.


1. 정수(integer) 형태

^[+-]?[0-9]+$
  • 앞뒤에 공백은 허용 (trim 후 판정)
  • EX) "0", "123", "-42", "+7"

2. 부동소수(float) 형태

^[+-]?(?:[0-9]*\.[0-9]+|[0-9]+\.[0-9]*)$
  • 소수점 앞이나 뒤는 비어 있어도 됨 (하나는 숫자여야 함)
  • EX) "3.14", "0.5", ".5", "5."

3. 지수 표기법(exponent) 형태

^[+-]?(?:[0-9]+(?:\.[0-9]*)?|\.[0-9]+)[eE][+-]?[0-9]+$
  • EX) "1e10", "3.5E+7", "2.0e-3"

4. 양끝 공백 허용

  • " 123 " → OK (공백 제거 후 판정)
  • " 3.14 " → OK

5. 허용 안되는 경우
아래와 같이 규칙을 벗어나면 숫자형 문자열이 아님

  • 숫자 뒤에 알파벳/특수문자 ("123abc", "1.2.3", "7.A")
  • 숫자 시작이 아니면서 숫자 부분이 없는 경우 ("abc", "-")
  • 지수부 형식이 불완전 ("1e", "2e+")

헷갈렸던 부분

내가 이 문제를 풀며 가장 헷갈렸던 부분은 총 두가지이다.
1. "숫자형 문자열"과 "숫자"는 엄연히 다르다.

  • "8"과 같은 것들이 다 숫자로 무조건 변환된다고 생각했으나, 기본적으로는 문자열이라는 사실을 잊으면 안된다.

2. 둘 중 하나라도 "숫자형 문자열"의 기준에 부합하지 않으면 "문자열"끼리는 아스키 비교

  • 하나라도 숫자형 문자열이면 둘다 숫자로 변환한다고 생각했다.

input1

 

input1에 해당하는 조건은
1. 빈칸이 아닐것
2. `input <”8”`
→ `“8”`: 숫자형 문자열
3. `“7.9” < input < “7.A”`
→ `“7.A”`: 문자열
→ `“7.9”`: 숫자형 문자열

비교하는 연산자 중에 하나라도 숫자형 문자열이 아니라면 문자열로 간주하여 비교한다. 
숫자로는 위 조건에 부합할 수 있는 것이 없으므로 전부다 아스키코드로 즉, 문자열로써 비교하게 만들어야 한다.

따라서 아스키코드 상으로 8과 7.A보다 작으며, 7.9보다 큰 경우를 찾으면 된다.
나는 **`7.;`** 을 입력값으로 지정했다.

 

input2

input2에 해당하는 조건은
1. 빈칸이 아닐것
2. 길이가 1과 3사이 
→ 2자리
3. `input_2 < 74`
→ 74는 숫자
4. `input_2 > "74"`
→ "74"는 숫자형 문자열
  
74 자체는 숫자이므로 3번 조건에서는 숫자로 비교할 수 밖에없다.
그러나, 4번 조건도 숫자로 비교하면 통과하는 것이 불가능하다.
따라서 74와는 숫자로 비교하고, "74"와는 문자열로 비교해야한다.

이 문제를 풀때는 드림핵의 아래 게시물의 도움을 조금 받았다.

 

`7a` 와 같은 값을 입력하면,
1. 74와 비교할때는 7이 되고
2. "74"와 비교할때는 2번째 자리의 아스키코드가 더 크므로
조건에 부합한다.

이 두값을 입력하면 무리없이 flag를 얻을 수 있다.