使用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://www.baidu.com/link?url=jZ_QuN3XINVhP2iN13WjrQFL69NeNecQwCYHcJ5LQwVcNCtg16wkSHA6ByDaKJXxWV6f0UsbLw_NxbWqp5xmuf6cP3QgQgdlXkLAP5Z8Bsm&wd=&eqid=b8fbbc510016d604000000035d73709b

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