今天發現自己項目一個漏洞:先為一賬戶充值100元,然后瞬間發送10次提現請求(都是提現100,提現接口是有做余額不足校驗的),其中大約有四五次都是成功的,剩下的會報余額不足。期望是,只有一次可以成功完成提現,分析到能部分請求能通過余額不足校驗原因是,由於是瞬間發出的提現請求,這些請求中拿到的余額數據都是余額扣減之前的數據。
以上場景可以提煉出兩個關鍵步驟:
- 查詢余額並校驗,select * from account where user_id = 123;
- 扣減余額並支付,update account set balance...
根據以上步驟,可知:1.在兩條SQL語句執行的中間這段時間,由於重復請求攻擊,可能會出現多次請求的第一步操作成功,並繼續執行第二步,最后導致資金損失。2.由於第一步操作是查詢操作,沒有數據庫會限制重復讀取數據,數據庫層面是沒有可能解決這個問題的,所以不用在這個上面浪費時間。
目前的解決方案是:為接口上鎖。已經有人做了輪子,比如redis-lock。以用戶ID為key,某個uuid為值。將類似提現這樣的接口上鎖。同一用戶在訪問添加了該中間件的接口時,第一次沒有執行完畢,拒絕執行第二個請求。第一次執行完畢時,釋放鎖,即清除redis緩存的鍵值對。同時可設定,緩存時長,以防中途宕機,鎖未釋放等問題。具體實現可以參考npm包redis-lock文檔。
