Security · 11 min read · Updated 2026
安全 · 阅读约 11 分钟 · 更新于 2026

Common Web Attacks and How to Defend Against Them

常见 Web 攻击与防御

Almost every successful web attack in the last decade fits into a small number of well-understood categories. The Open Web Application Security Project publishes a list of the most common ones — the OWASP Top 10 — and the list has been remarkably stable. Attackers win not because they invent new tricks, but because developers keep making the same small mistakes: trusting user input, exposing internal endpoints, gluing together dependencies without checking them, and assuming nobody will bother to look. This guide walks through the attacks you are most likely to meet, with concrete defenses for each.

过去十年里,几乎所有成功的 Web 攻击都可以归入少数几个已被充分理解的类别。OWASP(开放式 Web 应用安全项目)会定期公布这些最常见的问题 —— 即 OWASP Top 10 —— 这份列表多年来一直非常稳定。攻击者之所以能得手,不是因为他们发明了什么新花样,而是因为开发者一直在重复同样的小错误:信任用户输入、暴露内部接口、不加检查地拼装依赖,以及"以为没人会来看一眼"。本文逐一介绍你最可能遇到的攻击类型,并给出每种的具体防御方法。

The OWASP Top 10 in 2026: a map of the battlefield

2026 年的 OWASP Top 10:一份战场地图

The OWASP Top 10 is updated roughly every three to four years, drawing on data from hundreds of organizations about which vulnerabilities are actually being exploited. The 2025–2026 edition is dominated by familiar themes with refreshed entries: broken access control, cryptographic failures, injection (still!), insecure design, security misconfiguration, vulnerable and outdated components, identification and authentication failures, software and data integrity failures, security logging and monitoring failures, and server-side request forgery. Some of the categories are new labels for old problems, but each represents a real, observed attack pattern. The first five deserve the deepest treatment.

OWASP Top 10 大约每三到四年更新一次,依据数百家组织提供的数据,统计实际被利用的漏洞。2025–2026 版本依然围绕熟悉的主题,只是换了新条目:访问控制失效、加密失败、注入(仍然在!)、不安全设计、安全配置错误、已知漏洞的脆弱过时组件、身份识别与认证失败、软件与数据完整性失效、安全日志与监控失败、服务器端请求伪造(SSRF)。有些类别只是给老问题贴了新标签,但每一条都对应着真实出现的攻击模式。其中前五项值得深入讨论。

The pattern across them is striking. Almost every category is, at heart, a failure to treat untrusted input as untrusted, or a failure to keep trusted dependencies trustworthy. If you internalize those two ideas, the rest is detail. The defenses are not exotic. They are templates, frameworks, and small habits that thousands of teams have used to lock these holes.

这些类别背后的模式非常一致。几乎所有问题,归根结底都是:没有把不可信输入当作不可信,或者没有保持"受信依赖"确实值得信任。把这两点内化于心,其余的只是细节。防御方法并不稀奇,它们都是模板、框架,以及成百上千团队都使用过的小习惯。

XSS: when the browser trusts user input

XSS:浏览器太信任用户输入

Cross-Site Scripting (XSS) happens when a web application includes attacker-controlled text in a page without escaping it. The browser then interprets that text as HTML or JavaScript and runs it in the security context of the victim's session. There are three flavors:

跨站脚本(XSS)发生在 Web 应用把攻击者可控的文本放进页面、又没有进行转义时。浏览器随后把这些文本当作 HTML 或 JavaScript 来执行,运行在受害者会话的安全上下文里。XSS 有三种形态:

  • Reflected XSS — the malicious string is in the request (often a search query) and echoed back in the response. The attacker tricks the victim into clicking a crafted link.
  • 反射型 XSS —— 恶意字符串出现在请求里(通常是搜索词),随后在响应中原样回显。攻击者引诱受害者点击精心构造的链接。
  • Stored XSS — the malicious string is saved in a database (a comment, a profile field) and served to every visitor. Much more damaging.
  • 存储型 XSS —— 恶意字符串被存进数据库(一条评论、一个个人资料字段),随后分发给所有访问者,危害大得多。
  • DOM-based XSS — the server is innocent; the JavaScript on the page reads a URL fragment or other source and writes it into the DOM unsafely.
  • 基于 DOM 的 XSS —— 服务器完全无辜;页面里的 JavaScript 读取 URL 片段或其他来源,然后以不安全的方式写入 DOM。

The fix has three layers. First, escape on output: every value inserted into HTML, attributes, JavaScript, URLs, and CSS must be encoded for that context. Templating systems like Handlebars, Mustache, React, and Vue do this by default, but only when used as designed — interpolating raw HTML with v-html or dangerouslySetInnerHTML re-introduces the bug. Second, Content Security Policy (CSP): a response header that tells the browser which sources of script are allowed, disabling inline scripts and event handlers by default. A strict CSP is the single most effective defense against XSS payloads, even when an injection slips through. Third, sanitize on input when the data is HTML (rich text editors): use a vetted library like DOMPurify rather than a hand-rolled regex.

修复方案分三层。第一,输出时转义:任何被插入到 HTML、属性、JavaScript、URL 和 CSS 中的值,都必须针对那个上下文进行编码。Handlebars、Mustache、React、Vue 这类模板系统默认就会这么做,但前提是按设计使用 —— 用 v-html 或 dangerouslySetInnerHTML 插原始 HTML 就会重新引入漏洞。第二,内容安全策略(CSP):一个响应头,告诉浏览器允许哪些脚本来源,默认禁用内联脚本和事件处理器。即便有注入漏过,严格的 CSP 也是抵御 XSS payload 最有效的单一防线。第三,当数据是 HTML(如富文本编辑器)时,输入时做净化:用经过审核的库(如 DOMPurify),不要自己写正则。

CSRF: when your browser is too helpful

CSRF:你的浏览器"太勤快"

Cross-Site Request Forgery exploits the fact that browsers automatically attach session cookies to requests, even when the request was triggered from a different site. If you are logged into your bank and you visit a malicious page, that page can submit a hidden form to your bank's transfer endpoint, and the browser cheerfully attaches your session cookie. The bank sees a valid session and processes the transfer.

跨站请求伪造(CSRF)利用的是这样一个事实:浏览器会自动给请求附上会话 Cookie,即便该请求是从另一个网站触发的。如果你已经登录银行,又访问了一个恶意页面,恶意页面就能向银行的转账接口提交一个隐藏表单,而浏览器会"愉快地"附上你的会话 Cookie。银行看到一个有效会话,就执行了转账。

The defenses are well-established. SameSite cookies — set SameSite=Lax or SameSite=Strict on session cookies — are the modern baseline; browsers will not send them on cross-site navigations (for Strict) or only on top-level GET navigations (for Lax). CSRF tokens — per-request, unpredictable tokens that the server validates — are still valuable as a defense in depth. Double-submit cookies and custom request headers on state-changing endpoints are additional patterns. Above all, do not perform state changes on GET; require POST, PUT, PATCH, or DELETE for anything that mutates server state. CSRF cannot forge a request that the browser refuses to send.

防御方法已经非常成熟。SameSite Cookie —— 把会话 Cookie 设为 SameSite=Lax 或 SameSite=Strict —— 是现代的基线:Strict 时浏览器不会在跨站导航中发送该 Cookie;Lax 时只在顶级 GET 导航中发送。CSRF Token —— 每次请求一个不可预测的 token,由服务端校验 —— 仍然是纵深防御中的重要一环。双重提交 Cookie 和在状态变更接口上添加自定义请求头 也是常见补充模式。最关键的一条:不要用 GET 执行状态变更;任何会修改服务端状态的操作都应该用 POST、PUT、PATCH 或 DELETE。CSRF 无法伪造浏览器拒绝发送的请求。

SQL injection and SSRF: the classics still work

SQL 注入与 SSRF:经典仍然有效

SQL injection is decades old and still in the OWASP Top 10 for a reason. When user input is concatenated into a SQL string, an attacker can change the meaning of the query: ' OR 1=1; DROP TABLE users; -- is the famous example. The defense is simple and total: use parameterized queries (prepared statements). The database driver sends the query template and the values separately, so user input can never be interpreted as SQL syntax. Stored procedures are a partial alternative; ORM query builders are another. What is not a defense: escaping quotes by hand, blacklisting keywords, or relying on a web application firewall to "filter" the input.

SQL 注入存在了几十年,仍然位列 OWASP Top 10,是有原因的。当用户输入被拼接到 SQL 字符串中,攻击者就能改变查询的语义:' OR 1=1; DROP TABLE users; -- 是经典例子。防御方法简单而彻底:使用参数化查询(Prepared Statement)。数据库驱动会分开传输查询模板和参数值,用户输入永远不会被解析为 SQL 语法。存储过程是部分替代方案,ORM 的查询构造器也是一种。算不上防御的:手工转义引号、屏蔽关键字、依赖 WAF 去"过滤"输入。

Server-Side Request Forgery (SSRF) is the modern cousin. An application fetches a URL on behalf of the user — a profile picture import, a webhook, an "open graph" preview — and an attacker points that fetch at an internal address: http://169.254.169.254/latest/meta-data/ (the AWS metadata service) or http://localhost:6379/ (an internal Redis). The application reaches it and returns the contents. The fix is strict URL validation: parse the URL, check the scheme (http/https only), resolve the host, and reject any IP that is in a private range, link-local, or loopback. Maintain an allowlist of known external hosts. Disable HTTP redirects, or re-validate after each redirect. Run the fetcher with network-level restrictions (egress filtering, IMDSv2 on AWS).

服务器端请求伪造(SSRF)是 SQL 注入的现代"表亲"。应用代表用户去抓取一个 URL —— 一张头像的导入、一个 Webhook、一段"开放图谱"预览 —— 攻击者把这个抓取指向内网地址:http://169.254.169.254/latest/meta-data/(AWS 元数据服务)或 http://localhost:6379/(内网的 Redis)。应用真的访问到了,并把内容返回。修复方法是严格的 URL 校验:解析 URL、检查协议(只允许 http/https)、解析主机名,并拒绝任何落在私有网段、链路本地或回环地址的 IP。维护一份已知外部主机的白名单。禁用 HTTP 跳转,或者每次跳转后都重新校验。让抓取服务在网络层面受限(出站过滤、AWS 上使用 IMDSv2)。

Supply-chain attacks: when your dependencies betray you

供应链攻击:当你的"队友"出卖了你

Modern applications are mosaics of dependencies. A typical Node.js project pulls in hundreds of packages, each with its own tree of packages, each maintained by individuals or small teams anywhere in the world. The result is that an attacker who compromises a single obscure maintainer can ship malicious code to millions of projects through a routine update. The most dramatic examples are well-known: the SolarWinds compromise (a malicious update to the Orion platform distributed to 18,000 organizations, including US federal agencies), the Log4Shell vulnerability in the ubiquitous log4j library (December 2021, full RCE on millions of servers), and the ua-parser-js, colors.js, and event-stream incidents in the JavaScript ecosystem.

现代应用都是由大量依赖拼装而成的马赛克。一个典型的 Node.js 项目会拉入数百个包,每个包又各自有依赖树,背后是世界各地的个人或小团队维护。结果是:一旦某个不起眼的维护者被攻破,攻击者就能通过一次"例行更新"把恶意代码投递给数百万个项目。最广为人知的案例:SolarWinds 事件(Orion 平台的恶意更新分发给了 1.8 万家组织,包括美国联邦机构)、无处不在的 log4j 库的 Log4Shell 漏洞(2021 年 12 月,影响数百万台服务器上的远程代码执行),以及 JavaScript 生态中的 ua-parser-js、colors.js、event-stream 等事件。

Defenses have evolved. Lockfiles (package-lock.json, yarn.lock, Pipfile.lock, Cargo.lock) pin exact versions so a malicious update cannot silently land. Integrity hashes (SHA-512 in npm's lockfile, subresource integrity for CDN-loaded scripts) catch tampering. Signed packages (Sigstore, npm provenance attestations, PEP 740) cryptographically prove that a published version came from the actual maintainer. SBOMs (Software Bills of Materials) make it possible to answer "are we affected by CVE-2024-XXXX?" in minutes rather than weeks. Reproducible builds let you verify that the binary you got is byte-for-byte the one the maintainer produced. None of these are silver bullets, but together they raise the cost of an attack substantially.

防御手段也在演进。Lockfile(package-lock.json、yarn.lock、Pipfile.lock、Cargo.lock)锁定具体版本,让恶意更新不会悄悄溜进来。完整性哈希(npm lockfile 里的 SHA-512、CDN 脚本的 SRI)能发现篡改。包签名(Sigstore、npm provenance、PEP 740)以密码学方式证明发布的版本确实来自真实维护者。SBOM(软件物料清单)让人能在几分钟而不是几周内回答"我们是否受 CVE-2024-XXXX 影响"。可复现构建让你能验证拿到的二进制和作者构建出的那个逐字节相同。这些都不是银弹,但合在一起,能显著抬高攻击成本。

Clickjacking, dependency confusion, and other sneaky tricks

点击劫持、依赖混淆以及其他阴险招数

Clickjacking loads your site inside an invisible <iframe> on an attacker-controlled page, then tricks the user into clicking a button at a specific screen coordinate. The defense is the X-Frame-Options: DENY header (or the modern Content-Security-Policy: frame-ancestors 'none' directive), which tells the browser never to allow your site to be framed.

点击劫持把目标站点嵌入攻击者控制页面的一个不可见 <iframe> 中,然后诱导用户点击屏幕上某个特定坐标的按钮。防御手段是 X-Frame-Options: DENY 响应头(或更现代的 Content-Security-Policy: frame-ancestors 'none' 指令),它告诉浏览器永远不要让目标站点被嵌套在框架里。

Dependency confusion exploits package managers that prefer public registries over private ones. An attacker publishes a package to the public registry with the same name as an internal package and a higher version number; the build picks up the public one. The fix is to configure scopes or explicit registry mappings so internal names cannot leak, and to use integrity hashes for everything.

依赖混淆利用的是那些"公共源优先"的包管理器。攻击者把一个与内部包同名的包发布到公共源,并使用更高版本号;构建系统就拉到了公共的那个。修复方法是配置 scope 或显式 registry 映射,让内部名字不会泄露到公网,并对所有依赖使用完整性哈希

Subdomain takeover, open redirects, password reset poisoning, prototype pollution in JavaScript, XML External Entity (XXE) injection, race conditions in payment systems, mass assignment in ORMs — the list goes on. The good news is that the underlying defense is the same across most of them: validate input, authenticate every action, authorize every access, log what happens, and treat your dependency tree as a real part of your attack surface.

子域名接管、开放重定向、密码重置投毒、JavaScript 中的原型污染、XML 外部实体(XXE)注入、支付系统中的竞态条件、ORM 中的批量赋值 —— 类似清单还能列下去。好消息是,它们背后共通的防御方法是一样的:校验输入、为每个操作认证、为每次访问授权、记录发生的事情,并把依赖树当作真实的攻击面来对待。

Security through obscurity never works

靠隐蔽保安全,从来不管用

It is worth stating directly. Hiding a secret URL, an obscure admin path, or a custom-built crypto algorithm does not protect you. The history of security is a long sequence of "we changed the port from 22 so nobody will find our SSH" being defeated by single-digit minutes of scanning. The history of cryptography is a graveyard of proprietary ciphers broken before the press run finished cooling.

有一件事值得直说:靠藏住某个 URL、某个冷门管理路径、或者自研的密码算法,并不能保护你。安全史就是一部长长的"我们把端口从 22 改了所以没人能扫到我们的 SSH"被几分钟扫描击破的历史。密码学史就是一座坟场,里头躺满了在印张还烫手时就被攻破的私有密码。

Obscurity has a place — it slows down an attacker and may make them move on to an easier target. But it is not a defense. Real security comes from cryptography that is open, reviewed, and standardized; from frameworks and libraries that have been audited by thousands; from defaults that are safe; and from architectures that fail closed. The moment you tell yourself "nobody knows about this", imagine that everyone does — and design as if they do. The web is large, scanners run constantly, and the cost of looking is nearly zero.

隐蔽自有它的位置 —— 它能拖慢攻击者,也许能让他们转去找更简单的目标。但它本身并不是防御。真正的安全来自开放的、被审查的、标准化的密码学;来自被数千人审计过的框架和库;来自开箱即安全的默认配置;以及来自"失败即关闭"的架构。在你对自己说"没人知道这个"的那一刻,请设想所有人都知道 —— 并照此设计。Web 极其庞大,扫描器一刻不停,查看的成本几乎为零。

Try the tools

试试这些工具

The defenses above assume you have strong, unique credentials for the accounts that hold the keys. Generate them with the password generator, hash and verify test strings with the Base64 tool, or format JSON safely with the JSON formatter. Every tool runs in your browser; nothing you type is uploaded to a server.

上面这些防御方法,前提是你握有强而独立的凭据来保护那些"钥匙"。用密码生成器生成它们,用Base64 工具对测试字符串做哈希与校验,或用JSON 格式化工具安全地格式化 JSON。所有工具都在你的浏览器中运行,输入内容不会上传到服务器。