国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

Web 安全漏洞 SSRF 簡介及解決方案

xiaodao / 759人閱讀

摘要:本人非安全專業相關人士,了解不多,實在慚愧。工作原因,在所負責的內部服務中遭遇了的困擾,在此記錄一下學習過程及解決方案。,即服務端請求偽造,是一種由攻擊者構造形成由服務端發起請求的一個安全漏洞。通用的解決方案有過濾返回信息。

Update: 掘金評論區有同學提出通過域名獲取 IP 地址時可能遭遇攻擊,感謝提醒。本人非安全專業相關人士,了解不多,實在慚愧。


說到 Web 安全,我們前端可能接觸較多的是 XSS 和 CSRF。工作原因,在所負責的內部服務中遭遇了SSRF 的困擾,在此記錄一下學習過程及解決方案。SSRF(Server-Side Request Forgery),即服務端請求偽造,是一種由攻擊者構造形成由服務端發起請求的一個安全漏洞。一般情況下,SSRF 攻擊的目標是從外網無法訪問的內部系統。

SSRF 形成的原因大都是由于服務端提供了從其他服務器應用獲取數據的功能且沒有對目標地址做過濾與限制。比如從指定 URL 地址獲取網頁文本內容,加載指定地址的圖片,下載等等。攻擊者可根據程序流程,使用應用所在服務器發出攻擊者想發出的 http 請求,利用該漏洞來探測生產網中的服務,可以將攻擊者直接代理進內網中,可以讓攻擊者繞過網絡訪問控制,可以下載未授權的文件,可以直接訪問內網,甚至能夠獲取服務器憑證。

筆者負責的內部 web 應用中有一個下載文件的接口 /download,其接受一個 url 參數,指向需要下載的文件地址,應用向該地址發起請求,下載文件至應用所在服務器,然后作后續處理。問題便來了,應用所在服務器在這里成了跳板機,攻擊者利用這個接口相當于取得了內網權限,能夠進行不少具有危害的操作。

SSRF 帶來的危害有:

可以對外網、服務器所在內網、本地進行端口掃描,獲取一些服務的 banner 信息;

攻擊運行在內網或本地的應用程序(比如溢出);

對內網 web 應用進行指紋識別,通過訪問默認文件實現;

攻擊內外網的 web 應用,主要是使用 get 參數就可以實現的攻擊(比如 struts2,sqli 等);

利用 file 協議讀取本地文件等。

通用的解決方案有:

過濾返回信息。驗證遠程服務器對請求的響應是比較容易的方法。如果 web 應用是去獲取某一種類型的文件,那么在把返回結果展示給用戶之前先驗證返回的信息是否符合標準;

統一錯誤信息,避免用戶可以根據錯誤信息來判斷遠端服務器的端口狀態;

限制請求的端口為 http 常用的端口,比如 80, 443, 8080, 8090;

白名單內網 ip。避免應用被用來獲取獲取內網數據,攻擊內網;

禁用不需要的協議。僅僅允許 http 和 https 請求。可以防止類似于file:///,gopher://,ftp:// 等引起的問題。

由于筆者的應用 /download 接口請求的文件地址比較固定,因此采用了白名單 IP 的方式。當然,筆者也學習了一下更加全面的解決方案,下面給出安全部門同事的思路:

協議限制(默認允許協議為 HTTP、HTTPS)、30x跳轉(默認不允許 30x 跳轉)、統一錯誤信息(默認不統一,統一錯誤信息避免惡意攻擊通過錯誤信息判斷)

IP地址判斷:

禁止訪問 0.0.0.0/8,169.254.0.0/16,127.0.0.0/8 和 240.0.0.0/4 等保留網段

若 IP 為 10.0.0.0/8,172.16.0.0/12,192.168.0.0/16 私有網段,請求該 IP 地址并判斷響應 contents-type 是否為 application/json

解決 URL 獲取器和 URL 解析器不一致的方法為:解析 URL 后去除 RFC3986 中 user、pass 并重新組合 URL

然后是按照以上思路實現的 Node.js 版本的處理 SSRF 漏洞的主要函數的代碼:

const dns = require("dns")
const parse = require("url-parse")
const ip = require("ip")
const isReservedIp = require("martian-cidr").default

const protocolAndDomainRE = /^(?:https?:)?//(S+)$/

const localhostDomainRE = /^localhost[:?d]*(?:[^:?d]S*)?$/
const nonLocalhostDomainRE = /^[^s.]+.S{2,}$/

/**
 * 檢查鏈接是否合法
 * 僅支持 http/https 協議
 * @param {string} string
 * @returns {boolean}
 */
function isValidLink (string) {
    if (typeof string !== "string") {
        return false
    }

    var match = string.match(protocolAndDomainRE)
    if (!match) {
        return false
    }

    var everythingAfterProtocol = match[1]
    if (!everythingAfterProtocol) {
        return false
    }

    if (localhostDomainRE.test(everythingAfterProtocol) ||
        nonLocalhostDomainRE.test(everythingAfterProtocol)) {
        return true
    }

    return false
}

/**
 * @param {string} uri
 * @return string
 * host 解析為 ip 地址
 * 處理 SSRF 繞過:URL 解析器和 URL 獲取器之間的不一致性
 *
 */
async function filterIp(uri) {
    try {
        if (isValidLink(uri)) {
            const renwerurl = renewUrl(uri)
            const parseurl = parse(renwerurl)
            const host = await getHostByName(parseurl.host)
            const validataResult = isValidataIp(host)

            if(!validataResult) {
                return false
            } else {
                return renwerurl
            }
        } else {
            return false
        }
    } catch (e) {
        console.log(e)
    }
}

/**
 * 根據域名獲取 IP 地址
 * @param {string} domain
 */
function getHostByName (domain) {
    return new Promise((resolve, reject) => {
        dns.lookup(domain, (err, address, family) => {
            if(err) {
                reject(err)
            }
            resolve(address)
        })
    })
}

/**
 * @param {string} host
 * @return {array} 包含 host、狀態碼
 *
 * 驗證 host ip 是否合法
 * 返回值 array(host, value)
 * 禁止訪問 0.0.0.0/8,169.254.0.0/16,127.0.0.0/8,240.0.0.0/4 保留網段
 * 若訪問 10.0.0.0/8,172.16.0.0/12,192,168.0.0/16 私有網段,標記為 PrivIp 并返回
 */

function isValidataIp (host) {
    if ((ip.isV4Format(host) || ip.isV6Format(host)) && !isReservedIp(host)) {
        if (ip.isPrivate(host)) {
            return [host, "PrivIp"]
        } else {
            return [host, "WebIp"]
        }
    } else {
        return false
    }
}

/**
 * @param {string} uri
 * @return {string} validateuri
 * 解析并重新組合 url,其中禁止"user" "pass"組合
 */

function renewUrl(uri) {
    const uriObj = parse(uri)
    let validateuri = `${uriObj.protocol}//${uriObj.host}`
    if (uriObj.port) {
        validateuri += `:${uriObj.port}`
    }
    if (uriObj.pathname) {
        validateuri += `${uriObj.pathname}`
    }
    if (uriObj.query) {
        validateuri += `?${uriObj.query}`
    }
    if (uriObj.hash) {
        validateuri += `#${uriObj.hash}`
    }
    return validateuri
}

對于最主要的可能出現漏洞的接口處理函數,由于各邏輯不同,這里就不給出具體實現。但是只要按照上面提出的規避 SSRF 漏洞的原則,結合上述幾個函數,就能大致完成。

最后,一句話總結:永遠不要相信用戶的輸入!

本文首發于我的博客(點此查看),歡迎關注

文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。

轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/11437.html

相關文章

發表評論

0條評論

xiaodao

|高級講師

TA的文章

閱讀更多
最新活動
閱讀需要支付1元查看
<