数据防扒指北

数据防扒指北

背景介绍

企业的发展离不开数据的支撑,尤其是核心的底层数据,随着技术的升级,数据方面的生成、传输、获取、处理、清洗、归纳逐步加上了安全的外衣,同行业间企业的竞争也逐渐演变成数据的竞争。

最近项目中发现,系统中存在被爬取数据的行为,而且已经初步可以认定是竞对的手法,所以针对这一行为,需要升级防爬措施,避免关键数据的流失。

数据防扒是很常见的数据安全攻防的场景,这里先说一下结论,作为守的一方,本身就在明处,只要数据是正常用户可以看见或者使用的,很难可以完全杜绝数据被扒去,基本都可以被扒取的到,我们能做的就是让对方获取数据的成本增加,甚至达到超越数据本身所带来的价值,这是一个攻防不断升级的过程,需要有耐心,常思考常总结。

技术方面应对

常见技术简介

目前常见的反爬的技术手段大概有如下几个方面: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、合作共赢

打不过就加入,既然拦不住,那就寻求合作共赢的机会

结语

先整理这么多,算是把近期的工作小小总结一下,欢迎大家补充更新想法。