feat: 创建完整的安全测试
- 添加XSS防护测试(脚本注入、img标签、svg标签、javascript伪协议) - 添加SQL注入防护测试(SQL注入、OR注入、注释注入) - 添加路径遍历防护测试(路径遍历、编码路径遍历) - 添加CSRF防护测试(CSRF令牌、令牌验证) - 添加安全头测试(X-Frame-Options、X-Content-Type-Options等) - 添加HTTPS强制跳转测试 - 添加输入验证测试(邮箱格式、手机号格式、必填字段) - 添加敏感数据保护测试(页面源码、控制台日志)~
This commit is contained in:
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user