摘要:本人非安全專業(yè)相關(guān)人士,了解不多,實(shí)在慚愧。工作原因,在所負(fù)責(zé)的內(nèi)部服務(wù)中遭遇了的困擾,在此記錄一下學(xué)習(xí)過(guò)程及解決方案。,即服務(wù)端請(qǐng)求偽造,是一種由攻擊者構(gòu)造形成由服務(wù)端發(fā)起請(qǐng)求的一個(gè)安全漏洞。通用的解決方案有過(guò)濾返回信息。
Update: 掘金評(píng)論區(qū)有同學(xué)提出通過(guò)域名獲取 IP 地址時(shí)可能遭遇攻擊,感謝提醒。本人非安全專業(yè)相關(guān)人士,了解不多,實(shí)在慚愧。
說(shuō)到 Web 安全,我們前端可能接觸較多的是 XSS 和 CSRF。工作原因,在所負(fù)責(zé)的內(nèi)部服務(wù)中遭遇了SSRF 的困擾,在此記錄一下學(xué)習(xí)過(guò)程及解決方案。SSRF(Server-Side Request Forgery),即服務(wù)端請(qǐng)求偽造,是一種由攻擊者構(gòu)造形成由服務(wù)端發(fā)起請(qǐng)求的一個(gè)安全漏洞。一般情況下,SSRF 攻擊的目標(biāo)是從外網(wǎng)無(wú)法訪問(wèn)的內(nèi)部系統(tǒng)。
SSRF 形成的原因大都是由于服務(wù)端提供了從其他服務(wù)器應(yīng)用獲取數(shù)據(jù)的功能且沒(méi)有對(duì)目標(biāo)地址做過(guò)濾與限制。比如從指定 URL 地址獲取網(wǎng)頁(yè)文本內(nèi)容,加載指定地址的圖片,下載等等。攻擊者可根據(jù)程序流程,使用應(yīng)用所在服務(wù)器發(fā)出攻擊者想發(fā)出的 http 請(qǐng)求,利用該漏洞來(lái)探測(cè)生產(chǎn)網(wǎng)中的服務(wù),可以將攻擊者直接代理進(jìn)內(nèi)網(wǎng)中,可以讓攻擊者繞過(guò)網(wǎng)絡(luò)訪問(wèn)控制,可以下載未授權(quán)的文件,可以直接訪問(wèn)內(nèi)網(wǎng),甚至能夠獲取服務(wù)器憑證。
筆者負(fù)責(zé)的內(nèi)部 web 應(yīng)用中有一個(gè)下載文件的接口 /download,其接受一個(gè) url 參數(shù),指向需要下載的文件地址,應(yīng)用向該地址發(fā)起請(qǐng)求,下載文件至應(yīng)用所在服務(wù)器,然后作后續(xù)處理。問(wèn)題便來(lái)了,應(yīng)用所在服務(wù)器在這里成了跳板機(jī),攻擊者利用這個(gè)接口相當(dāng)于取得了內(nèi)網(wǎng)權(quán)限,能夠進(jìn)行不少具有危害的操作。
SSRF 帶來(lái)的危害有:
可以對(duì)外網(wǎng)、服務(wù)器所在內(nèi)網(wǎng)、本地進(jìn)行端口掃描,獲取一些服務(wù)的 banner 信息;
攻擊運(yùn)行在內(nèi)網(wǎng)或本地的應(yīng)用程序(比如溢出);
對(duì)內(nèi)網(wǎng) web 應(yīng)用進(jìn)行指紋識(shí)別,通過(guò)訪問(wèn)默認(rèn)文件實(shí)現(xiàn);
攻擊內(nèi)外網(wǎng)的 web 應(yīng)用,主要是使用 get 參數(shù)就可以實(shí)現(xiàn)的攻擊(比如 struts2,sqli 等);
利用 file 協(xié)議讀取本地文件等。
通用的解決方案有:
過(guò)濾返回信息。驗(yàn)證遠(yuǎn)程服務(wù)器對(duì)請(qǐng)求的響應(yīng)是比較容易的方法。如果 web 應(yīng)用是去獲取某一種類(lèi)型的文件,那么在把返回結(jié)果展示給用戶之前先驗(yàn)證返回的信息是否符合標(biāo)準(zhǔn);
統(tǒng)一錯(cuò)誤信息,避免用戶可以根據(jù)錯(cuò)誤信息來(lái)判斷遠(yuǎn)端服務(wù)器的端口狀態(tài);
限制請(qǐng)求的端口為 http 常用的端口,比如 80, 443, 8080, 8090;
白名單內(nèi)網(wǎng) ip。避免應(yīng)用被用來(lái)獲取獲取內(nèi)網(wǎng)數(shù)據(jù),攻擊內(nèi)網(wǎng);
禁用不需要的協(xié)議。僅僅允許 http 和 https 請(qǐng)求。可以防止類(lèi)似于file:///,gopher://,ftp:// 等引起的問(wèn)題。
由于筆者的應(yīng)用 /download 接口請(qǐng)求的文件地址比較固定,因此采用了白名單 IP 的方式。當(dāng)然,筆者也學(xué)習(xí)了一下更加全面的解決方案,下面給出安全部門(mén)同事的思路:
協(xié)議限制(默認(rèn)允許協(xié)議為 HTTP、HTTPS)、30x跳轉(zhuǎn)(默認(rèn)不允許 30x 跳轉(zhuǎn))、統(tǒng)一錯(cuò)誤信息(默認(rèn)不統(tǒng)一,統(tǒng)一錯(cuò)誤信息避免惡意攻擊通過(guò)錯(cuò)誤信息判斷)
IP地址判斷:
禁止訪問(wèn) 0.0.0.0/8,169.254.0.0/16,127.0.0.0/8 和 240.0.0.0/4 等保留網(wǎng)段
若 IP 為 10.0.0.0/8,172.16.0.0/12,192.168.0.0/16 私有網(wǎng)段,請(qǐng)求該 IP 地址并判斷響應(yīng) contents-type 是否為 application/json
解決 URL 獲取器和 URL 解析器不一致的方法為:解析 URL 后去除 RFC3986 中 user、pass 并重新組合 URL
然后是按照以上思路實(shí)現(xiàn)的 Node.js 版本的處理 SSRF 漏洞的主要函數(shù)的代碼:
</>復(fù)制代碼
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 協(xié)議
* @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 繞過(guò):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)
}
}
/**
* 根據(jù)域名獲取 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、狀態(tài)碼
*
* 驗(yàn)證 host ip 是否合法
* 返回值 array(host, value)
* 禁止訪問(wèn) 0.0.0.0/8,169.254.0.0/16,127.0.0.0/8,240.0.0.0/4 保留網(wǎng)段
* 若訪問(wèn) 10.0.0.0/8,172.16.0.0/12,192,168.0.0/16 私有網(wǎng)段,標(biāo)記為 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
}
對(duì)于最主要的可能出現(xiàn)漏洞的接口處理函數(shù),由于各邏輯不同,這里就不給出具體實(shí)現(xiàn)。但是只要按照上面提出的規(guī)避 SSRF 漏洞的原則,結(jié)合上述幾個(gè)函數(shù),就能大致完成。
最后,一句話總結(jié):永遠(yuǎn)不要相信用戶的輸入!
本文首發(fā)于我的博客(點(diǎn)此查看),歡迎關(guān)注
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.specialneedsforspecialkids.com/yun/11437.html
閱讀 3107·2021-08-03 14:05
閱讀 2152·2019-08-29 15:35
閱讀 689·2019-08-29 13:30
閱讀 3177·2019-08-29 13:20
閱讀 2542·2019-08-23 18:15
閱讀 1807·2019-08-23 14:57
閱讀 2224·2019-08-23 13:57
閱讀 1321·2019-08-23 12:10