こんにちは!今回はPHPで外部コマンドを実行する関数の一つ、「passthru」について詳しく解説していきます。
passthru関数とは?
passthruは、外部プログラムやシェルコマンドを実行し、その生の出力を直接ブラウザに送信する関数です。画像やバイナリファイルなど、加工せずにそのまま出力したい場合に特に便利です。
基本的な使い方
構文
passthru(string $command, int &$result_code = null): false|null
パラメータ
- $command: 実行したいコマンド文字列
- $result_code: コマンドの終了ステータスコード(参照渡し)
戻り値
- null: 成功時
- false: 失敗時
基本的な使用例
<?php
// シンプルなコマンド実行
passthru("ls -la");
// 終了コードを取得
passthru("ls -la", $returnCode);
if ($returnCode === 0) {
echo "コマンドが正常に実行されました";
} else {
echo "エラーが発生しました: " . $returnCode;
}
?>
他のコマンド実行関数との違い
PHPには複数のコマンド実行関数があり、それぞれ用途が異なります。
| 関数 | 出力の扱い | 用途 |
|---|---|---|
| passthru() | 直接ブラウザに出力 | バイナリデータ、画像の出力 |
| exec() | 最後の行のみ返す | 出力を変数に格納して処理 |
| system() | 直接ブラウザに出力 | テキスト出力のコマンド |
| shell_exec() | 文字列として返す | 出力を文字列として取得 |
| backtick (`) | 文字列として返す | shell_exec()の省略記法 |
比較例
<?php
echo "=== passthru ===\n";
passthru("echo 'Hello World'");
// 出力: Hello World (直接表示)
echo "\n\n=== exec ===\n";
exec("echo 'Hello World'", $output);
print_r($output);
// 出力: Array ( [0] => Hello World )
echo "\n=== system ===\n";
system("echo 'Hello World'");
// 出力: Hello World (直接表示)
echo "\n\n=== shell_exec ===\n";
$result = shell_exec("echo 'Hello World'");
echo $result;
// 出力: Hello World (変数経由)
?>
実践的な使用例
例1: 画像の動的生成と出力
<?php
// ImageMagickを使って画像を生成
header('Content-Type: image/png');
$text = "Hello World";
$fontSize = 24;
$outputFile = tempnam(sys_get_temp_dir(), 'img') . '.png';
// ImageMagickコマンドで画像生成
$command = sprintf(
"convert -size 300x100 xc:white -pointsize %d -gravity center -draw 'text 0,0 \"%s\"' %s",
$fontSize,
escapeshellarg($text),
escapeshellarg($outputFile)
);
exec($command);
// 画像をブラウザに出力
passthru("cat " . escapeshellarg($outputFile));
// 一時ファイルを削除
unlink($outputFile);
?>
例2: PDFファイルの生成と出力
<?php
// HTMLからPDFを生成(wkhtmltopdfを使用)
header('Content-Type: application/pdf');
header('Content-Disposition: attachment; filename="report.pdf"');
$htmlFile = '/tmp/report.html';
$pdfFile = '/tmp/report.pdf';
// HTMLファイルを作成
file_put_contents($htmlFile, '<h1>レポート</h1><p>これはテストレポートです。</p>');
// PDFに変換
$command = sprintf(
"wkhtmltopdf %s %s",
escapeshellarg($htmlFile),
escapeshellarg($pdfFile)
);
exec($command, $output, $returnCode);
if ($returnCode === 0 && file_exists($pdfFile)) {
// PDFをブラウザに出力
passthru("cat " . escapeshellarg($pdfFile));
// 一時ファイルを削除
unlink($htmlFile);
unlink($pdfFile);
} else {
header('Content-Type: text/plain');
echo "PDFの生成に失敗しました";
}
?>
例3: ファイルの圧縮とダウンロード
<?php
// 複数ファイルをZIP圧縮してダウンロード
$files = ['/path/to/file1.txt', '/path/to/file2.txt', '/path/to/file3.txt'];
$zipFile = tempnam(sys_get_temp_dir(), 'archive') . '.zip';
// ZIPファイルを作成
$fileList = implode(' ', array_map('escapeshellarg', $files));
$command = "zip -j " . escapeshellarg($zipFile) . " " . $fileList;
exec($command, $output, $returnCode);
if ($returnCode === 0 && file_exists($zipFile)) {
// ダウンロードヘッダーを設定
header('Content-Type: application/zip');
header('Content-Disposition: attachment; filename="archive.zip"');
header('Content-Length: ' . filesize($zipFile));
// ZIPファイルを出力
passthru("cat " . escapeshellarg($zipFile));
// 一時ファイルを削除
unlink($zipFile);
} else {
echo "ZIPファイルの作成に失敗しました";
}
?>
例4: 動画のサムネイル生成
<?php
// FFmpegを使って動画のサムネイルを生成
$videoFile = '/path/to/video.mp4';
$thumbnailFile = tempnam(sys_get_temp_dir(), 'thumb') . '.jpg';
// 動画の5秒地点からサムネイルを抽出
$command = sprintf(
"ffmpeg -i %s -ss 00:00:05 -vframes 1 %s 2>&1",
escapeshellarg($videoFile),
escapeshellarg($thumbnailFile)
);
exec($command, $output, $returnCode);
if ($returnCode === 0 && file_exists($thumbnailFile)) {
header('Content-Type: image/jpeg');
passthru("cat " . escapeshellarg($thumbnailFile));
unlink($thumbnailFile);
} else {
header('Content-Type: text/plain');
echo "サムネイルの生成に失敗しました";
}
?>
例5: ログファイルの表示
<?php
// システムログの最新100行を表示
header('Content-Type: text/plain; charset=utf-8');
$logFile = '/var/log/application.log';
if (file_exists($logFile)) {
echo "=== 最新ログ (最新100行) ===\n\n";
passthru("tail -n 100 " . escapeshellarg($logFile));
} else {
echo "ログファイルが見つかりません";
}
?>
例6: システム情報の表示
<?php
header('Content-Type: text/plain; charset=utf-8');
echo "=== システム情報 ===\n\n";
echo "【ディスク使用状況】\n";
passthru("df -h");
echo "\n\n【メモリ使用状況】\n";
passthru("free -h");
echo "\n\n【プロセス一覧(上位10件)】\n";
passthru("ps aux | head -n 11");
echo "\n\n【ネットワーク接続】\n";
passthru("netstat -tuln | head -n 20");
?>
重要なセキュリティ対策
1. コマンドインジェクション対策
最も重要: ユーザー入力を絶対にそのままコマンドに渡さない!
<?php
// ❌ 危険な例
$userInput = $_GET['file'];
passthru("cat " . $userInput); // コマンドインジェクションの危険性
// ✅ 安全な例
$userInput = $_GET['file'];
$safeInput = escapeshellarg($userInput);
passthru("cat " . $safeInput);
// さらに安全: ホワイトリストによる検証
$allowedFiles = ['file1.txt', 'file2.txt', 'file3.txt'];
if (in_array($userInput, $allowedFiles)) {
passthru("cat " . escapeshellarg("/safe/path/" . $userInput));
} else {
die("不正なファイル名です");
}
?>
2. escapeshellarg()の使用
<?php
// ユーザー入力をエスケープ
$filename = $_POST['filename'];
$safeFilename = escapeshellarg($filename);
passthru("cat " . $safeFilename);
// 複数の引数がある場合
$source = escapeshellarg($_POST['source']);
$destination = escapeshellarg($_POST['destination']);
passthru("cp " . $source . " " . $destination);
?>
3. escapeshellcmd()との違い
<?php
$userInput = "file.txt; rm -rf /";
// escapeshellarg - 引数全体をエスケープ(推奨)
$safe1 = escapeshellarg($userInput);
echo $safe1; // 'file.txt; rm -rf /'
// escapeshellcmd - コマンド全体をエスケープ
$safe2 = escapeshellcmd($userInput);
echo $safe2; // file.txt\; rm -rf /
// 推奨: escapeshellarg()を使用
passthru("cat " . escapeshellarg($userInput));
?>
4. ホワイトリスト方式の実装
<?php
class SafeCommandExecutor {
private $allowedCommands = [
'disk_usage' => 'df -h',
'memory_info' => 'free -h',
'date' => 'date',
'uptime' => 'uptime'
];
public function execute($commandName, &$returnCode = null) {
if (!isset($this->allowedCommands[$commandName])) {
throw new Exception("許可されていないコマンドです");
}
$command = $this->allowedCommands[$commandName];
passthru($command, $returnCode);
return $returnCode === 0;
}
}
// 使用例
$executor = new SafeCommandExecutor();
try {
header('Content-Type: text/plain');
$executor->execute('disk_usage');
} catch (Exception $e) {
echo "エラー: " . $e->getMessage();
}
?>
5. 権限の制限
<?php
// 特定のユーザーでコマンドを実行(sudoを使用)
function executeAsUser($command, $user) {
$safeCommand = escapeshellarg($command);
$safeUser = escapeshellarg($user);
// sudoで特定ユーザーとして実行
passthru("sudo -u " . $safeUser . " " . $safeCommand);
}
// 注意: sudoersファイルで適切な権限設定が必要
?>
エラーハンドリング
基本的なエラーハンドリング
<?php
$command = "some-command";
ob_start();
passthru($command . " 2>&1", $returnCode);
$output = ob_get_clean();
if ($returnCode !== 0) {
error_log("コマンド実行エラー: " . $output);
echo "処理に失敗しました";
} else {
echo $output;
}
?>
タイムアウト処理
<?php
function executeWithTimeout($command, $timeout = 30) {
// timeoutコマンドを使用
$safeCommand = escapeshellarg($command);
$timeoutCommand = "timeout " . (int)$timeout . " " . $command;
passthru($timeoutCommand . " 2>&1", $returnCode);
if ($returnCode === 124) {
throw new Exception("コマンドがタイムアウトしました");
} elseif ($returnCode !== 0) {
throw new Exception("コマンドの実行に失敗しました: " . $returnCode);
}
}
try {
executeWithTimeout("sleep 5", 10); // 成功
executeWithTimeout("sleep 15", 10); // タイムアウト
} catch (Exception $e) {
echo "エラー: " . $e->getMessage();
}
?>
ベストプラクティス
1. 出力バッファリングの制御
<?php
// 大きなファイルを出力する場合
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="largefile.zip"');
// バッファリングを無効化
if (ob_get_level()) {
ob_end_clean();
}
// ファイルを出力
passthru("cat " . escapeshellarg($largeFile));
?>
2. ロギング
<?php
function loggedPassthru($command, &$returnCode = null) {
$timestamp = date('Y-m-d H:i:s');
$logFile = '/var/log/passthru.log';
// コマンド実行前にログ
file_put_contents(
$logFile,
"[{$timestamp}] 実行: {$command}\n",
FILE_APPEND
);
// コマンド実行
ob_start();
passthru($command, $returnCode);
$output = ob_get_clean();
// 結果をログ
file_put_contents(
$logFile,
"[{$timestamp}] 終了コード: {$returnCode}\n",
FILE_APPEND
);
echo $output;
return $returnCode;
}
?>
3. 環境変数の設定
<?php
// 安全な環境変数で実行
putenv('PATH=/usr/bin:/bin');
putenv('LANG=ja_JP.UTF-8');
passthru("your-command");
?>
注意点とトラブルシューティング
1. セーフモードと無効化されている場合
<?php
// passthruが利用可能かチェック
if (function_exists('passthru')) {
// 無効化されていないかチェック
$disabled = explode(',', ini_get('disable_functions'));
if (in_array('passthru', $disabled)) {
die("passthru関数は無効化されています");
}
passthru("ls -la");
} else {
die("passthru関数は利用できません");
}
?>
2. Windows環境での考慮事項
<?php
// OSを判定
if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
// Windows
passthru("dir /w");
} else {
// Unix/Linux
passthru("ls -la");
}
?>
3. 文字コードの問題
<?php
// 日本語を含むコマンド実行
header('Content-Type: text/plain; charset=utf-8');
// ロケールを設定
setlocale(LC_ALL, 'ja_JP.UTF-8');
putenv('LANG=ja_JP.UTF-8');
passthru("your-command");
?>
まとめ
passthru関数は外部コマンドの実行結果を直接出力する強力な関数ですが、使用には十分な注意が必要です。
重要ポイント:
- バイナリデータの出力に最適: 画像、PDF、ZIPファイルなど
- セキュリティが最優先: 必ず
escapeshellarg()を使用 - ユーザー入力の検証: ホワイトリスト方式を推奨
- エラーハンドリング: 終了コードを必ずチェック
- 代替手段の検討: 可能であればPHPネイティブの関数を使用
セキュリティチェックリスト:
- ✅ ユーザー入力を
escapeshellarg()でエスケープ - ✅ ホワイトリストによるコマンド/ファイルの検証
- ✅ 適切なエラーハンドリング
- ✅ ログの記録
- ✅ 最小権限の原則を適用
外部コマンドの実行は便利ですが、セキュリティリスクも高いため、本当に必要な場合のみ使用し、必ず適切な対策を実施してください!
関連記事
exec()– コマンド実行結果を配列で取得system()– コマンドを実行して出力を表示shell_exec()– コマンド実行結果を文字列で取得escapeshellarg()– シェル引数のエスケープ
