Skip to content

前端安全考察

面试官考察安全,不是要你背漏洞定义,而是看你在编码习惯架构设计中有没有“红线意识”。 考察形式通常有三种:①直接问“XSS和CSRF的区别”②给你一段代码让你找漏洞③给一个场景(如文件上传、登录)问你需要注意什么

考点一:XSS(跨站脚本攻击)—— 前端头号杀手

  • 基础认知(面试官怎么问)

    • 问法1:“什么是XSS?有哪些类型?”
    • 问法2:“看这段代码 <div></div>,如果 userInput<script>alert(1)</script> 会发生什么?怎么修?”
    • 问法3:“React 中 dangerouslySetInnerHTML 为什么危险?”
  • 进阶原理(三大类型与防御本质)

    • 反射型(非持久):恶意脚本藏在URL参数里(如 ?q=<script>...),服务端直接回显到页面。攻击者诱导用户点击恶意链接。
    • 存储型(持久):恶意脚本存入数据库(如评论区、昵称),每次访问页面自动执行。危害最大(如某年微博蠕虫)。
    • DOM型:前端JS直接操作DOM(如 innerHTML = location.hash),绕过服务端过滤。
    • 防御本质永远不要信任用户的输入。核心原则是 “输出时转义(Escape)”,而不是“输入时过滤”(因为输出场景不同,富文本可能需要保留HTML)。
  • 源码/实战回答(结合你的项目,重点背)

    (基础回答) “在 WeLink IM 这种高频通讯场景,我严格遵循 ‘默认转义,按需放行’ 策略。React 的 JSX 默认会转义所有字符串(< 变成 &lt;),这是第一道防线。但如果有富文本消息(支持加粗、斜体),绝对不能直接拼接 HTML,必须用 DOMPurify 库进行消毒(Sanitize),只保留白名单标签,过滤 onerroronload 等事件属性。” (代码示例——找漏洞 + 修复)

    javascript
    // ❌ 高危代码(面试官最爱问)
    function Message({ content }) {
      return <div dangerouslySetInnerHTML={{ __html: content }} />;
    }
    // 如果 content = '<img src=x onerror="alert(1)">',弹窗直接执行!
    
    // ✅ 正确修复(结合你的实际写法)
    import DOMPurify from 'dompurify';
    function SafeMessage({ content }) {
      const cleanHtml = DOMPurify.sanitize(content, {
        ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'span'], // 白名单
        ALLOWED_ATTR: ['class'] // 只允许class,禁用onerror/onload
      });
      return <div dangerouslySetInnerHTML={{ __html: cleanHtml }} />;
    }

    (结合 IAMP 项目):“在 IAMP 隧道检测平台,我接收后端返回的点云文件路径,如果文件名是用户可控的(比如 ../etc/passwd),我不会直接拼接到 fetch URL 中。我会做 路径规范化(Normalize),限制只能访问特定目录下的 .las 文件。”

考点二:CSRF(跨站请求伪造)—— 借刀杀人

  • 基础认知(面试官怎么问)

    • 问法1:“用户登录了银行A,点开了黑客的钓鱼网站B,为什么B网站能转走A的钱?”
    • 问法2:“怎么防御 CSRF?Token 放在 Header 还是 Cookie?”
  • 进阶原理(为什么能成功)

    • 核心漏洞点:浏览器发送请求时,会自动携带目标域名的 Cookie(即使是第三方网站发起的请求)。如果服务器只靠 Cookie 识别身份,黑客就能让用户“不知不觉”发起恶意请求(如修改密码、转账)。
    • 防御三件套(必须脱口而出)
      1. SameSite Cookie(最强基础):设置 Set-Cookie: SameSite=Strict,跨站请求完全不携带 Cookie。如果业务需要跨站跳转带 Cookie,用 SameSite=Lax(允许顶级导航带,禁止 POST 跨站带)。
      2. CSRF Token(同步令牌):服务端下发给前端一个随机字符串,前端在请求体或 Header(如 X-CSRF-Token)中带上。黑客无法读取页面源码拿到 Token,所以无法伪造。
      3. 双重 Cookie 验证:请求时取 Cookie 里的值放在 Header,服务端比对两者是否一致(较古老,不推荐,因为子域可能被污染)。
  • 源码/实战回答(结合你的项目)

    “在 IRMP 报告生成平台(涉及合同金额敏感数据),我配合后端实现了 双重 CSRF 防御

    1. Cookie 设置:登录成功后,后端在 Set-Cookie 中携带 SameSite=Strict,确保跨站请求无法携带身份凭证。
    2. 请求拦截器(Axios):我在全局请求拦截器中,从服务端下发的 Meta 标签读取 Token,自动添加到请求头 X-CSRF-Token

    为什么需要两层?因为曾经发现有个别老旧浏览器不认 SameSite,所以前端主动带 Token 做兜底。”

    javascript
    // 你的 Axios 全局拦截器(展示给面试官看)
    axios.interceptors.request.use(config => {
      // 从 meta 标签获取服务端下发的 CSRF Token
      const token = document.querySelector('meta[name="csrf-token"]')?.content;
      if (token) {
        config.headers['X-CSRF-Token'] = token;
      }
      return config;
    });

考点三:点击劫持(Clickjacking)—— 看不见的陷阱

  • 基础认知:黑客把你的网站放在一个透明的 <iframe> 里,诱导用户点击看似“抽奖”的按钮,实际上点到了“转账”或“删除”按钮。

  • 进阶防御(响应头):服务端设置 X-FRAME-OPTIONS 响应头。

    • DENY:禁止任何页面嵌套。
    • SAMEORIGIN:只允许同域嵌套。
    • ALLOW-FROM uri:只允许指定域名嵌套(老旧)。
  • 实战结合(WeLink 企业级要求)

    “华为 WeLink 涉及企业内部通讯录和邮件,为了防止钓鱼,我在 Nginx 配置中强制添加了 add_header X-Frame-Options DENY always;。同时在登录页面,我用 window.top !== window.self 做前端 JS 兜底,检测到被嵌套就自动跳转破窗(Break Out)。”

考点四:CSP(内容安全策略)—— 终极白名单

  • 基础认知:CSP 是浏览器自带的一套“白名单”机制,告诉浏览器哪些资源(脚本、样式、图片)是合法的,不在白名单的一律不加载/不执行。即使 XSS 注入成功,恶意脚本也跑不起来。

  • 进阶回答(展示架构视野)

    “我们在 WeLink 生产环境开启了严格的 CSP 策略:Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline' https://cdn.wechat.com;。这意味着只允许加载同域脚本和指定 CDN 脚本,禁止 eval() 执行字符串代码(防止动态注入)。 注意事项:开启 CSP 后,如果用了 Vue/React 的运行时编译(template 字符串),会被 CSP 拦截报错。所以必须使用 Vue/React 的运行时构建(Runtime-only),即预编译好的 render 函数。这一点在 IRMP 项目升级 Vite 时我特别注意过。”

  • 基础认知(面试官怎么问)

    • 问法1:“登录后的 Token 应该存哪里?localStorage 还是 Cookie?”
    • 问法2:“前端代码里可以写死数据库密码吗?为什么?”
  • 进阶原理与答法(必杀技)

    • Token 存储之争(面试高频杠精题)
      • localStorage:容易受 XSS 攻击窃取(JS 能直接读)。
      • Cookie 且设置 HttpOnly:JS 无法读取,抵御 XSS 窃取,但容易受 CSRF 攻击。
      • 终极方案(你的选择):“在 WeLink 这种高安全级别应用,我们采用 ‘双管齐下’:身份凭证(Refresh Token)存在 HttpOnly Cookie 中防止 XSS 窃取;同时利用 SameSite=Lax 防止 CSRF。前端内存中只存一个短暂的 Session Token,用于接口鉴权。这样即便 XSS 注入成功,黑客也拿不到最重要的长令牌。”
    • 源码暴露:“Webpack/Vite 打包后的代码依然可以被反编译。绝对不能在代码里硬编码 数据库密码、AK/SK 密钥。我们所有机密配置通过 环境变量(.env)+ 后端接口下发 的方式注入,并且敏感字段在打包时会被 transform 替换为占位符。”

考点六:文件上传安全(结合你的 IRMP 报告上传)

  • 基础认知:用户上传 PDF/图片,存在恶意脚本伪装成文件的风险。

  • 实战回答(全面防御)

    “在 IRMP 平台支持用户上传检测报告附件。我做了三层防御:

    1. 前端限制(防君子)accept=".pdf,.docx" 限制文件选择类型。
    2. 文件头校验(魔数检测):上传前用 JS 读取文件前 4 个字节(如 %PDF),确保真实类型匹配扩展名,防止 .exe 伪装成 .pdf
    3. 重命名存储:上传前建议后端采用 UUID + 时间戳 重命名文件,剥离原始文件名中的危险字符(如 ../ 路径穿越)。”

考点七:依赖库安全(供应链攻击)

  • 基础认知:npm 生态中,一个恶意库可能盗取环境变量。

  • 进阶回答(体现责任心)

    “在项目中,我每周会跑一次 npm audit 检查已知漏洞。引入新库时,我遵循 ‘三原则’

    1. 看下载量(优先每周 > 10万 的稳定库)。
    2. 看维护状态(最近 3 个月有 commit)。
    3. 看依赖树(避免引入 left-pad 这种单行库或带有 postinstall 高危脚本的库)。

    在 WeLink,大型版本升级前,我会用 pnpmlockfile 与基线版本做 diff,审查所有间接依赖的升级变动,防止恶意版本偷渡。”

面试现场组合技(必杀话术——安全总结陈词)

如果面试官最后问:“你觉得前端安全的核心原则是什么?”,请用这段话来收尾,展现你的格局:

“面试官,在我看来,前端安全没有绝对的金钟罩,核心原则是 ‘纵深防御(Defense in Depth)’

  1. 编码层面:默认转义(React/Vue 自动转义),杜绝 innerHTML 拼接。
  2. 传输层面:全站 HTTPS + 安全响应头(CSP、X-Frame-Options)。
  3. 存储层面:敏感凭证存 HttpOnly Cookie,废弃 localStorage 存 Token 的习惯。
  4. 流程层面:依赖库定期审计 + 代码 Review 必查 dangerouslySetInnerHTMLeval

我举个具体例子:在 WeLink IM,即使开发图方便用了 innerHTML,MR 阶段一定会被打回,因为安全是华为的 ‘红线’。正是因为这套机制,WeLink 才通过了金融等高安全等保认证。”

关于 XSS 攻击,你提到的 xssprotect 这类方法,正是企业级防御体系中的一环。下面我从攻击方式和防御体系两个方面,帮你系统梳理一下。

🎯 一、 XSS 的三种注入方式

XSS 攻击的核心,就是攻击者找到了“注入”恶意脚本的入口。根据注入方式的不同,主要分为三类:

  • 1. 存储型 XSS(Stored XSS)—— 危害最大

    • 原理:恶意脚本被永久存储在目标服务器的数据库、留言板等地方。当用户正常访问页面时,这些脚本会从服务器端取出并执行。
    • 常见场景:在博客评论区、论坛帖子、用户个人信息等可以持久化存储文本的地方植入恶意代码。
    • 核心特征数据来源于服务器端数据库,影响所有访问该页面的用户
  • 2. 反射型 XSS(Reflected XSS)—— 最普遍

    • 原理:恶意脚本不存储在服务器,而是作为** URL 参数**的一部分发送给服务器。服务器未经处理,直接将这段参数“反射”回浏览器并执行。
    • 常见场景:攻击者构造一个含恶意参数的链接,通过钓鱼邮件等方式诱导用户点击。
    • 核心特征数据来源于当前请求的 URL,是一次性的,通常通过诱导用户点击链接触发
  • 3. DOM 型 XSS(DOM-based XSS)—— 纯前端

    • 原理:攻击者利用前端 JavaScript 代码本身的漏洞,通过修改页面 DOM 环境来执行恶意脚本。整个过程不经过服务器。
    • 常见场景:前端代码直接使用 document.writeinnerHTMLeval() 等不安全的方式处理来自 document.URLlocation.hash 等用户可控的数据。
    • 核心特征恶意代码的源头是客户端的 JavaScript 逻辑,不依赖服务器响应

🛡️ 二、 企业级 XSS 防御:四层纵深体系

企业通常采用“纵深防御”策略,构建多层次的防护体系。

  • 第一层:代码层的“过滤与净化”

    • 原则:永远不要信任用户的输入。
    • 做法
      1. 输入验证:对用户输入进行白名单校验,只接受符合预期格式的数据。
      2. 输出编码(核心):根据数据输出的上下文(HTML、属性、JavaScript等),将特殊字符(如 <, >, ")转义为对应的HTML实体。
      3. 使用净化库:对于需要保留部分富文本的场景,使用专业的净化库(如 DOMPurifyjs-xss)。
      4. 使用安全的 API:在操作 DOM 时,优先使用 textContent 替代 innerHTML,避免使用 eval() 等危险函数。
  • 第二层:浏览器层面的“安全策略”

    • Content Security Policy (CSP) —— 终极武器:通过设置 HTTP 响应头 Content-Security-Policy,告诉浏览器哪些资源是合法的,只执行白名单内的脚本。
    • X-XSS-Protection 响应头 —— 旧版浏览器兜底:这是一个非标准标头,用于启用旧版浏览器的内置XSS过滤器。但由于其存在安全问题且已被CSP取代,现代企业更推荐使用 CSP
  • 第三层:数据存储层的“安全加固”

    • HttpOnlySecure 的 Cookie:为关键的认证 Cookie 设置 HttpOnly 属性,使其无法被 JavaScript 读取,从根本上防止 XSS 窃取会话;设置 Secure 属性,确保其仅通过 HTTPS 传输。
  • 第四层:流程与工具层的“持续防护”

    • 依赖管理:定期使用 npm audit 等工具检查并升级项目依赖,修复已知漏洞。
    • 自动化扫描:在 CI/CD 流程中集成自动化安全扫描工具,尽早发现潜在问题。
    • 安全培训与代码审查:定期进行安全培训,并将 XSS 防护作为 Code Review 的硬性检查项。

💡 关于你提到的 xssprotect

你提到的 xssprotect 是一个自定义的过滤函数,这本质上就是“代码层的过滤与净化”实践。

一个典型的自定义 xssprotect 函数会做这些事情:

  1. 字符替换:将 <, >, ", ', & 等特殊字符替换为对应的 HTML 实体。
  2. 移除危险标签:通过正则表达式移除 <script>, <iframe>, onerror= 等危险的标签和属性。

需要注意的是,虽然自定义方法简单直接,但很容易因为考虑不周而被绕过。在企业级应用中,更推荐使用经过社区广泛验证的成熟库(如 DOMPurify),它们对各种复杂的攻击手段有更完善的防护。

💎 总结与面试话术

你可以这样组织你的回答:

“面试官,关于 XSS 防护,我从防御体系的角度来阐述。首先,XSS 根据注入方式分为存储型、反射型和 DOM 型三种。

其次,企业的常规做法是构建四层纵深防御体系

  1. 代码层:核心是‘过滤与净化’,包括输入验证、输出编码和使用 DOMPurify 等专业库。您提到的 xssprotect 就是一种自定义的过滤函数。
  2. 浏览器层:通过设置 CSP (内容安全策略) 响应头,限制浏览器只执行白名单内的脚本,这是最有效的防御手段。
  3. 数据层:为关键 Cookie 设置 HttpOnlySecure 属性,防止其被脚本窃取。
  4. 流程层:通过依赖管理、自动化扫描和代码审查,确保持续安全。

总的来说,XSS 防御是一个系统工程,需要从代码到架构全方位进行考量。”

最近更新