Zoe
Zoe
返回博客

安全技术在数据采集中的应用

21 分钟阅读

这个课题或者叫思路其实早在一年前就已经在构思了,在公司的内部做过相应的分享。当时很多东西可能只是口诉的,超出了PPT的成文范畴,最近在构思一个新的想法,正好把这个方面的知识、经验或者叫Tips做一些总结。 全文不会从网络安全或移动安全等安全领域来讲,因为我在这些专业领域也并不擅长,我仅从爬虫的角度,结合安全的相关知识来思考如何解决爬虫中遇到的问题。

爬虫流程.jpg

数据采集从解决问题的角度来看,一般可以划分为2个部分: 1. 寻找可以高效抓取数据的入口 2. 高效抓取的实现 本文涉及到的内容,均为旨在解决第一部分的问题。至于如何实现高效的数据抓取,我们可以利用已有的或自己实现的爬虫框架来做,它的难点一般都比较固定在解决系统层面的性能问题,关于如果设计一套数据抓取的系统,可详细阅读“高效爬虫的思考与实现”一文。

:warning: 本文不涉及漏洞的挖掘和利用。

信息安全

信息安全在维基百科中的解释如下:

保护信息信息系统免受未经授权的进入、使用、披露、破坏、修改、检视、记录及销毁。

广义上来说“信息安全”只的是"Security",意义是主动保护信息或设备的安全。但一般而言,我所理解的狭义上的信心安全特指,漏洞挖掘。这里可能会对名词的理解有所偏差,但是我们先明确接下来讨论的事情,会围绕“漏洞挖掘”这个角度来讨论。

原则上来说,在信息安全(以下简称:信安)领域做深入研究的话,需要掌握很多的知识,除了计算机的领域知识外,其对数学知识的要求会比其他的一般计算机方向要高得多,这主要是因为整个研究中,会较多地涉及到加密算法的研究和实现。原则上来说,信安的基础要求会有:

  • 数学知识
    • 线性代数
    • 离散数学
  • 加解密算法
  • 操作系统
  • 网络

另外,信安会有不同的方向,除了上面的基础外,还会有方向上的知识有较高的要求。大体上,分为:

  • Web安全
    • HTTP协议
    • SQL
    • 数据库
    • Javascript、HTML
    • 主流后端语言
  • 端安全
    • 计算机系统
    • 汇编
      • arm
      • x86
    • 终端系统
      • Windows
      • iOS
      • Android
    • 端上主流编程语言

很多时候,在解决问题时,绝对不是“一招鲜,吃遍天”,而是需要各种不同的手段和手法并用,才能快速地解决问题,这个时候基础和平时的积累也就体现了价值。

本文旨在给爬虫领域的信安应用做些抛砖引玉的工作,所以下文会从解决爬虫问题的角度出发来探讨如何利用信安的一些小技巧和工具。再者,笔者也并非信安的从业者,仅以爱好者的身份做过一些实践,由于笔者的知识和经验有限,在描述中可能会存在纰漏和错误,欢迎斧正。

常见的信安手段

这里我列出一些我时常所使用到的信安技巧,实际的案例会远远比空洞的言语描述使得更加清晰。我将工作和生活中曾经用到的一些方法和技巧进行了整理,挑出了一些比较有代表性的案例,进行实战分析,便于大家更好的理解思路和掌握相关的小技巧。

在引言中提到,我们的目的是在“寻找高效实现数据抓取的入口点”。将这个问题展开来看,一般而言大体的目标会分为:

  1. 获取数据
  2. 解密请求
  3. 绕过反爬

下面所列举的不同的手段可能会有不同的使用目的,但基本上都是以这3个为主。我们提到的这些方法并不是瞎蒙瞎尝试出来的,而是很清楚的知道这些方法产生背后的系统或逻辑的实现,对症下药的结果。所以,下面在介绍这些方法时,我们会先简单地说明这个方法解决的问题背后系统的大致逻辑,再来说明我们应对的思路,然后我们再用一个实际的案例来详细的说明,最后附上能够提高我们效率的辅助工具。这样一路跟着看下来,我想应该还是能够让大家有不少的收获。

1. Javascript 调试

2. HTTP请求参数调整

3. 防御系统绕过

4. App逆向

总结

本身数据抓取算是处在一个灰色的地带,其研究的方法与白帽有较多的重合,但是也有其独有的特点,如:

  • 目的性极为明确---数据
  • 要求稳定
  • 可规模化

大体上,我们可以划分为两种模式:

  1. 拿着安全的PoC来做工程化
  2. 用着安全的手段来找突破点

第一点,是不合法的操作,我们这里讲到的基本上都是属于第二点内容。

最后我在唠叨几句,我一直强调说,

眼界决定境界。

同时眼界也决定着方法论集合的大小。在工作中保持好奇,留意每一个领域和技术手段,虽然我们没办法以有限的时间成为各个领域的专家,但是我们可以借助其他领域的知识和方法,来解决我们所在的专业领域所遇到的问题,反之亦然。

知识的深度决定解决问题的效率,知识的广度决定解决问题的方法。

附录

工具永远是提高效率的最佳手段。

这里我整理了我平时使用到的一些工具,方便大家查阅,也欢迎大家向我提交自己所使用的高效率工具。如果数量较多或者更新比较频繁,我会考虑放在Github仓库,目前先放在文章末尾,我会保持列表的更新。

另外,推荐大家关注 awesome系列

子域名挖掘工具

在线工具

  1. dnsdumpster
  2. virustotal
  3. zoomeye
  4. censys
  5. netcraft
  6. crt
  7. shodan
  8. transparencyreport

爆破方式

  • Subbrute - 快速枚举 DNS 记录和子域名。
  • dnscan - 使用 Python 写的基于字典查询 DNS 的子域名扫描器。

Changelog

  • 2019年1月7日 增加“信息安全”章节
  • 2018年12月15日 初始化,公司
# 安全技术在数据采集中的应用 - 补充章节

以下内容用于补充原文中空缺的4个章节。


1. Javascript 调试

背景

现代网站大量使用 JavaScript 进行数据加载、参数加密和反爬检测。理解 JS 的执行逻辑是突破这些限制的关键。

常见场景

  1. 异步数据加载:页面 HTML 中无数据,由 JS 动态请求 API 填充
  2. 参数加密:请求参数(如 signtoken)由 JS 计算生成
  3. 反爬检测:通过 JS 检测浏览器环境、行为特征

调试方法

Chrome DevTools 核心技巧:

// 1. 断点类型
// - 普通断点:点击行号
// - 条件断点:右键行号 -> Add conditional breakpoint
// - XHR 断点:Sources -> XHR/fetch Breakpoints -> 添加 URL 关键词
// - DOM 断点:Elements 面板右键元素 -> Break on

// 2. 搜索加密函数
// Sources 面板 Ctrl+Shift+F 全局搜索关键词:
// - sign、token、encrypt、md5、sha、base64
// - 请求参数名、API 路径

// 3. Hook 关键函数
// 在 Console 中注入:
(function() {
    var _stringify = JSON.stringify;
    JSON.stringify = function(obj) {
        console.log('JSON.stringify called:', obj);
        console.trace(); // 打印调用栈
        return _stringify.apply(this, arguments);
    };
})();

// 4. Hook XMLHttpRequest
(function() {
    var _open = XMLHttpRequest.prototype.open;
    XMLHttpRequest.prototype.open = function(method, url) {
        console.log('XHR:', method, url);
        return _open.apply(this, arguments);
    };
})();

调试混淆代码:

// 1. 格式化:Sources 面板点击 {} 按钮
// 2. 使用 AST 工具还原:
//    - https://astexplorer.net/
//    - 本地使用 babel 解析
// 3. 动态执行追踪:设置断点后观察变量值

// 常见混淆模式识别:
// - 字符串数组 + 解密函数
// - eval/Function 动态执行
// - 控制流平坦化(switch-case 大循环)

实战案例:某电商网站签名破解

// 1. 抓包发现请求带有 sign 参数
// GET /api/goods?id=123&sign=a1b2c3d4...

// 2. 全局搜索 "sign" 找到生成位置
// 发现调用链:getSign() -> md5(params + secret)

// 3. 提取关键逻辑
function getSign(params) {
    var secret = "固定密钥或从页面获取";
    var str = Object.keys(params).sort().map(k => k + '=' + params[k]).join('&');
    return md5(str + secret);
}

// 4. Python 复现
import hashlib
def get_sign(params, secret):
    sorted_str = '&'.join(f'{k}={v}' for k, v in sorted(params.items()))
    return hashlib.md5((sorted_str + secret).encode()).hexdigest()

工具推荐

工具 用途
Chrome DevTools 断点调试、网络分析
Fiddler/Charles HTTP(S) 抓包代理
mitmproxy 可编程的代理,支持修改请求响应
Babel JS AST 解析,用于反混淆
JStillery 在线 JS 反混淆
de4js 浏览器端 JS 解混淆

2. HTTP请求参数调整

背景

很多 API 接口通过参数控制返回数据的范围和格式。合理调整参数可以:

  • 获取更多数据(突破分页限制)
  • 获取隐藏字段
  • 绕过某些校验

常见参数类型

# 1. 分页参数
page=1&size=20      -> 尝试 size=1000
offset=0&limit=10   -> 尝试 limit=500
cursor=xxx          -> 游标翻页,观察规律

# 2. 字段选择
fields=id,name      -> 添加更多字段 fields=id,name,email,phone
select=basic        -> 尝试 select=full 或 select=all

# 3. 格式控制
format=html         -> 改为 format=json 或 format=xml
callback=xxx        -> JSONP,去掉 callback 可能返回纯 JSON

# 4. 版本参数
v=1.0              -> 尝试老版本 v=0.9 可能少校验
api_version=2      -> 尝试 api_version=1

# 5. 调试参数
debug=0            -> debug=1 可能返回更多信息
env=prod           -> env=test/dev

实战技巧

1. 批量获取突破:

# 原始:单个 ID 查询
GET /user?id=123

# 尝试:批量查询
GET /user?id=123,124,125
GET /user?ids[]=123&ids[]=124
POST /user/batch {"ids": [123,124,125]}

2. GraphQL 接口:

# 原始查询
query { user(id: 1) { name } }

# 扩展查询 - 获取更多字段
query { user(id: 1) { name email phone address createdAt } }

# 内省查询 - 获取所有可用字段
query { __schema { types { name fields { name } } } }

3. 绕过限制:

# 服务端可能只检查部分参数
# 原始:必须登录
GET /api/data?token=xxx

# 尝试:添加内部参数
GET /api/data?internal=1
GET /api/data?_skip_auth=1
GET /api/data?source=app  # 可能 APP 端校验较松

4. 编码绕过:

# URL 编码变体
id=123 -> id=%31%32%33

# Unicode 编码
admin -> \u0061\u0064\u006d\u0069\u006e

# 大小写
/Admin/Users -> /admin/users -> /ADMIN/USERS

3. 防御系统绕过

常见反爬机制

类型 检测方式 绕过思路
IP 限制 请求频率、地理位置 代理池、降低频率
User-Agent 浏览器特征 随机 UA、模拟真实浏览器
Cookie/Session 会话有效性 维护登录态、定期刷新
Referer 来源页面 设置正确 Referer
验证码 人机识别 打码平台、行为模拟
JS 渲染 动态加载 Headless 浏览器
指纹检测 浏览器指纹 指纹伪造
行为分析 鼠标轨迹、点击模式 行为模拟

IP 限制绕过

# 代理池基础架构
class ProxyPool:
    def __init__(self):
        self.proxies = []
        self.bad_proxies = set()
    
    def get_proxy(self):
        # 随机选择,排除失效代理
        available = [p for p in self.proxies if p not in self.bad_proxies]
        return random.choice(available)
    
    def report_bad(self, proxy):
        self.bad_proxies.add(proxy)
        
# 代理来源
# 1. 付费代理服务:芝麻、快代理、讯代理
# 2. 自建:云服务器 + Squid/Tinyproxy
# 3. 住宅代理:Luminati、Smartproxy(贵但稳定)

浏览器指纹伪造

# Playwright/Puppeteer 指纹配置
from playwright.sync_api import sync_playwright

def create_browser_context():
    p = sync_playwright().start()
    browser = p.chromium.launch(headless=True)
    
    context = browser.new_context(
        viewport={'width': 1920, 'height': 1080},
        user_agent='Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
        locale='zh-CN',
        timezone_id='Asia/Shanghai',
        geolocation={'latitude': 39.9, 'longitude': 116.4},
        permissions=['geolocation'],
    )
    
    # 注入指纹伪造脚本
    context.add_init_script("""
        // 覆盖 navigator 属性
        Object.defineProperty(navigator, 'webdriver', {get: () => undefined});
        Object.defineProperty(navigator, 'plugins', {get: () => [1, 2, 3, 4, 5]});
        
        // 覆盖 Chrome 特征
        window.chrome = { runtime: {} };
        
        // Canvas 指纹干扰
        const originalToDataURL = HTMLCanvasElement.prototype.toDataURL;
        HTMLCanvasElement.prototype.toDataURL = function(type) {
            if (type === 'image/png') {
                const context = this.getContext('2d');
                const imageData = context.getImageData(0, 0, this.width, this.height);
                for (let i = 0; i < imageData.data.length; i += 4) {
                    imageData.data[i] ^= 1;  // 微小噪声
                }
                context.putImageData(imageData, 0, 0);
            }
            return originalToDataURL.apply(this, arguments);
        };
    """)
    
    return context

验证码处理策略

# 1. 简单验证码:OCR 识别
import ddddocr
ocr = ddddocr.DdddOcr()
result = ocr.classification(image_bytes)

# 2. 滑动验证码:计算滑动距离
# - 图像对比找缺口位置
# - 生成人性化滑动轨迹

def generate_track(distance):
    """生成人性化滑动轨迹"""
    tracks = []
    current = 0
    mid = distance * 4 / 5
    t = 0.2
    v = 0
    
    while current < distance:
        if current < mid:
            a = 2  # 加速
        else:
            a = -3  # 减速
        v0 = v
        v = v0 + a * t
        move = v0 * t + 0.5 * a * t * t
        current += move
        tracks.append(round(move))
    
    return tracks

# 3. 复杂验证码:打码平台
# - 2Captcha
# - Anti-Captcha
# - 超级鹰

4. App逆向

分析流程

1. 抓包分析 -> 确定目标接口和参数
2. 静态分析 -> 反编译查看代码逻辑
3. 动态调试 -> Hook 关键函数,观察运行时数据
4. 提取算法 -> 用目标语言复现签名算法

抓包方法

Android 抓包:

# 1. 系统代理 + 证书安装
# 适用于 Android 7.0 以下,或 targetSdkVersion < 24 的应用

# 2. VirtualXposed + TrustMeAlready
# 免 Root 绕过证书固定

# 3. Frida + objection(推荐)
# 动态绕过 SSL Pinning
pip install frida-tools objection

objection -g com.target.app explore
# 进入后执行:
android sslpinning disable

iOS 抓包:

# 使用 Surge/Shadowrocket 作为代理
# 或使用 SSL Kill Switch 2 (需越狱)

静态分析

Android APK 反编译:

# 1. 解包
apktool d target.apk -o output/

# 2. 转换为 JAR
d2j-dex2jar target.apk -o target.jar

# 3. 查看 Java 代码
# 使用 jadx-gui 打开 APK 或 JAR
jadx-gui target.apk

# 搜索关键词:
# - sign, signature, encrypt, token
# - md5, sha, aes, rsa
# - API 域名、路径

识别加密算法:

// 常见签名模式
// 1. MD5(params + secret)
String sign = MD5.encode(params + "固定密钥");

// 2. HMAC-SHA256
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(key, "HmacSHA256"));
String sign = Base64.encode(mac.doFinal(data));

// 3. 自定义混淆算法
// 需要逐步跟踪还原

动态分析 - Frida

// frida_hook.js
// 基础 Hook 模板

Java.perform(function() {
    // Hook 指定类的方法
    var TargetClass = Java.use("com.target.app.SignUtil");
    
    TargetClass.getSign.implementation = function(params) {
        console.log("Input params: " + params);
        var result = this.getSign(params);
        console.log("Output sign: " + result);
        return result;
    };
    
    // Hook 所有 String 参数的 MD5 调用
    var MessageDigest = Java.use("java.security.MessageDigest");
    MessageDigest.digest.overload('[B').implementation = function(input) {
        console.log("MD5 input: " + bytesToHex(input));
        var result = this.digest(input);
        console.log("MD5 output: " + bytesToHex(result));
        return result;
    };
});

function bytesToHex(bytes) {
    var hex = [];
    for (var i = 0; i < bytes.length; i++) {
        hex.push(('0' + (bytes[i] & 0xFF).toString(16)).slice(-2));
    }
    return hex.join('');
}
# 运行 Frida
frida -U -f com.target.app -l frida_hook.js --no-pause

实战:某 App 签名破解

# 分析结果:sign = md5(timestamp + params_sorted + device_id + secret)

import hashlib
import time

def generate_sign(params: dict, device_id: str) -> str:
    secret = "app_secret_key_from_reverse"
    timestamp = str(int(time.time()))
    
    # 参数排序拼接
    sorted_params = '&'.join(f'{k}={v}' for k, v in sorted(params.items()))
    
    # 组合签名字符串
    sign_str = f"{timestamp}{sorted_params}{device_id}{secret}"
    
    # MD5
    sign = hashlib.md5(sign_str.encode()).hexdigest()
    
    return sign, timestamp

# 使用
params = {'user_id': '123', 'action': 'query'}
device_id = 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'
sign, ts = generate_sign(params, device_id)

headers = {
    'X-Sign': sign,
    'X-Timestamp': ts,
    'X-Device-Id': device_id,
}

工具清单

工具 用途
jadx Android 反编译(推荐)
apktool APK 解包/重打包
Frida 动态插桩框架
objection Frida 的便捷封装
Charles/mitmproxy HTTPS 抓包
IDA Pro Native SO 分析
Ghidra 免费的逆向工具(NSA 出品)
Hopper macOS/iOS 逆向

补充总结

以上四个章节覆盖了爬虫中最常用的安全技术应用:

  1. JS 调试 - 理解前端加密逻辑
  2. 参数调整 - 挖掘 API 的隐藏能力
  3. 防御绕过 - 对抗反爬机制
  4. App 逆向 - 突破移动端限制

核心思路始终是:理解对方的实现逻辑,对症下药

技术手段是中性的,使用时请遵守法律法规,尊重数据所有者的权益。

Zoe

Zoe

全栈开发 · AI 工具制造者 · Go / Flutter / Rust · 开源偏执狂

https://zoe.im

评论