前言
現在基本上各種手機APP
注冊都會用到手機驗證碼,包括一些PC
端網站也會使用手機號作為唯一標識驗證!
恰巧,小明的老板,讓其開發一個用戶注冊的功能,並且強制用戶注冊綁定手機,美其名曰為了提升安全性,呵呵噠,就是為了多擼一點用戶信息。
案例
一般來說,發送手機驗證碼不能過於頻繁,前端發送按鈕點擊后一般會有一個60
秒倒計時的功能。也就是說,如果用戶點擊發送一直沒有收到驗證碼,只能60秒之后才可以進行重發。
那么問題來了,如果用戶繞過前端,直接向后台API
發送短信請求,然后寫個無限循環腳本,相信不久你的短信賬戶就會發來預警提示短信(一般來說大的短信商都有預警設置功能)。
其實很簡單,你只需要F12
,查看發送請求就可以查找出后台請求地址,然后你可以在控制台輸入相關JS
代碼,執行個十萬遍,是不是很爽?
這里以七牛雲為測試案例,打開注冊頁面,F12進入調試模式,輸入手機號,手動點擊發送,獲取其短信發送后台請求地址。下面是七牛雲的一個短信發送請求,擼主測試了一下,顯然沒有達到擼主的預期,畢竟是大廠,防御措施還是做的很牛逼的。
以下是JS
腳本,復制粘貼到控制台回車就可以執行:
var data = {"operation":1,"is_voice":false,"mobile_number":"17762018888","captcha_type":2};
for (var i = 0; i < 10; i++) {
$.ajax({
type: 'POST',
contentType: 'application/json;charset=UTF-8',
data:JSON.stringify(data),
url: 'https://portal.qiniu.com/api/gaea/verification/sms/send',
success: function(data) {
console.log(data)
}
});
}
控制台返回以下信息,前三次請求成功,后面的就出現了驗證碼校驗並進行了限流操作。
{"code":200,"message":""}
{"code":200,"message":""}
{"code":200,"message":""}
{"code": 7209,"message":"captcha required"}
{"code": 7209,"message":"captcha required"}
{"code": 429,"message":"too many requests"}
{"code": 429,"message":"too many requests"}
{"code": 429,"message":"too many requests"}
{"code": 429,"message":"too many requests"}
{"code": 7209,"message":"captcha required"}
擼主嘗試刷新頁面,隨便輸了一個手機號,再次點擊發送,提示用戶輸入驗證碼,顯然是加強了防備,觸發了惡意請求認證攔截機制。
安全機制
對於開發者來說,他們不僅要考慮用戶正常獲取驗證碼的體驗還要考慮短信接口的安全性,擼主總結了以下幾點,希望對大家有所幫助。
- 后台請求限流,對單位時間內發送頻率做限制。
- 驗證碼機制,切記不要一開始就限制驗證碼,體驗及其不友好,觸發限流以后開啟驗證碼校驗。
- 監控日發送短信數量,觸發一定的閾值做相應的處理,根據實際業務需求。
- 驗證碼存儲一定要保證
key
為手機號,切記不要以其它標識作為key
,比如sessionId
。 - 一定要設置驗證碼失效時間,比如五分鍾,或者更短。
- 驗證碼盡量保證短小精悍,四到六位即可。
- 如果后台不做限制,切記前台一定要做個倒計時的限制,至少過濾一部分小白用戶。
代碼案例
給小伙伴分享一個簡單的驗證碼生成、存儲、失效代碼案例:
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
public class Mobile {
/**
* 測試方便,這里設置了3秒失效
*/
private static LoadingCache<String, String> caches = CacheBuilder.newBuilder()
.maximumSize(1000)
.expireAfterWrite(3, TimeUnit.SECONDS)
.build(new CacheLoader<String, String>() {
@Override
public String load(String mobile) {
return "";
}
});
public static void main(String[] args) throws ExecutionException, InterruptedException {
Integer code = (int)((Math.random()*9+1)*100000);
caches.put("17762018888",code.toString());
System.out.println(caches.get("17762018888"));
Thread.sleep(4000);
System.out.println("是不是沒了:"+caches.get("17762018888"));
}
}
小結
重要的功能必須進行前后端校驗,必要的時候一定要做好限流、黑名單等騷操作!!!