Web安全漏洞之SSRF

什麼是 SSRF

你們使用的服務中或多或少是否是都有如下的功能:html

  • 經過 URL 地址分享內容
  • 經過 URL 地址把原地址的網頁內容調優使其適合手機屏幕瀏覽,即所謂的轉碼功能
  • 經過 URL 地址翻譯對應文本的內容,即相似 Google 的翻譯網頁功能
  • 經過 URL 地址加載或下載圖片,即相似圖片抓取功能
  • 以及圖片、文章抓取收藏功能

簡單的來講就是經過 URL 抓取其它服務器上數據而後作對應的操做的功能。以 ThinkJS 代碼爲例,咱們的實現方法大概以下:前端

const request = require('request-promise-native');
module.exports = class extends think.Controller {
  async indexAction() {
    const { url } = this.get();
    const ret = await request.get(url);
    // 這裏是處理抓取數據的邏輯
    // ...
    this.ctx.body = ret;
  }
}
複製代碼

原本是個不錯的功能,可是當用戶輸入一個服務器可訪問的內網地址,這個狀況下它就會把內網的內容抓取出來展示給外網的用戶。大多數公司會在內網中放置一些與公司相關的資料和關鍵數據,若是應用程序對用戶提供的URL和遠端服務器返回的信息沒有進行合適的驗證和過濾,就可能存在這種服務端請求僞造的缺陷,即 Server-Side Request Forgery,簡稱 SSRF。git

SSRF 的危害

簡單來講若是你的這個功能存在 SSRF 漏洞的話,至關於在攻擊者和內網之間牽了根線,透過該通能攻擊者能夠間接訪問到內網。攻擊者能夠利用 SSRF 實現的攻擊主要有 5 種:github

  1. 能夠對外網、服務器所在內網、本地進行端口掃描,獲取一些服務的 Banner 信息
  2. 攻擊運行在內網或本地的應用程序(好比溢出)
  3. 對內網 Web 應用進行指紋識別,經過訪問默認文件實現
  4. 攻擊內外網的 Web 應用,主要是使用 GET 參數就能夠實現的攻擊
  5. 利用 file 協議讀取本地文件

其中最後一條的實現方式是用戶輸入 file:// 本地文件協議地址,若是不作判斷,程序極可能就會把本地文件讀取出來返回給用戶,例如 file:///etc/password 服務器系統密碼。web

防護方法

首先咱們須要禁用掉不須要的協議,僅容許 HTTP(s) 請求,防止最後一條使用 file:// 等其它協議引發的問題,而後咱們須要對輸出內容進行判斷,例如我應該輸出一張圖片,若是抓取返回回來的是一段文本咱們就不該該返回。以及若是抓取遠端地址致使報錯返回的狀況,咱們須要統一處理返回給用戶的內容,而不是直接將遠端服務器的內容返回給用戶,這樣讓攻擊者瞭解到了更多遠端服務器的信息。數據庫

除了輸出內容的處理,咱們還要對輸入地址進行限制,過濾內網 IP,限制訪問內網行爲。以以前的示例代碼爲例,正常咱們會增長以下處理:promise

const ip = require('ip');
const dns = require('dns');
const { parse } = require('url');

const lookupAsync = think.promisify(dns.lookup, dns);
module.exports = class extends think.Logic {
  async indexAction() {
    const { url } = this.get();
    const { protocol, hostname } = parse(url);
    // 判斷協議
    if( !/https?:/i.test(protocol) ) {
        return this.fail();
    }
    // 判斷內網IP
    cost host = await lookupAsync(hostname);
    if( ip.isPrivate(host) ) {
      return this.fail();
    }
  }
}
複製代碼

短連接繞過

大部分狀況下這樣處理是沒有問題的,不過攻擊者可不是通常人。這裏存在一個兩個能夠繞過的方式,首先是短連接,短連接是先到短連接服務的地址以後再302跳轉到真實服務器上,若是攻擊者對內網地址進行短鏈處理以後以上代碼會判斷短鏈服務的 IP 爲合法 IP 而經過校驗。瀏覽器

針對這種繞過方式,咱們有兩種方法來阻止:緩存

  1. 直接根據請求返回的響應頭中的 HOST 來作內網 IP 判斷
  2. 因爲跳轉後的地址也仍是須要 DNS 解析的,因此只要在每次域名請求 DNS 解析處都作內網 IP 判斷的邏輯便可

DNS 從新綁定繞過

另一種繞過方式是利用 DNS 重綁定攻擊。安全

DNS如何從新綁定的工做

攻擊者註冊一個域名(如attacker.com),並在攻擊者控制下將其代理給DNS服務器。 服務器配置爲很短響應時間的TTL記錄,防止響應被緩存。 當受害者瀏覽到惡意域時,攻擊者的DNS服務器首先用託管惡意客戶端代碼的服務器的IP地址做出響應。 例如,他們能夠將受害者的瀏覽器指向包含旨在在受害者計算機上執行的惡意JavaScript或Flash腳本的網站。

惡意客戶端代碼會對原始域名(例如attacker.com)進行額外訪問。 這些都是由同源政策所容許的。 可是,當受害者的瀏覽器運行該腳本時,它會爲該域建立一個新的DNS請求,而且攻擊者會使用新的IP地址進行回覆。 例如,他們可使用內部IP地址或互聯網上某個目標的IP地址進行回覆。

via: 《DNS 從新綁定攻擊》

簡單來講就是利用 DNS 服務器來使得每次解析返回不一樣的 IP,當在校驗 IP 的時候 DNS 解析返回合法的值,等後續從新請求內容的時候 DNS 解析返回內網 IP。這種利用了屢次 DNS 解析的攻擊方式就是 DNS 從新綁定攻擊。

因爲 DNS 從新綁定攻擊是利用了屢次解析,因此咱們最好將校驗和抓取兩次 DNS 解析合併成一次,這裏咱們也有兩種方法來阻止:

  1. 將第一次 DNS 解析獲得的 IP 直接用於第二次請求的 DNS 解析,去除第二次解析的問題
  2. 在抓取請求發起的時候直接判斷解析的 IP,若是不符合的話直接拒絕鏈接

針對以上解決方法,有開發者直接封裝了 ssrf-agent 模塊,使用的時候只要將其傳入便可實現一次解析,屢次判斷的功能,下面是簡單的使用示例:

const ssrfAgent = require('ssrf-agent');
const request = require('request-promise-native');

module.exports = class extends think.Controller {
  async indexAction() {
    const { url } = this.get();
    try {
      const ret = await request(url, {agent: ssrfAgent(url)});
      this.ctx.body = ret;
    } catch(e) {
      return this.fail();
    }
  }
}
複製代碼

後記

SSRF 能夠說是經久不衰的漏洞攻擊了,早些年百度、人人、360搜索等都有過相應的案例。通常如下場景會可能存在 SSRF 問題,咱們須要多加註意:

  1. 可以對外發起網絡請求的地方,就可能存在 SSRF 漏洞
  2. 從遠程服務器請求資源(Upload from URL,Import & Export RSS Feed)
  3. 數據庫內置功能(Oracle、MongoDB、MSSQL、Postgres、CouchDB)
  4. Webmail 收取其餘郵箱郵件(POP三、IMAP、SMTP)
  5. 文件處理、編碼處理、屬性信息處理(ffmpeg、ImageMagic、DOCX、PDF、XML)

參考資料:

相關文章
相關標籤/搜索