What is Penetration Testing?
Penetration testing (pen testing) is the practice of testing a computer system, network, or web application to find security vulnerabilities that an attacker could exploit. Unlike vulnerability scanning, which is automated, penetration testing involves a human tester who thinks creatively and chains together multiple small issues to achieve a significant compromise.
As developers, understanding penetration testing methodology helps you think like an attacker. When you can anticipate how someone might try to break into your application, you can build stronger defenses. This topic covers defensive pen testing — testing your own applications to find and fix vulnerabilities before they are exploited.
Security Warning: Legal and Ethical Considerations
- Only test systems you own or have written authorization to test. Unauthorized testing is illegal in most jurisdictions.
- Always get written permission before testing, even for systems within your own organization.
- Define scope clearly: Specify which systems, endpoints, and testing methods are in scope.
- Never test production systems without proper safeguards and approval. Use staging environments when possible.
Penetration Testing Methodology
A structured methodology ensures thorough coverage and reproducible results. The most widely used framework is the PTES (Penetration Testing Execution Standard), which defines seven phases: pre-engagement interactions, intelligence gathering, threat modeling, vulnerability analysis, exploitation, post-exploitation, and reporting.
Penetration Testing Phases
- Reconnaissance: Gather information about the target. Map endpoints, identify technologies, discover hidden pages, and understand the application flow.
- Scanning: Use automated tools to identify potential vulnerabilities. Port scanning, vulnerability scanning, and web application scanning.
- Vulnerability Analysis: Analyze scan results and manually verify findings. Determine which vulnerabilities are real vs false positives.
- Exploitation: Attempt to exploit verified vulnerabilities in a controlled manner to prove their impact.
- Reporting: Document findings with severity ratings, evidence, and remediation recommendations.
Automated Security Testing in Your Pipeline
You do not need to be a professional pen tester to integrate security testing into your development workflow. Automated tools can catch many common vulnerabilities during CI/CD. Static Application Security Testing (SAST) analyzes source code, Dynamic Application Security Testing (DAST) tests running applications, and Software Composition Analysis (SCA) checks dependencies for known vulnerabilities.
// Automated security test suite using a testing framework
import { describe, it, expect } from "vitest";
describe("Security Tests", () => {
describe("SQL Injection", () => {
const sqlPayloads = [
"' OR '1'='1",
"'; DROP TABLE users; --",
"1; SELECT * FROM information_schema.tables",
"' UNION SELECT null, username, password FROM users --",
"1' AND '1'='1",
];
for (const payload of sqlPayloads) {
it(`should reject SQL injection payload: ${payload.slice(0, 30)}`, async () => {
const response = await fetch("http://localhost:3000/api/users/search", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ query: payload }),
});
// Should not return 200 with data
// Should return 400 (validation error) or safe results
expect(response.status).not.toBe(500); // No server errors
const body = await response.json();
expect(body.data?.length).not.toBeGreaterThan(1); // No data exfil
});
}
});
describe("XSS Prevention", () => {
const xssPayloads = [
'<script>alert("XSS")</script>',
'<img src=x onerror=alert("XSS")>',
'javascript:alert("XSS")',
'<svg onload=alert("XSS")>',
'" onfocus="alert(1)" autofocus="',
];
for (const payload of xssPayloads) {
it(`should sanitize XSS payload: ${payload.slice(0, 30)}`, async () => {
const response = await fetch("http://localhost:3000/api/posts", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: "Bearer test-token",
},
body: JSON.stringify({ title: "Test", content: payload }),
});
if (response.ok) {
const body = await response.json();
// Content should be sanitized
expect(body.content).not.toContain("<script");
expect(body.content).not.toContain("onerror=");
expect(body.content).not.toContain("javascript:");
}
});
}
});
describe("Authentication", () => {
it("should not expose data without authentication", async () => {
const response = await fetch("http://localhost:3000/api/users/me");
expect(response.status).toBe(401);
});
it("should reject expired tokens", async () => {
const expiredToken = createTestToken({ exp: Math.floor(Date.now() / 1000) - 3600 });
const response = await fetch("http://localhost:3000/api/users/me", {
headers: { Authorization: `Bearer ${expiredToken}` },
});
expect(response.status).toBe(401);
});
it("should enforce rate limiting on login", async () => {
const attempts = Array.from({ length: 10 }, () =>
fetch("http://localhost:3000/api/auth/login", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ email: "test@test.com", password: "wrong" }),
})
);
const responses = await Promise.all(attempts);
const rateLimited = responses.some((r) => r.status === 429);
expect(rateLimited).toBe(true);
});
});
describe("Security Headers", () => {
it("should set all required security headers", async () => {
const response = await fetch("http://localhost:3000/");
const headers = response.headers;
expect(headers.get("strict-transport-security")).toBeTruthy();
expect(headers.get("x-content-type-options")).toBe("nosniff");
expect(headers.get("x-frame-options")).toBeTruthy();
expect(headers.get("content-security-policy")).toBeTruthy();
expect(headers.has("x-powered-by")).toBe(false);
});
});
});
Common Testing Tools
Security Testing Toolkit
| Tool | Type | Purpose |
|---|---|---|
| OWASP ZAP | DAST | Free web application security scanner |
| ESLint Security Plugin | SAST | Static analysis for JavaScript/TypeScript |
| npm audit / Snyk | SCA | Dependency vulnerability scanning |
| Burp Suite | DAST | Intercepting proxy for manual testing |
| Nuclei | Scanner | Template-based vulnerability scanner |
Building a Security Testing Culture
- Integrate SAST into CI: Run static analysis on every pull request to catch vulnerabilities before merge.
- Run DAST on staging: Schedule automated dynamic scans against your staging environment weekly.
- Dependency audits: Run npm audit or Snyk on every build and block deployments with critical vulnerabilities.
- Bug bounty programs: Invite external researchers to find vulnerabilities you might have missed.
- Regular manual reviews: Automated tools miss logic flaws. Schedule manual security reviews for critical features.