防范服务短信接口笔记


最近项目添加了短信功能,给特定用户推送短信。逐想到以前在的一个项目也有短信推送功能,那时未对接口进行IP以及推送号码进行限制防范,被恶意攻击者拦截短信接口并进行短信轰炸,周末两天时间就轰炸了3、4万条消息,直接被运营商投诉。当时最后的解决方案:http请求方式改为https,推送号码每天限制最大推送次数3.
此次再碰到这个功能需求,未免以后再重新繁琐得搜寻各方资料,在这里把这几天写过的关键代码、搜集过的关键资料整理下来,加深这个功能涉及到的相关知识的理解。

常见的网络攻击方式

常见的web攻击方式有:SQL注入、XSS(跨站脚本攻击)、CSRF(跨站请求伪造)、DDOS(分布式拒绝服务)

1.SQL注入

攻击者向服务器提交恶意的SQL执行代码,程序在接受到代码后作为执行语句的一部分执行,改变了执行语句原有的逻辑,最终实现了攻击的意图。
例如输入: or 1=1
登陆时查询用户由:select * from users where username=’’ or 1=1
变成
select * from users
应对策略:
1)使用preparestatement预编译SQL语句,使用占位符来代替传入参数,这样语句结构不会发生变化
2)存储过程。存储过程(Stored Procedure)是一组完成特定功能的SQL语句集,经编译后存储在数据库中,用户通过调用存储过程并给定参数(如果该存储过程带有参数)就可以执行它,也可以避免SQL注入攻击
3)前端预处理语句,过滤非法字符比如;#等

2.XSS

XSS是向网页中注入恶意脚本在用户浏览网页时在用户浏览器中执行恶意脚本的攻击方式。
XSS有两种:
1.反射型攻击(诱使用户点击一个嵌入恶意脚本的链接以达到攻击的目标,目前有很多攻击者利用论坛、微博发布含有恶意脚本的URL就属于这种方式)
2.持久型攻击(将恶意脚本提交到被攻击网站的数据库中,用户浏览网页时,恶意脚本从数据库中被加载到页面执行,QQ邮箱的早期版本就曾经被利用作为持久型跨站脚本攻击的平台)。
应对策略:
1.过滤特殊字符:对用户输入的内容进行特殊字符过滤。
2.请求头指定类型 Content-Type: text/javascript

3.CSRF

攻击截取请求,盗取他人的身份(session、cookie)等,伪装该用户身份,执行相应的接口。
应对策略:
防范CSRF的主要手段是识别请求者的身份,主要有以下几种方式:
1.在表单中添加令牌(token)
2.验证码;
3.检查请求头中的Referer(前面提到防图片盗链接也是用的这种方式)。
令牌和验证都具有一次消费性的特征,因此在原理上一致的,但是验证码是一种糟糕的用户体验,不是必要的情况下不要轻易使用验证码,目前很多网站的做法是如果在短时间内多次提交一个表单未获得成功后才要求提供验证码,这样会获得较好的用户体验。

4.DDOS

分布式拒绝服务攻击(Distributed Denial of Service),简单说就是发送大量请求是使服务器瘫痪。
DDoS攻击可以针对网络通讯协议的各层,手段大致有:TCP类的SYN Flood、ACK Flood,UDP类的Fraggle、Trinoo,DNS Query Flood,ICMP Flood,Slowloris类等等。一般会根据攻击目标的情况,针对性的把技术手法混合,以达到最低的成本最难防御的目的,并且可以进行合理的节奏控制,以及隐藏保护攻击资源。
应对策略:
阿里巴巴的安全团队在实战中发现,DDoS 防御产品的核心是检测技术和清洗技术。检测技术就是检测网站是否正在遭受 DDoS 攻击,而清洗技术就是清洗掉异常流量。而检测技术的核心在于对业务深刻的理解,才能快速精确判断出是否真的发生了 DDoS 攻击。清洗技术对检测来讲,不同的业务场景下要求的粒度不一样。

防范短信接口被恶意调用

常用的预防策略:
1.增加图形验证
2.单IP请求次数限制
3.限制号码发送
这里,增加图形验证,对于在移动端的用户体验比较差,故我使用了后面两种方式:单IP请求次数限制、
限制号码发送。
我的实现思路:接口内对发送的号码进行限制,一天限制3次发送。接口拦截,对调用IP进行统计,多少时间内限制调用次数。
如果还有其他更好的思路,可以留言沟通。

接口限制发送次数主要部分代码:

            long nTime = new Date().getTime();
            boolean isUpdate = false;
            //查询该号码是否已调用过,调用过,校验其上次发送时间以及调用次数
            if (redisService.hasKey(phone+"_code")){

                isUpdate = true;

                long time = NumberUtils.toLong(redisService.hGet(phone+"_code","time").toString());
                if ((nTime- time)<(1000*60)){
                    return ResultMessage.getResultMessage(ResponseStatus.RESPONSE_FAIL.getCode(),"请过一分钟再次发送");
                }

                long i = NumberUtils.toLong(redisService.hGet(phone+"_code","count").toString());
                if (i>=3){
                    return ResultMessage.getResultMessage(ResponseStatus.RESPONSE_FAIL.getCode(),"该号码当天获取验证码次数已满3次");
                }

            }

			//推送验证码
            boolean bool = MessageUtils.sendPhoneMessage(phone, msgCode);
            if (bool) {

                userSms.setStatus(StatusContant.MsgStatusConstant.sendSuccess);
                userSmsService.save(userSms);
				//更新推送次数以及最新推送时间
                if (isUpdate){
                    redisService.hIncr(phone+"_code","count",1);
                    redisService.hSet(phone+"_code","time",nTime);
                } else {  //添加记录
                    long remain = DateUtils.getRemainingSeconds();
                    Map<String,Object> map = new HashMap<>();
                    map.put("count",1);
                    map.put("time",nTime);
                    if (remain>0) {
                        redisService.hSet(phone + "_code", map,remain);
                    }
                }

                return ResultMessage.getResultMessage(200,"验证码发送成功");
            }

接口拦截:

public class SendMsgIpInterceptor implements HandlerInterceptor {

    private static String text = "{\"msgtype\": \"text\",\"text\": {\"content\": \"ip:[%s] 频繁调用短信发送接口 \"}}";

    @Autowired
    private RedisService redisService;

    @Value("${web.hook}")
    private String webHook;


    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String ip = HttpContext.getReadIp(request);

        if (StringUtils.isEmpty(ip)){
            response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
            PrintWriter writer = response.getWriter();
            writer.write(new ObjectMapper().writeValueAsString(new ResultMessage<>(400, "请求源IP不明")));
            writer.close();
            return false;
        }

        if (redisService.hasKey(ip+"_invoke")){
            long count = NumberUtils.toLong(redisService.get(ip+"_invoke"));

            if (count>10){

                if (!redisService.hasKey(ip+"_invoke_day")){
                    long remain = DateUtils.getRemainingSeconds();
                    redisService.setEx(ip+"_invoke_day", String.valueOf(1),remain);
                } else {
                    redisService.incr(ip+"_invoke_day");
                    //该IP频繁请求,可以通知系统管理员,直接在系统上拦截该IP
                    String str = String.format(text,ip);
                    HttpClientUtils.doPost(webHook, JSON.parseObject(str));
                }

                response.setContentType("application/json; charse`t=utf-8");
                PrintWriter writer = response.getWriter();
                writer.write(new ObjectMapper().writeValueAsString(new ResultMessage<>(400, "请勿频繁调用")));
                writer.close();
                return false;
            } else {
                redisService.incr(ip+"_invoke");
                return true;
            }
        } else {
            //设置值为10分钟
            redisService.setEx(ip+"_invoke",String.valueOf(1),600);

        }
        return true;
    }
}

注册接口拦截:

@Configuration
public class WebAppConfig implements WebMvcConfigurer {

    @Autowired
    private SendMsgIpInterceptor sendMsgIpInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        InterceptorRegistration addInterceptor = registry.addInterceptor(sendMsgIpInterceptor);
        addInterceptor.addPathPatterns("/public/sendMobileMsg");
    }
}

另外,项目购买的阿里消息服务,也提供了关于IP、号码限制的功能,可以在其控制台配置,这样可以更好的保护短信服务被恶意攻击。

参考文献:

https://www.cnblogs.com/sweetheartly/articles/9439789.html
https://www.cnblogs.com/wzj4858/p/8259944.html
https://www.cnblogs.com/morethink/p/8734103.html
https://blog.csdn.net/weixin_41910244/article/details/80544102
https://blog.csdn.net/qq_35387940/article/details/84391784
https://blog.csdn.net/LikeBoke/article/details/85328421

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值