重放攻擊 重放攻擊是指黑客通過抓包的方式,得到客戶端的請求數據及請求連接,重覆的向伺服器發送請求的行為。 比如你有一個 “購買” 的操作,當你點擊購買按鈕時,向伺服器發送購買的請求。而這時黑客對你的請求進行了抓包,得到了你的傳輸數據。 因為你填寫的都是真實有效的數據,是可以購買成功的,因此他不用做任 ...
重放攻擊
重放攻擊是指黑客通過抓包的方式,得到客戶端的請求數據及請求連接,重覆的向伺服器發送請求的行為。 比如你有一個 “購買” 的操作,當你點擊購買按鈕時,向伺服器發送購買的請求。而這時黑客對你的請求進行了抓包,得到了你的傳輸數據。 因為你填寫的都是真實有效的數據,是可以購買成功的,因此他不用做任何改變,直接把你的數據再往伺服器提交一次就行了。這就導致了,你可能只想購買一個產品的,結果黑客重放攻擊,你就購買了多次。如果是用戶操作的話,肯定就會莫名奇妙:怎麼購買了那麼多同樣的產品,我只買了一個啊? 所以,重放攻擊的危害還是挺大的,特別是涉及到金錢交易時,因此防重放攻擊在電商項目中是必不可少的。
解決方案
時間戳(tamp) + 數字簽名(sign)。 也就是說每次發送請求時多傳兩個參數,分別為 tamp 和 sign。比如
原先請求為 http://127.0.0.1/api/buyproduct
修改之後為 http://127.0.0.1/api/buyproduct?tamp=1403149835&sign=945bf36r046bd84df2985ad625c9f92415eccd1w
數字簽名的作用是為了確保請求的有效性。因為簽名是經過加密的,只有客戶端和伺服器知道加密方式及Key,所以第三方模擬不了。我們通過對sign的驗證來判斷請求的有效性,如果sign驗證失敗則判定為無效的請求,反之有效。 但是數字簽名並不能阻止重放攻擊,因為黑客可以抓取你的tamp和sign(不需做任何修改),然後發送請求。這個時候就要對時間戳進行驗證。
時間戳的作用是為了確保請求的時效性。我們將上一次請求的時間戳進行存儲,在下一次請求時,將兩次時間戳進行比對。如果此次請求的時間戳和上次的相同或小於上一次的時間戳,則判定此請求為過時請求,無效。因為正常情況下,第二次請求的時間肯定是比上一次的時間大的,不可能相等或小於。
有人會問,我直接用時間戳不就行了,為什麼還要數字簽名?因為黑客可能對請求進行抓包,然後修改時間戳為有效的時間戳值。我們的數字簽名採用 tamp+key 進行組合加密,即使黑客修改了 tamp ,但是由於黑客不知道key,所以 sign 驗證這步就成功的阻止了黑客的請求。
實例代碼
加密方式採用 SHA1,SHA1加密方法進行了封裝,寫成了string的擴展方法。
/// <summary> /// 數字簽名 /// </summary> /// <param name="tamp">時間戳(由客戶端傳入)</param> /// <param name="key">Key</param> /// <returns></returns> private string Sign(string tamp, string key) { string txt = tamp + "|" + key; //在每個參數中間加了個 "|" ,增加複雜度 string sign = txt.GetSha1(); return sign; }
驗證方法, 客戶端的加密方式和服務端是一樣的,如果兩者的加密結果不一致,則驗證失敗。 如果客戶端是js,一定要對js做代碼混淆,禁止右鍵等。因為我是用Session存儲上一次請求的時間戳的,而Session是會過時的,當Session過時時黑客再進行攻擊,就會得手,所以限制請求有效期為30秒。
/// <summary> /// 檢查請求是否有效,防重放 /// </summary> /// <param name="tamp">時間戳(由客戶端傳入)</param> /// <param name="key">Key</param> /// <param name="sign">驗簽(由客戶端傳入)</param> /// <returns></returns> private bool CheckRequest(string tamp, string key, string sign) { //驗簽(比對客戶端的加密結果和服務端的加密結果,如果不相等,則驗簽失敗) if (sign.ToUpper() != Sign(tamp, key).ToUpper()) return false; //得到當前時間戳 DateTime DateStart = new DateTime(1970, 1, 1, 8, 0, 0); int nowTamp = Convert.ToInt32((DateTime.Now - DateStart).TotalSeconds);
if ((nowTamp - int.Parse(tamp)) > 30) return false; //因為Session可能過時,所以限定請求有效時間為30秒
//得到上一次的時間戳 string prevTamp = Session["tamp"] as string;
//判斷是否為空,為空說明是第一次請求 if (!string.IsNullOrWhiteSpace(prevTamp)) { if (int.Parse(tamp) > int.Parse(prevTamp)) { Session["tamp"] = tamp; return true; } else { return false; } } else { Session["tamp"] = tamp; return true; } }