Security Testing

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

Phong Do Phong Do · 03/09/2025 · 11 phút đọc

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.


Phong Do
Tác giả
Phong Do