前言:前段时间可能是碰到同行了,公司上线的公众号接口被人疯狂的访问,导致服务器CPU资源被严重消耗,系统也在接近崩溃的边缘,虽然后面紧急处理抢救回来,但是我觉得这个事情还是应该重视一下,记录下来。
一、经过这次事件,我把攻击手法进行分类
- 无token或者无效token访问
- 固定IP携带有效token访问
- 动态IP携带有效token访问
1.1 无token或者无效token访问防护
正常进入服务器的访问请求都会先经过拦截器,对不携带token和携带无效token的访问进行拦截(由于是公众号接口,这边放开了对空token的限制,没想到却被居心不良的人给钻了空子 o(╥﹏╥)o,一定要吸取教训,防人之心不可无!!!)
1.2 固定IP携带有效token访问防护
当用户使用有效的token进行访问,普通的拦截器是拦不住的,因为他就相当于正常的用户正常访问,这个就需要在原有拦截器的基础上新增一个IP地址限流,限制的规则有很多。
我采取的是:限制每秒内同一IP地址对同一接口的次数,也就是同一IP对同一接口每秒访问不得超过多少次,否则加入黑名单(加入黑名单多久可以自行处理)
1.3 动态IP携带有效token访问防护
原本我也以为,把IP地址防住就OK了,没想到我还是太天真 °(°ˊДˋ°) °,打开日志一看,全是虚拟IP,上网上一查,发现IP修改器实在太多了
苦逼的生活从无尽的填坑开始,既然IP地址防不住,那我就从你token里面携带的微信用户信息下手 (ง •̀_•́)ง,将之前IP黑名单拦截器升级为微信用户访问拦截器。
实现原理一样,只是把之前的IP地址改成微信用户的 openId:也就是同一 openId对同一接口每秒访问不得超过多少次,否则加入黑名单。
1.4 附加:动态IP访问登录接口(访问登录接口是不需要携带token的)
前三种都是在公众号上发现的,这我在网上查询的时候无意间浏览到的,也在这边记录一下,面对这种情况,之前所说的拦截器怕是也没有用武之地。
对于这种情况我能想到的只有在登录接口加上验证码,先判断验证码是否正确再去做用户判断。
这样做一来可以防止他人使用程序对用户名密码进行破解,二来可以避免不良分子向系统注水。
附:黑名单拦截器实现
token 拦截器的实现和 登录验证码的实现网上已经有很多了,我这边附上黑名单过滤器的实现仅供参考。
在访问进入请求方法的时候增加黑名单判断器,返回 true 拦截,返回 false 放行
/**
* 黑名单判断器(为了方便我这边保存到缓存中,最好是保存到 Redis)
* @param apiCode 接口编号
* @param uCode 用户编号(可以是ip 也可以是 openId)
* @return true 黑名单,false 正常
*/
public static boolean weChatBlacklist(String apiCode, String uCode) {
// 黑名单用户不予许访问
Object object = CacheMgr.getValue("wechatBlacklist-" + uCode);
if(object != null){
return true;
}
// 查询缓存中当前IP访问当前接口的次数
Integer count = (Integer) CacheMgr.getValue(apiCode+"-" + uCode);
if(count == null){
// 如何缓存为空,保存访问次数
CacheMgr.addCache(apiCode+"-" + uCode,1);
// 开启新的线程,定制一秒后计划
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
try {
// 暂停1秒
Thread.sleep(1000);
// 1秒后查询访问总数
Integer visits = (Integer) CacheMgr.getValue(apiCode+"-" + uCode);
if(visits!=null){
log.info(apiCode+"-" + uCode+":"+cumulative);
// 如果1秒后缓存访问次数不为空,删除缓存
CacheMgr.removeCache(apiCode+"-" + uCode);
// 一秒后缓存中访问次数超过x则加入黑名单(x值视接口具体情况而定)
if(visits>maxVisits){
// 用户每秒访问数超过 maxVisit,加入黑名单
CacheMgr.addCache("wechatBlacklist-" + uCode,uCode);
// 此处再将 uCode 持久化到数据库中,程序再次启动时读取
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
thread.start();
} else {
CacheMgr.addCache(apiCode+"-" + uCode,++count);
}
return false;
}