bool is_executable ( string $filename )
| 参数 | 描述 |
|---|---|
filename |
要检查的文件路径。可以是相对路径或绝对路径 |
truefalsefalse(如权限不足无法访问)| 权限位 | 八进制值 | 描述 | 对is_executable()的影响 |
|---|---|---|---|
--- |
0 | 无任何权限 | 返回false |
--x |
1 | 仅执行权限 | 可能返回true |
-w- |
2 | 仅写入权限 | 返回false |
-wx |
3 | 写入和执行权限 | 可能返回true |
r-- |
4 | 仅读取权限 | 返回false |
r-x |
5 | 读取和执行权限 | 可能返回true |
rw- |
6 | 读取和写入权限 | 返回false |
rwx |
7 | 读取、写入和执行权限 | 可能返回true |
注意:实际返回值还取决于当前运行PHP进程的用户权限和文件系统类型
<?php
// 检查常见文件类型的可执行性
$files = [
'/usr/bin/php' => 'PHP解释器',
'/bin/ls' => 'ls命令',
'/etc/passwd' => '系统密码文件',
'test.sh' => 'shell脚本',
'index.php' => 'PHP脚本',
'nonexistent.txt' => '不存在的文件'
];
// 创建测试脚本
file_put_contents('test.sh', '#!/bin/bash' . PHP_EOL . 'echo "Hello World"');
chmod('test.sh', 0755); // 设置可执行权限
file_put_contents('test.php', '<?php echo "Hello PHP"; ?>');
chmod('test.php', 0644); // 只设置读写权限
foreach ($files as $file => $description) {
// 跳过不存在的文件(除了我们特别测试的)
if ($file === 'nonexistent.txt') {
$result = is_executable($file) ? '可执行' : '不可执行或不存在';
echo "$description ($file): $result<br>";
continue;
}
if (file_exists($file)) {
$executable = is_executable($file);
$permissions = substr(sprintf('%o', fileperms($file)), -4);
echo "$description ($file): ";
echo $executable ? '✓ 可执行' : '✗ 不可执行';
echo " (权限: $permissions)<br>";
} else {
echo "$description ($file): 文件不存在<br>";
}
}
// 清理测试文件
unlink('test.sh');
unlink('test.php');
?>
<?php
class SafeCommandExecutor {
private $allowedCommands = [];
private $allowedPaths = [];
public function __construct() {
// 默认允许的系统路径
$this->allowedPaths = [
'/usr/bin/',
'/bin/',
'/usr/local/bin/'
];
}
/**
* 添加允许的命令
*/
public function allowCommand($command) {
$this->allowedCommands[] = $command;
return $this;
}
/**
* 添加允许的路径
*/
public function allowPath($path) {
$this->allowedPaths[] = rtrim($path, '/') . '/';
return $this;
}
/**
* 安全执行命令
*/
public function execute($command, $args = []) {
// 1. 检查命令是否在允许列表中
if (!empty($this->allowedCommands) && !in_array($command, $this->allowedCommands)) {
throw new Exception("命令 '$command' 不在允许列表中");
}
// 2. 查找命令的完整路径
$fullPath = $this->findCommand($command);
if (!$fullPath) {
throw new Exception("未找到命令: $command");
}
// 3. 检查文件可执行性
if (!$this->isSafeExecutable($fullPath)) {
throw new Exception("命令 '$fullPath' 不可执行或不安全");
}
// 4. 执行命令
$escapedArgs = array_map('escapeshellarg', $args);
$cmd = escapeshellcmd($fullPath) . ' ' . implode(' ', $escapedArgs);
$output = [];
$returnVar = 0;
exec($cmd, $output, $returnVar);
return [
'output' => $output,
'return_code' => $returnVar,
'command' => $cmd
];
}
/**
* 查找命令的完整路径
*/
private function findCommand($command) {
// 检查是否是绝对路径
if (strpos($command, '/') === 0) {
return file_exists($command) ? $command : false;
}
// 在允许的路径中查找
foreach ($this->allowedPaths as $path) {
$fullPath = $path . $command;
if (file_exists($fullPath)) {
return $fullPath;
}
}
return false;
}
/**
* 安全检查文件是否可执行
*/
private function isSafeExecutable($filepath) {
// 检查文件是否存在
if (!file_exists($filepath)) {
return false;
}
// 检查是否是普通文件(不是目录或特殊文件)
if (!is_file($filepath)) {
return false;
}
// 检查文件权限 - 必须是可执行文件
if (!is_executable($filepath)) {
return false;
}
// 检查文件路径是否在允许的路径中
foreach ($this->allowedPaths as $allowedPath) {
if (strpos($filepath, $allowedPath) === 0) {
return true;
}
}
return false;
}
/**
* 获取文件详细信息
*/
public function getFileInfo($filepath) {
if (!file_exists($filepath)) {
return ['error' => '文件不存在'];
}
return [
'path' => $filepath,
'realpath' => realpath($filepath),
'is_executable' => is_executable($filepath),
'is_file' => is_file($filepath),
'is_dir' => is_dir($filepath),
'is_link' => is_link($filepath),
'permissions' => substr(sprintf('%o', fileperms($filepath)), -4),
'owner' => fileowner($filepath),
'group' => filegroup($filepath),
'size' => filesize($filepath)
];
}
}
// 使用示例
try {
$executor = new SafeCommandExecutor();
// 配置允许的路径
$executor
->allowPath('/bin')
->allowPath('/usr/bin');
echo "<h5>安全命令执行示例:</h5>";
// 检查命令的可执行性
$commands = ['ls', 'php', 'python', 'nonexistent'];
foreach ($commands as $cmd) {
echo "检查命令: $cmd<br>";
try {
// 尝试查找命令
$executor->execute('echo', ['测试']);
$info = $executor->getFileInfo('/bin/' . $cmd);
if (isset($info['is_executable']) && $info['is_executable']) {
echo " ✓ $cmd 可执行 (权限: {$info['permissions']})<br>";
} else {
echo " ✗ $cmd 不可执行<br>";
}
} catch (Exception $e) {
echo " ✗ $cmd 错误: " . $e->getMessage() . "<br>";
}
}
echo "<br>";
// 执行一个安全命令
try {
$result = $executor->execute('ls', ['-la', '/tmp']);
echo "命令执行成功<br>";
echo "返回码: {$result['return_code']}<br>";
echo "输出行数: " . count($result['output']) . "<br>";
} catch (Exception $e) {
echo "命令执行失败: " . $e->getMessage() . "<br>";
}
} catch (Exception $e) {
echo "错误: " . $e->getMessage();
}
?>
<?php
class UploadSecurityChecker {
private $uploadDir;
private $allowedMimeTypes = [
'image/jpeg',
'image/png',
'image/gif',
'application/pdf',
'text/plain'
];
public function __construct($uploadDir = 'uploads') {
$this->uploadDir = rtrim($uploadDir, '/') . '/';
// 确保上传目录存在
if (!is_dir($this->uploadDir)) {
mkdir($this->uploadDir, 0755, true);
}
}
/**
* 检查上传的文件
*/
public function checkUploadedFile($fileField) {
if (!isset($_FILES[$fileField])) {
return ['error' => '没有文件上传'];
}
$file = $_FILES[$fileField];
// 基本检查
if ($file['error'] !== UPLOAD_ERR_OK) {
return ['error' => $this->getUploadError($file['error'])];
}
// 安全检查
$securityCheck = $this->performSecurityChecks($file);
if ($securityCheck['safe'] !== true) {
return ['error' => $securityCheck['reason']];
}
// 生成安全文件名
$safeFilename = $this->generateSafeFilename($file['name']);
$destination = $this->uploadDir . $safeFilename;
// 移动文件
if (move_uploaded_file($file['tmp_name'], $destination)) {
// 设置安全权限(移除执行权限)
chmod($destination, 0644);
return [
'success' => true,
'filename' => $safeFilename,
'path' => $destination,
'size' => $file['size'],
'mime_type' => $file['type']
];
}
return ['error' => '文件移动失败'];
}
/**
* 执行安全检查
*/
private function performSecurityChecks($file) {
// 1. 检查MIME类型
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mimeType = finfo_file($finfo, $file['tmp_name']);
finfo_close($finfo);
if (!in_array($mimeType, $this->allowedMimeTypes)) {
return ['safe' => false, 'reason' => '不允许的文件类型: ' . $mimeType];
}
// 2. 检查文件扩展名
$extension = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
$dangerousExtensions = ['php', 'php3', 'php4', 'php5', 'phtml', 'phar'];
if (in_array($extension, $dangerousExtensions)) {
return ['safe' => false, 'reason' => '危险的文件扩展名: ' . $extension];
}
// 3. 检查文件是否可执行(上传的临时文件)
if (is_executable($file['tmp_name'])) {
return ['safe' => false, 'reason' => '上传的文件具有可执行权限'];
}
// 4. 检查文件大小
$maxSize = 5 * 1024 * 1024; // 5MB
if ($file['size'] > $maxSize) {
return ['safe' => false, 'reason' => '文件大小超过限制'];
}
// 5. 检查文件内容(简单检查)
$content = file_get_contents($file['tmp_name'], false, null, 0, 100);
if (strpos($content, '<?php') !== false) {
return ['safe' => false, 'reason' => '文件可能包含PHP代码'];
}
return ['safe' => true];
}
/**
* 检查上传目录中的文件安全性
*/
public function checkUploadDirectory() {
$results = [
'safe' => [],
'dangerous' => []
];
$files = glob($this->uploadDir . '*');
foreach ($files as $file) {
$info = [
'filename' => basename($file),
'path' => $file,
'is_executable' => is_executable($file),
'permissions' => substr(sprintf('%o', fileperms($file)), -4),
'size' => filesize($file)
];
// 检查安全性
if ($this->isFileSafe($file)) {
$results['safe'][] = $info;
} else {
$results['dangerous'][] = $info;
}
}
return $results;
}
/**
* 检查单个文件是否安全
*/
private function isFileSafe($filepath) {
// 检查文件是否可执行
if (is_executable($filepath)) {
return false;
}
// 检查文件扩展名
$extension = strtolower(pathinfo($filepath, PATHINFO_EXTENSION));
$dangerousExtensions = ['php', 'php3', 'php4', 'php5', 'phtml', 'phar', 'exe', 'sh', 'bat'];
if (in_array($extension, $dangerousExtensions)) {
return false;
}
// 检查文件权限(不应有执行权限)
$permissions = fileperms($filepath);
if ($permissions & 0o111) { // 检查执行位
return false;
}
return true;
}
/**
* 生成安全的文件名
*/
private function generateSafeFilename($originalName) {
$extension = strtolower(pathinfo($originalName, PATHINFO_EXTENSION));
$safeExtension = in_array($extension, ['jpg', 'jpeg', 'png', 'gif', 'pdf', 'txt']) ? $extension : 'dat';
return uniqid('upload_', true) . '.' . $safeExtension;
}
/**
* 获取上传错误信息
*/
private function getUploadError($errorCode) {
$errors = [
UPLOAD_ERR_INI_SIZE => '文件大小超过服务器限制',
UPLOAD_ERR_FORM_SIZE => '文件大小超过表单限制',
UPLOAD_ERR_PARTIAL => '文件只有部分被上传',
UPLOAD_ERR_NO_FILE => '没有文件被上传',
UPLOAD_ERR_NO_TMP_DIR => '找不到临时文件夹',
UPLOAD_ERR_CANT_WRITE => '文件写入失败',
UPLOAD_ERR_EXTENSION => 'PHP扩展阻止了文件上传'
];
return $errors[$errorCode] ?? '未知上传错误';
}
}
// 使用示例
echo "<h5>文件上传安全检查示例:</h5>";
$checker = new UploadSecurityChecker('test_uploads');
// 检查上传目录
echo "<h6>检查上传目录:</h6>";
$dirCheck = $checker->checkUploadDirectory();
echo "安全文件数: " . count($dirCheck['safe']) . "<br>";
echo "危险文件数: " . count($dirCheck['dangerous']) . "<br>";
// 显示危险文件(如果有)
if (!empty($dirCheck['dangerous'])) {
echo "<h6>危险文件列表:</h6>";
foreach ($dirCheck['dangerous'] as $file) {
echo "- {$file['filename']} (权限: {$file['permissions']}, 可执行: " .
($file['is_executable'] ? '是' : '否') . ")<br>";
}
}
// 模拟上传表单
echo '<form method="post" enctype="multipart/form-data">';
echo '<div class="mb-3">';
echo '<label for="file" class="form-label">选择文件上传测试:</label>';
echo '<input type="file" class="form-control" id="file" name="uploaded_file">';
echo '</div>';
echo '<button type="submit" class="btn btn-primary">上传检查</button>';
echo '</form>';
// 处理上传(演示目的)
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['uploaded_file'])) {
echo "<h6>上传结果:</h6>";
$result = $checker->checkUploadedFile('uploaded_file');
if (isset($result['error'])) {
echo "<div class='alert alert-danger'>上传失败: {$result['error']}</div>";
} else {
echo "<div class='alert alert-success'>";
echo "上传成功!<br>";
echo "文件名: {$result['filename']}<br>";
echo "文件大小: {$result['size']} 字节<br>";
echo "MIME类型: {$result['mime_type']}<br>";
echo "</div>";
}
}
// 清理测试目录
if (is_dir('test_uploads')) {
$files = glob('test_uploads/*');
foreach ($files as $file) {
unlink($file);
}
rmdir('test_uploads');
}
?>
clearstatcache()清除缓存is_file() - 检查文件是否是普通文件is_dir() - 检查文件是否是目录is_link() - 检查文件是否是符号链接is_readable() - 检查文件是否可读is_writable() - 检查文件是否可写fileperms() - 获取文件权限chmod() - 改变文件模式clearstatcache() - 清除文件状态缓存exec() - 执行外部程序shell_exec() - 通过shell执行命令在执行外部命令前检查命令文件是否可执行,防止命令注入攻击。
检查上传的文件是否具有可执行权限,防止恶意文件上传。
检查系统中是否安装了特定工具或程序。
在文件管理系统中检查用户对文件的执行权限。