数据防扒指北
背景介绍
企业的发展离不开数据的支撑,尤其是核心的底层数据,随着技术的升级,数据方面的生成、传输、获取、处理、清洗、归纳逐步加上了安全的外衣,同行业间企业的竞争也逐渐演变成数据的竞争。
最近项目中发现,系统中存在被爬取数据的行为,而且已经初步可以认定是竞对的手法,所以针对这一行为,需要升级防爬措施,避免关键数据的流失。
数据防扒是很常见的数据安全攻防的场景,这里先说一下结论,作为守的一方,本身就在明处,只要数据是正常用户可以看见或者使用的,很难可以完全杜绝数据被扒去,基本都可以被扒取的到,我们能做的就是让对方获取数据的成本增加,甚至达到超越数据本身所带来的价值,这是一个攻防不断升级的过程,需要有耐心,常思考常总结。
技术方面应对
常见技术简介
目前常见的反爬的技术手段大概有如下几个方面:IP访问频率、黑名单限制、加密鉴权、浏览页面速度、用户的登录验证、图片伪装、验证码校验、代码混淆、字体变换等等,作为维护方,当然也需要根据自身的系统特点,适当的考虑防护成本来选择合适的技术。下面就介绍一下 我在系统中使用的部分方案
1、使用现有框架
- kk-anti-reptile
开源框架,内置两种规则,IP和UA;IP规则主要是可以对设置内的时间窗口、最大请求数、IP黑白名单进行设置;而UA规则是判断用户请求的操作系统、设备信息、浏览器等信息进行请求的过滤拦截。不满足既定规则,则会弹出验证码页面,详情请查看项目文档https://gitee.com/kekingcn/kk-anti-reptile
当然,既然是开源项目,完全可以下载到本地进行改造,上传至内部的Maven仓库,实现定制服务,比如,
1、增加拦截规则
2、修改现有IP和UA规则
3、将触发拦截的用户信息或者行为信息进行入库,方便后续的统计分析等等。
2、手动实现IP拦截
-
场景分析
通过日志分析,目前用户的扒取数据的行为是登录指定用户的账号,然后访问关键数据接口获取数据,而且比较明显的特征为,单个用户会使用大量不同的IP进行访问,访问频率固定为2秒或者3秒一次。所以如果使用上述的KK框架就不一定可以满足我的需求,时间窗口和最大请求的方式容易造成误伤,毕竟对方速率还是比较"拟人"的
-
目标描述
- 对于用户使用多IP访问的行为进行监控,在5分钟内使用3个IP以上访问的用户加入到黑名单,并将用户信息设置为禁用状态
- 对于固定速率访问的用户,如果连续
-
代码实现
java
package com.icotton.interceptor;
import com.icotton.platform.model.FastJsonUtil;
import com.icotton.platform.model.JsonMessage;
import com.icotton.system.dao.authority.UserInfoDao;
import com.icotton.util.RedisUtil;
import com.icotton.util.RedisUtilNew;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.Set;
import java.util.concurrent.TimeUnit;
@Component
public class IpInterceptor extends HandlerInterceptorAdapter {
public IpInterceptor() {
}
private static String user_key = "icotton:userip:count:";
private static String user_set = "icotton:userip:set:";
private static String block_key = "icotton:userip:block:";
@Autowired
private RedisUtilNew redisUtilNew;
@Autowired
private UserInfoDao userInfoDao;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
String characterEncoding = request.getCharacterEncoding();
RequestWrapper myRequestWrapper = new RequestWrapper(request);
String ip = parseRequestUrl(myRequestWrapper);
// 获取输入流
BufferedReader streamReader =
new BufferedReader(new InputStreamReader(myRequestWrapper.getInputStream(), StandardCharsets.UTF_8));
StringBuilder sb = new StringBuilder();
String line;
while ((line = streamReader.readLine()) != null) {
sb.append(line);
}
if (sb.length() == 0) {
return true;
}
JsonMessage jsonMessage = FastJsonUtil.json2Object(sb.toString(), JsonMessage.class);
if (jsonMessage == null || jsonMessage.getHeader() == null) {
return true;
}
// 获取用户ID
String userId = jsonMessage.getHeader().getUserId();
if (StringUtils.isBlank(userId)) {
return true;
}
//判断当前IP是否在黑名单中
if (redisUtilNew.hasKey(block_key + ip)) {
return false;
}
//主要策略为,5分钟内,同一个用户使用超过3个IP地址,则需要将该IP添加至黑名单
//用于对用户使用的IP进行计数
String userKey = user_key + userId;
//用于存放用户对应的IP
String userSetKey = user_set + userId;
//用于设置IP黑名单
String userBlockKey = block_key + ip;
Long num = redisUtilNew.incrBy(userKey, 1);
if (num == 1L) {
redisUtilNew.expire(userKey, 300, TimeUnit.SECONDS);
} else {
redisUtilNew.sAdd(user_set + userId, ip);
Set<String> ips = redisUtilNew.setMembers(userSetKey);
if (ips.size() > 2) {
redisUtilNew.set(userBlockKey, ip);
//触发用户禁用操作
userInfoDao.invlideUser(userId);
return false;
}
}
return true; // 放行请求
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
super.postHandle(request, response, handler, modelAndView);
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
super.afterCompletion(request, response, handler, ex);
}
@Override
public void afterConcurrentHandlingStarted(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
super.afterConcurrentHandlingStarted(request, response, handler);
}
public String parseRequestUrl(HttpServletRequest request) {
String ip = request.getHeader("x-forwarded-for");
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
return ip.equals("0:0:0:0:0:0:0:1") ? "127.0.0.1" : ip;
}
}
- 方案回顾
其实绕开这个限制的方法很简单,只要单一用户使用单一IP进行访问即可,只是对目前的爬取行为进行了限制。
3、图片伪装
-
场景分析
对于页面固定样式的数据可以使用图片进行代替,比如详情页面,只需要使用接口数据替换页面内容的场景,当然对于查询页面或者列表分页等动态页面就不是很适合了,因为图片伪装也是需要成本的,后台需要进行图片的转换处理。
-
方案实施
将接口数据按照页面的格式转换为一张图片,返回给前台即可,这里有几个优化点可以注意一下
- 频繁的页面访问,可以考虑后台增加图片缓存,将首次生成的图片文件上传文件服务器比如fastdfs,那只需要给前台传输图片链接即可,也优化了页面的响应速度。
- 既然是图片展示,就需要考虑OCR识别的风险,可以适当的给图片增加水印、干扰线、调整对比度等方式,增加OCR识别的难度,当然可能会降低用户体验,需要权衡一下。
-
方案回顾
这种方式确实避免接口的数据传输,但是带来了OCR识别的风险,而且现在OCR的识别能力都比较强,图片干扰的方法也不能完全避免文字被自动识别,也只是增加了难度。
4、蜜罐技术
- 场景分析
蜜罐技术算是一种借力打力的方式,在识别出对方的爬取行为和爬取方式之后,不要打草惊蛇,利用他们的爬取行为间接地进行数据防护。 - 方案实施
- 数据倒灌
在识别到是爬取用户之后,则根据接口信息响应给用户虚拟信息或者不准确信息,最好相应的数据逼近真实数据,但存在差异,那么就可以极大混淆信息的可信度,让对方不敢轻易使用爬取到的数据。 - 接口切换
对于关键数据的接口,可以不定期的进行接口地址的变更,但要保留之前的接口存在,那么在系统变更发布之后,根据日志分析,访问原有接口的请求都完全可以视作为非法用户,这样就可以放心地封用户、封IP了。
- 数据倒灌
- 方案回顾
有操作成本,但是有效。
非技术应对
1、业务层面
- 避免在页面上直接展示关键性的字段信息,对敏感数据进行脱敏
- 根据用户的角色,限制用户查询的数据范围和维度
2、法律层面
- 声明robots.tex文件,告知爬虫软件那些内容是可以允许爬取的,未提及的目录则不循序
- 非法获取计算机信息系统数据罪
- 侵入计算机获取数据,未经授权或者超越权限
- 使用反反爬虫技术
- 伪造header信息,绕开验证
- 使用大量IP或注册用户,绕开人机校验或频率限制
- 伪造Cookie或Token认证信息,获取权限
- 使用打码平台,绕开人机认证机制
- 构造非常规请求,绕开身份验证机制
- 网站声明,起到一定的警示和震慑作用
3、合作共赢
打不过就加入,既然拦不住,那就寻求合作共赢的机会
结语
先整理这么多,算是把近期的工作小小总结一下,欢迎大家补充更新想法。