xmi1e-vir.log

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

WARGAME/WEB

[Dreamhack] wargame 'phpreg' write-up

eunee22 2025. 9. 8. 22:37
🌱 Biginner
문제링크
php로 작성된 페이지입니다.
알맞은 Nickname과 Password를 입력하면 Step 2로 넘어갈 수 있습니다.
Step 2에서 system() 함수를 이용하여 플래그를 획득하세요.
플래그는 ../dream/flag.txt 에 위치합니다.
플래그의 형식은 DH{...} 입니다.

 

📌문제 파악

  • 닉네임이랑 패스워드를 입력해서 제출하면 step2로 넘어간다/
<nav class="navbar navbar-default navbar-fixed-top">
      <div class="container">
        <div class="navbar-header">
          <a class="navbar-brand" href="/">PHPreg</a>
        </div>
        <div id="navbar">
          <ul class="nav navbar-nav">
            <li><a href="/">Step 1</a></li>
            <li><a href="/step2.php">Step 2</a></li>
          </ul>
        </div><!--/.nav-collapse -->
      </div>
    </nav><br/><br/><br/>
    <div class="container">
      <div class="box">
        <h4>Step 1 : Open the door & Go to Step 2 !!</h4>
        <div class="door"><div class="door_cir"></div></div>
        <p>
          <form method="post" action="/step2.php">
              <input type="text" placeholder="Nickname" name="input1">
              <input type="text" placeholder="Password" name="input2">
              <input type="submit" value="제출">
          </form>
        </p>
      </div>
    </div>
  • step1에서 step2로 넘어가는 부분
    • 구체적으로는 if ($name === "dnyang0310" && $pw === "d4y0r50ng+1+13")를 참으로 만들어야 한다.
if ($_SERVER["REQUEST_METHOD"] == "POST") {
            $input_name = $_POST["input1"] ? $_POST["input1"] : "";
            $input_pw = $_POST["input2"] ? $_POST["input2"] : "";

            // pw filtering
            if (preg_match("/[a-zA-Z]/", $input_pw)) {
              echo "alphabet in the pw :(";
            }
            else{
              $name = preg_replace("/nyang/i", "", $input_name);
              $pw = preg_replace("/\d*\@\d{2,3}(31)+[^0-8\"]\!/", "d4y0r50ng", $input_pw);
              # 이게 step2로 넘어가기 위한 조건
              if ($name === "dnyang0310" && $pw === "d4y0r50ng+1+13") {
                echo '<h4>Step 2 : Almost done...</h4><div class="door_box"><div class="door_black"></div><div class="door"><div class="door_cir"></div></div></div>';

                $cmd = $_POST["cmd"] ? $_POST["cmd"] : "";

                if ($cmd === "") {
                  echo '
                        <p><form method="post" action="/step2.php">
                            <input type="hidden" name="input1" value="'.$input_name.'">
                            <input type="hidden" name="input2" value="'.$input_pw.'">
                            <input type="text" placeholder="Command" name="cmd">
                            <input type="submit" value="제출"><br/><br/>
                        </form></p>
                  ';
                }
                // cmd filtering
                else if (preg_match("/flag/i", $cmd)) {
                  echo "<pre>Error!</pre>";
                }
                else{
                  echo "<pre>--Output--\n";
                  # 이 함수를 이용
                  system($cmd);
                  echo "</pre>";
                }
              }
              else{
                echo "Wrong nickname or pw";
              }
            }
          }
          // GET request
          else{
            echo "Not GET request";
          }
      ?>

 

📌문제 풀이

STEP 1

주어진 식 if ($name === "dnyang0310" && $pw === "d4y0r50ng+1+13")는 다음 조건을 만족해야한다.

  1. 닉네임
    • "nyang"이라는 문자열(대소문자 구분 없이)을 찾아 제거
    • 따라서 "dnnyangyang0310"이라는 문자열을 입력하면 "nyang"부분이 지워지면서 dn[nyang]yang0310 이렇게 우리가 원하는 닉네임이 된다.
  2. 비밀번호
    • 비밀번호에 영문 알파벳이 미포함
    • pw 패턴 /\d*\@\d{2,3}(31)+[^0-8\"]\!/ d4y0r50ng로 치환
      • \d*: 0개 이상의 숫자
      • @: '@' 기호
      • \d{2,3}: 2개 또는 3개의 숫자
      • (31)+: '31'이 한 번 이상 반복
      • [^0-8"]: 0부터 8까지의 숫자와 큰따옴표(")를 제외한 모든 문자
      • !: '!' 기호
    • 조건에 맞는 임의의 비밀번호를 작성하면 된다.
      나는 "1@123319!+1+13"을 작성하였다.

STEP 2

  • ../dream/flag.txt 로 이동하되, "flag"라는 글자는 없이 이동해야 하는것이 조건이다.
$cmd = $_POST["cmd"] ? $_POST["cmd"] : "";

if ($cmd === "") {
  echo '
        <p><form method="post" action="/step2.php">
            <input type="hidden" name="input1" value="'.$input_name.'">
            <input type="hidden" name="input2" value="'.$input_pw.'">
            <input type="text" placeholder="Command" name="cmd">
            <input type="submit" value="제출"><br/><br/>
        </form></p>
  ';
}
// cmd filtering
else if (preg_match("/flag/i", $cmd)) {
  echo "<pre>Error!</pre>";
}
else{
  echo "<pre>--Output--\n";
  # 이 함수를 이용하라고
  system($cmd);
  echo "</pre>";
}

 

먼저 pwd 명령을 통해 현재 디렉토리를 파악할 수 있었다.

cd .. && ls 명령을 통해 이전 디렉토리를 확인해보면, 우리가 찾고자 하는 dream 디렉토리가 위치하고 있다.

flag 필터링을 어떻게 우회 할것인가?

구글링을 했을때, 대다수의 블로그가 웹사이트 취약점에 대해서 다루고 있었는데 이 문제의 경우 리눅스 커맨드라인에 입력해야 하기 때문에 애를 많이 먹었다.

  • 실패한 목록들
    • 대문자로 바꾸기 → 우리가 우회하려는 preg_match()함수에서 /i 플래그를 통해 이를 막아두었다.
    • cd ../dream && cat "$(printf '\x66\x6c\x61\x67').txt"
    • cat /var/dream/"$(printf '\\x66\\x6c\\x61\\x67').txt"
      → 애초에 printf '\x66\x6c\x61\x67' 명령이 안먹히는 환경이다.

많은 삽질을 하다가 cat ../dream/fla""g.txt을 통해 풀어냈다.
preg_match('/flag/i', $input)은 "flag"라는 연속된 문자열을 찾기 때문에 이렇게 끊어주는 것 만으로도 간단하게 우회가 가능하다.

그러면 아무 문자로 끊으면 될까?
"로 끊을수 있었던 이유도 따로 있다.

  • 셸(shell)에서 fla""g.txt는 완벽하게 붙여진 flag.txt로 인식한다.
  • PHP 코드 내에서는 "fla" + 빈 문자열 "" + "g"를 붙여서 "flag"가 되는 형태이다.

그러나, 정규식에서는 문자열이 붙여진다고 이해하지 못하고 이를 각각의 문자열로 인식한다. 따라서 우회가 가능한 것이다.

결론적으로 플래그를 얻어낼 수 있었다.

[참고할 자료]

문제를 풀고 나서 다른 분이 푼 풀이를 보다가 내가 어렵게 푼 편이라는 것을 깨달았다.
따라서 더 쉽게 푸신 분들의 풀이를 함께 언급한다.

 

파일 이름인 flag를 직접 작성하지 않고서도 내용 출력이 가능
https://taesan-smj.tistory.com/61
이럴때는 
을 이용하면 된다. 
의 의미는 해당 파일 내부에서 모든 파일을 가져오라는 의미이므로cat ../dream/*.txt 를 입력하게 된다면, txt 확장자를 가지고 있는 경로에 있는 파일을 읽어오라는 뜻이다.
파일 이름의 일부만 입력해도 접근이 가능
https://koharinn.tistory.com/701
파일 이름의 일부를 * 처리해도 패턴에 매칭되는 파일이 있다면 접근이 가능하다.