[SK shieldus Rookies 19기] 애플리케이션 보안 6일차

기록하는짱구·2024년 3월 19일
0

SK Shieldus Rookies 19기

목록 보기
14/43
post-thumbnail

📌 업로드 취약점

= 위험한 형식 파일 업로드

🔎 가이드

📝 개요

# 파일 업로드 기능이 제공되는 경우
파일의 크기와 개수, 종류를 제한하지 않고, 외부에서 접근 가능한 경로에 업로드 파일을 저장하는 경우 발생
~~~~~~~~~~~~~~~  ~~~~~~~~~~~~~~~~~  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|                |                  |  
|                |                  +-- URL을 통해 접근 가능한 경로에 위치
| 				 | 						→ Web Document Root 아래에 위치
|                |
|                +-- 바이러스와 같은 파일을 업로드해서 해당 서버를 악성코드 유포지로 활용
|                    서버에서 실행 가능한 파일을 업로드해서 실행
|					 → 웹쉘(WebShell)  
|					   ~~~~~~~~~~~~~
|                      운영체제 명령어 삽입 공격을 쉽게 할 수 있도록
|                      서버에서 실행 가능한 언어를 이용해서 
|                      하나의 파일로 만든 웹 애플리케이션
|                                                            
+-- 서버의 연결 및 디스크 자원을 고갈시켜 정상적인 서비스를 방해       

💡 webshell opensource project 로 검색하면 다양한 언어로 만들어진 웹쉘 파일을 쉽게 구할 수 있음
Ex. https://github.com/tennc/webshell

# 파일 업로드 기능 제공을 확인하는 방법

→ https://developer.mozilla.org/ko/docs/Web/HTML/Element/form
<form enctype="multipart/form-data"	method="post" ... >
      ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    <input type="file" ... >
    	   ~~~~~~~~~~~
</form>

🔐 방어 기법

  1. 업로드 파일의 크기와 개수 제한
    ▪ 설계 시 제공하는 서비스에 맞는 적절한 크기와 개수를 정의하는 것이 필요

  2. 파일의 종류 제한
    ▪ 업로드 가능한 파일의 종류를 미리 정의하고 정의된 범위 내에서만 업로드를 허용 → 허용 목록 (화이트 리스트) 방식으로 제한

💡 파일 종류를 비교하는 방법
⑴ 확장자
→ 파일명이 어떻게 끝나는가로 판단 (예: test.gif.jsp)
⑵ Content-Type
⑶ File Signature = Magic Number
https://www.garykessler.net/library/file_sigs.html

  1. 업로드 파일을 외부에서 접근할 수 없는 경로에 저장
    ▪ Web (Document) Root 밖

  2. 업로드 파일의 이름을 외부에서 알 수 없는 형태로 변경해서 알 수 없는 경로에 저장

  3. 업로드 파일의 실행 속성을 제거하고 저장

💻 Kali 가상머신에서 bee-box로 접속

💻 이미지 업로드 사이트

💻 이미지 업로드
▪ 외부에서 접근한 경로에 업로드 파일명과 동일한 이름으로 저장

💻 서버에서 실행 가능한 파일을 업로드
▪ PHP로 만들어진 웹쉘을 다운로드해서 업로드

┌──(kali㉿kali)-[~]
└─$ cd /usr/share/webshells 
                                                                                                      
┌──(kali㉿kali)-[/usr/share/webshells]
└─$ ls
asp  aspx  cfm  jsp  laudanum  perl  php
                                                                                                      
┌──(kali㉿kali)-[/usr/share/webshells]
└─$ cd php                 
                                                                                                      
┌──(kali㉿kali)-[/usr/share/webshells/php]
└─$ ls
findsocket  php-backdoor.php  php-reverse-shell.php  qsd-php-backdoor.php  simple-backdoor.php

/usr/share/webshells/php/simple-backdoor.php 파일을 업로드
▪ here 위에 마우스를 올려보면 링크 정보가 출력됨
▪ 정상적으로 업로드 된 것을 확인 가능

💻 해당 주소로 이동
▪ 업로드한 웹쉘 파일을 실행

▪ cmd 요청 파라미터에 서버에서 실행할 명령어를 전달
명령어의 실행 결과가 화면에 출력

🔎 취약한 소스 코드 확인
bee-box 가상머신에서 보기

bee@bee-box:~$ sudo gedit /var/www/bWAPP/unrestricted_file_upload.php 

<?php
include("security.php");
include("security_level_check.php");
include("functions_external.php");
include("selections.php");

if(isset($_POST["form"]))
{
    
    $file_error = "";
    
    switch($_COOKIE["security_level"])
    {
    	# https://www.php.net/manual/en/function.move-uploaded-file.php
        case "0" : 
           
           move_uploaded_file($_FILES["file"]["tmp_name"],
           					  ~~~~~~~~~~~~~~~~~~~~~~~~~~~
           					  # 업로드 파일을 
                              
           "images/" . $_FILES["file"]["name"]);
           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
           현재 디렉터리의 images 아래에 업로드 파일명으로 저장     
                          
           
           break;                 
           
        case "1" :
            $file_error = file_upload_check_1($_FILES["file"]);
            
            if(!$file_error)
            {
                move_uploaded_file($_FILES["file"]["tmp_name"], "images/" . $_FILES["file"]["name"]);
            }            
            break;
        
        case "2" :            
            $file_error = file_upload_check_2($_FILES["file"], array("jpg","png"));
            if(!$file_error)
            {
                move_uploaded_file($_FILES["file"]["tmp_name"], "images/" . $_FILES["file"]["name"]);
            }            
            break;
        
        default : 
            move_uploaded_file($_FILES["file"]["tmp_name"],"images/" . $_FILES["file"]["name"]);
            break;   
    }    
}    

?>

		... 생략 ...
    <?php

    if(isset($_POST["form"]))
    {

        if(!$file_error)
        {

            echo "The image has been uploaded <a href=\"images/" . $_FILES["file"]["name"] . "\" target=\"_blank\">here</a>.";

        }

        else
        {

            echo "<font color=\"red\">" . $file_error . "</font>";        

        }

    }

    ?> 

💻 보안 등급이 중간인 경우 파일 확장자를 블랙 리스트를 이용해서 제한

function file_upload_check_1($file, $file_extensions = array("asp", "aspx", "dll", "exe", "jsp", "php"), $directory = "images")     
                                    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
{
    $file_error = "";

    // Checks if the input field is empty
    if($file["name"] == "")
    {
        $file_error = "Please select a file...";
        return $file_error;
    }

    // Checks if there is an error with the file
    switch($file["error"])
    // URL: http://php.net/manual/en/features.file-upload.errors.php
    {
        case 1 : $file_error = "Sorry, the file is too large. Please try again...";
                 break;

        case 2 : $file_error = "Sorry, the file is too large. Please try again...";
                 break;

        case 3 : $file_error = "Sorry, the file was only partially uploaded. Please try again...";
                 break;

        case 6 : $file_error = "Sorry, a temporary folder is missing. Please try again...";
                 break;

        case 7 : $file_error = "Sorry, the file could not be written. Please try again...";
                 break;

        case 8 : $file_error = "Sorry, a PHP extension stopped the file upload. Please try again...";
                 break;
    }

    if($file_error)
    {
        return $file_error;
    }

    // Breaks the file in pieces (.) All pieces are put in an array
    
    # 확장자 검증을 위해서 .을 기준으로 분리
    $file_array = explode(".", $file["name"]);				

    // Puts the last part of the array (= the file extension) in a new variabele
    // Converts the characters to lower case
    
    # 확장자를 소문자로 변환
    $file_extension = strtolower($file_array[count($file_array) - 1]);	

    // Searches if the file extension exists in the 'allowed' file extensions array   
    
    # 제한 목록에 포함되어 있는 경우 오류 처리
    if(in_array($file_extension, $file_extensions))			
    {
       $file_error = "Sorry, the file extension is not allowed. The following extensions are blocked: <b>" . join(", ", $file_extensions) . "</b>";

       return $file_error;
    }

    // Checks if the file already exists in the directory
    
    # 동일한 파일이 존재하는지 검증
    if(is_file("$directory/" . $file["name"]))				 
    {
        $file_error = "Sorry, the file already exists. Please rename the file...";      
    }

    return $file_error;
}

💻 파일 확장자를 PHP3로 변경해서 업로드
▪ 블랙 리스트에는 PHP3가 포함되어 있지 않음

┌──(kali㉿kali)-[/usr/share/webshells/php]
└─$ sudo cp simple-backdoor.php simple-backdoor.php3

💻 보안 등급이 가장 높은 경우 화이트 리스트 방식으로 확장자를 제한

function file_upload_check_2($file, $file_extensions =
array("jpeg", "jpg", "png", "gif"), $directory = "images")
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~														
{
		... 생략 ...
    

    // Searches if the file extension exists in the 'allowed' file extensions array   
    
    # 화이트 리스트에 포함되지 않은 확장자인 경우 오류 처리
    if(!in_array($file_extension, $file_extensions))
      ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
      
    {
       $file_error = "Sorry, the file extension is not allowed. Only the following extensions are allowed: <b>" . join(", ", $file_extensions) . "</b>";

       return $file_error;
    }
		... 생략 ... 

💻 웹 루트 밖에 업로드 파일을 저장하면 주소를 통해서 접근하는 것이 불가능
▪ 다운로드 기능이 필요

Ex🔻

# 업로드 파일이 /data/images/myimage.gif 형태로 저장되어 있다면
              ~
              # 해당 서버의 루트 디렉터리를 의미   

<img src="/data/images/myimage.gif"> 또는 <a href="/data/images/myimage.gif" > 형태로 접근이 불가 
          ~~~~~~~~~~~~~~~~~~~~~~~~                ~~~~~~~~~~~~~~~~~~~~~~~~
          |  WEB_DOCUMENT_ROOT/data/images/myimage.gif 의미     
          +-- 웹 루트 디렉터리를 의미
          = 웹 서버에서 설정한 기준 디렉터리 (bWAPP의 경우 /var/www 디렉터리를 지정) 


<img src="download?file=/data/images/myimage.gif"> 또는 <a href="download?file=/data/images/myimage.gif">
          ~~~~~~~~                                               ~~~~~~~~
          # 요청 파라미터로 전달된 경로의 파일을 읽어서 응답으로 반환

이때 가급적이면 업로드 파일이 저장되어 있는 경로는 외부에 노출되지 않는 것이 안전
<img src="download?file=myimage.gif"> 또는 <a href="download?file=myimage.gif">
          ~~~~~~~~                                               ~~~~~~~~
          # 요청 파라미터로 전달된 파일을 해당 서버의
          /data/images 디렉터리에서 읽어서 응답으로 반환
          ~~~~~~~~~~~~~~~~~~~~
          → 업로드 파일을 저장하고 있는 디렉터리 

💻 다운로드 기능을 구현할 때 유의해야 할 사항
▪ 지정된 경로를 벗어나서 파일을 읽지 못하도록 제한
▪ 경로 조작 취약점(path traversal)에 노출될 수 있음

download?file=myimage.gif 
==========================
with open('/data/images/' + file, 'r') as f:
    return f.read()


# 시스템 파일을 다운로드할 수 있음 
download?file=../../../../../../../../../etc/passwd			
              ~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~
               |                          +-- 알려진 시스템 파일에 접근 
               +-- 루트 디렉터리가 나올 때까지 상위 디렉터리로 이동
               	   ../는 상위 디렉터리로의 이동을 의미

💻 /var/www/bWAPP/unrestricted_file_upload.php 파일에서 업로드 취약점 제거

<?php

/*

bWAPP, or a buggy web application, is a free and open source deliberately insecure web application.
It helps security enthusiasts, developers and students to discover and to prevent web vulnerabilities.
bWAPP covers all major known web vulnerabilities, including all risks from the OWASP Top 10 project!
It is for security-testing and educational purposes only.

Enjoy!

Malik Mesellem
Twitter: @MME_IT

bWAPP is licensed under a Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License (http://creativecommons.org/licenses/by-nc-nd/4.0/). Copyright © 2014 MME BVBA. All rights reserved.

*/

include("security.php");
include("security_level_check.php");
include("functions_external.php");
include("selections.php");

if(isset($_POST["form"]))
{
    $file_error = "";
    
    switch($_COOKIE["security_level"])
    {
         
        case "0" : 
            
            move_uploaded_file($_FILES["file"]["tmp_name"], "images/" . $_FILES["file"]["name"]);
            
            break;
        
        case "1" :
            
            $file_error = file_upload_check_1($_FILES["file"]);
            
            if(!$file_error)
            {
                
                move_uploaded_file($_FILES["file"]["tmp_name"], "images/" . $_FILES["file"]["name"]);
    
            }            
            
            break;
        
        case "2" :            
                       
            $file_error = file_upload_check_2($_FILES["file"], array("jpg","png"));
            
            if(!$file_error)
            {
            
                # 외부에서 접근할 수 없는 경로에 파일을 저장하도록 수정
                move_uploaded_file($_FILES["file"]["tmp_name"],
                "/data/images/" . $_FILES["file"]["name"]);
    							  
            }            
            
            break;
        
        default : 
            
            move_uploaded_file($_FILES["file"]["tmp_name"],"images/" . $_FILES["file"]["name"]);
            
            break;   

    }    
        
}    

?>
<!DOCTYPE html>
<html>
    
<head>
        
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">

<!--<link rel="stylesheet" type="text/css" href="https://fonts.googleapis.com/css?family=Architects+Daughter">-->
<link rel="stylesheet" type="text/css" href="stylesheets/stylesheet.css" media="screen" />
<link rel="shortcut icon" href="images/favicon.ico" type="image/x-icon" />

<!--<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>-->
<script src="js/html5.js"></script>

<title>bWAPP - Unrestricted File Upload</title>

</head>

<body>
    
<header>

<h1>bWAPP</h1>

<h2>an extremely buggy web app !</h2>

</header>    

<div id="menu">
      
    <table>
        
        <tr>
            
            <td><a href="portal.php">Bugs</a></td>
            <td><a href="password_change.php">Change Password</a></td>
            <td><a href="user_extra.php">Create User</a></td>
            <td><a href="security_level_set.php">Set Security Level</a></td>
            <td><a href="reset.php" onclick="return confirm('All settings will be cleared. Are you sure?');">Reset</a></td>            
            <td><a href="credits.php">Credits</a></td>
            <td><a href="http://itsecgames.blogspot.com" target="_blank">Blog</a></td>
            <td><a href="logout.php" onclick="return confirm('Are you sure you want to leave?');">Logout</a></td>
            <td><font color="red">Welcome <?php if(isset($_SESSION["login"])){echo ucwords($_SESSION["login"]);}?></font></td>
            
        </tr>
        
    </table>   
   
</div> 

<div id="main">
    
    <h1>Unrestricted File Upload</h1>

    <form action="<?php echo($_SERVER["SCRIPT_NAME"]);?>" method="POST" enctype="multipart/form-data">

        <p><label for="file">Please upload an image:</label><br />
        <input type="file" name="file"></p>

        <input type="hidden" name="MAX_FILE_SIZE" value="10">
        <!-- <input type="hidden" name="MAX_FILE_SIZE" value="100000"> -->

        <input type="submit" name="form" value="Upload">

    </form>

    <br />
    <?php

    if(isset($_POST["form"]))
    {

        if(!$file_error)
        {
            echo "The image has been uploaded <a href=\"/data/images/" . $_FILES["file"]["name"] . "\" target=\"_blank\">here</a>.";

			# 업로드된 파일에 대한 링크(<a>)와 출력(<img>)을 저장된 경로로 수정 
            → 주소를 통해서 접근할 수 없으므로 링크도 출력도 되지 않음
	    	echo "<img src=\"/data/images/" . $_FILES["file"]["name"] . "\">";
            
        }			
			   
        else
        {

            echo "<font color=\"red\">" . $file_error . "</font>";        

        }

    }

    ?>  
</div>
    
<div id="side">    
    
    <a href="http://twitter.com/MME_IT" target="blank_" class="button"><img src="./images/twitter.png"></a>
    <a href="http://be.linkedin.com/in/malikmesellem" target="blank_" class="button"><img src="./images/linkedin.png"></a>
    <a href="http://www.facebook.com/pages/MME-IT-Audits-Security/104153019664877" target="blank_" class="button"><img src="./images/facebook.png"></a>
    <a href="http://itsecgames.blogspot.com" target="blank_" class="button"><img src="./images/blogger.png"></a>

</div>     
    
<div id="disclaimer">
          
    <p>bWAPP is licensed under <a rel="license" href="http://creativecommons.org/licenses/by-nc-nd/4.0/" target="_blank"><img style="vertical-align:middle" src="./images/cc.png"></a> &copy; 2014 MME BVBA / Follow <a href="http://twitter.com/MME_IT" target="_blank">@MME_IT</a> on Twitter and ask for our cheat sheet, containing all solutions! / Need an exclusive <a href="http://www.mmebvba.com" target="_blank">training</a>?</p>
   
</div>
    
<div id="bee">
    
    <img src="./images/bee_1.png">
    
</div>
    
<div id="security_level">
  
    <form action="<?php echo($_SERVER["SCRIPT_NAME"]);?>" method="POST">
        
        <label>Set your security level:</label><br />
        
        <select name="security_level">
            
            <option value="0">low</option>
            <option value="1">medium</option>
            <option value="2">high</option> 
            
        </select>
        
        <button type="submit" name="form_security_level" value="submit">Set</button>
        <font size="4">Current: <b><?php echo $security_level?></b></font>
        
    </form>   
    
</div>
    
<div id="bug">

    <form action="<?php echo($_SERVER["SCRIPT_NAME"]);?>" method="POST">
        
        <label>Choose your bug:</label><br />
        
        <select name="bug">
   
<?php

// Lists the options from the array 'bugs' (bugs.txt)
foreach ($bugs as $key => $value)
{
    
   $bug = explode(",", trim($value));
   
   // Debugging
   // echo "key: " . $key;
   // echo " value: " . $bug[0];
   // echo " filename: " . $bug[1] . "<br />";
   
   echo "<option value='$key'>$bug[0]</option>";
 
}

?>


        </select>
        
        <button type="submit" name="form_bug" value="submit">Hack</button>
        
    </form>
    
</div>
      
</body>
    
</html>

💻 bee-box 가상머신에 파일을 저장하는 디렉터리를 생성

bee@bee-box:~$ sudo mkdir -p /data/images
bee@bee-box:~$ ls /data/images
bee@bee-box:~$ sudo chown root:www-data /data/images
bee@bee-box:~$ sudo chmod 777 /data/images

💻 kali 가상머신에서 이미지 파일을 업로드

→ 웹 루트 디렉터리 아래에 존재하지 않는 디렉터리와 파일을 참조하므로 링크도 동작하지 않고 이미지도 나타나지 않음

bee@bee-box:~$ ls /data/images
dog.jpg					# 지정한 디렉터리에 파일이 저장된 것을 확인 

💻 bee-box에 다운로드 기능 추가

bee@bee-box:~$ sudo gedit /var/www/bWAPP/download.php

<?php
  $target_Dir = "/data/images/";
  $file = $_GET['file'];
  $down = $target_Dir.$file;
  $filesize = filesize($down);
  
  if(file_exists($down)){
    header("Content-Type:application/octet-stream");
    header("Content-Disposition:attachment;filename=$file");
    header("Content-Transfer-Encoding:binary");
    header("Content-Length:".filesize($target_Dir.$file));
    header("Cache-Control:cache,must-revalidate");
    header("Pragma:no-cache");
    header("Expires:0");
    if(is_file($down)){
        $fp = fopen($down,"r");
        while(!feof($fp)){
          $buf = fread($fp,8096);
          $read = strlen($buf);
          print($buf);
          flush();
        }
        fclose($fp);
    }
  } else{
    ?><script>alert("존재하지 않는 파일입니다.")</script><?
  }
?>

💻 링크와 이미지 출력에 반영

  if(isset($_POST["form"]))
   {

       if(!$file_error)
       {

/*


           echo "The image has been uploaded <a href=\"/data/images/" 
           . $_FILES["file"]["name"] . "\" target=\"_blank\">here</a>.";

	    echo "<img src=\"/data/images/" . $_FILES["file"]["name"] . "\">";
*/
           echo "The image has been uploaded <a href=\"download.php?file=" . $_FILES["file"]["name"] . "\" target=\"_blank\">here</a>.";

	    echo "<img src=\"download.php?file=" . $_FILES["file"]["name"] . "\">";


       }

       else
       {

           echo "<font color=\"red\">" . $file_error . "</font>";        

       }

   }

💻 링크와 이미지가 출력되는 것을 확인

💻 파일명에 경로조작 문자열과 함께 시스템 파일명을 함께 전달

┌──(kali㉿kali)-[~]
└─$ curl -v  http://bee.box/bWAPP/
download.php?file=../../../../../../../etc/passwd
                  ~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~
			      # 경로 조작 문자열    # 시스템 파일 경로  
                  
*   Trying 192.168.40.130:80...                       
* Connected to bee.box (192.168.40.130) port 80
> GET /bWAPP/download.php?file=../../../../../../../etc/passwd HTTP/1.1
> Host: bee.box
> User-Agent: curl/8.4.0
> Accept: */*
> 
< HTTP/1.1 200 OK
< Date: Tue, 19 Mar 2024 04:41:12 GMT
< Server: Apache/2.2.8 (Ubuntu) DAV/2 mod_fastcgi/2.4.6 PHP/5.2.4-2ubuntu5 with Suhosin-Patch mod_ssl/2.2.8 OpenSSL/0.9.8g
< X-Powered-By: PHP/5.2.4-2ubuntu5
< Content-Disposition: attachment;filename=../../../../../../../etc/passwd
< Content-Transfer-Encoding: binary
< Content-Length: 2217
< Cache-Control: cache,must-revalidate
< Pragma: no-cache
< Expires: 0
< Content-Type: application/octet-stream
< 
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/bin/sh
bin:x:2:2:bin:/bin:/bin/sh
sys:x:3:3:sys:/dev:/bin/sh
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/bin/sh
man:x:6:12:man:/var/cache/man:/bin/sh
lp:x:7:7:lp:/var/spool/lpd:/bin/sh
mail:x:8:8:mail:/var/mail:/bin/sh
news:x:9:9:news:/var/spool/news:/bin/sh
uucp:x:10:10:uucp:/var/spool/uucp:/bin/sh
proxy:x:13:13:proxy:/bin:/bin/sh
www-data:x:33:33:www-data:/var/www:/bin/sh
backup:x:34:34:backup:/var/backups:/bin/sh
list:x:38:38:Mailing List Manager:/var/list:/bin/sh
irc:x:39:39:ircd:/var/run/ircd:/bin/sh
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/bin/sh
nobody:x:65534:65534:nobody:/nonexistent:/bin/sh
libuuid:x:100:101::/var/lib/libuuid:/bin/sh
dhcp:x:101:102::/nonexistent:/bin/false
syslog:x:102:103::/home/syslog:/bin/false
klog:x:103:104::/home/klog:/bin/false
hplip:x:104:7:HPLIP system user,,,:/var/run/hplip:/bin/false
avahi-autoipd:x:105:113:Avahi autoip daemon,,,:/var/lib/avahi-autoipd:/bin/false
gdm:x:106:114:Gnome Display Manager:/var/lib/gdm:/bin/false
pulse:x:107:116:PulseAudio daemon,,,:/var/run/pulse:/bin/false
messagebus:x:108:119::/var/run/dbus:/bin/false
avahi:x:109:120:Avahi mDNS daemon,,,:/var/run/avahi-daemon:/bin/false
polkituser:x:110:122:PolicyKit,,,:/var/run/PolicyKit:/bin/false
haldaemon:x:111:123:Hardware abstraction layer,,,:/var/run/hald:/bin/false

bee:x:1000:1000:bee,,,:/home/bee:/bin/bash    → beebox 웹 서버 사용자 계정

mysql:x:112:124:MySQL Server,,,:/var/lib/mysql:/bin/false
sshd:x:113:65534::/var/run/sshd:/usr/sbin/nologin
dovecot:x:114:126:Dovecot mail server,,,:/usr/lib/dovecot:/bin/false
smmta:x:115:127:Mail Transfer Agent,,,:/var/lib/sendmail:/bin/false
smmsp:x:116:128:Mail Submission Program,,,:/var/lib/sendmail:/bin/false
neo:x:1001:1001::/home/neo:/bin/sh
alice:x:1002:1002::/home/alice:/bin/sh
thor:x:1003:1003::/home/thor:/bin/sh
wolverine:x:1004:1004::/home/wolverine:/bin/sh
johnny:x:1005:1005::/home/johnny:/bin/sh
selene:x:1006:1006::/home/selene:/bin/sh
postfix:x:117:129::/var/spool/postfix:/bin/false
proftpd:x:118:65534::/var/run/proftpd:/bin/false
ftp:x:119:65534::/home/ftp:/bin/false
snmp:x:120:65534::/var/lib/snmp:/bin/false
ntp:x:121:131::/home/ntp:/bin/false
* Connection #0 to host bee.box left intact

📌 Python 시큐어코딩 가이드

https://www.kisa.or.kr/2060204/form?postSeq=13&lang_type=KO#fnPostAttachDownload

📌 입력 데이터 검증 및 표현

1. SQL 삽입

데이터베이스(DB)와 연동된 웹 응용 프로그램에서 입력된 데이터에 대한 유효성 검증을 하지 않을 경우 공격자가 입력 폼 및 URL 입력란에 SQL 문을 삽입하여 DB로부터 정보를 열람하거나 조작할 수 있는 보안 약점

2. 코드 삽입

3. 경로 조작 및 자원 삽입

외부 입력값이 자원 식별자로 사용되는 경우

💡 자원이란 파일, 소켓, 포트, 연결, ... 등을 의미

▪ 그 값을 검증·제한하지 않고 사용하지 않으면 권한 밖 자원에 대해 접근할 수 있고 자원을 선점하거나 충돌시켜 정상적인 서비스를 방해

💻 pybo\urls.py

from django.urls import path
from . import views

app_name = 'pybo'

urlpatterns = [
    path('', views.index, name='index'),
    path('<int:question_id>/', views.detail, name="detail"),
    path('answer/create/<int:question_id>', views.answer_create, name='answer_create'),
    path('question/create/', views.question_create, name='question_create'),
    
    path('download/', views.download, name='download'),
    
]

💻 pybo\views.py

from django.shortcuts import render, get_object_or_404, redirect
from .models import Question
from .forms import QuestionForm
from django.utils import timezone
import subprocess

import os 

def index(request):
    question_list = Question.objects.order_by('-create_date')
    
    context = { 'question_list': question_list }
    
    return render(request, 'pybo/question_list.html', context)
    
    
def download(request):
    request_file = request.GET.get('request_file')
    (filename, file_ext) = os.path.splitext(request_file)
    file_ext = file_ext.lower()
    if file_ext not in ['.txt', '.csv']:
        return render(request, 'pybo/error.html', {'error':'파일을 열 수 없습니다.'})
    
    with open(request_file) as f:
        data = f.read()
        return render(request, 'pybo/success.html', {'data':data})

💻 templates\pybo\error.html

{% extends 'base.html' %}

{% block content %}
<div class="container my-3">
    {{ error }}
</div>
{% endblock %}

💻 templates\pybo\success.html

{% extends 'base.html' %}

{% block content %}
<div class="container my-3">
    {{ data }}
</div>
{% endblock %}

💻 테스트 파일 생성

# 웹 페이지에서 접근할 수 있는 파일 
c:\Users\r2com> echo "Hello, Python" > c:\temp\hello.txt		

c:\Users\r2com> type c:\temp\hello.txt
"Hello, Python"

# 웹 페이지에서 접근할 수 없는 파일
c:\Users\r2com> echo Python user information > c:\python\userinfo.txt

c:\Users\r2com> type c:\python\userinfo.txt
Python user information

💻 개발서버 실행

c:\python\projects\mysite> c:\python\mysite\Scripts\activate

(mysite) c:\python\projects\mysite> python manage.py runserver

💻 request_file 요청 파라미터의 값으로 확장자가 txt인 파일을 전달
▪ 확장자만 txt 또는 csv이면 파일의 위치와 관계없이 읽어서 출력 가능

💻 지정된 디렉터리의 파일만 읽을 수 있도록 소스 코드 수정

def download(request):
    request_file = request.GET.get('request_file')
    (filename, file_ext) = os.path.splitext(request_file)
    file_ext = file_ext.lower()
    if file_ext not in ['.txt', '.csv']:
        return render(request, 'pybo/error.html', {'error':'파일을 열 수 없습니다.'})
    
    
    request_file = f"c:\\temp\\{filename}{file_ext}" 
    				 ~~~~~~~~~~
    				 # 지정된 디렉터리
    
    with open(request_file) as f:
        data = f.read()
        return render(request, 'pybo/success.html', {'data':data})

💻 파일 경로를 전달하면 오류가 발생하는 것을 확인

💻 파일명만 전달하는 경우, 소스 코드에 정의된 경로와 결합해서 파일 접근

💻 경로 조작 문자열과 함께 전달하는 경우 지정된 경로를 벗어나 파일에 접근 가능

💻 파일 이름에 경로 조작 문자열을 제거하고 사용하도록 소스 코드 수정

def download(request):
    request_file = request.GET.get('request_file')
    (filename, file_ext) = os.path.splitext(request_file)
    file_ext = file_ext.lower()
    if file_ext not in ['.txt', '.csv']:
        return render(request, 'pybo/error.html', {'error':'파일을 열 수 없습니다.'})
    
    
    filename = filename.replace('.', '')
    filename = filename.replace('/', '')
    filename = filename.replace('\\', '')


    request_file = f"c:\\temp\\{filename}{file_ext}"
    with open(request_file) as f:
        data = f.read()
        return render(request, 'pybo/success.html', {'data':data})

💻 경로 조작 문자열을 포함해서 요청하면 오류 발생
▪ 해당 파일이 지정된 디렉터리에 존재하지 않기 때문

4. 크로스 사이트 스크립트(XSS)

유형 1 : Reflective XSS (or Non-persistent XSS)
유형 2 : Persistent XSS (or Stored XSS)
유형 3 : DOM XSS (or Client-Side XSS)

5. 운영체제 명령어 삽입

💻 pybo\urls.py

from django.urls import path
from . import views

app_name = 'pybo'

urlpatterns = [
    path('', views.index, name='index'),
    path('<int:question_id>/', views.detail, name="detail"),
    path('answer/create/<int:question_id>', views.answer_create, name='answer_create'),
    path('question/create/', views.question_create, name='question_create'),
    path('download/', views.download, name='download'), 
    
    path('execute/app/<str:app_name>', views.execute_app, name='execute_app'), 
    
]

💻 pybo\views.py 파일에 함수를 추가

def execute_app(request, app_name):
    os.system(app_name)
    return render(request, 'pybo/success.html', {'data': f'{app_name}을 실행했습니다.'})

💻 http://localhost:8000/pybo/execute/app/notepad 주소로 요청 시 메모장이 실행되는 것을 확인

💻 http://localhost:8000/pybo/execute/app/어플리케이션이름 형식으로 요청하면 의도(계획)하지 않은 어플리케이션이 실행

💻 웹 페이지에서 실행할 수 있는 어플리케이션을 제한하는 방식으로 프로그램 수정

ALLOW_PROGRAM = ['notepad', 'calc']		# 허용 목록(white list)

def execute_app(request, app_name):


    if app_name in ALLOW_PROGRAM:
    
    
        os.system(app_name)
        return render(request, 'pybo/success.html', {'data': f'{app_name}을 실행했습니다.'})
        
        
    else:
        return render(request, 'pybo/error.html', {'error': f'{app_name}을 실행할 수 없습니다.'})

💻 /pybo/execute/cmd/?cmd=실행할명령어 형식으로 요청했을 때 명령어 실행 결과를 화면에 하도록 코드를 추가해보세요.
▪ 안전하지 않은 코드 참조

💻 pybo\urls.py

urlpatterns = [
    path('', views.index, name='index'),
    path('<int:question_id>/', views.detail, name="detail"),
    path('answer/create/<int:question_id>', views.answer_create, name='answer_create'),
    path('question/create/', views.question_create, name='question_create'),
    path('download/', views.download, name='download'), 
    path('execute/app/<str:app_name>', views.execute_app, name='execute_app'), 
    
    path('execute/cmd/', views.execute_cmd, name='execute_cmd'), 

]

💻pybo\views.py

from django.utils.safestring import mark_safe

def execute_cmd(request):
    cmd = request.GET.get('cmd','')
    result = subprocess.check_output(cmd, shell=True, encoding='MS949')
    data = mark_safe(f'<pre>{cmd}을 실행했습니다.\n {result}</pre>')
    return render(request, 'pybo/success.html', {'data': data})

💻 실행될 수 있는 명령어를 제한하지 않았기 때문에 의도하지 않은 명령어가 실행될 수 있음

실행할 명령어를 허용목록 방식으로 제한

ALLOW_COMMANDS = ['dir', 'whoami']

def execute_cmd(request):
    cmd = request.GET.get('cmd','')
    
    
    if cmd in ALLOW_COMMANDS:
    
    
        result = subprocess.check_output(cmd, shell=True, encoding='MS949')
        data = mark_safe(f'<pre>{cmd}을 실행했습니다.\n {result}</pre>')
        return render(request, 'pybo/success.html', {'data': data})
        
        
    else:
        return render(request, 'pybo/error.html', {'error': f'{cmd}는 실행할 수 없습니다.'})

6. 위험한 형식 파일 업로드

7. 신뢰되지 않은 URL주소로 자동접속 연결

Open Redirection

https://developer.mozilla.org/ko/docs/Web/HTTP/Redirections

리다이렉션 기능이 존재하는 경우
→ 외부 입력값을 검증/제한 없이 리다이렉션의 주소로 사용하는 경우 발생

url_string = request.GET.get('url')   # 요청 파라미터로 전달된 URL 주소
redirect(url_string)			      # 검증/제한 없이 리다이렉션 주소로 사용

요청 ▷ http://우리은행.com/go/?url=main	→ 최초 요청한 주소
                                  ~~~~
                                  요청 파라미터가 리다이렉션 주소로
                                  사용된 것을 알 수 있음
                                  
결과 ▷ http://우리은행.com/main/	 → 리다이렉트 된 주소

리다이렉션 주소를 검증/제한하지 않는 경우

요청 ▷ http://우리은행.com/go/?url=http://우리들은행.com
결과 ▷ http://우리은행들.com   → 공격자가 우리은행과 동일하게 만들어 놓은 사이트

공격자는 아래와 같은 공격 문자열을 만들어서 이메일, SMS, 카톡 등을 통해 불특정 다수에게 전달

<a href="http://우리은행.com/go/?url=http://우리들은행.com"> 꼭 보세요. </a>

📢 주의!
피싱 공격에 악용될 수 있음

💻 리다이렉션(redirection)

💻 포워드(forward)

0개의 댓글