Hacking (CTF)/CTF

[Dreamhack/드림핵] PHPreg

gapsoo 2024. 5. 7. 03:38

 

 

 

https://dreamhack.io/wargame/challenges/873

 

phpreg

Description php로 작성된 페이지입니다. 알맞은 Nickname과 Password를 입력하면 Step 2로 넘어갈 수 있습니다. Step 2에서 system() 함수를 이용하여 플래그를 획득하세요. 플래그는 ../dream/flag.txt에 위치합니

dreamhack.io


[1] 문제 풀이

 

 

문제 설명을 다시 보자면,

 

이러하다!

 

그러니까 php로 작성한 페이지에 접속을 하면, Ncickname과 Password를 입력할 수 있는 화면이 나온다.

먼저 알맞은 Nickname과 Password를 입력하여 Step 2로 넘어가보자!

 

 

Step 1 페이지의 소스코드 페이지를 살펴보았는데 별 게 없어서 문제 파일을 살펴보았다,

 

 

index.php는 위의 문제 페이지 소스코드 파일과 내용이 똑같았다.

 

다음은 step2.php 파일의 내용이다.

<html>
<head>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css">
<title>PHPreg</title>
</head>
<body>
  <!-- Fixed navbar -->
  <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">
      <!-- PHP code -->
      <?php
          // POST request
          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);
              
              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";
          }
      ?>
    </div>
  </div>

  <style type="text/css">
    h4 {
      color: rgb(84, 84, 84);
    }
    .box{
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: center;
    }
    pre {
      width: 80%;
    }
    .door_box {
      position: relative;
      width: 240px;
      height: 180px;
      margin: 20px 0px;
    }
    .door_black {
      position: absolute;
      width: 140px;
      height: 180px;
      background-color: black;
      border-radius: 10px;
      right:0px;
    }
    .door {
      z-index: 2;
      position: absolute;
      width: 140px;
      height: 180px;
      background-color: #b9abf7;
      border-radius: 10px;
      right: 100px;
    }
    .door_cir{
      z-index: 3;
      position: absolute;
      border-radius: 50%;
      width: 20px;
      height: 20px;
      border: 2px solid rgba(255, 222, 113, 0.873);
      background-color: #ffea98;
      top: calc( 180px / 2 - 10px );
      right: 10px;
    }
  </style>
</body>
</html>

 

 

"<?php"로 시작하는 php 문 시작 부분이 눈에 띈다.

 

 

 

다음은 step2.php 파일 내용의 일부이다.

<?php
          // POST request
          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);
              
              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>';

 

 

php 문법을 이해하기 위해, preg match() 함수와 preg_replace() 함수에 대해 찾아보았다.

 

 


⭐ preg_match() 함수란?

주어진 정규 표현식 패턴을 사용하여 문자열 내에서 일치(match)하는 부분을 찾아내는 함수이다.

 

특징

- 일치하는 패턴을 문자열에서 처음으로 발견하면 검색을 중단하고 1을 반환한다.

- 일치하는 패턴을 찾지 못하면 0을 반환한다.

- 에러가 발생한 경우 false를 반환한다.

- 이메일 주소, 전화번호, URL과 같은 특정 패턴의 문자열을 검사할 때 주로 사용한다.

- 문자열에서 특정 패턴을 검색하거나 문자열에서 특정 형식의 데이터를 추출하는 데에도 사용한다.

- 주의할 점: 기본적으로 preg_match() 함수는 대소문자를 구분한다. 만약 대소문자를 구분하지 않고 싶다면 정규 표현식에 /i 플래그를 추가한다.

 

php_match() 함수의 예시

// 전화번호 출력

$text = '문자열에서 전화번호는 010-1234-5678과 같이 나타날 수 있습니다.';
$pattern = "/\b(?:\d{2,3}[-.])?\d{3,4}[-.]\d{4}\b/";

if (preg_match($pattern, $text, $matches)) {
    $phoneNumber = $matches[0];
    echo '추출된 전화번호: ' . $phoneNumber;
} else {
    echo '전화번호를 찾을 수 없습니다.';
}

// 출력: '추출된 전화번호: 010-1234-5678'

 


 

 

다시 돌아와서, step2.php에서 사용된 preg_match 파일을 분석해보자.

 

if (preg_match("/[a-zA-Z]/", $input_pw)) {
              echo "alphabet in the pw :(";
            }

 

 

여기에서 사용된 정규 표현식은 /[a-zA-Z]/ 이다.

[a-zA-Z]는 소문자 a부터 z까지 또는 대문자 A부터 Z까지의 알파벳 문자를 의미한다. 이 정규 표현식은 문자열에 알파벳 문자가 포함되어 있는지를 검사한다.

 

따라서, 만약 $input_pw 변수에 알파벳 문자가 포함되어 있다면, "alphabet in the pw :("를 출력한다.

 

확인을 위해 'admin', 'admin'을 입력해보았다.

 

 

 

위에서 코드를 분석하여 예상한 결과대로 나왔다.

 

이제, preg_replace() 함수를 분석해보자.

 


⭐preg_replace() 함수란?

preg_replace() 함수는 정규 표현식을 사용하여 문자열을 치환하는 함수이다. 정규 표현식을 사용하여 보다 복잡한 치환을 할 수 있다.

 

사용되는 인수

$pattern 찾을 정규 표현식
$replacement 치환할 문자열
$subject 대상 문자열
$flags 정규 표현식 플래그

 

 

사용 예시

// 문자열에서 특정 문자를 다른 문자로 치환
$str = "Hello, world!";

// "Hello"를 "Goodbye"로 치환
$str = str_replace("Hello", "Goodbye", $str);

echo $str; // Goodbye, world!

// 문자열에서 특정 문자열을 제거
$str = "Hello, world!";

// "Hello"를 제거
$str = str_replace("Hello", "", $str);

echo $str; // world!

// 정규 표현식을 사용하여 문자열을 치환
$str = "Hello, world! 12345";

// "Hello"와 "world"를 "Goodbye"로 치환
$str = preg_replace("/Hello|world/", "Goodbye", $str);

echo $str; // Goodbye, Goodbye! 12345

 

 


 

다시 돌아와서 확인해보면,

 

 else{
              $name = preg_replace("/nyang/i", "", $input_name);
              $pw = preg_replace("/\d*\@\d{2,3}(31)+[^0-8\"]\!/", "d4y0r50ng", $input_pw);
              
              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>';

 

 

- $input_name 변수에서 "nyang"이라는 문자열을 대소문자를 구분하지 않고 찾아 제거한 후, 결과를 $name 변수에 할당한다.
- preg_replace() 함수를 사용하여 정규 표현식 /nyang/i를 적용한다. 여기서 /i는 대소문자를 구분하지 않도록 하는 옵션이다.
- $input_pw 변수에서 특정한 패턴을 찾아 "d4y0r50ng"으로 대체한 후, 결과를 $pw 변수에 할당한다.
- preg_replace() 함수를 사용하고, 제공된 정규 표현식을 적용한다.
- 만약 $name이 "dnyang0310"이고, $pw가 "d4y0r50ng+1+13"과 정확히 일치하는 경우: HTML 코드를 출력한다.
- 출력되는 내용은 "Step 2 : Almost done..."이며, 이어서 문을 연상시키는 요소들이 포함된다.

 

 

 

먼저 name의 값에 대해 살펴보자.

 

name의 값이 "dnyang0310"이어야 하므로, 필터링을 우회하기 위해
"dnynyangang0310"이라는 Nickname을 입력하면 될 것이라고 예상했다.

 

nyang이 공백으로 치환되면서 필터링 되고 결국 "dnyang0310"만 남는 것이다.

 

 

 

그 다음으로는 pw의 필터링을 살펴보자.

 

$pw = preg_replace("/\d*\@\d{2,3}(31)+[^0-8]!/", "d4y0r50ng", $input_pw);

 

에서, 정규 표현식을 하나하나 해체하여 살펴보자.

 

\d*

- 0에서 9까지의 범위의 한 자리 숫자를 의미한다.

- *는 문자 또는 숫자가 0개 이상 나타나는 것을 의미한다.

=> 따라서 0~9 사이의 숫자가 0개 이상 나타나야 한다.

 

\@

- @를 의미한다.

 

\d{2,3}

- 숫자가 2개 또는 3개 연속됩니다.

 

(31)+

- '31'이라는 숫자가 하나 이상 반복된다.

 

[^0-8\"]

- '0'에서 '8' 사이의 숫자 또는 '"' 이외의 문자를 찾는다.

 

\!

- '!' 기호를 찾는다.

 

 

 

==> 위의 모든 요소를 조합하여 1@12319! 라는 문자열을 만들었다.

이 문자열은 preg_replace() 함수에 의해 "d4y0r50ng"로 변환될 것이다.

비밀번호는 "d4y0r50ng+1+13"이 되어야 하므로 뒤에 "+1+13"를 붙여

"123@12319!+1+13"을 비밀번호에 입력해보자.

 

 

 

 

이렇게 Step2로 넘어가게 되었다.

 


                $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 2에서 system() 함수를 이용하여 플래그를 획득하세요.

플래그는 ../dream/flag.txt에 위치합니다.

 

라고 했었다.

 

 

먼저 php 문법에서의 system 함수를 알아보자.

 

 


⭐system 함수란?

외부 프로그램을 실행하고 출력을 표시하는 함수이다.

 

system 함수의 기본 구문

system(string $command, int &$return_var): string|false

 

- $command: 실행할 명령어를 나타내는 문자열이다.
- &$return_var: 명령어 실행 후의 상태 코드를 받는 데 사용될 변수이다. 이 변수는 선택적으로 전달될 수 있다. 명령어가 성공하면 0이 아닌 값을 반환하며, 실패하면 0을 반환한다.
- 반환 값: 명령어의 출력을 나타내는 문자열을 반환한다. 실패한 경우 false를 반환한다.

 

예를 들어, ls 명령어를 실행하여 현재 디렉토리의 파일 목록을 가져오는 PHP 코드는 다음과 같이 작성할 수 있다:

$output = system('ls', $return_var);
echo $output;

 

 

또는 명령어의 실행 결과에 상관 없이 단순히 명령어를 실행하고 싶은 경우에는 다음과 같이 사용할 수 있다:

system('ls');

 

 

 

 


다시 돌아와서,

 

                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";

 

이 부분을 자세히 보면, 대소문자 구별없이 문자열에 "flag"가 포함되어 있으면 "Error!"가 출력된다.

 

그렇다면 system() 함수를 실행시키기 위해서는 "flag" 가 포함되어 있으면 안 된다.

하지만 플래그는 ../dream/flag.txt에 위치하는데, 어떤 방법으로 플래그 파일에 접근할 수 있을까?

 

나는 '*' 문자를 사용하여 ../dream 디렉토리 안의 모든 파일의 내용을 출력되게 할 것이다.

 

 

 

플래그를 출력하게 되었다.

 

 

 

 

풀이 완료!

 


[2] 참고 자료

 

정규표현식:
http://www.ktword.co.kr/test/view/view.php?no=5720

이스케이프:
https://choseongho93.tistory.com/entry/PHP-%EB%AC%B8%EC%9E%90%EC%97%B4%EC%97%90-%EC%82%AC%EC%9A%A9%ED%95%A0-%EC%88%98-%EC%9E%88%EB%8A%94-%EC%9D%B4%EC%8A%A4%EC%BC%80%EC%9D%B4%ED%94%84-%EC%8B%9C%ED%80%80%EC%8A%A4

preg_match:
https://codingeverybody.kr/php-preg_match-%ED%95%A8%EC%88%98/

'Hacking (CTF) > CTF' 카테고리의 다른 글

[Dreamhack/드림핵] pathtraversal  (0) 2024.05.15
[Dreamhack/드림핵] baby-union  (0) 2024.05.08
[Dreamhack/드림핵] php7cmp4re  (0) 2024.04.28
[Dreamhack/드림핵] Carve Party  (0) 2024.04.01
[Dreamhack/드림핵] devtools-sources  (0) 2024.03.30