收紧输入渲染与后台配置权限
This commit is contained in:
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* 文件功能:后台更新日志安全测试
|
||||
* 验证广播 payload 与系统消息在发布时会对危险标题进行转义。
|
||||
*/
|
||||
|
||||
namespace Tests\Feature\Feature;
|
||||
|
||||
use App\Events\ChangelogPublished;
|
||||
use App\Jobs\SaveMessageJob;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\Event;
|
||||
use Illuminate\Support\Facades\Queue;
|
||||
use Tests\TestCase;
|
||||
|
||||
/**
|
||||
* 后台更新日志安全测试
|
||||
* 负责回归标题进入广播和系统公告前会被安全处理。
|
||||
*/
|
||||
class AdminChangelogControllerSecurityTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
/**
|
||||
* 测试发布更新日志时会对广播字段与系统消息正文做 XSS 防护。
|
||||
*/
|
||||
public function test_published_changelog_escapes_title_for_broadcast_and_system_message(): void
|
||||
{
|
||||
Event::fake([ChangelogPublished::class]);
|
||||
Queue::fake();
|
||||
|
||||
$admin = User::factory()->create([
|
||||
'id' => 1,
|
||||
'user_level' => 100,
|
||||
]);
|
||||
|
||||
$dangerousTitle = '<img src=x onerror=alert(1)>';
|
||||
$dangerousVersion = '2026-04-" onclick="alert(2)';
|
||||
|
||||
$response = $this->actingAs($admin)->post(route('admin.changelogs.store'), [
|
||||
'version' => $dangerousVersion,
|
||||
'title' => $dangerousTitle,
|
||||
'type' => 'fix',
|
||||
'content' => '修复说明',
|
||||
'is_published' => '1',
|
||||
'notify_chat' => '1',
|
||||
]);
|
||||
|
||||
$response->assertRedirect(route('admin.changelogs.index'));
|
||||
$this->assertDatabaseHas('dev_changelogs', [
|
||||
'title' => $dangerousTitle,
|
||||
'version' => $dangerousVersion,
|
||||
'is_published' => true,
|
||||
]);
|
||||
|
||||
Event::assertDispatched(ChangelogPublished::class, function (ChangelogPublished $event) use ($dangerousTitle, $dangerousVersion) {
|
||||
$payload = $event->broadcastWith();
|
||||
|
||||
$this->assertSame($dangerousTitle, $payload['title']);
|
||||
$this->assertSame(e($dangerousTitle), $payload['safe_title']);
|
||||
$this->assertSame(e($dangerousVersion), $payload['safe_version']);
|
||||
$this->assertStringContainsString(rawurlencode($dangerousVersion), $payload['url']);
|
||||
$this->assertStringNotContainsString('" onclick="', $payload['url']);
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
Queue::assertPushed(SaveMessageJob::class, function (SaveMessageJob $job) use ($dangerousTitle, $dangerousVersion) {
|
||||
$content = $job->messageData['content'] ?? '';
|
||||
|
||||
$this->assertStringContainsString(e($dangerousTitle), $content);
|
||||
$this->assertStringContainsString(e($dangerousVersion), $content);
|
||||
$this->assertStringContainsString('rel="noopener"', $content);
|
||||
$this->assertStringContainsString(rawurlencode($dangerousVersion), $content);
|
||||
$this->assertStringNotContainsString($dangerousTitle, $content);
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* 文件功能:后台通用系统参数页权限边界测试
|
||||
*
|
||||
* 覆盖通用系统参数页对站长专属敏感配置的读写隔离,
|
||||
* 防止 SMTP、VIP 支付、微信机器人及 AI 机器人等配置被越权访问。
|
||||
*/
|
||||
|
||||
namespace Tests\Feature\Feature;
|
||||
|
||||
use App\Models\Sysparam;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Tests\TestCase;
|
||||
|
||||
/**
|
||||
* 类功能:验证后台通用系统参数页只允许维护白名单公共配置。
|
||||
*/
|
||||
class AdminSystemControllerTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
/**
|
||||
* 验证通用系统参数页不会展示站长专属敏感配置。
|
||||
*/
|
||||
public function test_system_page_does_not_show_site_owner_only_sensitive_configs(): void
|
||||
{
|
||||
$this->seedSystemParams();
|
||||
$admin = $this->createSuperAdmin();
|
||||
|
||||
$response = $this->actingAs($admin)->get(route('admin.system.edit'));
|
||||
|
||||
$response->assertOk();
|
||||
$response->assertSee('sys_name');
|
||||
$response->assertSee('sys_notice');
|
||||
$response->assertDontSee('smtp_host');
|
||||
$response->assertDontSee('vip_payment_app_secret');
|
||||
$response->assertDontSee('wechat_bot_config');
|
||||
$response->assertDontSee('chatbot_max_gold');
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证通用系统参数页更新时只会持久化白名单字段。
|
||||
*/
|
||||
public function test_system_page_update_only_persists_whitelisted_configs(): void
|
||||
{
|
||||
$this->seedSystemParams();
|
||||
$admin = $this->createSuperAdmin();
|
||||
|
||||
$response = $this->actingAs($admin)->put(route('admin.system.update'), [
|
||||
'sys_name' => '新版聊天室',
|
||||
'sys_notice' => '新的公共公告',
|
||||
'smtp_host' => 'attacker.smtp.example',
|
||||
'vip_payment_app_secret' => 'tampered-secret',
|
||||
'wechat_bot_config' => '{"api":{"bot_key":"stolen"}}',
|
||||
'chatbot_max_gold' => '999999',
|
||||
'rogue_secret_token' => 'hacked',
|
||||
]);
|
||||
|
||||
$response->assertRedirect(route('admin.system.edit'));
|
||||
$response->assertSessionHas('success');
|
||||
|
||||
$this->assertDatabaseHas('sysparam', [
|
||||
'alias' => 'sys_name',
|
||||
'body' => '新版聊天室',
|
||||
]);
|
||||
$this->assertDatabaseHas('sysparam', [
|
||||
'alias' => 'sys_notice',
|
||||
'body' => '新的公共公告',
|
||||
]);
|
||||
|
||||
// 敏感配置必须保持原值,不能被通用系统页伪造请求覆盖。
|
||||
$this->assertDatabaseHas('sysparam', [
|
||||
'alias' => 'smtp_host',
|
||||
'body' => 'owner.smtp.example',
|
||||
]);
|
||||
$this->assertDatabaseHas('sysparam', [
|
||||
'alias' => 'vip_payment_app_secret',
|
||||
'body' => 'owner-secret',
|
||||
]);
|
||||
$this->assertDatabaseHas('sysparam', [
|
||||
'alias' => 'wechat_bot_config',
|
||||
'body' => '{"api":{"bot_key":"owner-only"}}',
|
||||
]);
|
||||
$this->assertDatabaseHas('sysparam', [
|
||||
'alias' => 'chatbot_max_gold',
|
||||
'body' => '5000',
|
||||
]);
|
||||
$this->assertDatabaseMissing('sysparam', [
|
||||
'alias' => 'rogue_secret_token',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建可访问后台通用系统页的超级管理员账号。
|
||||
*/
|
||||
private function createSuperAdmin(): User
|
||||
{
|
||||
return User::factory()->create([
|
||||
'user_level' => 100,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 预置通用系统页测试所需的公共参数与敏感参数。
|
||||
*/
|
||||
private function seedSystemParams(): void
|
||||
{
|
||||
foreach ($this->systemParams() as $alias => $body) {
|
||||
Sysparam::updateOrCreate(
|
||||
['alias' => $alias],
|
||||
[
|
||||
'body' => $body,
|
||||
'guidetxt' => strtoupper($alias).' 配置说明',
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回本轮测试覆盖的系统参数样本。
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
private function systemParams(): array
|
||||
{
|
||||
return [
|
||||
'sys_name' => '原始聊天室',
|
||||
'sys_notice' => '原始公告',
|
||||
'smtp_host' => 'owner.smtp.example',
|
||||
'vip_payment_app_secret' => 'owner-secret',
|
||||
'wechat_bot_config' => '{"api":{"bot_key":"owner-only"}}',
|
||||
'chatbot_max_gold' => '5000',
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* 文件功能:房间名称安全校验测试
|
||||
* 验证建房与改房时会拦截可能注入前端的危险名称。
|
||||
*/
|
||||
|
||||
namespace Tests\Feature;
|
||||
|
||||
use App\Models\Room;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Tests\TestCase;
|
||||
|
||||
/**
|
||||
* 房间名称安全校验测试
|
||||
* 负责回归房间名称中的尖括号会被后端验证直接拦截。
|
||||
*/
|
||||
class RoomRequestSecurityTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
/**
|
||||
* 测试建房时不能提交包含尖括号的危险房间名称。
|
||||
*/
|
||||
public function test_cannot_create_room_with_html_like_name(): void
|
||||
{
|
||||
$user = User::factory()->create(['user_level' => 10]);
|
||||
|
||||
$response = $this->actingAs($user)->post(route('rooms.store'), [
|
||||
'name' => '<img src=x onerror=alert(1)>',
|
||||
'description' => '危险名称测试',
|
||||
]);
|
||||
|
||||
$response->assertSessionHasErrors('name');
|
||||
$this->assertDatabaseMissing('rooms', [
|
||||
'room_name' => '<img src=x onerror=alert(1)>',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试修改房间时同样不能把危险名称写入数据库。
|
||||
*/
|
||||
public function test_cannot_update_room_with_html_like_name(): void
|
||||
{
|
||||
$owner = User::factory()->create();
|
||||
$room = Room::create([
|
||||
'room_name' => '安全房间',
|
||||
'room_owner' => $owner->username,
|
||||
'room_keep' => false,
|
||||
]);
|
||||
|
||||
$response = $this->actingAs($owner)->from(route('rooms.index'))->put(route('rooms.update', $room->id), [
|
||||
'name' => '<svg onload=alert(1)>',
|
||||
'description' => '危险更新测试',
|
||||
]);
|
||||
|
||||
$response->assertSessionHasErrors('name');
|
||||
$this->assertDatabaseHas('rooms', [
|
||||
'id' => $room->id,
|
||||
'room_name' => '安全房间',
|
||||
]);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user