首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 相關知識

PHP 可變變數(Variable Variables)完整教學與安全風險分析

     編輯:PHP 相關知識

什麼是可變變數?

PHP 允許用一個變數的「值」作為另一個變數的「名稱」。語法:在變數前加雙美元符號 $$var。

基礎範例

<?php
$a = 'hello';
$$a = 'world'; // 等同於 $hello = 'world';

echo "$a ${$a}"; // hello world
echo "$a $hello"; // hello world
?>

此時存在兩個變數:$a (值: 'hello')、$hello (值: 'world')。

陣列與可變變數的歧義

$$a[1] 有兩種解讀:

  • ${$a[1]}:$a[1] 的值作為變數名
  • ${$a}[1]:$a 的值作為變數名,取該陣列索引 1

解決:用大括號明確指定

<?php
$a = ['foo', 'bar'];
${$a[1]} = 'baz'; // $bar = 'baz'
${$a}[1] = 'qux'; // $foo[1] = 'qux'
?>

物件屬性的可變存取

<?php
class Foo {
  public $bar = 'I am bar.';
  public $baz = ['apple', 'banana', 'cherry'];
}

$obj = new Foo();
$prop = 'bar';
echo $obj->$prop . "\n"; // I am bar.

$idx = 1;
echo $obj->$baz[$idx] . "\n"; // banana
?>

可變屬性名從「呼叫處的區域變數」查找,不是從物件內部。

限制與禁忌

場景是否支援說明
Superglobals ($GLOBALS, $_GET...)❌語法錯誤
$this❌特殊變數,不可動態命名
函數/方法名稱✅$func = 'strlen'; $func('abc'); 可呼叫
靜態屬性 Class::$var✅$class = 'Foo'; $var = 'bar'; $class::$$var

實戰應用場景

1. 動態表單欄位對應

<?php
$fields = ['username', 'email', 'phone'];
foreach ($fields as $field) {
  ${'form_' . $field} = $_POST[$field] ?? '';
  // 產生 $form_username, $form_email, $form_phone
}
?>

2. 設定檔動態載入

<?php
$config = [
  'db_host' => 'localhost',
  'db_name' => 'myapp',
  'db_user' => 'root',
];
foreach ($config as $key => $value) {
  define(strtoupper($key), $value);
  // 或 ${$key} = $value; // 產生區域變數
}
?>

3. 模板引擎簡易實作

<?php
function render($tpl, $data) {
  foreach ($data as $k => $v) {
    ${$k} = $v; // 將資料展開為區域變數
  }
  ob_start();
  include $tpl;
  return ob_get_clean();
}

// template.php: 你好,!
echo render('template.php', ['name' => '張三']);
?>

⚠️ 安全風險深度剖析

可變變數 + 雙引號字串解析 = 程式碼注入風險。

漏洞演示

<?php
// 惡意輸入:${phpinfo()}
$a = 'phpinfo';
$a(); // 直接呼叫函數

// 進階:嵌套可變變數
$a = "phpinfo";
${$a()}; // 仍會執行 phpinfo()

// 極簡單行版(Webshell 原型)
$a = "${${phpinfo()}}";
// 執行流程:
// 1. 雙引號解析 -> 遇到 ${...} 執行內部運算式
// 2. 內層 ${phpinfo()} -> 呼叫 phpinfo() 並取得回傳值 (true/1)
// 3. 外層 ${1} -> 存取 $1 (不存在) -> 但 phpinfo() 已經執行了!
?>

單引號 vs 雙引號的關鍵差異

<?php
$a = 'phpinfo()';
echo $a;       // 輸出字串 "phpinfo()"
echo '$a';     // 輸出字串 "$a"
echo "$a";     // 輸出字串 "phpinfo()" (變數解析)
// 但若:
$a = 'phpinfo';
echo "$a()";   // 輸出 "phpinfo()" -- 只是字串拼接,不會執行
// 唯有在 ${...} 或 ${...()} 等「運算式語境」才會真正執行
?>

防護原則

  1. 絕不將使用者輸入直接用於可變變數、可變函數、eval()、preg_replace('/e') 等動態執行語境
  2. 若需動態存取,白名單驗證:if (in_array($input, $allowed)) { $$input = ... }
  3. 關閉 allow_url_include、enable_dl 等危險設定
  4. 使用靜態分析工具(Psalm, PHPStan)偵測動態特性濫用

現代 PHP 的替代方案

需求舊寫法 (可變變數)現代寫法
動態屬性存取$obj->$prop$obj->{$prop} (明確大括號) + 型別提示
動態方法呼叫$obj->$method()$obj->{$method}() + method_exists() 檢查
動態陣列鍵${$key}$data[$key] (陣列天生支援動態鍵)
設定管理可變變數展開單一 Config 物件 / 陣列 / 環境變數

結語

可變變數是 PHP 獨特的動態特性,理解其運作機制有助於閱讀舊代碼、除錯奇異錯誤。但新專案應盡量避免使用,改用陣列、物件、依賴注入等結構化方式,並嚴格防範動態執行注入風險。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved