1.修复上传漏洞和SQL注入漏洞-增强登录验证功能

This commit is contained in:
2026-06-24 11:47:27 +08:00
parent 546d350f3a
commit 5187330c38
25 changed files with 303 additions and 1462 deletions

View File

@@ -4,7 +4,6 @@ declare(strict_types=1);
namespace app\admin\controller;
use ba\Terminal;
use ba\TableManager;
use support\think\Db;
use app\admin\model\AdminLog;
@@ -166,18 +165,6 @@ class Ajax extends Backend
]);
}
public function changeTerminalConfig(Request $request): Response
{
$response = $this->initializeBackend($request);
if ($response !== null) return $response;
AdminLog::instance($request)->setTitle(__('Change terminal config'));
if (Terminal::changeTerminalConfig()) {
return $this->success();
}
return $this->error(__('Failed to modify the terminal configuration. Please modify the configuration file manually:%s', ['/config/terminal.php']));
}
public function clearCache(Request $request): Response
{
$response = $this->initializeBackend($request);
@@ -193,13 +180,4 @@ class Ajax extends Backend
event_trigger('cacheClearAfter');
return $this->success(__('Cache cleaned~'));
}
public function terminal(Request $request): Response
{
$response = $this->initializeBackend($request);
if ($response !== null) return $response;
(new Terminal())->exec();
return $this->success();
}
}

View File

@@ -46,14 +46,10 @@ class Index extends Backend
'siteName' => get_sys_config('site_name'),
'version' => get_sys_config('version'),
'apiUrl' => $apiUrl,
'upload' => keys_to_camel_case(get_upload_config($request), ['max_size', 'save_name', 'allowed_suffixes', 'allowed_mime_types']),
'upload' => keys_to_camel_case(get_upload_config($request), ['max_size', 'save_name', 'allowed_suffixes', 'allowed_mime_types', 'forbidden_suffixes']),
'cdnUrl' => full_url(),
'cdnUrlParams' => config('buildadmin.cdn_url_params'),
],
'terminal' => [
'phpDevelopmentServer' => str_contains($_SERVER['SERVER_SOFTWARE'] ?? '', 'Development Server'),
'npmPackageManager' => config('terminal.npm_package_manager'),
]
]);
}

View File

@@ -30,6 +30,7 @@ return [
'File uploaded successfully' => 'File uploaded successfully',
'No files were uploaded' => 'No files were uploaded',
'The uploaded file format is not allowed' => 'The uploaded file format is no allowance.',
'The uploaded file contains prohibited content' => 'The uploaded file contains prohibited content',
'The uploaded image file is not a valid image' => 'The uploaded image file is not a valid image',
'The uploaded file is too large (%sMiB), Maximum file size:%sMiB' => 'The uploaded file is too large (%sMiB), maximum file size:%sMiB',
'No files have been uploaded or the file size exceeds the upload limit of the server' => 'No files have been uploaded or the file size exceeds the server upload limit.',

View File

@@ -31,6 +31,7 @@ return [
'File uploaded successfully' => '文件上传成功!',
'No files were uploaded' => '没有文件被上传',
'The uploaded file format is not allowed' => '上传的文件格式未被允许',
'The uploaded file contains prohibited content' => '上传的文件包含禁止的内容',
'The uploaded image file is not a valid image' => '上传的图片文件不是有效的图像',
'The uploaded file is too large (%sMiB), Maximum file size:%sMiB' => '上传的文件太大(%sM),最大文件大小:%sM',
'No files have been uploaded or the file size exceeds the upload limit of the server' => '没有文件被上传或文件大小超出服务器上传限制!',

View File

@@ -58,7 +58,7 @@ class Index extends Frontend
'siteName' => get_sys_config('site_name'),
'version' => get_sys_config('version'),
'cdnUrl' => full_url(),
'upload' => keys_to_camel_case(get_upload_config($request), ['max_size', 'save_name', 'allowed_suffixes', 'allowed_mime_types']),
'upload' => keys_to_camel_case(get_upload_config($request), ['max_size', 'save_name', 'allowed_suffixes', 'allowed_mime_types', 'forbidden_suffixes']),
'recordNumber' => get_sys_config('record_number'),
'cdnUrlParams' => config('buildadmin.cdn_url_params'),
],

View File

@@ -21,6 +21,7 @@ return [
'File uploaded successfully' => 'File uploaded successfully!',
'No files were uploaded' => 'No files were uploaded',
'The uploaded file format is not allowed' => 'The uploaded file format is not allowed',
'The uploaded file contains prohibited content' => 'The uploaded file contains prohibited content',
'The uploaded image file is not a valid image' => 'The uploaded image is not valid',
'The uploaded file is too large (%sMiB), Maximum file size:%sMiB' => 'The file is too large (%sMiB), maximum: %sMiB',
'No files have been uploaded or the file size exceeds the upload limit of the server' => 'No file uploaded or size exceeds server limit',

View File

@@ -22,6 +22,7 @@ return [
'File uploaded successfully' => 'Fail berjaya dimuat naik!',
'No files were uploaded' => 'Tiada fail dimuat naik',
'The uploaded file format is not allowed' => 'Format fail yang dimuat naik tidak dibenarkan',
'The uploaded file contains prohibited content' => 'Fail yang dimuat naik mengandungi kandungan yang dilarang',
'The uploaded image file is not a valid image' => 'Fail imej yang dimuat naik tidak sah',
'The uploaded file is too large (%sMiB), Maximum file size:%sMiB' => 'Fail terlalu besar (%sM), saiz maksimum: %sM',
'No files have been uploaded or the file size exceeds the upload limit of the server' => 'Tiada fail dimuat naik atau saiz melebihi had pelayan',

View File

@@ -20,6 +20,7 @@ return [
'File uploaded successfully' => '文件上传成功!',
'No files were uploaded' => '没有文件被上传',
'The uploaded file format is not allowed' => '上传的文件格式未被允许',
'The uploaded file contains prohibited content' => '上传的文件包含禁止的内容',
'The uploaded image file is not a valid image' => '上传的图片文件不是有效的图像',
'The uploaded file is too large (%sMiB), Maximum file size:%sMiB' => '上传的文件太大(%sM),最大文件大小:%sM',
'No files have been uploaded or the file size exceeds the upload limit of the server' => '没有文件被上传或文件大小超出服务器上传限制!',

View File

@@ -59,6 +59,13 @@ class Upload
public function setDriver(string $driver): self
{
if (!preg_match('/^[a-zA-Z0-9_-]+$/', $driver)) {
throw new InvalidArgumentException(__('Driver %s not supported', [$driver]));
}
$allowed = $this->checkConfig($this->config['allowed_drivers'] ?? 'local');
if ($allowed && !in_array(strtolower($driver), $allowed)) {
throw new InvalidArgumentException(__('Driver %s not supported', [$driver]));
}
$this->driver['name'] = $driver;
return $this;
}
@@ -79,11 +86,16 @@ class Upload
protected function resolveDriverClass(string $driver): string|false
{
if ($this->driver['namespace'] || str_contains($driver, '\\')) {
$class = str_contains($driver, '\\') ? $driver : $this->driver['namespace'] . $this->studly($driver);
if (class_exists($class)) {
return $class;
}
if (!preg_match('/^[a-zA-Z0-9_-]+$/', $driver)) {
return false;
}
$allowed = $this->checkConfig($this->config['allowed_drivers'] ?? 'local');
if ($allowed && !in_array(strtolower($driver), $allowed)) {
return false;
}
$class = $this->driver['namespace'] . $this->studly($driver);
if (class_exists($class)) {
return $class;
}
return false;
}
@@ -181,6 +193,10 @@ class Upload
throw new RuntimeException(__('The uploaded file format is not allowed'));
}
$this->assertSafeFileName($this->fileInfo['name']);
$this->assertNoMaliciousContent();
$this->assertImageMime();
if (!preg_match('/^[a-zA-Z0-9_-]+$/', $this->topic)) {
throw new RuntimeException(__('Topic format error'));
}
@@ -257,4 +273,95 @@ class Upload
}
return $configItem ? explode(',', strtolower((string)$configItem)) : [];
}
protected function getForbiddenSuffixes(): array
{
return $this->checkConfig($this->config['forbidden_suffixes'] ?? '');
}
protected function assertSafeFileName(string $fileName): void
{
$forbidden = $this->getForbiddenSuffixes();
if (!$forbidden) {
return;
}
$parts = explode('.', strtolower($fileName));
if (count($parts) < 2) {
return;
}
foreach ($parts as $index => $part) {
if ($part === '') {
throw new RuntimeException(__('The uploaded file format is not allowed'));
}
if ($index === count($parts) - 1) {
continue;
}
if (in_array($part, $forbidden)) {
throw new RuntimeException(__('The uploaded file format is not allowed'));
}
}
$suffix = $parts[count($parts) - 1];
if (in_array($suffix, $forbidden)) {
throw new RuntimeException(__('The uploaded file format is not allowed'));
}
}
protected function assertNoMaliciousContent(): void
{
$path = $this->file->getPathname();
if (!is_file($path) || !is_readable($path)) {
return;
}
$handle = fopen($path, 'rb');
if (!$handle) {
throw new RuntimeException(__('The uploaded file format is not allowed'));
}
$chunk = fread($handle, 8192);
fclose($handle);
if ($chunk === false || $chunk === '') {
return;
}
$patterns = [
'/<\?php/i',
'/<\?=/i',
'/<\?(?!xml)/i',
'/<%[@=]?/i',
'/<script\b/i',
'/\b(eval|assert|shell_exec|system|passthru|proc_open|popen|base64_decode)\s*\(/i',
];
foreach ($patterns as $pattern) {
if (preg_match($pattern, $chunk)) {
throw new RuntimeException(__('The uploaded file contains prohibited content'));
}
}
}
protected function assertImageMime(): void
{
$imageSuffixes = ['gif', 'jpg', 'jpeg', 'bmp', 'png', 'webp'];
if (!in_array($this->fileInfo['suffix'], $imageSuffixes, true)) {
return;
}
$path = $this->file->getPathname();
if (!is_file($path)) {
throw new RuntimeException(__('The uploaded image file is not a valid image'));
}
if (function_exists('finfo_open')) {
$finfo = finfo_open(FILEINFO_MIME_TYPE);
if ($finfo) {
$detected = finfo_file($finfo, $path);
finfo_close($finfo);
if (!$detected || !str_starts_with(strtolower($detected), 'image/')) {
throw new RuntimeException(__('The uploaded image file is not a valid image'));
}
}
}
}
}