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

PHP 線上編輯器實作:簡易程式碼編輯與儲存功能

     編輯:PHP 相關知識

前言:線上編輯器的應用場景與風險

線上編輯 PHP 檔案功能常用於 CMS 後台模板編輯、快速修改設定檔、教學示範環境。但這是極高風險的功能:一旦被攻擊者利用,可直接寫入 Web Shell 控制整個伺服器。本文實作一個簡易版本並詳細說明安全防護措施。

基礎實作 (教學用,生產環境嚴禁直接使用)

<?php
// 簡單的存取控制:固定密碼參數 (實際應用應用健全的認證授權)
if (($_GET['login'] ?? '') !== 'fuying') {
    header('HTTP/1.1 403 Forbidden');
    exit('存取被拒絕');
}

header('Content-Type: text/html; charset=utf-8');

// 處理 Magic Quotes (PHP 5.4+ 已移除,保留相容)
if (function_exists('get_magic_quotes_gpc') && get_magic_quotes_gpc()) {
    foreach ($_POST as $k => $v) {
        $_POST[$k] = stripslashes($v);
    }
}

// 處理儲存
if (isset($_POST['file'], $_POST['text'])) {
    $file = $_POST['file'];
    $content = $_POST['text'];

    // ⚠️ 關鍵安全檢查 (必須實作)
    if (!isValidEditableFile($file)) {
        die('非法檔案路徑');
    }

    $fp = @fopen($file, 'wb');
    if ($fp) {
        $bytes = fwrite($fp, $content);
        fclose($fp);
        $msg = $bytes !== false ? "✅ 儲存成功 ($bytes bytes)" : "❌ 寫入失敗";
    } else {
        $msg = "❌ 無法開啟檔案 (權限不足或路徑錯誤)";
    }
}

// 讀取檔案內容供編輯
$editFile = $_GET['file'] ?? $_POST['file'] ?? __FILE__;
$content = file_exists($editFile) ? file_get_contents($editFile) : '';

// 防 XSS 輸出
$safeFile = htmlspecialchars($editFile);
$safeContent = htmlspecialchars($content);
$safeMsg = htmlspecialchars($msg ?? '');
?>
<!DOCTYPE html>
<html>
<head>
    <title>PHP 線上編輯器</title>
    <style>
        body { font-family: monospace; padding: 20px; max-width: 1000px; margin: 0 auto; }
        textarea { width: 100%; height: 500px; font-family: 'Monaco', 'Consolas', monospace; font-size: 14px; line-height: 1.5; }
        .btn { padding: 8px 16px; background: #007bff; color: white; border: none; cursor: pointer; }
        .msg { padding: 10px; margin: 10px 0; background: #f8f9fa; border-left: 4px solid #007bff; }
    </style>
</head>
<body>
    <h1>PHP 線上編輯器</h1>
    <p><strong>⚠️ 警告:</strong>此功能極度危險,僅限受信任管理員在受控環境使用!</p>
    
    <?php if ($safeMsg): ?>
        <div class="msg"><?= $safeMsg ?></div>
    <?php endif; ?>

    <form method="post">
        <input type="hidden" name="login" value="fuying" />
        <div>
            <label>檔案路徑: </label>
            <input type="text" name="file" size="80" value="<?= $safeFile ?>" />
        </div>
        <br/>
        <textarea name="text"><?= $safeContent ?></textarea>
        <br/><br/>
        <button type="submit" name="submit" class="btn">儲存</button>
    </form>
</body>
</html>

關鍵安全函數 isValidEditableFile()

function isValidEditableFile(string $path): bool {
    // 1. 正規化路徑
    $realPath = realpath($path);
    if (!$realPath) return false; // 檔案不存在

    // 2. 限制在允許目錄內 (防止目錄遍歷)
    $allowedDirs = [
        realpath(__DIR__ . '/templates'),
        realpath(__DIR__ . '/config'),
        realpath(__DIR__ . '/themes'),
    ];
    
    $allowed = false;
    foreach ($allowedDirs as $dir) {
        if (strpos($realPath, $dir) === 0) {
            $allowed = true;
            break;
        }
    }
    if (!$allowed) return false;

    // 3. 限制副檔名
    $allowedExt = ['php', 'html', 'htm', 'tpl', 'css', 'js', 'json', 'xml', 'txt', 'ini', 'yaml', 'yml'];
    $ext = strtolower(pathinfo($realPath, PATHINFO_EXTENSION));
    if (!in_array($ext, $allowedExt)) return false;

    // 4. 排除敏感檔案
    $forbidden = ['config.php', 'database.php', '.env', 'bootstrap.php', 'autoload.php'];
    if (in_array(basename($realPath), $forbidden)) return false;

    // 5. 檢查寫入權限
    if (!is_writable($realPath)) return false;

    return true;
}

進階安全防護清單

風險對策實作優先級
任意檔案寫入 (Web Shell)路徑白名單、副檔名白名單、內容掃描🔴 必須
目錄遍歷 (../)realpath() 正規化、strpos 檢查前綴🔴 必須
權限提升僅限特定管理員角色、IP 白名單、二次驗證🔴 必須
CSRF 攻擊表單 Token、SameSite Cookie🟠 高
XSS (編輯器內容)輸出時 htmlspecialchars、CSP 標頭🟠 高
惡意程式碼執行儲存前靜態分析 (PHP-Parser)、禁用危險函數🟡 中
誤刪/誤改版本控制 (Git 自動提交)、備份機制、操作日誌🟡 中
編碼問題統一 UTF-8、BOM 處理🟢 低

程式碼掃描整合範例 (PHP-Parser)

// composer require nikic/php-parser
use PhpParser\ParserFactory;
use PhpParser\NodeTraverser;
use PhpParser\NodeVisitor\NameResolver;
use PhpParser\Node\Expr\FuncCall;

function scanPhpCode(string $code): array {
    $parser = (new ParserFactory())->create(ParserFactory::PREFER_PHP7);
    try {
        $ast = $parser->parse($code);
    } catch (\Throwable $e) {
        return ['error' => '語法錯誤: ' . $e->getMessage()];
    }

    $dangerous = [
        'exec', 'shell_exec', 'system', 'passthru', 'proc_open', 'popen',
        'eval', 'assert', 'create_function',
        'file_put_contents', 'fopen', 'fwrite', 'unlink', 'rmdir',
        'chmod', 'chown', 'chgrp',
        'mail', 'header', 'setcookie',
        'mysqli_query', 'pdo_exec', 'mysql_query',
        'base64_decode', 'gzuncompress', 'str_rot13',
    ];

    $found = [];
    $traverser = new NodeTraverser();
    $traverser->addVisitor(new class($dangerous, &$found) extends \PhpParser\NodeVisitorAbstract {
        private $dangerous;
        private $found;
        public function __construct($dangerous, &$found) { $this->dangerous = $dangerous; $this->found = &$found; }
        public function enterNode(\PhpParser\Node $node) {
            if ($node instanceof FuncCall && $node->name instanceof \PhpParser\Node\Name) {
                $name = strtolower($node->name->getLast());
                if (in_array($name, $this->dangerous)) {
                    $this->found[] = ['function' => $name, 'line' => $node->getStartLine()];
                }
            }
        }
    });
    $traverser->traverse($ast);
    return $found;
}

// 使用
$issues = scanPhpCode($content);
if (!empty($issues)) {
    die('檢測到危險函數: ' . json_encode($issues));
}

現代化替代方案:程式碼編輯器元件

不要重造輪子,整合成熟的前端編輯器:

  • Monaco Editor (VS Code 核心):功能最完整、TypeScript 支援佳
  • CodeMirror 6:模組化、輕量、行動裝置友善
  • Ace Editor:成熟穩定、嵌入簡單

Monaco Editor 整合範例

<!-- HTML -->
<div id="editor" style="height: 500px;"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.44.0/min/vs/loader.js"></script>
<script>
require.config({ paths: { vs: 'https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.44.0/min/vs' } });
require(['vs/editor/editor.main'], function() {
    window.editor = monaco.editor.create(document.getElementById('editor'), {
        value: <?= json_encode($content) ?>,
        language: 'php',
        theme: 'vs-dark',
        automaticLayout: true,
        minimap: { enabled: true },
    });
});
</script>

<!-- 儲存時 -->
<script>
document.querySelector('form').addEventListener('submit', async (e) => {
    e.preventDefault();
    const content = window.editor.getValue();
    const res = await fetch('save.php', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ file: '', content })
    });
    alert(await res.text());
});
</script>

版本控制自動備份

function autoGitCommit(string $file, string $message = 'Auto save'): bool {
    $repoDir = dirname($file);
    $fileName = basename($file);
    
    // 檢查是否為 Git 倉庫
    if (!is_dir($repoDir . '/.git')) return false;
    
    $cmd = "cd " . escapeshellarg($repoDir) . " && git add " . escapeshellarg($fileName) . " && git commit -m " . escapeshellarg($message);
    exec($cmd, $output, $returnVar);
    return $returnVar === 0;
}

// 儲存後呼叫
if ($bytes !== false) {
    autoGitCommit($file, "Web editor: update $file");
}

總結

PHP 線上編輯器是把雙面刃:開發便利與安全風險並存。生產環境原則上應禁用,改用 Git 部署流程 (開發機編輯 → Commit → CI/CD 部署)。若業務必須提供,務必實施:嚴格路徑白名單、強認證授權、程式碼靜態掃描、操作審計日誌、自動版本備份。並考慮整合 Monaco Editor 等專業前端元件提升體驗。

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