DEV Community

Cover image for Master the Art of Penetration Testing: A Hands-On Guide for Developers
David Au Yeung
David Au Yeung

Posted on

Master the Art of Penetration Testing: A Hands-On Guide for Developers

Introduction

Before delivering a solution to end users, ensuring its security is critical. Two common practices, especially in Hong Kong, are:

  1. Security Risk Assessment and Audit (SRAA): Evaluates the security risks inherent in the system.
  2. Privacy Impact Assessment (PIA): Ensures the system complies with privacy regulations and safeguards sensitive data.

While these assessments are crucial, they often require external audits or third-party tools. To smoothen the process and identify vulnerabilities early, developers can step into the realm of penetration testing (pen-testing).

Penetration testing is a proactive approach where developers simulate real-world attacks on their applications. This helps uncover vulnerabilities like injection flaws, weak authentication, and insecure configurations before attackers exploit them.

In this guide, we'll explore:

  • Common attacks your application may face.
  • Preventive measures to secure your application.
  • How to conduct penetration testing yourself using a reusable C# helper class.

What Attacks Could You Face?

Web applications face various types of attacks. Here are the most common ones:

  1. Injection Attacks

    • Attackers inject malicious input to manipulate queries or commands.
    • Examples: SQL Injection, Command Injection.
    • Prevention: Use parameterized queries and input validation.
  2. Cross-Site Scripting (XSS)

    • Injected scripts execute in a user's browser, stealing cookies or manipulating content.
    • Prevention: Encode all output, use a Content-Security-Policy (CSP), and validate inputs.
  3. Cross-Site Request Forgery (CSRF)

    • Tricks users into performing unintended actions while authenticated.
    • Prevention: Use anti-forgery tokens and enforce SameSite cookie policies.
  4. Insecure Authentication and Session Management

    • Weak password policies, session hijacking, or missing HttpOnly/Secure cookies.
    • Prevention: Enforce strong password policies, secure session cookies, and implement multi-factor authentication (MFA).
  5. Security Misconfigurations

    • Missing security headers, default credentials, or unnecessary services exposed.
    • Prevention: Use security headers, disable unused services, and regularly patch your environment.

Step 1: Build Your Penetration Testing Helper

To automate penetration testing tasks, we'll create a reusable C# utility class. This class will help you:

  1. Perform reconnaissance to gather information about your app.
  2. Check for missing security headers.
  3. Validate cookies for critical flags like HttpOnly and Secure.
  4. Test for CSRF protection.
  5. Conduct fuzzing to evaluate input validation.

Here's the full code for the PenTestingHelper class:

using System.Net.Http.Json;
using System.Text;
using System.Text.Json;

namespace MyPlaygroundApp.Utils
{
    public class PenTestingHelper
    {
        private readonly HttpClient _httpClient;

        public PenTestingHelper(string baseUrl)
        {
            _httpClient = new HttpClient { BaseAddress = new Uri(baseUrl) };
        }

        public async Task RunReconnaissanceAsync()
        {
            Console.WriteLine("\n[Reconnaissance]");
            var endpoints = new[] { "/robots.txt", "/.well-known/security.txt", "/favicon.ico" };
            foreach (var ep in endpoints)
            {
                var res = await _httpClient.GetAsync(ep);
                Console.WriteLine($"{ep} -> {(int)res.StatusCode} {res.ReasonPhrase}");
            }
        }

        public async Task CheckSecurityHeadersAsync()
        {
            Console.WriteLine("\n[Security Header Check]");
            var res = await _httpClient.GetAsync("/");
            var headers = new[] { "X-Frame-Options", "X-Content-Type-Options", "Content-Security-Policy", "Referrer-Policy" };
            foreach (var header in headers)
            {
                Console.WriteLine($"{header}: {(res.Headers.Contains(header) ? "Present" : "Missing")}");
            }
        }

        public async Task CheckCookieFlagsAsync(string loginUrl)
        {
            Console.WriteLine("\n[Cookie Flags Validation]");
            var res = await _httpClient.GetAsync(loginUrl);
            if (res.Headers.TryGetValues("Set-Cookie", out var setCookies))
            {
                foreach (var cookie in setCookies)
                {
                    Console.WriteLine($"Set-Cookie -> {cookie}");
                    if (!cookie.Contains("HttpOnly")) Console.WriteLine("  Warning: Cookie missing HttpOnly");
                    if (!cookie.Contains("Secure")) Console.WriteLine("  Warning: Cookie missing Secure");
                }
            }
            else
            {
                Console.WriteLine("No cookies found in the response.");
            }
        }

        public async Task TestCsrfProtectionAsync(string endpoint)
        {
            Console.WriteLine("\n[CSRF Protection Test]");
            var res = await _httpClient.PostAsync(endpoint, new StringContent("{\"dummy\":\"x\"}", Encoding.UTF8, "application/json"));
            Console.WriteLine($"{endpoint} -> {(int)res.StatusCode} {res.ReasonPhrase}");
        }

        public async Task FuzzEndpointAsync(string endpoint)
        {
            Console.WriteLine("\n[Fuzzing Endpoint]");
            var payloads = new[]
            {
                new { name = "normal" },
                new { name = "' OR '1'='1" },
                new { name = "<script>alert(1)</script>" },
                new { name = new string('A', 5000) }
            };

            foreach (var payload in payloads)
            {
                var res = await _httpClient.PostAsJsonAsync(endpoint, payload);
                Console.WriteLine($"Payload: {JsonSerializer.Serialize(payload)} -> {(int)res.StatusCode} {res.ReasonPhrase}");
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Step 2: Use the Helper in Your Application

Here's how to use the PenTestingHelper to test your application:

using MyPlaygroundApp.Utils;

class Program
{
    static async Task Main(string[] args)
    {
        var baseUrl = "https://your-app-url.com";
        var helper = new PenTestingHelper(baseUrl);

        // Run penetration tests
        await helper.RunReconnaissanceAsync();
        await helper.CheckSecurityHeadersAsync();
        await helper.CheckCookieFlagsAsync("/login");
        await helper.TestCsrfProtectionAsync("/bank/transfer");
        await helper.FuzzEndpointAsync("/api/test");
    }
}
Enter fullscreen mode Exit fullscreen mode

Step 3: Run and Analyze Results

  • Run the application:
  dotnet run
Enter fullscreen mode Exit fullscreen mode
  • Analyze the results to identify vulnerabilities and areas for improvement.

Here's what to expect when running the PenTestingHelper against a secure application versus a vulnerable one:

Reconnaissance

Endpoint Expected Response in Secure App Vulnerable App Response
/robots.txt 200 OK (Minimal or no sensitive paths exposed) 200 OK (Sensitive paths like /admin exposed)
/.well-known/security.txt 200 OK (Contains security contact details) 404 Not Found (No security.txt file present)
/favicon.ico 200 OK or 404 Not Found (Optional) 404 Not Found (No impact)

Security Header Check

Header Secure App Vulnerable App
X-Frame-Options Present (e.g., DENY) Missing
X-Content-Type-Options Present (e.g., nosniff) Missing
Content-Security-Policy Present (e.g., default-src 'self') Missing
Referrer-Policy Present (e.g., no-referrer) Missing

Cookie Flags Validation

Observation Secure App Vulnerable App
Cookies Returned HttpOnly, Secure, SameSite Missing these flags
Example Cookie Output Set-Cookie -> sessionid=xyz; HttpOnly; Secure; SameSite=Strict Set-Cookie -> sessionid=xyz

CSRF Protection Testing

Test Secure App Vulnerable App
CSRF Test 403 Forbidden (Reject without token) 200 OK or 400 Bad Request (No CSRF protection)
CSRF Test with Token 200 OK 400 Bad Request (Incorrect implementation)

Fuzzing

Payload Secure App Vulnerable App
Normal Input 200 OK 200 OK
SQL Injection (' OR '1'='1) 400 Bad Request (Rejected) 500 Internal Server Error (SQL Injection Possible)
XSS (<script>alert(1)</script>) 400 Bad Request (Rejected) 200 OK (XSS Possible)
Large Input 400 Bad Request 500 Internal Server Error (DoS Possible)

Step 4: Prevent Vulnerabilities

  1. Injection Attacks: Use parameterized queries and validate inputs.
  2. XSS: Use output encoding and a strong Content-Security-Policy.
  3. CSRF: Implement anti-forgery tokens and enforce SameSite cookie policies.
  4. Cookies: Secure cookies with HttpOnly, Secure, and SameSite flags.
  5. Security Headers: Ensure all recommended headers are present.

Step 5: Sample Output

Here's an example of the output you'd see when running a secure application:

[Reconnaissance]
/robots.txt -> 200 OK
/.well-known/security.txt -> 404 Not Found
/favicon.ico -> 404 Not Found

[Security Header Check]
X-Frame-Options: Present
X-Content-Type-Options: Present
Content-Security-Policy: Present
Referrer-Policy: Present

[Cookie Flags Validation]
Set-Cookie -> sessionid=xyz; HttpOnly; Secure; SameSite=Strict

[CSRF Protection Test]
/bank/transfer -> 403 Forbidden

[Fuzzing Endpoint]
Payload: {"name":"normal"} -> 200 OK
Payload: {"name":"' OR '1'='1"} -> 400 Bad Request
Payload: {"name":"<script>alert(1)</script>"} -> 400 Bad Request
Payload: {"name":"AAAA..."} -> 400 Bad Request
Enter fullscreen mode Exit fullscreen mode

Additional: How to Add Security Headers Middleware

To further enhance your application's security, you can add middleware in Program.cs to configure essential security headers. These headers help protect your application against attacks like clickjacking, XSS, and MIME sniffing.

Here's how you can set up the middleware:

// Security headers middleware
app.Use(async (context, next) =>
{
    // X-Content-Type-Options: Prevent MIME type sniffing
    context.Response.Headers.Append("X-Content-Type-Options", "nosniff");

    // X-Frame-Options: Prevent clickjacking
    context.Response.Headers.Append("X-Frame-Options", "DENY");

    // Referrer-Policy: Control referrer information
    context.Response.Headers.Append("Referrer-Policy", "strict-origin-when-cross-origin");

    // X-XSS-Protection: Enable XSS protection (legacy browsers)
    context.Response.Headers.Append("X-XSS-Protection", "1; mode=block");

    // Permissions-Policy: Control browser features
    context.Response.Headers.Append("Permissions-Policy",
        "camera=(), microphone=(), geolocation=(), payment=(), usb=()");

    // Content-Security-Policy: Define allowed resources
    var connectSrc = app.Environment.IsDevelopment()
        ? "'self' ws: wss: http://localhost:* https://localhost:* https://cdnjs.cloudflare.com https://cdn.jsdelivr.net https://ajax.googleapis.com https://ajax.aspnetcdn.com"
        : "'self' https://cdnjs.cloudflare.com https://cdn.jsdelivr.net https://ajax.googleapis.com https://ajax.aspnetcdn.com";

    var csp = "default-src 'self'; " +
              "script-src 'self' 'unsafe-inline' 'unsafe-eval' https://cdnjs.cloudflare.com https://cdn.jsdelivr.net https://ajax.googleapis.com https://ajax.aspnetcdn.com; " +
              "style-src 'self' 'unsafe-inline' https://fonts.googleapis.com https://cdnjs.cloudflare.com https://cdn.jsdelivr.net https://ajax.googleapis.com https://ajax.aspnetcdn.com; " +
              "font-src 'self' https://fonts.gstatic.com https://cdnjs.cloudflare.com https://cdn.jsdelivr.net https://ajax.googleapis.com; " +
              "img-src 'self' data: https:; " +
              $"connect-src {connectSrc}; " +
              "frame-ancestors 'none'; " +
              "base-uri 'self'; " +
              "form-action 'self'; " +
              "upgrade-insecure-requests";

    context.Response.Headers.Append("Content-Security-Policy", csp);

    // Strict-Transport-Security: Force HTTPS (only in production)
    if (!app.Environment.IsDevelopment())
    {
        context.Response.Headers.Append("Strict-Transport-Security",
            "max-age=31536000; includeSubDomains; preload");
    }

    // Remove server information
    context.Response.Headers.Remove("Server");
    context.Response.Headers.Remove("X-Powered-By");

    await next();
});
Enter fullscreen mode Exit fullscreen mode

Benefits of the Middleware

This middleware provides the following benefits:

  • X-Content-Type-Options: Prevents MIME sniffing.
  • X-Frame-Options: Blocks clickjacking attacks.
  • Referrer-Policy: Limits sensitive information in referrer headers.
  • X-XSS-Protection: Enables XSS protection for legacy browsers.
  • Permissions-Policy: Restricts the use of unnecessary browser features.
  • Content-Security-Policy: Mitigates XSS and injection attacks.
  • Strict-Transport-Security: Enforces HTTPS connections.

By adding this middleware, you can ensure that your app adheres to modern security standards.


Conclusion

Penetration testing is an essential step in securing your application. With the PenTestingHelper, you can quickly identify common vulnerabilities and address them before attackers exploit them.

By incorporating penetration testing into your development workflow, you'll not only improve security but also build trust with your users.

Try it out, and let me know how it works for you!

Reference

Love C#!

Top comments (0)