$$var 是高危操作,因它允许攻击者通过用户输入动态控制变量名,导致变量污染、配置覆盖甚至远程代码执行,必须通过映射隔离或哈希前缀等方式切断用户输入与变量解析的直连路径。

直接用 $$var 或 ${$var} 接收用户输入并赋值/执行,等于把变量命名权交给攻击者——这在生产环境里基本等同于开门放狗。
为什么 $$user_input 是高危操作
可变变量不是语法糖,它是运行时动态解析变量名的机制。一旦 $user_input 来自 $_GET、$_POST 或任何未过滤上下文,攻击者就能构造任意变量名,覆盖关键配置、注入恶意值,甚至配合 eval、assert 触发远程代码执行。
- 典型危险链:
$a = $_GET['key']; $$a = $_GET['val'];→ 攻击者传?key=_SESSION&val=1可篡改会话内容 - 更隐蔽的写法:
${$_POST['x']} = $_POST['y'];,表面看没用eval,但已具备变量污染能力 - 和可变函数叠加时风险倍增:
$func = $_GET['f']; $$func();→ 既控制函数名,又控制调用变量名
$$var 在哪些场景下“看似安全”实则危险
开发者常误以为加了白名单或类型判断就安全,但绕过方式极多,且逻辑漏洞往往藏在边界条件里。
- 白名单校验不严格:只检查
$var是否在数组中,但没限制其长度或字符集,攻击者可用str_replace绕过或利用 PHP 的变量名自动截断(如超长名只取前 64 字节) - 类型强制转换失效:
$var = (string)$_GET['k']; $$var = 'x';——(string)不阻止空字节、控制字符或 Unicode 归一化绕过 - 与魔术方法联动:
class A { public function __get($n) { return $this->{$n}; } } $obj->${$_GET['p']},触发__get时仍走变量解析
真正可行的规避策略,不是禁用而是隔离
完全禁止可变变量不现实(比如某些模板引擎、配置映射逻辑依赖它),关键是切断用户输入到变量名解析的直连路径。
立即学习“PHP免费学习笔记(深入)”;
- 永远不把原始请求参数直接喂给
$$或${};先做明确的映射:$map = ['name' => 'user_name', 'email' => 'user_email']; $key = $map[$_GET['field'] ?? ''] ?? null; if ($key) $$key = $_GET['value']; - 若必须动态生成变量名,用哈希或编码固定前缀:
$safe_key = 'dyn_' . md5($_GET['id']); $$safe_key = $data;,这样攻击者无法预测或复现变量名 - 在开发/测试环境开启
display_errors = Off+log_errors = On,因为可变变量出错时(如$$null)常触发 Notice 级错误,暴露内部逻辑 - 静态分析工具(如 PHPStan、Psalm)能识别
$$模式,建议在 CI 流程中加入规则:if (preg_match('/\$\$[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/', $code)) { die('Forbidden variable variable usage'); }
最容易被忽略的一点:可变变量和 autoloader 的交互风险
当类名或命名空间来自用户输入,再配合 __autoload 或 spl_autoload_register,$$class_name 可能意外触发类加载,而加载过程若含 eval、include 或反射调用,就会形成第二跳漏洞链。
这种组合在 CMS 插件机制、路由分发器中很常见,但调试时很难复现——因为只有当变量名恰好匹配某个未加载类时才触发,属于概率型漏洞。上线前务必检查所有 autoload 回调是否对输入做过滤,尤其警惕 str_replace、ucfirst 等“看似无害”的处理函数。



















