<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Web 安全 on 我在栀南镇的日子</title>
    <link>https://blog.ifcalm.org/tags/web-%E5%AE%89%E5%85%A8/</link>
    <description>Recent content in Web 安全 on 我在栀南镇的日子</description>
    <generator>Hugo -- 0.147.7</generator>
    <language>zh-cn</language>
    <lastBuildDate>Tue, 01 Sep 2026 00:00:00 +0000</lastBuildDate>
    <atom:link href="https://blog.ifcalm.org/tags/web-%E5%AE%89%E5%85%A8/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>第 1 篇：注入的一般形式——为什么「过滤危险字符」是错的解法</title>
      <link>https://blog.ifcalm.org/posts/security/web/01-injection-general-form/</link>
      <pubDate>Tue, 01 Sep 2026 00:00:00 +0000</pubDate>
      <guid>https://blog.ifcalm.org/posts/security/web/01-injection-general-form/</guid>
      <description>从一个查订单的接口讲起：你以为你在往句子里填一个空，实际上你在改这句话的语法结构。这一篇先让你撞上这件事，再给出覆盖 SQL、命令、模板、XSS、反序列化的统一说法，然后用能跑的代码证明「过滤危险字符」为什么方向就错了——1 OR 1=1 不含任何一个危险字符。最后给出按强度排序的三层防御，以及参数化管不到的地方。</description>
    </item>
    <item>
      <title>第 2 篇：SQL 注入——参数化都用了，为什么还会中招</title>
      <link>https://blog.ifcalm.org/posts/security/web/02-sql-injection/</link>
      <pubDate>Tue, 01 Sep 2026 00:00:00 +0000</pubDate>
      <guid>https://blog.ifcalm.org/posts/security/web/02-sql-injection/</guid>
      <description>上一篇说「用参数化」。这一篇假设你已经照做了——所有查询都参数化了——然后给你三个它挡不住的场景：恶意值从你自己的数据库里来（二阶注入）、接口什么都不回显却照样被逐位读走（盲注）、以及参数化在定义上就管不到的动态列名。核心是一句话：参数化保护的是「拼接的那一点」，不是「数据的入口」。全部用 Python 标准库可复现。</description>
    </item>
    <item>
      <title>第 3 篇：表达式语言注入——你以为在填值，其实在求值</title>
      <link>https://blog.ifcalm.org/posts/security/web/03-expression-injection/</link>
      <pubDate>Tue, 01 Sep 2026 00:00:00 +0000</pubDate>
      <guid>https://blog.ifcalm.org/posts/security/web/03-expression-injection/</guid>
      <description>前两篇的解析器读的是查询和命令，后果是读写数据。这一篇的解析器读的是代码本身，后果直接跳到「接管整个进程」。从一个「支持变量的通知模板」讲起，用标准库两段能跑的代码，展示同一个填值的位置怎么先变成读全局密钥、再变成执行任意代码——这就是 OGNL、SpEL、JNDI、SSTI、以及 Log4Shell 与 Struts/Equifax 共用的那台机器。</description>
    </item>
    <item>
      <title>第 4 篇：命令注入——shell 才是那个解析器</title>
      <link>https://blog.ifcalm.org/posts/security/web/04-command-injection/</link>
      <pubDate>Tue, 01 Sep 2026 00:00:00 +0000</pubDate>
      <guid>https://blog.ifcalm.org/posts/security/web/04-command-injection/</guid>
      <description>把用户输入拼进一条 shell 命令，解析它的是 shell，而 shell 的元字符远不止一个分号。这一篇用标准库演示：分号、管道、&amp;amp;&amp;amp;、$() 各是一条独立的注入路径；给变量&amp;rsquo;加引号&amp;rsquo;为什么救不了；换成 argv 数组为什么能根治 shell 解析——以及它没能根治的那件事：参数注入。</description>
    </item>
    <item>
      <title>第 5 篇：XSS——注入的目标是别人的浏览器</title>
      <link>https://blog.ifcalm.org/posts/security/web/05-xss/</link>
      <pubDate>Tue, 01 Sep 2026 00:00:00 +0000</pubDate>
      <guid>https://blog.ifcalm.org/posts/security/web/05-xss/</guid>
      <description>XSS 是注入的一种，只是解析器换成了受害者的浏览器、被注入的语法是 HTML/JS。这一篇用标准库证明三件事：一份 html.escape 为什么只在“HTML 文本节点”一个上下文里安全（对 javascript: 和无引号属性一个字符都改不动）；用真实的 html.parser 站在浏览器那一侧看，转义过的值放进无引号属性后仍然解析出了事件处理器；以及用一个真实的 HTTP 服务器测出——URL 片段里的 payload 根本不发给服务端，所以服务端转义对 DOM 型完全够不着。</description>
    </item>
    <item>
      <title>第 6 篇：不安全反序列化——「还原对象」本身就是执行</title>
      <link>https://blog.ifcalm.org/posts/security/web/06-insecure-deserialization/</link>
      <pubDate>Tue, 01 Sep 2026 00:00:00 +0000</pubDate>
      <guid>https://blog.ifcalm.org/posts/security/web/06-insecure-deserialization/</guid>
      <description>反序列化不是&amp;rsquo;把字节读成数据&amp;rsquo;，而是&amp;rsquo;按字节的指示重建对象&amp;rsquo;——而&amp;rsquo;重建&amp;rsquo;这一步可以携带并触发代码。这一篇用标准库 pickle 证明：一段看着只是数据的字节，loads 时就执行了代码；再用 json 对照，说明为什么换成&amp;rsquo;只能表达数据&amp;rsquo;的格式，同一个攻击就无处落脚。这是第 3 篇&amp;rsquo;填值处藏求值器&amp;rsquo;的孪生兄弟。</description>
    </item>
    <item>
      <title>第 7 篇：密码存储——为什么快哈希是错的</title>
      <link>https://blog.ifcalm.org/posts/security/web/07-password-storage/</link>
      <pubDate>Tue, 01 Sep 2026 00:00:00 +0000</pubDate>
      <guid>https://blog.ifcalm.org/posts/security/web/07-password-storage/</guid>
      <description>W2 开篇。密码存储的每一个决定，都在为&amp;rsquo;数据库已经泄露&amp;rsquo;这个前提做准备。用标准库演示三件事：sha256 这类快哈希为什么让弱密码几乎瞬破、加盐为什么能废掉彩虹表、慢哈希为什么用&amp;rsquo;拖慢自己&amp;rsquo;来拖垮攻击者。以及一个容易忽略的收尾：比较哈希要用时序恒定比较。</description>
    </item>
    <item>
      <title>第 8 篇：会话与令牌——签发、存储、撤销的一整条命</title>
      <link>https://blog.ifcalm.org/posts/security/web/08-session-tokens/</link>
      <pubDate>Tue, 01 Sep 2026 00:00:00 +0000</pubDate>
      <guid>https://blog.ifcalm.org/posts/security/web/08-session-tokens/</guid>
      <description>登录只是把&amp;rsquo;你是谁&amp;rsquo;换成一个令牌，之后每个请求都靠它。这一篇顺着令牌的一生走：生成要不可预测（random 和 secrets 的天壤之别）、登录后必须换新令牌（会话固定）、以及存储与撤销的取舍。核心是把&amp;rsquo;认证&amp;rsquo;和&amp;rsquo;会话&amp;rsquo;分开看——密码对不对是一瞬间的事，令牌有没有被人接管是往后每一个请求的事。</description>
    </item>
    <item>
      <title>第 9 篇：JWT——签名是给谁看的</title>
      <link>https://blog.ifcalm.org/posts/security/web/09-jwt/</link>
      <pubDate>Tue, 01 Sep 2026 00:00:00 +0000</pubDate>
      <guid>https://blog.ifcalm.org/posts/security/web/09-jwt/</guid>
      <description>JWT 把用户信息和一个签名装进令牌自己身上，服务端不用存。但它的坑几乎都出在&amp;rsquo;签名到底验没验、用什么验&amp;rsquo;上。这一篇手写 JWT，实测三个经典误用：接受 alg=none（无签名）、算法混淆、以及最常见的——直接读 payload 不验签。核心是一句：payload 是明文，谁都能改；唯一能拦住篡改的是签名，而签名只有在你正确验证时才有意义。</description>
    </item>
    <item>
      <title>第 10 篇：OAuth 与 MFA——把第三方和第二因子接进来</title>
      <link>https://blog.ifcalm.org/posts/security/web/10-oauth-mfa/</link>
      <pubDate>Tue, 01 Sep 2026 00:00:00 +0000</pubDate>
      <guid>https://blog.ifcalm.org/posts/security/web/10-oauth-mfa/</guid>
      <description>W2 收尾，讲两件把外部环节接进认证的事：用第三方登录（OAuth）和加第二因子（MFA）。OAuth 的坑集中在&amp;rsquo;回调回来的这个 code 是不是本会话发起的&amp;rsquo;——缺了 state 就是登录 CSRF。MFA 的坑在于&amp;rsquo;开了 ≠ 安全&amp;rsquo;：用一张矩阵说清短信/TOTP/推送/passkey 各自挡得住和挡不住什么，为什么只有绑定源域的 passkey 能扛住实时钓鱼。</description>
    </item>
    <item>
      <title>第 11 篇：横向越权（IDOR）——改个数字看别人的数据</title>
      <link>https://blog.ifcalm.org/posts/security/web/11-idor/</link>
      <pubDate>Tue, 01 Sep 2026 00:00:00 +0000</pubDate>
      <guid>https://blog.ifcalm.org/posts/security/web/11-idor/</guid>
      <description>IDOR 是最不起眼也最常见的漏洞：接口按 id 取资源，却没检查这个 id 是不是当前用户的。把 URL 里的 1001 改成 1002，就看到了别人的订单。这一篇用十几行说清它的机制，以及为什么真正的修复不是&amp;rsquo;把 id 换成猜不到的 UUID&amp;rsquo;，而是&amp;rsquo;把归属校验写进每一次数据访问&amp;rsquo;。</description>
    </item>
    <item>
      <title>第 12 篇：纵向越权——「前端不显示」从来不是访问控制</title>
      <link>https://blog.ifcalm.org/posts/security/web/12-privilege-escalation/</link>
      <pubDate>Tue, 01 Sep 2026 00:00:00 +0000</pubDate>
      <guid>https://blog.ifcalm.org/posts/security/web/12-privilege-escalation/</guid>
      <description>纵向越权是普通用户够到了管理员的能力。它最常见的成因只有一句话：鉴权做在了前端（藏起按钮、隐藏菜单），而后端端点谁都能调。这一篇证明「藏按钮 ≠ 鉴权」，说清鉴权该放在哪一层、为什么必须默认拒绝，以及为什么客户端上的任何检查都只是体验、不是安全。</description>
    </item>
    <item>
      <title>第 13 篇：CSRF——浏览器替受害者带上了 cookie</title>
      <link>https://blog.ifcalm.org/posts/security/web/13-csrf/</link>
      <pubDate>Tue, 01 Sep 2026 00:00:00 +0000</pubDate>
      <guid>https://blog.ifcalm.org/posts/security/web/13-csrf/</guid>
      <description>W4 开篇，先讲同源策略到底保护什么。CSRF 钻的正是它的一个空子：浏览器向某站发请求会自动带上该站的 cookie，不管请求从哪个页面发起。于是&amp;rsquo;只认 cookie&amp;rsquo;的状态改变端点能被任意站点伪造。这一篇起一个真实的 HTTP 服务器，用标准库的 http.cookiejar 当受害者的浏览器——cookie 由它按浏览器规则自动附带，于是“跨站提交就把邮箱改掉了”这件事是真的跑出来的。再说清 CSRF 令牌为什么有效、以及 SameSite 把这道防线前移到了浏览器。</description>
    </item>
    <item>
      <title>第 14 篇：CORS——给同源策略开的口子别开成敞口</title>
      <link>https://blog.ifcalm.org/posts/security/web/14-cors/</link>
      <pubDate>Tue, 01 Sep 2026 00:00:00 +0000</pubDate>
      <guid>https://blog.ifcalm.org/posts/security/web/14-cors/</guid>
      <description>CORS 不是&amp;rsquo;防护&amp;rsquo;，是同源策略的一份放行名单——它决定哪些外站的 JS 能读你的响应。配错了，等于把上一篇讲的那道&amp;rsquo;拦读内容&amp;rsquo;的墙拆掉。这一篇先用一个真实的服务器复现最常见的致命配置：把请求的 Origin 原样反射回去 + 允许携带凭据；再说清为什么真正的洞几乎总是&amp;rsquo;动态反射 Origin&amp;rsquo;而不是一个星号；最后把四种常见的&amp;rsquo;支持所有子域&amp;rsquo;写法跑一遍，看哪几种会放进攻击者自己能注册的域名。</description>
    </item>
    <item>
      <title>第 15 篇：Clickjacking 与 postMessage——窗口之间的信任</title>
      <link>https://blog.ifcalm.org/posts/security/web/15-clickjacking-postmessage/</link>
      <pubDate>Tue, 01 Sep 2026 00:00:00 +0000</pubDate>
      <guid>https://blog.ifcalm.org/posts/security/web/15-clickjacking-postmessage/</guid>
      <description>W4 收尾，讲两个跨窗口的信任问题。Clickjacking：攻击者用透明 iframe 把你的页面叠在他的页面上，用户以为点的是这边、其实点的是那边——防线是告诉浏览器&amp;rsquo;我不许被别人嵌&amp;rsquo;。postMessage：跨窗口通信时接收方不校验来源，就把任何页面发来的消息当可信处理。两段 demo 都得在浏览器里跑，因为这两件事的判定权都在浏览器手里：能不能被 iframe、消息的 origin 是什么，都是它说了算。</description>
    </item>
    <item>
      <title>第 16 篇：越界读——长度字段来自对方</title>
      <link>https://blog.ifcalm.org/posts/security/web/16-out-of-bounds-read/</link>
      <pubDate>Tue, 01 Sep 2026 00:00:00 +0000</pubDate>
      <guid>https://blog.ifcalm.org/posts/security/web/16-out-of-bounds-read/</guid>
      <description>Heartbleed 的核心只有一句话：它信任了对方声称的长度。你发 5 字节说自己是 25 字节，服务器就回给你 25 字节——多出来的 20 字节是隔壁内存里别人的数据。这一篇用最小骨架复现这个机制，说明为什么&amp;rsquo;读&amp;rsquo;也能是重大漏洞，以及为什么&amp;rsquo;凡是来自对方的长度/大小/偏移都要用实际数据核验&amp;rsquo;是一条通用规则。</description>
    </item>
    <item>
      <title>第 17 篇：解析器差异——同一段字节，两个理解</title>
      <link>https://blog.ifcalm.org/posts/security/web/17-parser-differential/</link>
      <pubDate>Tue, 01 Sep 2026 00:00:00 +0000</pubDate>
      <guid>https://blog.ifcalm.org/posts/security/web/17-parser-differential/</guid>
      <description>很多漏洞不在&amp;rsquo;解析错了&amp;rsquo;，而在&amp;rsquo;两个解析器把同一段字节解析成了不同的东西&amp;rsquo;。当你的前端代理和后端服务器对同一个请求的理解不一致，两者理解的分歧处就能塞进一个走私的请求。这一篇用可运行的例子复现 HTTP 请求走私的核心分歧，并把它上升为一条通用教训：任何&amp;rsquo;多个组件各自解析同一份输入&amp;rsquo;的地方，都要保证它们理解一致。</description>
    </item>
    <item>
      <title>第 18 篇：依赖投毒——你装的包，不一定是作者写的包</title>
      <link>https://blog.ifcalm.org/posts/security/web/18-dependency-poisoning/</link>
      <pubDate>Tue, 01 Sep 2026 00:00:00 +0000</pubDate>
      <guid>https://blog.ifcalm.org/posts/security/web/18-dependency-poisoning/</guid>
      <description>W6 开篇。你的代码里，自己写的可能只占几个百分点，其余全是依赖——而每一个依赖都是一条你没审过的代码进入你系统的通道。这一篇讲攻击者往这条通道里塞东西的几条路：抢注、依赖混淆、账号接管、维护者移交。其中依赖混淆用真实的 pip 复现：两个源里放同名包，pip 自己跳过内网源选走了公共源那个高版本。</description>
    </item>
    <item>
      <title>第 19 篇：构建与 CI——源码干净，不等于产物干净</title>
      <link>https://blog.ifcalm.org/posts/security/web/19-build-ci/</link>
      <pubDate>Tue, 01 Sep 2026 00:00:00 +0000</pubDate>
      <guid>https://blog.ifcalm.org/posts/security/web/19-build-ci/</guid>
      <description>上一篇防的是&amp;rsquo;装进来的依赖有毒&amp;rsquo;。这一篇往上游一步：连你自己的代码，从源码到用户手里的产物之间，还隔着构建和 CI 这一段——而这一段本身就是攻击面。SolarWinds 的教训是&amp;rsquo;源码干净、构建被改、产物带毒&amp;rsquo;；Codecov 的教训是&amp;rsquo;一个 CI 脚本卷走了环境里的所有密钥&amp;rsquo;。核心两句：验证要验最终产物、CI 令牌要最小权限。</description>
    </item>
    <item>
      <title>第 20 篇：配置与暴露——「默认能跑」不等于「默认安全」</title>
      <link>https://blog.ifcalm.org/posts/security/web/20-misconfiguration/</link>
      <pubDate>Tue, 01 Sep 2026 00:00:00 +0000</pubDate>
      <guid>https://blog.ifcalm.org/posts/security/web/20-misconfiguration/</guid>
      <description>W7 开篇。前面十九篇多在讲代码里的漏洞，这一篇讲一类根本不用写错代码的漏洞：你什么都没做错，只是用了默认配置——而默认配置是为&amp;rsquo;开箱能跑&amp;rsquo;优化的，不是为&amp;rsquo;安全&amp;rsquo;优化的。调试模式、默认口令、目录列举、暴露的管理端口，逐项看它们各自泄露了什么，以及为什么&amp;rsquo;默认拒绝&amp;rsquo;要贯彻到配置层。</description>
    </item>
    <item>
      <title>第 21 篇：密钥进了版本库——轮换，不是删 commit</title>
      <link>https://blog.ifcalm.org/posts/security/web/21-secrets-in-git/</link>
      <pubDate>Tue, 01 Sep 2026 00:00:00 +0000</pubDate>
      <guid>https://blog.ifcalm.org/posts/security/web/21-secrets-in-git/</guid>
      <description>全清单收尾。一个密钥被 commit 进了 git，你发现后删掉文件再提交——用真实 git 仓库证明：密钥还在历史里，任何 clone 都带着它。这一篇讲清&amp;rsquo;删文件 ≠ 删历史&amp;rsquo;，以及为什么唯一可靠的修复是&amp;rsquo;假设它已泄露、立即轮换&amp;rsquo;，而不是想办法把它从历史里擦干净。</description>
    </item>
    <item>
      <title>第 22 篇：SSRF——你的服务器替攻击者发请求</title>
      <link>https://blog.ifcalm.org/posts/security/web/22-ssrf/</link>
      <pubDate>Tue, 01 Sep 2026 00:00:00 +0000</pubDate>
      <guid>https://blog.ifcalm.org/posts/security/web/22-ssrf/</guid>
      <description>只要你的服务端会去访问一个用户给的 URL——抓网页标题、下载头像、调 webhook——攻击者就能把这个地址改成你的内网。这一篇用真实的服务器演示凭据是怎么被取走的，再用操作系统的 DNS 解析证明：拿主机名做黑名单在定义上就必输，因为 127.1、2130706433、0x7f000001 都解析到同一个地址。正确的修复是先解析、判断解析出来的 IP、再连到那个 IP。</description>
    </item>
    <item>
      <title>第 23 篇：路径穿越——文件名也是一个地址</title>
      <link>https://blog.ifcalm.org/posts/security/web/23-path-traversal/</link>
      <pubDate>Tue, 01 Sep 2026 00:00:00 +0000</pubDate>
      <guid>https://blog.ifcalm.org/posts/security/web/23-path-traversal/</guid>
      <description>只要你的代码里有 os.path.join(某个目录, 用户给的名字)，就要看这一篇。用标准库演示三件事：../ 怎么跳出去；为什么给一个绝对路径时 join 会把你的基准目录整个丢掉（这一条最容易漏）；以及为什么 replace(&amp;rsquo;../&amp;rsquo;,&amp;rsquo;&amp;rsquo;) 这种清洗被 &amp;hellip;.// 一击即破。正确的修复和 SSRF 那篇是同一句话——别判断名字长什么样，判断它最终落在哪。</description>
    </item>
    <item>
      <title>第 24 篇：XXE——解析器替你把文件读了出来</title>
      <link>https://blog.ifcalm.org/posts/security/web/24-xxe/</link>
      <pubDate>Tue, 01 Sep 2026 00:00:00 +0000</pubDate>
      <guid>https://blog.ifcalm.org/posts/security/web/24-xxe/</guid>
      <description>你收了一份 XML，只想读里面两个字段。可 XML 这门格式允许文档自己声明「这里插入某个文件的内容」，而解析器会照做——于是一次「解析」变成了一次任意文件读取，甚至一次 SSRF。这一篇用同一份文档、同一个解析器、只差一个开关，把它跑出来：关着的时候什么都没有，打开的时候密钥被读了出来。核心是一句：保护你的是一个默认值，不是一条定律。</description>
    </item>
    <item>
      <title>第 25 篇：批量赋值——你没打算让他改的那个字段</title>
      <link>https://blog.ifcalm.org/posts/security/web/25-mass-assignment/</link>
      <pubDate>Tue, 01 Sep 2026 00:00:00 +0000</pubDate>
      <guid>https://blog.ifcalm.org/posts/security/web/25-mass-assignment/</guid>
      <description>「把请求体里的字段更新到用户对象上」是每个框架都提供的便利。问题是：请求体里有哪些字段，是攻击者说了算的。这一篇的 demo 值得单看——它的 SQL 完全参数化、一处拼接都没有，第 2 篇的功课全做对了，普通用户照样把自己变成了管理员。因为这一次，可控的不是值，是字段名。</description>
    </item>
    <item>
      <title>第 26 篇：竞态与 TOCTOU——检查过了，然后世界变了</title>
      <link>https://blog.ifcalm.org/posts/security/web/26-race-condition/</link>
      <pubDate>Tue, 01 Sep 2026 00:00:00 +0000</pubDate>
      <guid>https://blog.ifcalm.org/posts/security/web/26-race-condition/</guid>
      <description>一张只能用一次的优惠券，同时发五个请求过去，五个都兑换成功了。代码没写错任何一行：先查、再判、再改，逻辑无懈可击——只是这三步之间有缝，而并发会钻进那条缝。这一篇用真实的数据库和真实的线程把它跑出来（每次都是 5 比 1），并给出唯一可靠的修法：别把检查和动作分成两步。</description>
    </item>
  </channel>
</rss>
