授權綁定、資產管理、節點識別等場景常需取得伺服器/客戶端硬體唯一識別碼。PHP 在 Windows/Linux 下可透過 exec()/shell_exec() 呼叫系統指令獲取 CPU ID、主機板序號、MAC 位址等資訊。本文提供跨平台實作、安全考量與替代方案。
function getWindowsCpuId(): ?string {
$cmd = 'wmic cpu get ProcessorId /value';
$output = @shell_exec($cmd);
if (!$output) return null;
// 輸出格式:ProcessorId=BFEBFBFF000906E9
if (preg_match('/ProcessorId\s*=\s*(\S+)/i', $output, $m)) {
return strtoupper(trim($m[1]));
}
return null;
}
function getWindowsBoardSerial(): ?string {
$cmd = 'wmic baseboard get SerialNumber /value';
$output = @shell_exec($cmd);
if (!$output) return null;
if (preg_match('/SerialNumber\s*=\s*(\S+)/i', $output, $m)) {
return trim($m[1]);
}
return null;
}
function getWindowsMacAddresses(): array {
$cmd = 'getmac /fo csv /nh';
$output = @shell_exec($cmd);
if (!$output) return [];
$macs = [];
$lines = explode("\n", $output);
foreach ($lines as $line) {
$line = trim($line);
if ($line === '') continue;
// CSV 格式:"Realtek PCIe GBE Family Controller","\Device\Tcpip_{...}","00-11-22-33-44-55"
if (str_getcsv($line, ',', '"') === false) continue;
$cols = str_getcsv($line, ',', '"');
if (count($cols) >= 3) {
$mac = strtoupper(str_replace('-', ':', $cols[2]));
// 過濾 00:00:00:00:00:00、虛擬介面
if ($mac !== '00:00:00:00:00:00' && !preg_match('/^(00:50:56|00:0C:29|00:1C:14)/', $mac)) {
$macs[] = $mac;
}
}
}
return $macs;
}
function getLinuxCpuId(): ?string {
$content = @file_get_contents('/proc/cpuinfo');
if (!$content) return null;
// 取第一顆 CPU 的 serial(ARM)或 processor ID(x86 需 dmidecode)
if (preg_match('/^Serial\s+:\s+(\S+)/m', $content, $m)) {
return strtoupper($m[1]); // ARM Raspberry Pi 等
}
// x86 通常無 serial,改用 dmidecode(需 root)
return null;
}
function getLinuxBoardSerial(): ?string {
$cmd = 'dmidecode -s baseboard-serial-number 2>/dev/null';
$output = @shell_exec($cmd);
return $output ? trim($output) : null;
}
function getLinuxMacAddresses(): array {
$macs = [];
$netDir = '/sys/class/net/';
if (is_dir($netDir)) {
foreach (scandir($netDir) as $iface) {
if ($iface === '.' || $iface === '..' || $iface === 'lo') continue;
$path = $netDir . $iface . '/address';
if (is_file($path)) {
$mac = strtoupper(trim(file_get_contents($path)));
if ($mac !== '00:00:00:00:00:00') $macs[] = $mac;
}
}
}
return $macs;
}
class HardwareId {
public static function getMachineFingerprint(): string {
$parts = [];
// CPU
$cpu = PHP_OS_FAMILY === 'Windows'
? self::getWindowsCpuId()
: self::getLinuxCpuId();
if ($cpu) $parts[] = 'cpu:' . $cpu;
// 主機板
$board = PHP_OS_FAMILY === 'Windows'
? self::getWindowsBoardSerial()
: self::getLinuxBoardSerial();
if ($board) $parts[] = 'board:' . $board;
// MAC 位址(取第一張實體網卡)
$macs = PHP_OS_FAMILY === 'Windows'
? self::getWindowsMacAddresses()
: self::getLinuxMacAddresses();
if ($macs) $parts[] = 'mac:' . $macs[0];
// 組合雜湊作為指紋
return hash('sha256', implode('|', $parts));
}
// ... 以上私有靜態方法 ...
}
| 風險 | 說明 | 緩解措施 |
|---|---|---|
| 指令注入 | 參數未過濾導致任意命令執行 | 不使用使用者輸入拼接指令;固定指令字串 |
| 權限不足 | dmidecode、wmic 需管理員/root | 服務帳號授予最小權限;或改讀 /sys、/proc |
| 資訊洩露 | 硬體 ID 屬敏感資產資訊 | 僅在授權邏輯使用,不記錄日誌、不回傳前端 |
| 虛擬化/容器環境 | MAC/CPU ID 可能重複或為虛擬值 | 結合多項指標、使用雲端實例 ID(AWS instance-id、Azure VM ID) |
| 函數被禁用 | disable_functions=exec,shell_exec,... | 改用 com_dotnet (Windows WMI)、php_sysinfo 擴充、純 PHP 讀 /proc//sys |
com_dotnet 擴充(Windows WMI)if (class_exists('COM')) {
$wmi = new COM('winmgmts://./root/cimv2');
$cpus = $wmi->ExecQuery('SELECT ProcessorId FROM Win32_Processor');
foreach ($cpus as $cpu) {
echo $cpu->ProcessorId;
}
}
/etc/machine-id(Linux systemd)$machineId = @file_get_contents('/etc/machine-id');
if ($machineId) echo trim($machineId); // 32 字元 hex
php_sysinfo / php_hardware PECL 擴充提供結構化硬體資訊 API,效能優於指令呼叫,但需編譯安裝。
結語:跨平台取得硬體 ID 需處理指令差異、權限限制、虛擬化環境等複雜度。建議封裝為 HardwareId 類別,提供統一介面與降級策略。授權系統應採多因子指紋+容差機制,避免單點故障導致合法用戶無法使用。