Likejacking攻击原理与前端防御实战方案

玲玲 安全 阅读 2,001
赞 28 收藏
二维码
手机扫码查看
反馈

我的写法,亲测靠谱

Likejacking 这玩意儿,说白了就是利用 Facebook 的 Like 按钮(或其他社交按钮)的 iframe 埋点机制,在用户无感知下触发点赞、分享等操作。它早年是黑产常用手段,现在主流平台都做了防护,但——如果你在做社交组件封装、第三方 SDK、或者需要嵌入外部 like 按钮的后台系统,它依然会从缝隙里钻出来咬你一口。

Likejacking攻击原理与前端防御实战方案

我去年在给一个内容聚合平台做“一键分享到 Facebook”功能时,就撞上了 Likejacking 的边角料问题:不是被攻击,而是自己写的封装逻辑,被误判为潜在 Likejacking 行为,导致 Facebook 的 X-Frame-Options 和 CSP 策略直接拦截了按钮渲染。折腾了两天,翻了 FB 的文档、调试了几十次 iframe 加载链路,最后发现根本不是代码逻辑错,而是加载时机 + DOM 插入方式踩中了它的启发式检测规则。

所以我的最佳实践核心就一条:永远不要动态插入未授权来源的社交按钮 iframe,更别用 innerHTML 直接拼接。下面是我现在固定下来的写法:

<div id="fb-like-container" data-url="https://jztheme.com/article/123"></div>
<script>
  // 等 FB SDK 加载完成后再初始化
  window.fbAsyncInit = function() {
    FB.init({
      appId: 'YOUR_APP_ID',
      xfbml: true,
      version: 'v19.0'
    });
  };

  (function(d, s, id) {
    var js, fjs = d.getElementsByTagName(s)[0];
    if (d.getElementById(id)) return;
    js = d.createElement(s); js.id = id;
    js.src = 'https://connect.facebook.net/en_US/sdk.js';
    fjs.parentNode.insertBefore(js, fjs);
  }(document, 'script', 'facebook-jssdk'));

  // 手动触发渲染,确保 DOM 稳定后再调用
  document.addEventListener('DOMContentLoaded', () => {
    const container = document.getElementById('fb-like-container');
    if (container && !container.hasAttribute('data-rendered')) {
      // 用 FB.XFBML.parse 显式触发,不依赖自动扫描
      FB.XFBML.parse(container);
      container.setAttribute('data-rendered', 'true');
    }
  });
</script>

为什么这样写?因为 Facebook 的 JS SDK 会监听 DOM 变化,自动扫描 fb-like 标签。但如果你在 innerHTML += '

' 这种方式插入,SDK 可能还没加载完,或者扫描时 DOM 还没稳定,它就会跳过,甚至某些浏览器下(特别是 Safari 的 iframe 隐私策略)直接拒绝加载。而手动调用 FB.XFBML.parse(),配合 DOMContentLoaded 和显式标记 data-rendered,能保证每次只渲染一次、只在合适时机渲染——这比靠运气等自动扫描靠谱得多。

这几种错误写法,别再踩坑了

以下这些,都是我在项目里亲手写过、线上炸过、回滚过三次的反面案例,列出来省得你重蹈覆辙:

  • 用 jQuery 的 .html().append() 直接塞 iframe:比如 $container.html('')。这是 Likejacking 最典型的载体形式,Facebook 的前端防护层(尤其是新版的 fb-root 安全校验)会直接拒绝加载,控制台报 Refused to display 'https://www.facebook.com/' in a frame because an ancestor violates the following Content Security Policy directive —— 别怀疑,就是它干的。
  • 在 React/Vue 组件里用 v-htmldangerouslySetInnerHTML 渲染 FB 按钮 HTML 片段:哪怕你只是 copy-paste 官方给的 snippet,只要没走 SDK 初始化流程,它就是个裸 iframe。现代框架的 diff 机制还会反复销毁重建,更容易触发 FB 的防劫持逻辑。我见过最离谱的一次:Vue 组件 mounted 后 render 了按钮,3 秒后因数据更新又重新 render 了一遍,FB 直接判定为“可疑重复注入”,整个域名被临时限流。
  • 把 FB SDK 加载脚本写在 <head> 里,然后在 <body> 顶部就写 <div class="fb-like"></div>:看起来很规范对吧?但问题在于,FB SDK 是异步加载的,而 class="fb-like" 的 DOM 元素在页面解析时就被静态写了进去。SDK 加载完后扫描,可能已经错过首次渲染时机,尤其在弱网下,按钮永远是空白。这不是 bug,是 race condition。
  • fetch 拿 FB 按钮的 HTML 字符串再插入:比如请求 https://jztheme.com/api/fb-like?articleId=123 返回一段带 iframe 的 HTML,再 insertAdjacentHTML 插入。这等于把 Likejacking 的整套流程自己复现了一遍——服务端生成 iframe src,前端无校验插入。FB 的防护机制对这种“服务端兜底+前端盲插”的组合特别敏感,轻则按钮不显示,重则你的 app ID 被临时限制。

实际项目中的坑

除了代码层面,还有几个非技术但致命的细节:

第一,别信“官方 snippet 就一定安全”。Facebook 文档里给的那段代码,是给静态网站用的。你把它塞进 Webpack 打包流程、放进 SSR 渲染(比如 Next.js)、或者混在微前端子应用里,大概率出问题。我们当时在微前端主应用里直接挂载子应用的 FB 按钮,结果子应用卸载时没清理 FB 全局对象,下一个子应用加载时 SDK 初始化失败,报 FB is not defined —— 查了三小时才发现是全局污染。

第二,CSP 策略必须加 frame-src 'self' https://www.facebook.com;。很多人只配了 script-src,忘了 frame-src。Chrome 90+ 开始,frame-src 已经取代 child-src 成为强制项。漏配的话,按钮 iframe 直接 403,连加载日志都看不到。

第三,“用户未登录”场景容易被忽略。FB 按钮在用户未登录 FB 时,会展示一个灰色“Login to like”按钮。这个按钮点击后弹的是 FB 官方 OAuth 流程,但如果你的页面本身有 CSP 或者 Samesite Cookie 限制,OAuth 回跳可能会失败。我们曾在线上遇到用户点登录后卡在空白页,排查发现是我们的 set-cookie 响应头没加 SameSite=None; Secure,导致 FB 的回调 cookie 被浏览器丢弃。

第四,别在测试环境硬编码 APP_ID。我们有个 staging 环境用了生产 APP_ID,结果测试人员随便点了个 like,Facebook 把行为记到了生产账号下……虽然没造成资损,但运营同事跑来问“为啥我们公众号被点了 500 次赞”,尴尬了整整半天。

结尾

以上是我踩坑近两年总结出来的 Likejacking 相关实战经验。它不是那种“一招鲜吃遍天”的技术,而是一堆边界条件、加载顺序、策略配置和平台演进交织出来的灰度地带。没有银弹,只有一个个 case 的血泪适配。

目前这套写法在我们三个上线项目里稳定跑了 8 个月,没再出现按钮不显示、被限流、或误判为恶意行为的情况。当然,它也不是最优解——比如它还是依赖全局 FB 对象,和现代模块化开发有点拧巴;后续我打算抽时间封装成自定义 Hook(React)或 Composition API(Vue),彻底隔离 SDK 依赖。

如果你有更好的方案,比如完全绕开 iframe、用 Graph API 手动构造分享链接(虽然没点赞按钮那么直观),或者在 SSR 场景下更优雅的处理方式,欢迎评论区交流。这个坑,咱们一起填。

本文章不代表JZTHEME立场,仅为作者个人观点 / 研究心得 / 经验分享,旨在交流探讨,供读者参考。
发表评论

暂无评论