在瀏覽器中快速探測IP端口是否開放

0×00 前言

前兩天 freebuf上的的XSS到內網的公開課很受啓發,從一個頁面到局域網,威力着實加強很多javascript

公開課上檢測內網 IP 實現方式用的是 img 標籤,加載網站的 favicon.ico 圖標,而後監聽 onload 事件,看圖片能不能加載成功簡單易用,就是太慢了 --html

0×02 分析

瀏覽器有幾秒的嘗試容錯時間,對於媒體資源,瀏覽器限制同一域名併發請求爲個位數(通常爲 6 個),也就是說,把圖片全放到頁面上,也不能同時開始檢測,只能一點一點來,掃端口的時候會很是慢。前端

管理員在內網搭建的東西也可能沒有圖標。。。java

探測 IP 端口開放原理就是向目標發送請求,看是否有迴應。在上面的 img 中是在加載成功和失敗體現出來的。img 嫌慢也能夠試試別的嘛,通過一番搜索找到了這個這個這個git

第一個頁面:

document.getElementById("testdiv").innerHTML = '<img src="http://' + ip + ':' + port + '" alt="" onerror="error_handler(' + our_scanobj_index + ');" />';

是經過加載單個 img,使用的 onerror 事件,10 秒之內觸發判斷爲 open。html 頁面不能解析爲圖片格式,因此也會觸發 onerror。github

第二個頁面:

switch(port){
     case 21:  src = 'ftp://' + this.id() + '@' + host + '/'; break;//ftp
     case 25:  src = 'mailto://' + this.getid() + '@' + host ; break;//smtp **
     case 70:  src = 'gopher://' + host + '/'; break;//gopher
     case 119: src = 'news://' + host + '/'; break;//nntp **
     case 443: src = 'https://' + host + '/' + this.getid() + '.jpg';
     default:  src = 'http://' + host + ':' + port + '/' + this.getid() + '.jpg';// getid is here to prevent cache seekings;
  }
  // ports 19,70,110,143 always return up in IE
  // ** if outlook is the default mail client and default newsreader in  IE the request does not return anything
  img.src = src;
  setTimeout(function () {
     if (!img) return;
     img = undefined;
     callback( host, port, 'down',id);
  }, timeout);

功能比第一個多很多,嘗試多種協議,80 端口能夠探測出來圖片描述77,79 被瀏覽器屏蔽,立刻觸發事件,因此誤報了。web

第三個頁面:

用 websocket 準確率很好,特殊端口作過處理沒有誤報,做者還有一篇筆記,就是 js 和 html 耦合嚴重,很差提取出來用,趁着放假乾脆造了個新輪子。ajax

0×03 構架

最終選用的 WebSocket,聽着就高大上,非 http 的端口也能探測,文檔看的 ruanyifengMDN 查到 websocket 併發鏈接 挺高的 (255 in Chrome and 200 in Firefox),加入了一個隊列模塊shell

js 以下:
js.later.js 簡單的包裝了 setInterval,超時檢測全靠它
js.queue.js 隊列和併發控制
js.portscan.js端口掃描的邏輯都在這
前端界面用的是 semantic-ui 和 Vuesegmentfault

介紹
js.later.js 做用比較簡單,在這裏就是一個 setTimeout 的用法,就很少寫了。

js.queue.js 結合 websocket 的高併發,很給力,能夠動態調整併發數量,鼠標鍵盤沒反應人離開後多跑點任務加快效率

使用示例(能夠在演示頁面測試)

var q = new Queue(function(task, next, timer) {
    // 跳過部分任務,不執行next,模擬超時
    if (task % 5 === 0) return;
    //模擬延遲異步任務
    $.get('https://httpbin.org/delay/1', function() {
        console.log(task);
        //此任務完成,繼續下一個
        next();
    });
}, 3); //3個併發請求
for (var i = 0; i < 15; i++) {
    q.push(i);
}
q.timeout = 10000;
q.onTimeout = function(task) {
    console.warn('queue:timeout:' + task);
}
q.onfinish = function() {
    console.info('隊列執行完畢');
}
q.start();

js.portscan.js 有三種掃描方式:
scan_single 掃描單個目標
scan_batch 掃描一個數組
scan_range 生成列表並掃描以上三個接口均可以直接使用 scan,根據傳入參數不一樣自動選擇對應的方法去執行。

var ps = new PortScan();
ps.onscan = function(flag, task){
   alert(task + '掃描完成,狀態爲:' + flag)
}
ps.onopen = function(task){
   prompt('開放端口', task)
}
ps.onfinsh = function(){
   alert('scan完成')
}

// 分別執行如下三個方法
// 探測單個目標
ps.scan('baidu.com');

// 批量探測
ps.scan(['baidu.com:22', 'baidu.com:443', 'baidu.com:1024'])

// 生成一段地址並探測
ps.scan('baidu.com:*', 75, 85)

使用 WebSocket 發送請求的核心方法:

// 使用websocket探測端口
PortScan.prototype.wscan = function (target, callback) {
    var _this = this;
    var ws = new WebSocket(this.wsprotocol + target);
    ws.onerror = ws.onopen = function (e) {
        stopTimer();
        _this.portstate('open', target, callback);
    }
    var workerkiller = function (flag) {
        stopTimer(flag);
        ws.onerror = null;
        ws.close();
        // 若是是隊列控制超時此處就再也不執行next
        callback = flag === 'worker_timeout' ? null : callback;
        _this.portstate(flag, target, callback);
    }
    var stopTimer = this.timeoutexit(workerkiller);
    return workerkiller;
}

wscan接收兩個參數,target 是目標地址,callback 是回調方法,請求結束後會把掃描結果傳入此方法。新建 WebSocket 請求後,在 ws 對象上設置了 ws.onerror 和 ws.onopen 事件。

想要成功創建鏈接須要服務器端先回應HTTP/1.1 101 Switching Protocols,若是被掃描的端口開放而且返回了數據,數據格式和 WebSocket 不一致會觸發 onerror 事件,成功創建鏈接後則觸發 onopen。

workerkiller 方法用來在超時後中止當前這個請求繼續等待端口響應,首先清空 onerror 事件,而後執行 ws.close() 關閉鏈接。

var stopTimer = this.timeoutexit(workerkiller);timeoutexit 調用的是 js.later.js 裏的方法。根據設置的超時時間(默認設置的 5 秒),啓動 workerkiller 中止此請求。端口斷定爲不通。

_this.portstate('open', target, callback);portstate 是 PortScan 對象提供的一個方法,請求結束傳入open或是timeout,在 portstate 內部會觸發掃描事件。在 ws 的 onerror 觸發能觸發,說明服務端有迴應數據,狀態是open,ps.onopen 就會被調用,上面例子中 ps.onopen 彈出的 prompt 窗口顯示掃到的開放端口就是由 portstate 去執行的。

// 掃描批量目標
PortScan.prototype.scan_batch = function (tasks, onfinish, onscan, isonopen_onopen) {
    this.setEvents(onfinish, onscan, isonopen_onopen);
    var _this = this;
    var q = this.queue = new Queue(this.scan_single, this.portscan_concurrence);
    q.tasks = tasks;
    q.onfinish = function () {
        _this.onfinish && _this.onfinish(_this.opentarget);
    }
    q.start();
    return q;
}

掃描批量目標使用 js.queue.js 的併發隊列功能,去執行的 scan_single 執行單個任務,scan_single 在掃描前作了一些額外的工做:把瀏覽器屏蔽的端口過濾掉了,ps.onscan 收到的狀態就是blocked

scan_range 遍歷指定的範圍,host.replace('*', i),生成目標地址,最後調用 scan_batch。

0×04 測試

上面搞那麼複雜就是爲了 PortScan 的代碼用起來簡單靈活。

var ps = new PortScan();
ps.onfinsh = function(opentarget){
    alert(opentarget);
   // opentarget是全部探測到端口開放的IP地址,花費時間大約10秒多
   // 探測到目標後可接Black-Hole思路,自動化檢測cms,根據相關漏洞getshell繼而漫遊內網
}
// webrtc得到內網網段參見 http://zone.wooyun.org/content/24219
ps.scan('192.168.0.*', 1, 254); // 加端口號也能夠 '192.168.0.*:8080'

http://js-port-scan.sec.dog/

-

(上個月寫的,有些地方描述不夠詳細準確,有空了再更新,那三個js,在demo頁面上 - 2016)
(demo連接已更新,代碼略有改動,修復了ie下運行的bug,增長了ws,ajax,video,image等請求方式,ps.use('websocket') - 2017-02-10 )

相關文章
相關標籤/搜索