Categories:

恶意流量来袭,守护我的华为云 CDN 流量袋子,利用远程鉴权实现有效流量鉴权

半夜被华为云欠费通知惊醒,原本在睡觉前再三确认过云主机的 CDN 套餐和账号余额都充足,可为何突然间就停止服务了呢?带着满腹的疑惑和因加班而挂着的黑眼圈,再次奋战在电脑前。

恶意流量来袭,守护我的华为云 CDN 流量袋子,利用远程鉴权实现有效流量鉴权插图

登录华为云控制台后,瞬间目瞪口呆,CDN 套餐的 3T 流量和280元的华为云欠费账单,让苦逼的日子雪上加霜。查看图表上更显示下载量突然出现了高达 8.4 万次的峰值,难道是小宾软件突然爆火,下载量飙升所致?这一下子让我的肾上腺素急剧上升,随即查看了软件的安装统计,结果却发现并没有增加多少。真是人在家中坐,祸从天上来啊!

恶意流量来袭,守护我的华为云 CDN 流量袋子,利用远程鉴权实现有效流量鉴权插图1

直觉告诉我,一定是有人在恶意消耗小宾的下载流量!!!天地良心,小宾一直都在本本分分地开发软件,诚心诚意地为每一个用户服务,用自己的辛勤劳动服务用户,能在这苦逼的岁月里靠点手艺活着是多么不容易,为何会遭遇这样的事情呢?而且之前放在阿里云时也遇到过类似情况,当时只是以为有不明真相的群众用小宾的下载链接测网速,所以并未放在心上,但现在再次遇到,肯定不是什么巧合了。

难道就这样束手无策吗?不,我小宾可是个技术型人才,绝不可能就此罢休!正如孙红雷所说,不年轻气盛还能叫年轻人吗?

恶意流量来袭,守护我的华为云 CDN 流量袋子,利用远程鉴权实现有效流量鉴权插图2

在华为云平台下载日志,开始仔细查探,果然发现有大量被下载的记录,其中这个 IP 为 116.179.152.137 的一直重复下载,而且从日志分析来看,他并非是新手的无意之举,而是一个老手,其伪造的 UserAgent(请求头)都是随机且不同的。

恶意流量来袭,守护我的华为云 CDN 流量袋子,利用远程鉴权实现有效流量鉴权插图3

果断将这个 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

恶意流量来袭,守护我的华为云 CDN 流量袋子,利用远程鉴权实现有效流量鉴权插图4
恶意流量来袭,守护我的华为云 CDN 流量袋子,利用远程鉴权实现有效流量鉴权插图5

这样就行了,安心等着这个浏览大盗再次光临验证,都有点期望他早点来,好验证这套机制是否合理,在等待了七七21个小时后,他来了他来了,带着流量走来了,这一次的流量更猛。

恶意流量来袭,守护我的华为云 CDN 流量袋子,利用远程鉴权实现有效流量鉴权插图6
恶意流量来袭,守护我的华为云 CDN 流量袋子,利用远程鉴权实现有效流量鉴权插图7

通过华为云官方的图片可以看了,从19点45分开始, 7.72万次的请求,到23点20分止,共访问330万次,MD,这要是不做处理,流量费又不知道要损失多少。

值得回味的事,在整个过程中,我都和华为云客服保持着沟通,很多细节都得到了他们的帮助,但是有一点就是这么大规模的恶意行为,他们能做的也仅有帮助在现有CDN产品体系上封堵而已,并不能从法律或者技术上给予更多帮助。

在此也正告所有攻击者,法网恢恢,疏而不漏,你的行为已经触碰到法律边缘。

最后,这个世界总是有一些坏人和非良善之辈,用深刻的教训来告诉我们更多的道理,提醒我们学习的知识。而万千牵连之间的缘起,可能仅仅只是因为看不顺眼你或者就是不爽你。“深刻”总是给自己过去伤痛的总结,“警醒”和“敬畏”才是对自己未来的态度。

水平有限,能力一般,希望给予大家一点思路。



发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注