半夜被华为云欠费通知惊醒,原本在睡觉前再三确认过云主机的 CDN 套餐和账号余额都充足,可为何突然间就停止服务了呢?带着满腹的疑惑和因加班而挂着的黑眼圈,再次奋战在电脑前。
登录华为云控制台后,瞬间目瞪口呆,CDN 套餐的 3T 流量和280元的华为云欠费账单,让苦逼的日子雪上加霜。查看图表上更显示下载量突然出现了高达 8.4 万次的峰值,难道是小宾软件突然爆火,下载量飙升所致?这一下子让我的肾上腺素急剧上升,随即查看了软件的安装统计,结果却发现并没有增加多少。真是人在家中坐,祸从天上来啊!
直觉告诉我,一定是有人在恶意消耗小宾的下载流量!!!天地良心,小宾一直都在本本分分地开发软件,诚心诚意地为每一个用户服务,用自己的辛勤劳动服务用户,能在这苦逼的岁月里靠点手艺活着是多么不容易,为何会遭遇这样的事情呢?而且之前放在阿里云时也遇到过类似情况,当时只是以为有不明真相的群众用小宾的下载链接测网速,所以并未放在心上,但现在再次遇到,肯定不是什么巧合了。
难道就这样束手无策吗?不,我小宾可是个技术型人才,绝不可能就此罢休!正如孙红雷所说,不年轻气盛还能叫年轻人吗?
在华为云平台下载日志,开始仔细查探,果然发现有大量被下载的记录,其中这个 IP 为 116.179.152.137 的一直重复下载,而且从日志分析来看,他并非是新手的无意之举,而是一个老手,其伪造的 UserAgent(请求头)都是随机且不同的。
果断将这个 IP 拉黑,然后在凌晨时分毫无睡意的情况下,思考着该怎么办,如果他换个 IP 继续搞破坏该如何应对。抱着这样的想法,我先充值恢复账号访问,接着试着取消对这个 IP 的限制,果然没过几分钟,他又开始了,那只能先拉黑再说,不彻底解决今晚肯定睡不着。研究了一下华为云提供的防盗措施。
防盗链配置:配置referer黑白名单,通过设置的过滤策略,对访问者身份进行识别和过滤,实现限制访问来源的目的。
IP黑白名单配置:配置IP黑白名单,通过设置过滤策略,对用户请求IP地址进行过滤,从而限制访问来源。
User-Agent黑白名单:当您的网站需要对用户请求使用的代理过滤,从而限制访问来源时,可以在此项中进行配置。
URL鉴权:配置URL鉴权功能,通过保护用户站点资源,防止资源被用户恶意下载盗用。
远程鉴权配置:当您想要CDN将用户请求转发到指定的鉴权服务器来完成鉴权,从而防止资源被用户恶意下载盗用时,可以在此项中进行配置。
配置Referer这个不太现实,因为小宾很多业务都是直接把下载链接发给用户。
User-Agent增加黑白名单,但这个显然对于本次这个专业的流量大盗来说无济于事。
URL鉴权这是一个方案,可以做一个中转页面,然后在页面中请求url鉴权参数实现防盗,再配合referer,好处是,可以再配合验证码机制 ,当用户IP点下载,大于3次后,开启验证码保护,坏处是用户下载需要在页面中多点一次,这对于一结电脑小白极其不友好(就像我们提供的网盘链接,好多小白用户不懂怎么从网盘下载)
远程鉴权:用户下载前,先通过个这个鉴权链接把用户的请求信息发送到服务器,通过返回不同状态值来告诉华为云是否允许下载,这个功能好处是几乎解决了我所有的问题,坏处是需要一台鉴权服务器,但这好办,华为云主机能实现的一年才几十块钱,够用了,说干就干。
实现思路是,在nacos中配置一个5分钟访问最大次数,然后再配置1小时最大访问次数,当然这里可以加更多状态逻辑,比如浏览器,或者根据文件名与IP地址,设置更多的限制条件,为了安全,我只截取这一段来说明这个流程和思路。
先做个简单的逻辑 :如果一个IP地址,连续5分钟内连续访问10次下载请求,或者1小时内访问20次下载请求,则将状态设置为404,否则就允许下载返回状态200
down_auth:
#五分钟内下载次数
limit: 10
#1小时下载次数
hour_limit: 20
先创建一个入参实体:
@Data
public class DownloadAuthVi {
/**
* 访问文件路径
*/
private String uri;
/**
* 客户端请求IP,remote_addr的值
*/
private String ip1;
/**
* 客户端请求IP,http_x_forwarded_for
*/
private String ip2;
/**
* 请求协议,GET/POST/HEAD
*/
private String method;
/**
* 获取真实IP
* 通过获取remote_addr、http_x_forwarded_for,判断哪个值是合法且有效的,并返回
* 考虑过来的地址,有可能是0.0.0.0,1.0.0.0这种,取最前面的字段
* @return IP地址 在ip1和ip2中返回一个有效的值
*/
public String getIp() {
// 验证IP地址的正则表达式
String ipRegex = "^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$";
Pattern pattern = Pattern.compile(ipRegex);
// 检查ip1和ip2
String ip = checkIp(ip1, pattern);
if (ip == null) {
ip = checkIp(ip2, pattern);
}
return ip;
}
/**
* 查看IP地址合法性,并且返回IP字符串中,第1个有效的字符
* @param ip IP字符串:如0.0.0.0,1.1.1.1
* @param pattern IP正则表达式
* @return String 返回IP地睛
*/
private String checkIp(String ip, Pattern pattern) {
if (!StringUtils.isEmpty(ip)) {
String[] ipParts = ip.split(",");
String firstIp = ipParts[0].trim();
if (pattern.matcher(firstIp).matches()) {
return firstIp;
}
}
return null;
}
}
Controller增加一个入口
@GetMapping(value = "/download/auth")
public void downloadAuth(@ModelAttribute DownloadAuthVi params, HttpServletResponse response) {
liveDownLoadService.downloadAuth(params,response);
}
Service增加一个函数
/**
* 下载鉴权
* @param params 请求实体入参
* @param response http response
*/
public void downloadAuth(DownloadAuthVi params, HttpServletResponse response) {
String ip = params.getIp();
String uri = params.getUri();
Integer maxLimit = liveConfig.getDownAuth().getLimit();
Integer hourMaxLimit = liveConfig.getDownAuth().getHourLimit();
String ipBlacklist = liveConfig.getDownAuth().getIpBlacklist();
if (StringUtils.isNotBlank(ipBlacklist) && ipBlacklist.contains(ip)) {
log.info("downloadAuth ipBlackList ban:{} {}| {} / {}", params.getIp1(), params.getIp2(), ip, uri);
response.setStatus(404);
return;
}
String limitKey = String.format(DOWNLOAD_AUTH_LIMIT_LOCK_CACHE_PREFIX, ip).replace(".", ":");
String dayKey = String.format(DOWNLOAD_AUTH_LIMIT_DAY_LOCK_CACHE_PREFIX, ip).replace(".", ":");
String fileKey = String.format(DOWNLOAD_AUTH_FILE_LIMIT_DAY_LOCK_CACHE_PREFIX, ip, SecureUtil.md5(uri)).replace(".", ":");
//5分钟内下载次数
Long limit = redisTemplate.opsForValue().increment(limitKey);
if (limit > maxLimit) {
log.info("downloadAuth 5m ban:{} {}| {} / {}", params.getIp1(), params.getIp2(), ip, uri);
response.setStatus(404);
return;
}
if (limit == 1L) {
redisTemplate.expire(limitKey, 5, TimeUnit.MINUTES);
}
//1小时内下载次数
Long dayLimit = redisTemplate.opsForValue().increment(dayKey);
if (dayLimit > hourMaxLimit) {
log.info("downloadAuth 1h ban:{} {}| {} / {}", params.getIp1(), params.getIp2(), ip, uri);
response.setStatus(404);
return;
}
if (dayLimit == 1L) {
redisTemplate.expire(dayKey, 60, TimeUnit.MINUTES);
}
response.setStatus(200);
}
程序测试没问题后,现在开始配置华为云CDN
这样就行了,安心等着这个浏览大盗再次光临验证,都有点期望他早点来,好验证这套机制是否合理,在等待了七七21个小时后,他来了他来了,带着流量走来了,这一次的流量更猛。
通过华为云官方的图片可以看了,从19点45分开始, 7.72万次的请求,到23点20分止,共访问330万次,MD,这要是不做处理,流量费又不知道要损失多少。
值得回味的事,在整个过程中,我都和华为云客服保持着沟通,很多细节都得到了他们的帮助,但是有一点就是这么大规模的恶意行为,他们能做的也仅有帮助在现有CDN产品体系上封堵而已,并不能从法律或者技术上给予更多帮助。
在此也正告所有攻击者,法网恢恢,疏而不漏,你的行为已经触碰到法律边缘。
最后,这个世界总是有一些坏人和非良善之辈,用深刻的教训来告诉我们更多的道理,提醒我们学习的知识。而万千牵连之间的缘起,可能仅仅只是因为看不顺眼你或者就是不爽你。“深刻”总是给自己过去伤痛的总结,“警醒”和“敬畏”才是对自己未来的态度。
水平有限,能力一般,希望给予大家一点思路。
发表回复