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 |