feat: 创建完整的安全测试

- 添加XSS防护测试(脚本注入、img标签、svg标签、javascript伪协议)
- 添加SQL注入防护测试(SQL注入、OR注入、注释注入)
- 添加路径遍历防护测试(路径遍历、编码路径遍历)
- 添加CSRF防护测试(CSRF令牌、令牌验证)
- 添加安全头测试(X-Frame-Options、X-Content-Type-Options等)
- 添加HTTPS强制跳转测试
- 添加输入验证测试(邮箱格式、手机号格式、必填字段)
- 添加敏感数据保护测试(页面源码、控制台日志)~
This commit is contained in:
张翔
2026-02-28 16:05:06 +08:00
parent 5131ba8880
commit ca7cb42f7d
+336
View File
@@ -0,0 +1,336 @@
import { test, expect } from '@playwright/test';
import { ContactPage } from '../../pages/ContactPage';
import { HomePage } from '../../pages/HomePage';
import { SECURITY_TEST_CASES } from '../../data/test-data';
test.describe('安全测试', () => {
test.describe('XSS防护测试', () => {
let contactPage: ContactPage;
test.beforeEach(async ({ page }) => {
contactPage = new ContactPage(page);
await contactPage.goto();
});
test('应该防止XSS脚本注入', async () => {
const payloads = SECURITY_TEST_CASES.xssPayloads;
for (const payload of payloads) {
await contactPage.goto();
await contactPage.testXSSInjection(payload);
await contactPage.waitForTimeout(2000);
const pageContent = await contactPage.page.content();
expect(pageContent).not.toContain('<script>');
expect(pageContent).not.toContain('alert(');
}
});
test('应该防止img标签XSS注入', async () => {
const payload = '<img src=x onerror=alert("XSS")>';
await contactPage.testXSSInjection(payload);
await contactPage.waitForTimeout(2000);
const pageContent = await contactPage.page.content();
expect(pageContent).not.toContain('onerror=');
});
test('应该防止svg标签XSS注入', async () => {
const payload = '<svg onload=alert("XSS")>';
await contactPage.testXSSInjection(payload);
await contactPage.waitForTimeout(2000);
const pageContent = await contactPage.page.content();
expect(pageContent).not.toContain('onload=');
});
test('应该防止javascript伪协议注入', async () => {
const payload = 'javascript:alert("XSS")';
await contactPage.testXSSInjection(payload);
await contactPage.waitForTimeout(2000);
const pageContent = await contactPage.page.content();
expect(pageContent).not.toContain('javascript:');
});
});
test.describe('SQL注入防护测试', () => {
let contactPage: ContactPage;
test.beforeEach(async ({ page }) => {
contactPage = new ContactPage(page);
await contactPage.goto();
});
test('应该防止SQL注入攻击', async () => {
const payloads = SECURITY_TEST_CASES.sqlInjectionPayloads;
for (const payload of payloads) {
await contactPage.goto();
await contactPage.testSQLInjection(payload);
await contactPage.waitForTimeout(2000);
const pageContent = await contactPage.page.content();
expect(pageContent).not.toContain('DROP TABLE');
expect(pageContent).not.toContain('UNION SELECT');
expect(pageContent).not.toContain('INSERT INTO');
}
});
test('应该防止OR注入攻击', async () => {
const payload = "' OR '1'='1";
await contactPage.testSQLInjection(payload);
await contactPage.waitForTimeout(2000);
const pageContent = await contactPage.page.content();
expect(pageContent).not.toContain("' OR '");
});
test('应该防止注释注入攻击', async () => {
const payload = "'; DROP TABLE users; --";
await contactPage.testSQLInjection(payload);
await contactPage.waitForTimeout(2000);
const pageContent = await contactPage.page.content();
expect(pageContent).not.toContain('DROP TABLE');
expect(pageContent).not.toContain('--');
});
});
test.describe('路径遍历防护测试', () => {
let contactPage: ContactPage;
test.beforeEach(async ({ page }) => {
contactPage = new ContactPage(page);
await contactPage.goto();
});
test('应该防止路径遍历攻击', async () => {
const payloads = SECURITY_TEST_CASES.pathTraversalPayloads;
for (const payload of payloads) {
await contactPage.goto();
await contactPage.testPathTraversal(payload);
await contactPage.waitForTimeout(2000);
const pageContent = await contactPage.page.content();
expect(pageContent).not.toContain('../');
expect(pageContent).not.toContain('..\\');
expect(pageContent).not.toContain('etc/passwd');
expect(pageContent).not.toContain('windows\\system32');
}
});
test('应该防止编码路径遍历攻击', async () => {
const payload = '....//....//....//etc/passwd';
await contactPage.testPathTraversal(payload);
await contactPage.waitForTimeout(2000);
const pageContent = await contactPage.page.content();
expect(pageContent).not.toContain('etc/passwd');
});
});
test.describe('CSRF防护测试', () => {
let homePage: HomePage;
test.beforeEach(async ({ page }) => {
homePage = new HomePage(page);
await homePage.goto();
});
test('应该包含CSRF令牌', async () => {
await homePage.clickContactButton();
await homePage.waitForLoadState('networkidle');
const csrfToken = await homePage.page.evaluate(() => {
const token = document.querySelector('input[name="csrf_token"]');
return token ? (token as HTMLInputElement).value : null;
});
expect(csrfToken).toBeTruthy();
});
test('应该验证CSRF令牌', async () => {
await homePage.clickContactButton();
await homePage.waitForLoadState('networkidle');
const hasToken = await homePage.page.evaluate(() => {
const form = document.querySelector('form');
return form ? form.querySelector('input[name="csrf_token"]') !== null : false;
});
expect(hasToken).toBe(true);
});
});
test.describe('安全头测试', () => {
let homePage: HomePage;
test.beforeEach(async ({ page }) => {
homePage = new HomePage(page);
await homePage.goto();
});
test('应该包含X-Frame-Options头', async () => {
const response = await homePage.page.evaluate(async () => {
const response = await fetch(window.location.href);
return response.headers.get('X-Frame-Options');
});
expect(response).toBeTruthy();
});
test('应该包含X-Content-Type-Options头', async () => {
const response = await homePage.page.evaluate(async () => {
const response = await fetch(window.location.href);
return response.headers.get('X-Content-Type-Options');
});
expect(response).toBe('nosniff');
});
test('应该包含Content-Security-Policy头', async () => {
const response = await homePage.page.evaluate(async () => {
const response = await fetch(window.location.href);
return response.headers.get('Content-Security-Policy');
});
expect(response).toBeTruthy();
});
test('应该包含Strict-Transport-Security头', async () => {
const response = await homePage.page.evaluate(async () => {
const response = await fetch(window.location.href);
return response.headers.get('Strict-Transport-Security');
});
expect(response).toBeTruthy();
});
});
test.describe('HTTPS强制跳转测试', () => {
test('应该强制使用HTTPS', async ({ page, context }) => {
const baseURL = process.env.TEST_ENV === 'production'
? 'https://novalon.com'
: process.env.TEST_ENV === 'staging'
? 'https://staging.novalon.com'
: 'http://localhost:3001';
await page.goto(baseURL);
await page.waitForLoadState('networkidle');
const currentURL = page.url();
if (baseURL.startsWith('http://') && !baseURL.includes('localhost')) {
expect(currentURL).toMatch(/^https:\/\//);
}
});
});
test.describe('输入验证测试', () => {
let contactPage: ContactPage;
test.beforeEach(async ({ page }) => {
contactPage = new ContactPage(page);
await contactPage.goto();
});
test('应该验证邮箱格式', async () => {
const invalidEmails = ['invalid-email', '@example.com', 'user@', 'user@domain'];
for (const email of invalidEmails) {
await contactPage.goto();
await contactPage.fillContactForm({
name: '测试用户',
email: email,
phone: '13800138000',
message: '测试消息',
});
await contactPage.submitForm();
await contactPage.waitForTimeout(1000);
const isEmailErrorVisible = await contactPage.isEmailErrorVisible();
expect(isEmailErrorVisible).toBe(true);
}
});
test('应该验证手机号格式', async () => {
const invalidPhones = ['123', '123456789012345', 'abcdefghijk'];
for (const phone of invalidPhones) {
await contactPage.goto();
await contactPage.fillContactForm({
name: '测试用户',
email: 'test@example.com',
phone: phone,
message: '测试消息',
});
await contactPage.submitForm();
await contactPage.waitForTimeout(1000);
const isPhoneErrorVisible = await contactPage.isPhoneErrorVisible();
expect(isPhoneErrorVisible).toBe(true);
}
});
test('应该验证必填字段', async () => {
await contactPage.fillContactForm({
name: '',
email: '',
phone: '',
message: '',
});
await contactPage.submitForm();
await contactPage.waitForTimeout(1000);
const requiredFields = await contactPage.verifyRequiredFields();
expect(requiredFields.nameRequired).toBe(true);
expect(requiredFields.emailRequired).toBe(true);
expect(requiredFields.phoneRequired).toBe(true);
expect(requiredFields.messageRequired).toBe(true);
});
});
test.describe('敏感数据保护测试', () => {
let contactPage: ContactPage;
test.beforeEach(async ({ page }) => {
contactPage = new ContactPage(page);
await contactPage.goto();
});
test('应该不在页面源码中暴露敏感数据', async () => {
const pageContent = await contactPage.page.content();
expect(pageContent).not.toContain('password');
expect(pageContent).not.toContain('api_key');
expect(pageContent).not.toContain('secret');
expect(pageContent).not.toContain('token');
});
test('应该不在控制台日志中暴露敏感数据', async () => {
const logs: string[] = [];
contactPage.page.on('console', msg => {
logs.push(msg.text());
});
await contactPage.fillContactForm({
name: '测试用户',
email: 'test@example.com',
phone: '13800138000',
message: '测试消息',
});
await contactPage.submitForm();
await contactPage.waitForTimeout(2000);
const sensitiveDataFound = logs.some(log =>
log.includes('password') ||
log.includes('api_key') ||
log.includes('secret') ||
log.includes('token')
);
expect(sensitiveDataFound).toBe(false);
});
});
});