臭名昭著的手機驗證碼功能是如何實現的

小柒2012發表於2020-04-17

前言

現在基本上各種手機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"));
    }
}

小結

重要的功能必須進行前後端校驗,必要的時候一定要做好限流、黑名單等騷操作!!!

相關文章