使用DNS名稱作為安全性依據的漏洞優化
- 2019 年 10 月 5 日
- 筆記
版權聲明:本文為部落客原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接和本聲明。
本文鏈接:https://blog.csdn.net/weixin_38004638/article/details/100605199
漏洞
問題描述:程式中採用DNS名稱進行安全認證,但DNS名稱是容易被攻擊者進行欺騙的。
許多 DNS 伺服器都很容易被攻擊者欺騙,所以應考慮到某天軟體有可能會在有問題的 DNS 伺服器環境下運行。如果允許攻擊者進行 DNS 更新(有時稱為 DNS 快取中毒),則他們會通過自己的機器路由您的網路流量,或者讓他們的 IP 地址看上去就在您的域中。勿將系統安全寄托在 DNS 名稱上。
例如:下面程式碼片段中,如果發生DNS欺騙,會繞過安全驗證。
String ip = request.getRemoteAddr();
InetAddress inetAddress = InetAddress.getByName(ip);
if (inetAddress.getCanonicalHostName().endsWith("demo.com")) {
//Verification passed
}
修復建議: 不要依賴DNS名稱進行安全認證。
IP 地址相比 DNS 名稱而言更為可靠,但也還是可以被欺騙的。攻擊者可以輕易修改要發送的數據包的源 IP 地址,但是響應數據包會返回到修改後的 IP 地址。為了看到響應的數據包,攻擊者需要在受害者機器與修改的 IP 地址之間截取網路數據流。為實現這個目的,攻擊者通常會嘗試把自己的機器和受害者的機器部署在同一子網內。攻擊者可能會巧妙地採取源地址路由的方法來迴避這一要求,但是在今天的互聯網上通常會禁止源地址路由。總而言之,核實 IP 地址是一種有用的 authentication 方式,但不應僅使用這一種方法進行 authentication。
解決方案:如果通過域名檢查的方式可以確保主機接受和發送的 DNS 記錄的一致性,您可以更加信任這一方式。攻擊者如若不能控制目標域的域名伺服器,就無法同時欺騙接受和發送的 DNS 記錄。雖然這種方法並不簡單,但是:攻擊者也許可以說服域註冊者把域移交給一個惡意的域名伺服器。依賴於 DNS 記錄的 authentication 是有風險的。 雖然沒有十分簡單的 authentication 機制,但是還有比基於主機的 authentication 更好的方法。密碼系統提供了比較不錯的安全性,但是這種安全性卻易受密碼選擇不當、不安全的密碼傳送和 password management 失誤的影響。類似於 SSL 的方法值得考慮,但是通常這樣的方法過於複雜,以至於使用時會有運行出錯的風險,而關鍵資源也隨時面臨著被竊取的危險。在大多數情況下,包括一個物理標記的多重 authentication 可以在合理的代價範圍內提供最大程度的安全保障。 Tips: 1. 檢查 DNS 資訊的使用情況。除了考慮程式設計師的 authentication 機制能否起作用以外,還應該考慮在社會工程攻擊中是如何利用 DNS 欺騙的。例如,如果攻擊者可以使自己發出的數據包看上去像是來自內部機器的,他們是否可以通過驗證程式獲得信任呢?
修復方案1:添加ip校驗
ip = ipMatch(IpUtil.getIpAddress().getHostAddress());
private static String ipMatch(String ip){ if (Pattern.matches("[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(/.[a-zA-Z0-9][-a-zA-Z0-9]{0,62})+/.?", ip)) { return ip; } return "0"; }
對於IP
可以簡單的用JDK提供的方法進行校驗: try { InetAddress.getByName(ip); } catch (UnknownHostException uhe) { throw new Exception("Ip address " +ip + " is invalid!"); }
getByName方法原本是傳入host name,解析成IP返回,但是也支援傳入IP,返回IP,同時對該IP做了校驗。
再對IP進行正則表達式判斷:
一個IP字串,由四段組成,每一段是0~255的數字,段與段之間用小數點隔開,比如61.139.2.69就是一個合法的IP字串。
如果正則表達式寫成/d{1,3}(/./d{1,3}){3}無疑是不負責的,因為它可以匹配300.400.555.666這樣的非法IP字串。
要匹配一個0~255之間的數,有幾種匹配方式,下面是其中一種:
匹配 正則表達式 說明 0~9 /d 單個數字 10~99 [1-9]/d 兩位數 100~199 1/d/d 百位為1的三位數 200~249 2[0-4]/d 三位數,百位是2,十位是0~9 250~255 25[0-5] 三位數,百位是2,十位是5,個位是0~5 寫成正則表達式,即:(/d|([1-9]/d)|(1/d/d)|(2[0-4]/d)|(25[0-5])),但是這樣的正則表達式在匹配254這樣的字串時,會分別匹配2、5、4,得到3個匹配,達不到預期效果,正確做法是將次序顛倒為((25[0-5])|(2[0-4]/d)|(1/d/d)|([1-9]/d)|/d),因為在(xxx|yyy)這種匹配行為中,是從左向右搜索的。
完整的正則表達式是:
((25[0-5])|(2[0-4]/d)|(1/d/d)|([1-9]/d)|/d)(/.((25[0-5])|(2[0-4]/d)|(1/d/d)|([1-9]/d)|/d)){3} 按:
象061這樣的高位為0的數是不能被匹配的。 太麻煩,不如自己寫一小段程式碼解析來得容易,呵呵 一個完整的域名,由根域、頂級域、二級、三級……域名構成,每級域名之間用點分開,每級域名由字母、數字和減號構成(第一個字母不能是減號),不區分大小寫,長度不超過63。
很顯然,單獨的名字可以由正則表達式[a-zA-Z0-9][-a-zA-Z0-9]{0,62}來匹配,而完整的域名至少包括兩個名字(比如google.com,由google和com構成),最後可以有一個表示根域的點(在規範中,最後有一個點的才是完整域名,但一般認為包括兩個以上名字的域名也是完整域名,哪怕它後面沒有點)。
匹配完整域名的正則表達式:
[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(/.[a-zA-Z0-9][-a-zA-Z0-9]{0,62})+/.?
對於HOSTNAME
用正則表達式: 這裡順便把校驗IPV4和V6的REG也寫出來: public static String REG_IPV4 = "\b((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\.|$)){4}\b"; public static String REG_IPV6 = "^(((?=(?>.*?::)(?!.*::)))(::)?([0-9A-F]{1,4}::?){0,5}|([0-9A-F]{1,4}:){6})(\2([0-9A-F]{1,4}(::?|$)){0,2}|((25[0-5]|(2[0-4]|1\d|[1-9])?\d)(\.|$)){4}|[0-9A-F]{1,4}:[0-9A-F]{1,4})(?<![^:]:|\.)\z";
public static String VALID_HOST_REGEX = "^(?=.{1,255}$)[0-9A-Za-z](?:(?:[0-9A-Za-z]|\b-){0,61}[0-9A-Za-z])?(?:\.[0-9A-Za-z](?:(?:[0-9A-Za-z]|\b-){0,61}[0-9A-Za-z])?)*\.?$";
用的時候很簡單: Pattern pattern = Pattern.compile(VALID_HOST_REGEX, Pattern.CASE_INSENSITIVE); Matcher matcher = pattern.matcher(host); if(!matcher.find()) {
throw new Exception("Invalid gateway host"); }
https://www.jb51.net/article/72601.htm
https://blog.csdn.net/seawave/article/details/1520988
修復方案2:更換獲取IP的方法
https://www.iteye.com/blog/lafecat-2229184
https://www.jianshu.com/p/6c445ea8211b
參考:
https://www.cnblogs.com/eyesmoon/p/7421477.html