[Q&A] Anh ơi, anh có thể hướng dẫn thêm SQL Injection trong automation testing với ạ?

Hỏi: Anh ơi, anh có thể hướng dẫn thêm SQL Injection trong automation testing với ạ?
Đáp
SQL Injection là một trong những lỗ hổng bảo mật phổ biến và nguy hiểm nhất trong ứng dụng web. Bài viết này sẽ giúp bạn hiểu rõ về SQL Injection và cách phòng chống, với các ví dụ thực tế sử dụng Playwright TypeScript để kiểm thử.
SQL Injection là gì?
SQL Injection xảy ra khi kẻ tấn công có thể chèn (còn hiểu cách khác là “tiêm” – nguồn gốc của từ injection) mã SQL độc hại vào các truy vấn cơ sở dữ liệu thông qua input của người dùng. Điều này có thể dẫn đến việc truy cập trái phép, sửa đổi hoặc xóa dữ liệu.
Cách SQL Injection hoạt động
Giả sử bạn có một form đăng nhập với truy vấn SQL như sau:
SELECT * FROM users WHERE username = '${username}' AND password = '${password}'
Nếu người dùng nhập username là admin' -- và password bất kỳ, truy vấn sẽ trở thành:
SELECT * FROM users WHERE username = 'admin' <em>--' AND password = 'anything
Phần -- là comment trong SQL, làm vô hiệu hóa phần kiểm tra password.
Test SQL Injection với Playwright TypeScript
Thiết lập môi trường test
// sql-injection.spec.ts
import { test, expect } from '@playwright/test';
test.describe('SQL Injection Security Tests', () => {
const baseURL = 'http://localhost:3000';
test.beforeEach(async ({ page }) => {
await page.goto(`${baseURL}/login`);
});
test('should prevent basic SQL injection in login form', async ({ page }) => {
// Các payload SQL injection phổ biến
const sqlInjectionPayloads = [
"admin' --",
"' OR '1'='1",
"'; DROP TABLE users; --", <em>// Cân nhắc, có thể bỏ qua vì lỗi sẽ xoá 1 bảng trong database của dev</em>
"admin' OR '1'='1' --",
"' UNION SELECT * FROM users --"
];
for (const payload of sqlInjectionPayloads) {
await page.fill('#username', payload);
await page.fill('#password', 'anypassword');
await page.click('#login-button');
// Kiểm tra response không chứa thông tin nhạy cảm
const errorMessage = await page.locator('.error-message').textContent();
expect(errorMessage).not.toContain('SQL');
expect(errorMessage).not.toContain('syntax error');
// Đảm bảo không đăng nhập thành công
await expect(page).not.toHaveURL(/dashboard/);
}
});
test('should sanitize search input against SQL injection', async ({ page }) => {
await page.goto(`${baseURL}/search`);
const searchPayloads = [
"'; SELECT * FROM products WHERE '1'='1",
"' OR price > 0 --",
"1' UNION SELECT username, password FROM users --"
];
for (const payload of searchPayloads) {
await page.fill('#search-input', payload);
await page.click('#search-button');
// Kiểm tra không có lỗi SQL được hiển thị
const pageContent = await page.content();
expect(pageContent).not.toMatch(/SQL.*error/i);
expect(pageContent).not.toMatch(/mysql_fetch/i);
// Kiểm tra không có dữ liệu nhạy cảm bị lộ
expect(pageContent).not.toContain('password');
expect(pageContent).not.toContain('admin@');
}
});
});
Test nâng cao với nhiều loại injection khác nhau
// advanced-sql-injection.spec.ts
import { test, expect } from '@playwright/test';
test.describe('Advanced SQL Injection Tests', () => {
test('should prevent blind SQL injection', async ({ page }) => {
await page.goto('http://localhost:3000/product');
// Blind SQL injection payloads
const blindSQLPayloads = [
"1' AND SLEEP(5) --",
"1' AND (SELECT COUNT(*) FROM users) > 0 --",
"1'; IF(1=1, SLEEP(5), 0) --"
];
for (const payload of blindSQLPayloads) {
const startTime = Date.now();
await page.fill('#product-id', payload);
await page.click('#view-product');
const responseTime = Date.now() - startTime;
// Đảm bảo không có delay bất thường
expect(responseTime).toBeLessThan(2000);
// Kiểm tra response hợp lệ
const errorElement = page.locator('.error');
if (await errorElement.isVisible()) {
const errorText = await errorElement.textContent();
expect(errorText).toBe('Invalid product ID');
}
}
});
// Đố bạn: second-order SQL injection là gì?
test('should prevent second-order SQL injection', async ({ page }) => {
// Đăng ký với username chứa SQL payload
await page.goto('http://localhost:3000/register');
const maliciousUsername = "admin'--";
await page.fill('#reg-username', maliciousUsername);
await page.fill('#reg-password', 'Test123!');
await page.fill('#reg-email', 'test@example.com');
await page.click('#register-button');
// Kiểm tra username được lưu an toàn
await page.goto('http://localhost:3000/profile');
const displayedUsername = await page.locator('#username-display').textContent();
// Username phải được escape hoặc parameterized
expect(displayedUsername).toBe(maliciousUsername);
// Thử trigger second-order injection
await page.click('#update-profile');
// Kiểm tra không có lỗi SQL
const pageContent = await page.content();
expect(pageContent).not.toMatch(/SQL.*error/i);
});
});
Full code automation test SQLi cho Playwright TypeScript
// comprehensive-security.spec.ts
import { test, expect, Page } from '@playwright/test';
class SQLInjectionTester {
private page: Page;
constructor(page: Page) {
this.page = page;
}
async testFormField(selector: string, payloads: string[]): Promise<boolean> {
let isVulnerable = false;
for (const payload of payloads) {
await this.page.fill(selector, payload);
await this.page.keyboard.press('Enter');
// Chờ response
await this.page.waitForLoadState('networkidle');
// Kiểm tra các dấu hiệu của SQL injection
const pageContent = await this.page.content();
const vulnerabilityIndicators = [
/SQL syntax.*MySQL/i,
/Warning.*mysql_/i,
/MySQLSyntaxErrorException/i,
/PostgreSQL.*ERROR/i,
/warning.*pg_/i,
/valid PostgreSQL result/i,
/mssql_query()/i,
/SqlException/i,
/ORA-[0-9]{5}/i
];
for (const indicator of vulnerabilityIndicators) {
if (indicator.test(pageContent)) {
isVulnerable = true;
console.error(`Vulnerability detected with payload: ${payload}`);
break;
}
}
}
return isVulnerable;
}
async generateReport(): Promise<object> {
const report = {
timestamp: new Date().toISOString(),
url: this.page.url(),
vulnerabilities: []
};
// Test các form fields phổ biến
const fieldsToTest = [
{ selector: '#username', name: 'Username field' },
{ selector: '#password', name: 'Password field' },
{ selector: '#email', name: 'Email field' },
{ selector: '#search', name: 'Search field' },
{ selector: '#id', name: 'ID parameter' }
];
const commonPayloads = [
"' OR '1'='1",
"admin' --",
"' UNION SELECT NULL--",
"1' AND '1' = '1",
"' OR 1=1--"
];
for (const field of fieldsToTest) {
const element = this.page.locator(field.selector);
if (await element.count() > 0) {
const isVulnerable = await this.testFormField(field.selector, commonPayloads);
if (isVulnerable) {
report.vulnerabilities.push({
field: field.name,
selector: field.selector,
severity: 'HIGH'
});
}
}
}
return report;
}
}
test('comprehensive SQL injection scan', async ({ page }) => {
const tester = new SQLInjectionTester(page);
// Danh sách các trang cần test
const pagesToTest = [
'/login',
'/search',
'/products',
'/contact'
];
const allReports = [];
for (const pagePath of pagesToTest) {
await page.goto(`http://localhost:3000${pagePath}`);
const report = await tester.generateReport();
allReports.push(report);
}
// Kiểm tra không có vulnerability nào
const totalVulnerabilities = allReports.reduce(
(sum, report) => sum + report.vulnerabilities.length,
0
);
expect(totalVulnerabilities).toBe(0);
// Lưu report nếu cần
if (totalVulnerabilities > 0) {
console.log('Security Report:', JSON.stringify(allReports, null, 2));
}
});
Bảo mật là một quá trình liên tục. Hãy thường xuyên cập nhật kiến thức và kiểm tra ứng dụng của bạn với các kỹ thuật tấn công mới nhất để đảm bảo an toàn cho người dùng.
