Cybersecurity Engineer Top 100 Interview Questions
100 cybersecurity engineer interview questions with structured answers, comparison tables, flow diagrams, worked calculations and runnable code. Covers security fundamentals, cryptography, network and application security, identity, detection, incident response and vulnerability management, from the basics to design and scenario questions.
Questions 41-60 of 100
Q41.What is the difference between authentication and authorization?
Authentication proves who someone is; authorization decides what that proven identity is allowed to do. Authentication happens first, but passing it says nothing about which data or actions a user should reach.
Authentication Authorization Answers: who are you? Answers: what may you do? Evidence: password, key, token, biometric Evidence: roles, permissions, ownership, policy Runs at login or session start Runs on every request to a protected resource Fails when credentials are stolen or guessed Fails when a user reads another user's records HTTP 401 Unauthorized on failure HTTP 403 Forbidden on failure Points that matter in practice:
- Most serious web application flaws are authorization failures: the user is genuinely logged in but reaches data that is not theirs by changing an ID.
- Authorization must be enforced on the server for every request. Hiding a button in the user interface is not access control.
- Keep authorization rules in one shared place, such as a policy module, so they are applied consistently.
Interview tip: the confusing status code names are a common trick question; 401 actually means "not authenticated", while 403 means "authenticated but not allowed".
Q42.What makes multi-factor authentication phishing-resistant?
Multi-factor authentication is phishing-resistant when the user cannot hand a usable secret to a fake website. The strongest methods sign a challenge with a private key that never leaves the device, and the signature is bound to the real website's address.
Text message code Authenticator app code Push approval Possession of a phone number Possession of a device Possession of a device Low phishing resistance Low phishing resistance Medium phishing resistance Weakness: SIM swapping, codes typed into fake sites Weakness: codes relayed in real time by a phishing proxy Weakness: approval fatigue from repeated prompts Push with number matching Hardware security key or passkey Possession plus a user check Possession, often plus biometric or PIN Medium phishing resistance High phishing resistance Weakness: still approvable if the user is fooled Weakness: recovery processes and lost devices Why the strongest methods resist phishing:
- The browser tells the authenticator which website is asking. A look-alike domain gets a signature that is useless on the real site.
- There is no code for the user to read out or type, so nothing can be relayed.
- The private key cannot be exported, so it cannot be stolen from a server breach either.
The weak point then becomes account recovery. Protect recovery and help-desk resets as strongly as the login itself.
Q43.How do SAML, OAuth 2.0 and OpenID Connect differ?
SAML is an XML-based standard for single sign-on, mostly used for enterprise web applications. OAuth 2.0 is a framework for delegated authorization, letting an app call an API on a user's behalf. OpenID Connect adds a standard login layer on top of OAuth 2.0.
SAML OAuth 2.0 OpenID Connect For authentication and single sign-on For delegated access to APIs For authentication on top of OAuth 2.0 Token: a signed XML assertion Token: an access token, often opaque Token: an ID token as a signed JWT, plus an access token Used for workforce sign-on to business apps Used when an app reads a user's calendar Used for sign-in to web and mobile apps Poor fit for mobile apps and APIs Good fit for mobile apps and APIs Good fit for mobile apps and APIs Tells the app who the user is Does not by itself tell the app who the user is Tells the app who the user is When to use which:
- Use SAML when integrating with enterprise applications that only support it.
- Use OAuth 2.0 when an application needs scoped access to an API.
- Use OpenID Connect for new login integrations, especially for mobile and single-page apps.
A common mistake is treating an OAuth access token as proof of who logged in. Access tokens are meant for the API, so use the OpenID Connect ID token to establish identity.
Q44.How does the OAuth authorization code flow with PKCE work?
The application sends the user to the authorization server to log in, receives a short-lived authorization code, and exchanges that code for tokens. PKCE adds a one-time secret so that only the app that started the flow can redeem the code.
- The app creates a random
code_verifierand derivescode_challenge = BASE64URL(SHA256(code_verifier)). - It redirects the user to the authorization server with its client ID, redirect address, scopes, a random
statevalue and the code challenge. - The user logs in and consents; the server redirects back with a code and the same
state. - The app checks that
statematches, which blocks forged callbacks. - The app sends the code plus the original
code_verifierto the token endpoint. - The server hashes the verifier, compares it with the stored challenge, and only then returns tokens.
sequenceDiagram participant U as User browser participant A as App participant S as Authorization server A->>U: Redirect with code challenge and state U->>S: Log in and consent S->>U: Redirect back with code and state U->>A: Code and state A->>S: Code plus code verifier S->>S: Hash verifier and compare with challenge S->>A: Access token and refresh tokenPKCE matters because codes can leak through browser history, logs or a malicious app registered for the same redirect address. A leaked code is useless without the verifier, which never left the app.
Example
Deriving the PKCE challenge from a verifier, using the published test values for the method:
import base64 import hashlib verifier = "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk" challenge = base64.urlsafe_b64encode(hashlib.sha256(verifier.encode()).digest()).rstrip(b"=").decode() print(challenge) # E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM print(len(verifier), len(challenge)) # 43 43The app sends only the challenge at the start. An attacker who intercepts the code cannot compute the verifier from the challenge, because the hash cannot be reversed.
- The app creates a random
Q45.What is a JSON Web Token, and what mistakes make JWT validation unsafe?
A JSON Web Token is a compact, signed set of claims written as
header.payload.signature, with each part Base64URL-encoded. The signature proves the claims were issued by the key holder and not changed, but the payload is readable by anyone.Mistakes that make validation unsafe:
- Trusting the token's
algheader: acceptingnone, or letting an attacker switch from an asymmetric algorithm to an HMAC one keyed with the public key. Pin the expected algorithm on the server. - Skipping claim checks: always validate the expiry, the issuer and the audience, so a token for another service is rejected.
- Weak HMAC secrets: a short secret can be brute-forced offline from any captured token.
- Following key URLs from the header: fetching keys from a location the token names lets attackers supply their own key.
- Putting secrets in the payload: it is encoded, not encrypted.
- Long lifetimes with no revocation: keep access tokens short-lived and use refresh tokens that the server can revoke.
Use a maintained library with an explicit configuration for algorithm, issuer, audience and clock skew, rather than decoding tokens by hand.
Example
A minimal issuer and a strict verifier, tested against the common attacks:
import base64 import hashlib import hmac import json KEY = b"a-long-random-server-side-signing-key-0042" def b64url(data: bytes) -> str: return base64.urlsafe_b64encode(data).rstrip(b"=").decode() def b64url_decode(text: str) -> bytes: return base64.urlsafe_b64decode(text + "=" * (-len(text) % 4)) def sign(header_b64: str, payload_b64: str) -> str: return b64url(hmac.new(KEY, f"{header_b64}.{payload_b64}".encode(), hashlib.sha256).digest()) def issue(claims: dict) -> str: header = b64url(json.dumps({"alg": "HS256", "typ": "JWT"}).encode()) payload = b64url(json.dumps(claims).encode()) return f"{header}.{payload}.{sign(header, payload)}" def verify(token: str, audience: str, now: int) -> dict: header_b64, payload_b64, signature = token.split(".") if json.loads(b64url_decode(header_b64)).get("alg") != "HS256": raise ValueError("unexpected algorithm") # pinned, never taken from the token if not hmac.compare_digest(sign(header_b64, payload_b64), signature): raise ValueError("bad signature") claims = json.loads(b64url_decode(payload_b64)) if claims.get("aud") != audience: raise ValueError("wrong audience") if now >= claims["exp"]: raise ValueError("expired") return claims NOW = 1_700_000_000 token = issue({"sub": "user-17", "aud": "orders-api", "exp": NOW + 900}) header, payload, signature = token.split(".") forged_payload = b64url(json.dumps({"sub": "admin", "aud": "orders-api", "exp": NOW + 900}).encode()) unsigned_header = b64url(json.dumps({"alg": "none"}).encode()) cases = { "valid": (token, "orders-api", NOW), "changed payload": (f"{header}.{forged_payload}.{signature}", "orders-api", NOW), "alg none": (f"{unsigned_header}.{payload}.", "orders-api", NOW), "other service": (token, "billing-api", NOW), "expired": (token, "orders-api", NOW + 901), } for name, (candidate, audience, at) in cases.items(): try: print(f"{name}: accepted as {verify(candidate, audience, at)['sub']}") except ValueError as error: print(f"{name}: rejected, {error}") print(json.loads(b64url_decode(payload))["sub"]) # user-17 # Output: # valid: accepted as user-17 # changed payload: rejected, bad signature # alg none: rejected, unexpected algorithm # other service: rejected, wrong audience # expired: rejected, expired # user-17The last line decodes the payload without any key, which is why tokens must never carry secrets.
- Trusting the token's
Q46.Which cookie attributes protect a session, and what does each one prevent?
Session cookies carry the user's authenticated session, so their attributes decide who can read them, where they are sent and how long they live. Set them deliberately rather than relying on defaults.
HttpOnly Secure SameSite=Lax or Strict Scripts in the page cannot read the cookie Sent only over HTTPS Not sent on most cross-site requests Reduces session theft through cross-site scripting Reduces sniffing on the network Reduces cross-site request forgery No Domain attribute __Host-name prefixMax-Age or Expires The cookie stays on the exact host that set it The browser enforces Secure, Path=/ and no Domain Limits how long a stolen cookie works Reduces exposure to other subdomains Stops subdomains overwriting the cookie Reduces long-lived session reuse Session handling beyond attributes:
- Issue a new session ID at login to prevent session fixation, where an attacker plants a known ID before the victim logs in.
- Invalidate the session on the server at logout; deleting the cookie alone leaves a stolen copy valid.
- Apply both an idle timeout and an absolute lifetime, shorter for sensitive applications.
- Bind sensitive actions, such as changing a password, to a fresh re-authentication.
Q47.How do role-based and attribute-based access control differ?
Role-based access control grants permissions to roles and assigns users to roles. Attribute-based access control decides each request from attributes of the user, the resource, the action and the context, evaluated against policies.
Role-based Attribute-based Decides based on the user's roles Decides based on attributes and policy rules Example: support agents can view tickets Example: users can edit documents they own, from managed devices, in working hours Coarse to medium granularity Very fine granularity Simple to audit: list a role's members Harder to audit: depends on rule logic and data Problem: role explosion as special cases grow Problem: complex policies that are hard to test When to use which:
- Use roles for most applications and for administrative permissions; they are easy to explain and review.
- Add attributes where access depends on data, such as ownership, region, data classification or tenant.
- Many systems combine both: roles decide which actions exist for a user, and attribute or ownership checks decide which records those actions apply to.
Whichever model you use, centralize the decision in one policy component and log every denial.
Q48.How does privileged access management reduce the risk of admin accounts?
Privileged access management controls, monitors and limits the accounts that can change systems, so that a stolen or misused admin credential does far less damage. The aim is to remove standing admin power wherever possible.
Core practices:
- Inventory every privileged account, including service accounts, shared root accounts and cloud administrators.
- Separate accounts: administrators use a dedicated admin identity, never their everyday email and browsing account.
- Store credentials centrally: shared and service passwords live in a secrets manager, are checked out when needed, and rotate automatically after use.
- Grant just-in-time access: elevation is requested, approved for a stated task and expires after a set period.
- Require strong authentication with phishing-resistant multi-factor authentication for every elevation.
- Record and monitor sessions, so unusual commands raise alerts and investigations have evidence.
- Keep break-glass accounts for emergencies, with alerts whenever they are used.
The measure of success is how few people hold admin rights at any given moment. Attackers who steal a normal account then have nothing powerful to escalate into.
Q49.How do you defend a login page against credential stuffing and brute-force attacks?
Layer controls that make each guess expensive, spot automated patterns across many accounts, and make a correct password insufficient on its own. Different attacks need different signals.
Brute force Password spraying Credential stuffing Many passwords against one account A few common passwords against many accounts Leaked username and password pairs from other sites, spread over many addresses Stop with progressive delays per account and multi-factor authentication Stop by blocking common and breached passwords, and alerting on one source failing across many accounts Stop with multi-factor authentication, breached-credential checks, bot detection and device signals Further controls:
- Rate limits by account, by source address and by network range, since stuffing attacks rotate addresses.
- Uniform responses: the same message and timing for unknown users and wrong passwords, so accounts cannot be enumerated.
- Careful lockouts: hard lockouts let attackers lock real users out, so prefer temporary delays or extra challenges.
- Monitoring: watch the global login success rate; a sudden drop with a spike in attempts signals an attack in progress.
- Notify users about new-device sign-ins so they can report account takeover quickly.
Q50.Implement a time-based one-time password generator.
A time-based one-time password is an HMAC of the current 30-second time step, keyed with a secret shared between the server and the user's authenticator app, shortened to six digits.
- Decode the shared secret, which apps exchange in Base32 inside a QR code.
- Compute the counter:
counter = floor(unix_time / 30), packed as an 8-byte big-endian integer. - Compute
HMAC-SHA1(secret, counter). - Dynamic truncation: take the low 4 bits of the last byte as an offset, read 4 bytes from that offset and clear the top bit.
- Take that number modulo
10^digitsand pad with leading zeros.
Points for a production implementation:
- Allow small clock drift by also accepting the previous and next time step, but no more.
- Reject reuse: remember the last accepted step per user so an intercepted code cannot be replayed within its window.
- Rate-limit attempts, because six digits give only a million combinations.
- Compare in constant time and store the shared secrets encrypted.
These codes stop password-only attacks, but they can still be phished in real time. Use them where phishing-resistant keys are not yet possible.
Example
A generator checked against the published test vectors for the algorithm, which use the ASCII secret "12345678901234567890":
import base64 import hashlib import hmac import struct def totp(secret_base32: str, unix_time: int, digits: int = 6, step: int = 30) -> str: key = base64.b32decode(secret_base32) counter = unix_time // step digest = hmac.new(key, struct.pack(">Q", counter), hashlib.sha1).digest() offset = digest[-1] & 0x0F number = struct.unpack(">I", digest[offset:offset + 4])[0] & 0x7FFFFFFF return str(number % 10**digits).zfill(digits) SECRET = base64.b32encode(b"12345678901234567890").decode() print(SECRET) # GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ print(totp(SECRET, 59, digits=8)) # 94287082 print(totp(SECRET, 1111111109, digits=8)) # 07081804 print(totp(SECRET, 1234567890, digits=8)) # 89005924 print(totp(SECRET, 59)) # 287082The 8-digit results match the published vectors, and the 6-digit code is the same number's last six digits.
Q51.How does SQL injection work, and how do parameterized queries prevent it?
SQL injection happens when an application builds a query by pasting user input into SQL text, so specially crafted input changes the query's meaning. A parameterized query sends the SQL and the values separately, so input is always treated as data and can never become code.
How the attack works:
- The code builds
... WHERE name = '+ input +'. - The attacker enters
x' OR '1'='1, closing the quote and adding a condition that is always true. - The database runs the altered query and returns every row. Other payloads can read other tables, change data or, on some databases, run commands.
How to prevent it:
- Parameterized queries or prepared statements for every value, in every query.
- Allowlists for identifiers, because table and column names cannot be parameters. Map user choices such as a sort field to fixed names in code.
- Least-privilege database accounts, so a successful injection reaches as little as possible.
- Avoid building SQL by hand in stored procedures and query builders too; string concatenation inside them is just as vulnerable.
Escaping input by hand is fragile and differs between databases, so treat it as a last resort.
Example
The same lookup written unsafely and safely, run against an in-memory database:
import sqlite3 db = sqlite3.connect(":memory:") db.execute("CREATE TABLE users (name TEXT, role TEXT)") db.executemany("INSERT INTO users VALUES (?, ?)", [("alice", "admin"), ("bob", "user")]) def find_unsafe(name: str) -> list: query = f"SELECT name, role FROM users WHERE name = '{name}'" # input pasted into SQL return db.execute(query).fetchall() def find_safe(name: str) -> list: return db.execute("SELECT name, role FROM users WHERE name = ?", (name,)).fetchall() attack = "x' OR '1'='1" print(find_unsafe("bob")) # [('bob', 'user')] print(find_unsafe(attack)) # [('alice', 'admin'), ('bob', 'user')] print(find_safe(attack)) # []With the parameter, the database looks for a user literally named
x' OR '1'='1and finds none.- The code builds
Q52.What are the types of cross-site scripting, and how do you prevent each one?
Cross-site scripting lets an attacker run their script in another user's browser on your site, where it can steal data, act as the user or change the page. The three types differ in where the malicious script comes from.
Stored Reflected DOM-based The script is saved on the server and shown to other users The script is echoed back from the current request The script is written into the page by client-side code A comment containing a script tag A search page that prints the query unescaped Script copies the URL fragment into the page as HTML Prevent by encoding output for its context when rendering Prevent by encoding output and never echoing raw input Prevent with safe APIs that set text, not HTML Prevention that covers all three:
- Context-aware output encoding: HTML body, attributes, JavaScript strings and URLs each need different encoding. Templating engines with automatic escaping handle most cases.
- Avoid dangerous sinks such as assigning to
innerHTMLor building code from strings; usetextContentand DOM methods instead. - Sanitize rich text with a well-maintained HTML sanitizer when users must be able to format content.
- Content Security Policy as a second layer, blocking inline scripts and limiting where scripts load from.
- HttpOnly cookies to limit what a successful attack can steal.
Input validation helps but cannot replace output encoding. The same value can be safe in one context and dangerous in another.
Q53.How does cross-site request forgery work, and which defenses stop it?
Cross-site request forgery tricks a logged-in user's browser into sending a request the user did not intend. It works because browsers automatically attach the site's cookies to requests, even when another website triggers them.
sequenceDiagram participant U as Victim browser participant E as Attacker page participant B as Bank site U->>B: Log in, receive session cookie U->>E: Visit attacker page E->>U: Hidden form that posts a transfer to the bank U->>B: Transfer request with session cookie attached B->>B: Cookie is valid, transfer acceptedSameSite cookies Anti-forgery token Origin or Referer check Re-authentication The browser does not send the cookie on most cross-site requests Each form carries a secret the attacker's page cannot read The server rejects state changes from foreign origins Sensitive actions need the password or a second factor again Further points:
- Never change state with GET requests, because links and images can trigger them.
- APIs that read a token from a custom header, not from a cookie, are not exposed to this attack, since attacker pages cannot set that header across sites.
- Cross-site scripting defeats every forgery defense, because injected script runs on your own origin.
Q54.What is server-side request forgery, and why is it dangerous in cloud environments?
Server-side request forgery happens when an attacker makes your server send requests to a destination of the attacker's choosing, usually by supplying a URL to a feature such as "import from link" or "fetch a preview". The server's network position and credentials then work for the attacker.
Why it is dangerous in the cloud:
- Instance metadata: cloud servers can reach a local metadata address, such as
169.254.169.254, that can return temporary credentials for the server's role. - Internal services: admin panels, databases and other services trust requests from inside the network.
- Port scanning from inside: response times and errors reveal the internal network layout.
flowchart LR A["Attacker"] -->|URL to fetch| B["Vulnerable app server"] B -->|request| C["Metadata address"] C -->|temporary credentials| B B -->|response shown| A A -->|uses credentials| D["Cloud storage and APIs"]Defenses:
- Allowlist the destinations and schemes the feature may fetch, rather than blocking known-bad addresses.
- Resolve and check the address before connecting, reject private, loopback and link-local ranges, and connect to the address you checked, so a DNS change cannot swap it.
- Disable redirects, or validate every hop again.
- Require session tokens on the metadata service and give servers minimal roles.
- Restrict egress from servers that fetch user-supplied URLs.
Example
How a link preview feature became a credentials leak:
- A chat app fetched any URL a user pasted to show a preview card with the page title.
- An attacker pasted a link to the metadata address
169.254.169.254, with the path that returns the server role's credentials. - The server fetched it and placed the response, a temporary access key and secret, into the "page title" field of the preview.
- With those credentials, the attacker listed and downloaded the company's storage buckets.
The fix combined an allowlist of public schemes and ports, address validation after DNS resolution, token-protected metadata and a narrower server role.
- Instance metadata: cloud servers can reach a local metadata address, such as
Q55.What is broken object-level authorization, and how do you test for it?
Broken object-level authorization means an API returns or changes a record without checking that the caller is allowed to access that specific record. The user is properly logged in, but changing an ID in the request reaches someone else's data.
Why it is common:
- Frameworks handle authentication globally, but object ownership checks must be written per endpoint, and some get missed.
- Hard-to-guess IDs such as random identifiers make it less obvious but do not fix it, because IDs leak through links, logs and other responses.
How to test for it:
- Create two test accounts in the same role, and if possible a third in another tenant.
- With account A, create resources and record their IDs.
- With account B's token, request each of A's resources through every method: read, update, delete and any custom actions.
- Try IDs in every location: the path, query string, request body and nested objects.
- Check list and search endpoints, and exports, for records that leak across accounts.
- Expect a 403 or 404 response every time. A 200, or a change that took effect, is a finding.
How to fix it: enforce ownership in a shared data-access layer, for example by always filtering queries on the caller's tenant and user, rather than in each controller.
Example
A short test session against an order API:
- Account A creates order
8812; account B creates order8813. GET /api/orders/8812with B's token returns 200 and A's address. Finding.PATCH /api/orders/8812with B's token and a new delivery address returns 200, and the change is saved. Critical finding: B can redirect A's deliveries.GET /api/orders?customerId=Awith B's token returns A's order history. Finding.
All three endpoints trusted the ID in the request. The fix added the condition "order belongs to the caller" to the shared order lookup used by every endpoint, and the test was added to the automated suite.
Q56.How do you prevent path traversal when serving files?
Path traversal happens when user input becomes part of a file path and sequences such as
../walk out of the intended folder. The reliable defense is to resolve the final path and confirm it is still inside the allowed base directory before opening it.Key points:
- Avoid user-supplied paths entirely when possible: store files under generated IDs and look up the real path in a database.
- Resolve, then check: compute the absolute, normalized path, following any symbolic links, and verify it starts inside the base folder. Checking the raw string for
../misses encoded and nested variants. - Reject absolute paths and drive letters, which can replace the base path when joined.
- Decode before checking, and decode only once, so
%2e%2e%2fcannot slip past. - Run with least privilege so that even a successful traversal cannot read secrets such as configuration files or keys.
Archive extraction has the same flaw: entries named
../../filecan be written outside the target folder, so apply the same resolve-and-check rule to every entry.Example
A resolver that blocks traversal attempts, using a temporary folder as the allowed root:
import tempfile from pathlib import Path root = Path(tempfile.mkdtemp()).resolve() (root / "reports").mkdir() (root / "reports" / "q1.pdf").write_text("report") def safe_path(user_path: str) -> Path: candidate = (root / user_path).resolve() if not candidate.is_relative_to(root): raise PermissionError("outside the allowed folder") return candidate for request in ["reports/q1.pdf", "../../app/config.env", "reports/../../secret.txt", "reports/./q1.pdf"]: try: print(request, "->", safe_path(request).relative_to(root).as_posix()) except PermissionError as error: print(request, "->", "blocked:", error) # Output: # reports/q1.pdf -> reports/q1.pdf # ../../app/config.env -> blocked: outside the allowed folder # reports/../../secret.txt -> blocked: outside the allowed folder # reports/./q1.pdf -> reports/q1.pdfThe third request starts inside the allowed folder and still escapes, which is why the check runs on the resolved path rather than the input text.
Q57.How do you secure a file upload feature?
Treat every uploaded file as hostile: check what it really is, limit it, store it where it cannot execute, and serve it so the browser cannot treat it as part of your site.
- Allowlist file types by checking the file's content signature, not just its extension or the content type the client claims.
- Limit size and count per file and per user, and reject compressed files that expand enormously.
- Rename files to random identifiers and keep the original name only as metadata.
- Store outside the web root, ideally in separate object storage, so an uploaded script can never be executed by the web server.
- Serve from a separate domain with
Content-Disposition: attachmentandX-Content-Type-Options: nosniff, so a disguised HTML file cannot run scripts on your main domain. - Scan for malware before making files available to other users.
- Re-encode images and strip metadata, which removes hidden payloads and location data.
- Keep parsers patched, because image, document and archive libraries are a frequent source of serious vulnerabilities.
Process uploads asynchronously in an isolated worker with minimal permissions. A parser exploit then lands in a sandbox rather than on the main application server.
Q58.Why is deserializing untrusted data dangerous?
Many languages have native serialization formats that can recreate arbitrary objects and call their methods while doing so. If an attacker controls the serialized data, they can choose which objects are built, and chains of existing classes, called gadget chains, can end in running commands on the server.
What can go wrong:
- Remote code execution: the most severe outcome, from object construction or "magic" methods that run automatically.
- Authentication bypass: tampered session objects that claim a different user or role.
- Denial of service: deeply nested or self-referencing structures that exhaust memory or CPU.
How to prevent it:
- Use data-only formats such as JSON for anything crossing a trust boundary, and validate it against a schema.
- Never deserialize native objects from cookies, request bodies, message queues or files that users can influence.
- If you cannot avoid it, sign the data with an HMAC and verify the signature before deserializing, and restrict the classes allowed.
- Run deserialization with least privilege and keep libraries patched, since new gadget chains are found regularly.
Interview tip: point out that the danger lies in the format's ability to create objects, not in the parsing itself; that is why switching to a data-only format fixes the class of bug.
Q59.Why is allowlist input validation stronger than denylist validation?
An allowlist defines exactly what valid input looks like and rejects everything else; a denylist tries to list what is bad and accepts the rest. Attackers only need one variant the denylist forgot, while an allowlist fails safe.
Allowlist Denylist Accepts only known-good patterns Rejects known-bad patterns Rejects unknown input Accepts unknown input Hard to bypass with encoding tricks Often bypassed with encoding tricks Changes when the business rules change Grows forever as new attacks appear Example: a postcode must match a strict format Example: block input containing <script>When to use which:
- Use allowlists wherever the valid set is known: IDs, dates, enums, sort fields, file types, country codes and redirect targets.
- Use denylists only as an extra layer, such as blocking known-malicious addresses.
- For free text, such as names and comments, strict allowlists break legitimate input. Validate length and character encoding, then rely on output encoding and parameterized queries for safety.
Validate on the server, after decoding and normalizing the input. Client-side validation is for user experience only.
Q60.Which HTTP security headers should a web application set?
Security headers tell the browser to apply protections that the page itself cannot enforce. They are cheap to add and close whole classes of attacks, though they never replace fixing the application.
Strict-Transport-Security Content-Security-Policy Content-Security-Policy frame-ancestors X-Content-Type-Options Forces HTTPS for future visits Limits where scripts and other content load from Stops other sites framing the page, preventing clickjacking Stops the browser guessing content types max-age=31536000; includeSubDomainsScript sources restricted to self and nonces frame-ancestors 'none'nosniffReferrer-Policy Permissions-Policy Cache-Control Limits URL data sent to other sites Disables browser features the site does not use Keeps sensitive pages out of shared caches strict-origin-when-cross-origincamera=(), microphone=()no-storeon account pagesRolling them out:
- Start a Content Security Policy in report-only mode, collect violation reports, fix them, then enforce.
- Add HSTS with a short
max-agefirst, because a mistake locks users into HTTPS for the whole period. - Do not rely on deprecated headers such as the old browser cross-site scripting filter; modern browsers ignore it.
Example
A response header set for an account page, and what each line buys:
Strict-Transport-Security: max-age=31536000; includeSubDomains Content-Security-Policy: default-src 'self'; script-src 'self' 'nonce-r4nd0m'; object-src 'none'; frame-ancestors 'none'; base-uri 'self' X-Content-Type-Options: nosniff Referrer-Policy: strict-origin-when-cross-origin Permissions-Policy: camera=(), microphone=(), geolocation=() Cache-Control: no-storeThe policy allows only the site's own scripts plus inline scripts carrying this response's nonce, so an injected script tag without the nonce is blocked.
frame-ancestors 'none'stops the page being loaded invisibly inside an attacker's frame, andno-storekeeps account data out of shared caches.