227 Php Developer Interview Questions & Answers

116 top • 13 Amazon • 21 Google • 10 Netflix • 7 Meta • 18 NVIDIA • 21 Apple • 21 Microsoft

Php Developer icon

Questions with Detailed ExplanationsWith Detailed Explanations

(Last Updated: August 28, 2026)

101. Describe a time you led a risky migration or major PHP upgrade.BehavioralHard

Question Details

Explain planning, compatibility assessment, rollout stages, testing, stakeholder communication, rollback strategy, outcome, and lessons.

Interview tip:

Use STAR to structure your answer: briefly explain the Situation and Task, make Action the most detailed part, and finish with the Result. For example, describe a major PHP upgrade where you assessed compatibility risks, planned staged releases, improved automated testing, communicated clearly with stakeholders, prepared a rollback path, and guided the team to a stable outcome.

Situation

In my last role, I led an upgrade of a large PHP application from an older PHP version to PHP 8.2. The application supported important daily operations and included legacy code, older libraries, scheduled jobs, and several external integrations. A direct upgrade was risky because one hidden compatibility issue could affect users or background processing.

Task

I was responsible for creating the migration plan, identifying technical risks, coordinating the development work, and making sure we could release safely. My goal was to complete the upgrade without disrupting normal operations and to leave the application easier to maintain.

Action

I started by creating an inventory of the application, its Composer packages, PHP extensions, external services, scheduled jobs, and deployment requirements. I checked each dependency for PHP 8.2 support and separated the findings into items we could upgrade, items we needed to replace, and custom code we needed to change. I used static analysis, deprecation reports, application logs, and targeted code reviews to find weak areas such as stricter type behavior, removed functions, dynamic properties, and outdated library usage. I then divided the migration into small stages instead of treating it as one large release. First, I updated compatible dependencies and removed abandoned packages while the application was still running on the old PHP version. Next, I changed our development and test environments to PHP 8.2 and fixed compatibility problems there. I asked the team to add tests around critical workflows before changing sensitive code, because tests gave us evidence that behavior remained correct. I also created a focused regression checklist for areas that were difficult to cover with automated tests, including authentication, file processing, scheduled jobs, payment related flows, and external integrations. I kept stakeholders informed through regular updates that explained the current risk, completed work, remaining blockers, and release plan in simple terms. Before production deployment, I prepared a rollback procedure that restored the previous application build, PHP runtime, dependency lock file, and configuration. We rehearsed that procedure in a staging environment so it was a real recovery option, not only a document. For the rollout, I used a limited release window, monitored error logs, response failures, job queues, and important user flows, and kept the team available to respond. I made the final release decision based on test results and operational evidence rather than the planned date alone.

Result

The upgrade was completed without a major service disruption. The application ran reliably on PHP 8.2, unsupported dependencies were removed, and the team had stronger tests and clearer deployment checks for future changes. I learned that the safest way to lead a risky migration is to reduce uncertainty early, release in controlled stages, communicate risks honestly, and make rollback steps simple enough to execute under pressure.

Why Interviewers Ask This

Interviewers ask this question to understand whether a developer can lead a high risk technical change with sound judgment. A strong answer shows careful planning, technical awareness, ownership, clear communication, staged delivery, testing discipline, and the ability to protect the business when a migration does not go as expected.

Interviewer may ask next
How did you decide whether the application was ready for the production upgrade?

I used clear readiness checks instead of relying on the schedule. All critical workflows had to pass automated and manual testing, important dependencies had to support PHP 8.2, known compatibility issues had to be resolved or accepted, monitoring had to be ready, and the rollback procedure had to succeed in staging. I approved the release only after those conditions were met.

What would you do differently if you led a similar PHP upgrade today?

I would add compatibility checks and deprecation scanning to the continuous integration process earlier. That would make future PHP changes visible during normal development instead of allowing them to build up before a major upgrade. I would also schedule regular dependency reviews so abandoned packages can be replaced before they become migration blockers.

102. Tell me about a time you challenged a proposed architecture or implementation.BehavioralHard

Question Details

Describe the proposal, your concerns, evidence and alternatives, how you influenced the decision, and the result even if your view was not chosen.

Interview tip:

Use STAR to structure your answer: briefly explain the Situation and Task, make Action the most detailed part, and finish with the Result. For example, describe a proposed PHP implementation, the technical or operational risks you identified, the evidence you gathered, the safer alternative you presented, how you discussed the disagreement respectfully, and the final outcome.

Situation

In my last role, our team was adding a feature that generated large customer reports. The proposed implementation created the entire report inside one PHP web request and returned the file immediately. I was concerned that this design would keep PHP workers busy for too long, use a large amount of memory, and create timeouts when several users requested reports at the same time.

Task

I was responsible for reviewing the implementation and helping the team deliver a reliable solution. I needed to raise my concerns without blocking progress or making the discussion personal. I also needed to provide evidence and a practical alternative instead of only saying that the proposal was risky.

Action

I first created a small test using realistic report data and ran several report requests at the same time. I recorded the request duration, memory use, and worker activity. The test showed that the web process remained occupied while building each file and that larger reports could reach our request limits. I shared these findings with the developer who proposed the design before discussing them with the wider team. I explained that the business requirement was valid, but the synchronous implementation connected a long running task to the web request. I then proposed placing each report request in a queue. A background PHP worker would generate the file, store it in our existing private storage, and update the report status. The user could continue using the application and download the report when it was ready. To keep the change manageable, I prepared a simple flow diagram and a small working example using our existing queue system. During the architecture discussion, I compared both options based on reliability, implementation effort, user experience, failure handling, and support needs. I listened to concerns that the queued approach would add more states to the user interface. We agreed to keep the first version simple with clear pending, completed, and failed states. I also suggested adding retry handling and preventing duplicate jobs when a user submitted the same request more than once.

Result

The team chose the queued implementation. Report generation no longer held a web request open, failures could be retried safely, and users received a clearer status instead of an unexpected timeout. The discussion also improved our review process because we started testing risky assumptions with realistic data before approving similar designs. I learned that challenging an architecture is most effective when I bring evidence, respect the original idea, and offer an alternative that fits the team’s existing tools.

Why Interviewers Ask This

Interviewers ask this question to evaluate whether a candidate can identify technical risk, support concerns with evidence, disagree respectfully, and influence a decision without creating unnecessary conflict. A strong answer shows sound judgment, clear communication, collaboration, and a focus on the best outcome rather than personal ownership of an idea.

Interviewer may ask next
How did you handle resistance to the queued approach?

I focused on the specific concerns behind the resistance. The main concern was added user interface and operational complexity, so I reduced the first version to three clear states and reused our existing queue system. This showed that the alternative could improve reliability without creating a large new platform.

What would you do differently in a similar situation now?

I would involve the developer and operations team even earlier and agree on evaluation criteria before comparing designs. That would make the discussion less about defending proposals and more about testing each option against shared requirements such as response time, failure recovery, and maintenance effort.

103. Describe a time you had to make a decision with incomplete technical information.BehavioralHard

Question Details

Explain the uncertainty, reversible versus irreversible choices, risk controls, stakeholders consulted, decision, and subsequent adjustment.

Interview tip:

Use STAR to structure your answer: briefly explain the Situation and Task, make Action the most detailed part, and finish with the Result. For example, describe a PHP project where an urgent integration decision had to be made before all technical details were available. Explain the uncertainty, separate reversible choices from difficult to reverse choices, add risk controls, consult the right stakeholders, make a practical decision, and adjust the solution when new information appears.

Situation

In my last role, I was working on a PHP application that needed to send customer orders to an external service. The service documentation was incomplete, and its support team could not confirm how some error responses and duplicate requests would be handled. The release date was close, so waiting for every answer would have delayed an important business feature.

Task

I was responsible for choosing a safe integration approach. I needed to make progress without risking duplicate orders, lost data, or a design that would be difficult to change later.

Action

I first listed what we knew and what was still uncertain. We knew the required request fields and the normal success response. We did not know whether the service safely handled repeated requests or how long some failures could last. I then separated reversible decisions from difficult to reverse decisions. Choosing a timeout value or retry delay was easy to change later. Sending requests directly during the user checkout process and treating every failed response as safe to retry could create duplicate orders and would be much harder to correct. I decided to place outgoing requests in a database backed queue instead of sending them directly from the web request. I added a unique internal request key, stored every request and response, and allowed retries only for temporary connection failures. For unclear responses, I moved the item to a review state instead of retrying automatically. I discussed the business impact with the product owner, reviewed the failure controls with another developer, and confirmed the operational process with the support team. We agreed that a short processing delay was safer than creating duplicate orders. I also kept the integration behind a configuration setting so we could disable it quickly. After the external service clarified its duplicate request behavior, I updated the retry logic and added the supported request key to each call.

Result

We released the feature with controlled risk and without blocking the checkout process. The queue and review state gave the team clear visibility when the external service behaved unexpectedly. When better information became available, I adjusted the retry logic without redesigning the whole integration. I learned that incomplete information does not always require waiting. It requires identifying which decisions are reversible, protecting the serious failure cases, and creating a clear path to adjust later.

Why Interviewers Ask This

Interviewers ask this question to evaluate judgment under uncertainty. A strong answer shows that the candidate can identify missing information, compare reversible and difficult to reverse choices, control risk, involve the right stakeholders, make a timely decision, and adjust responsibly when new facts become available.

Interviewer may ask next
Why did you choose a queue instead of calling the external service directly?

A direct call would have made checkout depend on an unreliable service and could have encouraged unsafe retries. The queue separated customer checkout from external processing, preserved each request, and gave us a controlled way to review failures.

What would you do differently in a similar situation now?

I would create the uncertainty list and contact the external service earlier. I would also define the review process with the support team sooner, because operational ownership is part of the technical risk control.

104. What is SQL injection?NEWSecurityEasy

Question Details

Define SQL injection as untrusted input changing the structure or meaning of a database command. Show the boundary between SQL code and data, explain potential reading, modification, deletion, and authentication impact, and cover parameterized prepared statements, allow-listing for identifiers, least privilege, safe error handling, and why manual escaping alone is not a complete defense.

Short Interview Answer (30-60 seconds)

SQL injection is when untrusted input changes the structure or meaning of a database command. Prevent it with parameterized prepared statements for values, allow-listing for dynamic identifiers, least-privilege database permissions, safe error handling, and verification. Manual escaping alone is not a complete defense.

Detailed Explanation

See the Code while reading this explanation.

SQL injection happens when information supplied by a person is allowed to change what a database request means. Instead of treating that information only as a value, the application accidentally lets it become part of the instruction. An attacker may then make the application read information they should not see, change or remove stored information, or sometimes get past a sign-in check. The safe design keeps the fixed instruction separate from supplied values, limits what the application is allowed to do, and avoids showing private details when something goes wrong.

Useful Questions to Ask the Interviewer
  1. Are you asking mainly about SQL injection through data values, or should I also cover dynamic table names, column names, and sort directions?
  2. Should I show the prevention approach using PHP PDO prepared statements?
What is SQL injection? diagram
How to Explain It in an Interview

SQL injection is a vulnerability where untrusted input changes the structure or meaning of an SQL command. The important security boundary is between SQL code, which the application controls, and data, which may come from an untrusted user.

A vulnerable application might build SQL by concatenating a submitted value directly into a query string. That mixes SQL code and untrusted data. Special characters or SQL syntax in the supplied value may then change the intended command instead of being treated only as data.

The impact depends on the vulnerable query and the permissions of the application's database account. SQL injection may allow unauthorized reading of data, modification of records, deletion of data, or other database operations that the account is permitted to perform. In a vulnerable sign-in query, changed SQL logic may also cause the authentication condition to evaluate differently and potentially bypass the intended login check.

Authentication answers, "Who are you?" Authorization answers, "What are you allowed to do?" SQL injection is different from both, but a successful injection can undermine application logic used for authentication or authorization when that logic depends on a vulnerable database query.

The primary defense for data values is parameterized prepared statements. The application writes the SQL structure separately and sends untrusted values as parameters. The database driver and database handle those values as data rather than letting their contents become SQL syntax. In PHP, PDO prepared statements are a common way to apply this control.

Prepared-statement parameters are for data values, not arbitrary SQL identifiers such as table names, column names, or sort directions. If an identifier must be dynamic, map the requested choice to a small application-controlled allow-list. For example, a request for "name" can map to the fixed identifier "display_name". Never copy an arbitrary user-supplied identifier directly into SQL.

Input validation is still useful for enforcing business rules. An application can verify that an ID has the expected form or that a requested sort option is supported. However, validation or filtering does not replace parameterized queries. A value can be valid for the business rule and still be dangerous if it is concatenated into SQL.

Least privilege reduces the damage if another control fails. The database account used by the PHP application should have only the permissions that the application actually needs. A read-only operation should not use an account that can modify or delete unrelated data or perform database-administration tasks.

Errors should fail safely. Users should receive a generic error message rather than SQL text, database details, credentials, or stack traces. Server-side logs should contain enough information for investigation, such as an internal request identifier or error category, but should not contain passwords, database credentials, session secrets, or unnecessary sensitive data.

Manual escaping alone is not a complete defense. Correct escaping depends on the database driver, connection configuration, character handling, and the exact SQL context. It is also easy for a developer to forget to escape one value or to apply the wrong rule. Parameterized prepared statements provide a clearer and more reliable code-and-data boundary for values.

Other security controls solve different problems. Output encoding helps prevent injection into HTML or other output contexts. CSRF protection helps stop unwanted state-changing requests made with a victim's authenticated session. Secure session handling protects session state. File-upload controls protect uploaded content, and dependency management reduces third-party software risk. These controls are important when relevant, but none replaces SQL injection prevention.

To verify the defense, review database calls for string concatenation involving untrusted data. Confirm that data values use parameters and that dynamic identifiers come only from fixed allow-lists. Test with ordinary values and malicious-looking strings containing quotes, operators, comments, or SQL keywords. Those strings must remain ordinary data and must not change the SQL command. Also verify that database errors shown to users do not reveal sensitive implementation details and that the database account has only the required privileges.

Key Insight / Why This Solution Works
  1. Identify every database input that can be influenced by an untrusted source.
  2. Keep the SQL statement structure fixed.
  3. Send data values through parameterized prepared statements.
  4. If a table name, column name, or sort direction must be dynamic, map it through an application-controlled allow-list.
  5. Validate input for business rules without treating validation as the SQL injection defense.
  6. Run the application with only the database permissions it requires.
  7. Return generic errors to users and log useful diagnostic information without secrets.
  8. Review and test the database calls to confirm hostile-looking input remains data and cannot alter the SQL command.
Code
<?php

declare(strict_types=1);

$dsn = getenv('APP_DSN');
$dbUser = getenv('APP_DB_USER');
$dbPassword = getenv('APP_DB_PASSWORD');

if ($dsn === false || $dbUser === false || $dbPassword === false) {
    http_response_code(500);
    exit('Service unavailable.');
}

try {
    $pdo = new PDO(
        $dsn,
        $dbUser,
        $dbPassword,
        [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
            PDO::ATTR_EMULATE_PREPARES => false,
        ]
    );

    $email = (string) ($_GET['email'] ?? '');
    $requestedSort = (string) ($_GET['sort'] ?? 'created');

    $allowedSortColumns = [
        'created' => 'created_at',
        'name' => 'display_name',
    ];

    $sortColumn = $allowedSortColumns[$requestedSort] ?? 'created_at';

    $sql = "SELECT id, display_name, email, created_at
            FROM users
            WHERE email = :email
            ORDER BY {$sortColumn} DESC";

    $statement = $pdo->prepare($sql);
    $statement->execute(['email' => $email]);

    header('Content-Type: application/json; charset=utf-8');
    echo json_encode(
        $statement->fetchAll(),
        JSON_THROW_ON_ERROR
    );
} catch (PDOException $exception) {
    error_log('Database operation failed: ' . $exception::class);
    http_response_code(500);
    echo 'Unable to complete the request.';
} catch (JsonException $exception) {
    error_log('JSON encoding failed: ' . $exception::class);
    http_response_code(500);
    echo 'Unable to complete the request.';
}
Why Interviewers Ask This

Interviewers want to know whether the candidate understands the trust boundary between SQL code and untrusted data, the possible impact of SQL injection, and the correct layered defenses. They also evaluate whether the candidate knows that prepared statements protect data values, identifiers need allow-listing, database permissions should be limited, errors should fail safely, and defenses should be verified instead of assumed.

Common interview mistakes

Common mistakes include concatenating request values into SQL, assuming validation or filtering alone prevents SQL injection, relying on manual escaping instead of parameterized statements, trying to bind table or column names as data parameters, inserting user-controlled identifiers directly into SQL, using a database account with unnecessary privileges, exposing SQL text or stack traces in production error responses, logging credentials or other sensitive values, and testing only normal inputs instead of verifying that hostile-looking inputs remain data.

Interview tip

Start with the central rule: keep SQL code and untrusted data separate. Then explain parameterized prepared statements for values, allow-listing for dynamic identifiers, least privilege, safe error handling, and verification. Finish by saying that input filtering and manual escaping alone are not complete defenses.

Interviewer may ask next
Why are prepared statements safer than concatenating manually escaped input into an SQL string?

Prepared statements keep the SQL structure separate from parameter values. Values are supplied through the database API instead of being inserted into the SQL text, so their contents are handled as data. Manual escaping depends on correct rules for the database, connection, character handling, and SQL context, and developers can easily forget or misuse it. Therefore, manual escaping alone is not a complete defense.

How should you safely handle a user-selected column name for sorting if prepared-statement parameters cannot represent identifiers?

Do not insert the submitted column name directly into SQL. Map the user's accepted choices to fixed identifiers controlled by the application, such as "name" to "display_name" and "created" to "created_at". Insert only the trusted mapped identifier into the SQL structure. Continue using prepared-statement parameters for ordinary data values.

105. What is cross-site scripting (XSS)?NEWSecurityEasy

Question Details

Define XSS as untrusted content being interpreted as executable browser code in another user session. Explain reflected, stored, and DOM-based XSS, execution context, session and data impact, contextual output encoding, safe templating, sanitization for permitted HTML, Content Security Policy as defense in depth, and why input validation alone is insufficient.

Short Interview Answer (30-60 seconds)

XSS happens when untrusted content is interpreted as executable browser code in another user's session. Prevent it mainly with context-aware output encoding and safe rendering. Sanitize intentionally permitted HTML, use safe templates and DOM APIs, and use Content Security Policy as defense in depth.

Detailed Explanation

Cross-site scripting is a security problem where information controlled by one person is shown to another visitor in a way that the browser treats as instructions instead of normal content. This can let the attacker change what the visitor sees, perform actions using the visitor's signed-in session, read information that the page is allowed to access, or send accessible information elsewhere. The safest design is to keep untrusted values as plain data when displaying them. If a feature intentionally allows formatted content, it needs stricter handling and additional browser protections.

Useful Questions to Ask the Interviewer
  1. Are users allowed to submit any HTML, or should all user-controlled content be displayed only as text?
  2. Should I cover both server-rendered PHP output and browser-side JavaScript handling?
What is cross-site scripting (XSS)? diagram
How to Explain It in an Interview

XSS means untrusted content reaches a browser and is interpreted as executable browser code instead of harmless data. The important security decision is to make the data safe for the exact context where it is inserted into the page.

There are three common forms of XSS. Reflected XSS happens when attacker-controlled input, such as a query parameter, is immediately included in a server response without the correct protection. A victim may execute the attack by opening a crafted URL or submitting a crafted request. Stored XSS happens when attacker-controlled content is saved, for example in a comment, profile field, or message, and is later rendered to other users. DOM-based XSS happens when browser-side JavaScript reads untrusted data and passes it to an unsafe DOM operation, allowing the browser to create executable content even when the server response itself did not directly contain the final dangerous markup.

The impact depends on the execution context and the privileges of the victim's page and session. XSS can change page content, read data available to JavaScript, capture information typed into the page, send accessible information to another server, or make requests using the victim's authenticated session. An HttpOnly session cookie cannot be read directly by JavaScript, which reduces cookie theft, but XSS can still perform same-origin actions from the victim's page if the application accepts those actions.

The primary defense is contextual output encoding. Contextual means that the escaping method must match where the untrusted value is inserted. HTML text, HTML attributes, URLs, JavaScript, and CSS are different contexts and do not all have the same escaping rules. For ordinary HTML text or a properly quoted HTML attribute in PHP, htmlspecialchars() with ENT_QUOTES | ENT_SUBSTITUTE and UTF-8 is a common safe baseline. Developers should still avoid placing untrusted data directly into executable JavaScript or CSS contexts when a safer design is available.

Safe templating systems reduce risk because they normally escape variables automatically for the context they support. Automatic escaping should remain enabled. Raw-output features should be avoided unless the value has been deliberately made safe for that exact context.

Browser-side code should also use APIs that treat untrusted values as data. For example, assigning plain text with textContent is safer than placing untrusted content into innerHTML. If HTML markup is not required, do not parse user-controlled input as HTML.

If an application intentionally permits users to submit a limited subset of HTML, normal output encoding would display the markup as text instead of rendering it. In that case, use a well-maintained HTML sanitizer configured with an allowlist of permitted elements, attributes, and URL schemes. Sanitization must understand HTML structure; simple string replacement, regular-expression filtering, or a blacklist is not a complete XSS defense.

Content Security Policy, or CSP, is an HTTP response policy that limits which scripts and other resources the browser may execute or load. A strong CSP can reduce the impact of some XSS mistakes, especially when arbitrary inline script execution is restricted and trusted scripts use nonces or hashes. CSP is defense in depth. It does not replace correct output encoding, safe templating, safe DOM APIs, or sanitization.

Input validation is useful for enforcing business rules. For example, an application can require a numeric identifier to contain only the expected numeric format or limit the length of a display name. However, input validation alone cannot prevent XSS because a value may be valid for the application and still become dangerous when inserted into the wrong browser context. The application must therefore protect the data at the point where it is rendered or interpreted.

For safe failure behavior, reject malformed input when the application's business rules require rejection, but do not expose internal implementation details, stack traces, tokens, session identifiers, or secrets. Security logging can record useful information such as the affected endpoint, the type of validation failure, or a CSP violation, but logs should avoid passwords, authentication tokens, raw session identifiers, and unnecessary sensitive data.

To verify the controls, test each location that renders untrusted data with representative hostile values and confirm that the browser treats them as data rather than executable code. Test server-rendered templates and browser-side DOM insertion points separately. Automated tests can verify expected escaping or sanitization behavior, and browser testing can confirm that no script executes. CSP violation reporting can provide additional production evidence, but it should supplement rather than replace direct security testing.

Technical Approach
  1. Find every place where untrusted data reaches an HTML response or browser-side DOM operation.
  2. Identify the exact context: HTML text, HTML attribute, URL, JavaScript, CSS, or intentionally permitted HTML.
  3. Prefer safe templates and browser APIs that treat values as data.
  4. Apply context-appropriate output encoding when rendering ordinary untrusted content.
  5. If limited HTML is intentionally permitted, sanitize it with a maintained allowlist-based HTML sanitizer.
  6. Add a restrictive Content Security Policy as defense in depth.
  7. Keep input validation for business rules, but do not rely on it as the primary XSS control.
  8. Test hostile inputs in every rendering context and verify that the browser does not execute them.
Practical Insights

Output encoding normally processes each character in the value, so its time cost grows roughly with the amount of text being rendered. It also creates an encoded output string, so memory use grows with that output. HTML sanitization is more expensive because the sanitizer must parse and inspect the permitted markup. CSP adds little request-processing cost but creates configuration and maintenance work. The largest long-term cost is ensuring that every new template and browser-side rendering path continues to use the correct protection for its context.

Why Interviewers Ask This

Interviewers want to know whether the candidate understands how untrusted content can become executable browser code, can distinguish reflected, stored, and DOM-based XSS, and can select defenses based on the output context. They also want to see whether the candidate understands that input validation alone is insufficient, permitted HTML requires sanitization, and Content Security Policy is an additional defense rather than a replacement for safe output handling.

Common interview mistakes

Common mistakes include relying only on input validation or blacklist filtering, escaping data when it enters the database instead of when it is rendered, assuming one escaping function works for every browser context, disabling template auto-escaping, placing untrusted strings into innerHTML, allowing raw HTML without a robust sanitizer, assuming HttpOnly cookies remove all XSS impact, and treating Content Security Policy as a replacement for fixing the unsafe rendering path.

Interview tip

Start with the core rule: XSS occurs when untrusted data becomes executable browser code in another user's session. Then distinguish reflected, stored, and DOM-based XSS. Explain context-aware output encoding as the primary defense, safe templates and DOM APIs, sanitization for intentionally permitted HTML, and CSP as defense in depth. Explicitly state that input validation alone is insufficient.

Interviewer may ask next
What is the difference between reflected, stored, and DOM-based XSS?

Reflected XSS happens when attacker-controlled input is immediately included in a response and becomes executable when a victim opens or submits the crafted request. Stored XSS happens when malicious content is saved and later rendered to users. DOM-based XSS happens when browser-side JavaScript reads untrusted data and sends it to an unsafe DOM operation, causing executable content to be created in the browser.

Why is input validation not enough to prevent XSS?

Input validation checks whether data follows expected business rules, but XSS risk depends on how that data is later interpreted by the browser. A value can be valid application input and still become dangerous in HTML, an attribute, JavaScript, CSS, or another context. XSS therefore requires protection at the rendering point through context-aware output encoding, safe browser APIs, or sanitization when limited HTML is intentionally allowed.

106. What is cross-site request forgery (CSRF)?NEWSecurityEasy

Question Details

Define CSRF as tricking a user browser into sending an unwanted state-changing request with credentials that the browser includes automatically. Explain the conditions required for the attack, CSRF tokens, SameSite cookies, origin checks, safe HTTP methods, re-authentication for sensitive actions, and why HTTPS or POST alone does not prevent CSRF.

Short Interview Answer (30-60 seconds)

CSRF tricks a signed-in user's browser into sending an unwanted state-changing request to a trusted site. Because the browser may automatically include session cookies, the site may accept it. Prevent it with CSRF tokens, SameSite cookies, origin checks, safe HTTP methods, and stronger confirmation for sensitive actions.

Detailed Explanation

A harmful website can sometimes make your browser perform an action on another website where you are already signed in. Your browser may automatically carry proof that you are signed in, so the second website may believe you chose the action yourself. For example, an attacker might try to make your browser change an account setting without your intention. The protection must make sure important actions really came from the website page the user was using. Several protections should work together because no single browser or network feature solves every case.

Useful Questions to Ask the Interviewer
  1. Should I explain CSRF protection for a traditional PHP application that uses session cookies?
  2. Should I cover both browser-level protections and server-side verification?
  3. Do you want an example of handling highly sensitive actions such as changing an email address or password?
What is cross-site request forgery (CSRF)? diagram
How to Explain It in an Interview

CSRF, or cross-site request forgery, happens when an attacker causes a user's browser to send an unwanted request to a site where that user is already authenticated. Authentication proves who the user is, but it does not prove that the user intentionally initiated that particular action. Authorization separately decides whether the authenticated user is allowed to perform the action. CSRF protection is needed because a request can be both authenticated and authorized while still not representing the user's intent.

A typical CSRF attack needs three important conditions. First, the application uses credentials that the browser sends automatically, commonly a session cookie. Second, the targeted endpoint changes server state, such as changing an email address, updating settings, or making a purchase. Third, the server accepts the request without requiring evidence that an attacker on another origin normally cannot provide.

For a traditional PHP application using cookie-based sessions, the main server-side defense is usually a CSRF token. The application generates a cryptographically unpredictable value and associates it with the user's session. Legitimate forms or application requests include that token. Before processing a protected state-changing request, the server checks that the submitted token matches the expected token. If it is missing or invalid, the application must reject the request before changing any data. In a custom PHP implementation, a token can be generated with random_bytes() and compared with hash_equals(). Frameworks commonly provide their own tested CSRF protection and should normally be used instead of rebuilding it.

The token works because an attacker can often cause the browser to send a request but normally cannot read pages or token values from another origin because of the browser's same-origin policy. If the application also has a cross-site scripting vulnerability, however, injected script running in the application's own origin may be able to read or submit CSRF tokens. CSRF protection therefore does not replace protection against cross-site scripting.

SameSite cookies provide another layer of protection. SameSite=Lax prevents cookies from being sent on many cross-site requests while still allowing some top-level navigations. SameSite=Strict is more restrictive and generally prevents the cookie from being sent during cross-site navigation, but it can interfere with legitimate flows from external sites. SameSite=None permits cross-site cookie use and requires the Secure attribute. SameSite is useful defense in depth, but the correct setting depends on the application's legitimate cross-site requirements and should not automatically replace server-side CSRF validation for important cookie-authenticated actions.

Origin checking is another useful layer. For state-changing requests, the server can verify that the Origin header matches an explicitly trusted origin. When Origin is unavailable, carefully validating Referer can be an appropriate fallback. The comparison should use an exact trusted scheme, host, and port policy rather than unsafe substring matching. Deployment details such as reverse proxies and canonical hostnames must also be handled correctly.

HTTP methods must have correct semantics. GET and HEAD are defined as safe methods and should not perform state-changing operations. Operations that create, update, or delete data should use an appropriate state-changing method such as POST, PUT, PATCH, or DELETE. However, simply changing an endpoint from GET to POST does not prevent CSRF. An attacker can often make a browser submit a cross-site POST form, and the browser may still attach applicable cookies automatically.

HTTPS also does not prevent CSRF. HTTPS protects the connection against network eavesdropping and modification, but CSRF uses the victim's own browser to create the request. The forged request can therefore be fully encrypted with HTTPS and still be unwanted.

For especially sensitive operations, such as changing a password, changing account recovery information, or confirming a financial action, the application can require re-authentication or another strong explicit confirmation in addition to normal CSRF defenses. This reduces the chance that possession of an existing authenticated session is enough to complete a high-impact action.

Protection should fail safely. If CSRF validation fails, return an appropriate error such as HTTP 403 and perform no state change. Log enough information to investigate repeated failures, such as the endpoint, time, and a request correlation identifier, but do not log passwords, session identifiers, CSRF tokens, or other secrets.

To verify the control, test a legitimate state-changing request with a valid token and confirm that it succeeds. Then send requests with a missing token, an incorrect token, and an unexpected Origin and confirm that every invalid request is rejected before any data changes. Also verify that GET and HEAD endpoints do not change application state and review the session cookie's SameSite, Secure, and HttpOnly settings.

Technical Approach
  1. Identify every endpoint that changes server state.
  2. Ensure safe methods such as GET and HEAD do not change state.
  3. For cookie-authenticated browser requests, generate or use the framework's cryptographically unpredictable CSRF token associated with the user's session.
  4. Include that token in legitimate state-changing forms or requests.
  5. Validate the token before performing the action.
  6. Configure session cookies with an appropriate SameSite policy and use Secure and HttpOnly where applicable.
  7. Validate Origin, with a carefully implemented Referer fallback where appropriate, as defense in depth.
  8. Require re-authentication or stronger confirmation for highly sensitive operations.
  9. Reject failures before any state change and log diagnostic information without secrets.
  10. Test valid, missing-token, invalid-token, and unexpected-origin cases.
Practical Insights

CSRF protection adds very little processing or memory cost. Creating or comparing a small token and checking request headers take constant work per request and use only a small amount of data. The larger cost is operational and maintenance work: every state-changing endpoint must consistently use the protection, cookie settings must match legitimate browser flows, and automated tests should prevent future endpoints from accidentally bypassing the controls.

Why Interviewers Ask This

Interviewers want to know whether the candidate understands why an authenticated browser can still be abused, what conditions make CSRF possible, and how to design layered protections instead of relying on weak assumptions such as HTTPS or POST requests alone. They also want practical judgment about tokens, cookies, request origins, HTTP methods, sensitive actions, safe failures, logging, and verification.

Common interview mistakes

Common mistakes are believing that authentication alone prevents CSRF, treating authorization as proof of user intent, using POST without CSRF validation, assuming HTTPS blocks forged requests, allowing GET requests to change data, using predictable tokens, accepting missing or invalid tokens, comparing origins with unsafe substring checks, treating SameSite as the only defense without considering application requirements, disabling protection for convenient endpoints, logging session identifiers or CSRF tokens, and changing application state before validation finishes.

Interview tip

Define the attack first, then explain the conditions that make it possible. Say clearly that authentication identifies the user but does not prove intent. Present CSRF tokens as the main application defense for traditional cookie-based sessions, then add SameSite cookies, origin checks, safe HTTP methods, and re-authentication as layered protections. Explicitly state that HTTPS and POST alone do not prevent CSRF.

Interviewer may ask next
Why does a CSRF token prevent a forged request?

A CSRF token is an unpredictable value that the trusted application provides to the legitimate user's page and verifies on a state-changing request. An attacker on another origin can often trigger a request but normally cannot read the application's token because of browser same-origin restrictions. The forged request therefore lacks the correct value and is rejected before state changes. An XSS vulnerability can weaken this protection because script running in the trusted origin may be able to access or submit the token.

If a session cookie uses SameSite=Strict, do we still need CSRF tokens?

SameSite=Strict provides strong browser-level protection because the cookie generally is not sent during cross-site requests or navigation. Whether a CSRF token can safely be omitted depends on the complete authentication design, supported browsers, application flows, and whether any credentials or endpoints behave differently. For important traditional cookie-authenticated applications, keeping server-side CSRF validation provides defense in depth and avoids relying on one browser control alone.

107. What is the difference between authentication and authorization?NEWSecurityEasy

Question Details

Define authentication as proving who a user or service is and authorization as deciding which actions and resources that identity may access. Explain sessions or tokens, roles and permissions, object-level checks, least privilege, deny by default, enforcement on every request, and why a successful login never replaces authorization checks.

Short Interview Answer (30-60 seconds)

Authentication answers, "Who are you?" Authorization answers, "What are you allowed to do?" Login may create a session or token that represents the authenticated identity, but every protected request must still check whether that identity may perform the requested action on the requested resource.

Detailed Explanation

A secure application makes two separate decisions. First, it checks that a person or connected service really is who it claims to be. Second, it decides what that person or service is allowed to see or change. Passing the first check does not give unlimited access. A signed-in customer should not automatically see another customer's information, and a normal employee should not automatically perform administrator actions. The application should give only the access that is needed and refuse access when permission has not been clearly granted.

Useful Questions to Ask the Interviewer
  1. Should I explain this mainly for a normal PHP web application with user sessions, or also include API tokens?
  2. Do you want examples of role-based permissions and checks for access to individual records?
What is the difference between authentication and authorization? diagram
How to Explain It in an Interview

Authentication proves identity. For example, a PHP application may verify login credentials and then create a secure server-side session. An API may instead validate a token that represents an authenticated user or service. A session or token helps the application recognize the same authenticated identity on later requests, but it does not by itself grant permission to every resource or action.

Authorization happens after the identity is known. It decides whether that identity may perform a specific action on a specific resource. For example, a user may be authenticated but still be forbidden from deleting users, opening an administrator page, or reading another customer's order.

Roles and permissions can provide broad authorization rules. A role such as admin may have permissions that a normal user role does not have. However, role checks alone are often not enough. The application may also need an object-level check. For example, before returning order 123, the server should verify that the current user is allowed to read that specific order, not merely that the user is logged in.

Authorization should follow least privilege. Each identity should receive only the permissions it needs. It should also follow deny by default. If no authorization rule clearly allows an action, the request should be rejected.

The server must enforce authorization on every protected request. A previous successful login does not replace the authorization check. The application must not rely on hidden buttons, disabled controls, URLs that are difficult to guess, or checks performed only in browser code because a requester can send HTTP requests directly to the server.

Failure should be safe. If authentication is missing or invalid, reject access without exposing sensitive information. If authentication succeeds but authorization fails, refuse the protected action. Log useful security information such as the identity identifier, attempted action, requested resource identifier, result, and request correlation information when appropriate. Do not log passwords, session identifiers, access tokens, or other secrets.

To verify the controls, test both allowed and denied cases. Confirm that an unauthenticated requester cannot reach protected functionality, a lower-privileged authenticated user cannot perform a higher-privileged action, and one authenticated user cannot access another user's protected object merely by changing an identifier. Also verify that missing or unknown permission rules result in denial rather than accidental access.

Technical Approach
  1. Authenticate the requester and establish a trusted identity using a secure session or validated token.
  2. Identify the requested action and resource.
  3. Determine the permissions or policy that apply to that identity.
  4. Check broad authorization rules such as roles or explicit permissions.
  5. Perform an object-level authorization check when access depends on ownership, membership, tenancy, or another relationship to the resource.
  6. Deny the request when no rule explicitly allows it.
  7. Execute the protected action only after authorization succeeds.
  8. Fail safely and log useful security events without secrets.
  9. Test successful access, missing authentication, insufficient permission, and access to another user's protected object.
Practical Insights

Authentication and authorization add some work to every protected request. Session authentication may require reading session data, while token authentication requires validating the token. Authorization may require checking permissions and sometimes reading ownership, membership, or other resource information from storage. Memory use is usually small. The larger cost is maintenance: roles, permissions, and object-level rules must stay correct as the application changes. Centralized and reusable authorization policies make these rules easier to test and maintain.

Why Interviewers Ask This

Interviewers want to know whether the candidate understands that proving identity and granting access are separate security responsibilities. They also evaluate whether the candidate would enforce permissions on every protected request, apply least privilege and deny-by-default rules, perform object-level checks, fail safely, and verify that unauthorized users cannot access protected resources.

Common interview mistakes

Common mistakes include treating successful login as permission to access everything, checking only a user's role without checking the requested object, trusting authorization decisions made only in browser code, protecting a page while forgetting the underlying API endpoint, allowing access when no permission rule exists, checking authorization only once instead of on every protected request, and logging passwords, session identifiers, access tokens, or other secrets.

Interview tip

Start with the simple distinction: authentication proves identity; authorization decides permitted actions and resources. Then give one example of a logged-in user being denied access to another user's record. Mention least privilege, deny by default, server-side checks on every protected request, and object-level authorization.

Interviewer may ask next
Why is checking that a user is logged in not enough to protect an object such as an order?

Login proves only which user is making the request. It does not prove that the user may access a particular order. The server must also perform an object-level authorization check, such as confirming that the order belongs to the current user or that the user has an explicit permission to access it. Otherwise, an authenticated user could change an order identifier and attempt to access another user's data.

What should happen if an authenticated user requests an action for which no authorization rule explicitly grants permission?

The application should deny the action. This is the deny-by-default principle. Access should be granted only when a defined authorization rule clearly permits the authenticated identity to perform that action on that resource. The application should fail safely, avoid exposing sensitive information, and may log the denied attempt without recording passwords, access tokens, session identifiers, or other secrets.

108. How should passwords be stored and verified in PHP?SecurityEasy

Question Details

Explain password_hash, password_verify, modern adaptive algorithms, salts handled by the API, rehashing, and why encryption or fast hashes are inappropriate.

Short Interview Answer (30-60 seconds)

Use password_hash() to create an adaptive one-way hash and password_verify() to check it. Prefer PASSWORD_ARGON2ID when available, otherwise use PASSWORD_DEFAULT. PHP handles the salt. After successful verification, use password_needs_rehash() to upgrade hashes created with an older algorithm or cost.

Detailed Explanation

See the Code while reading this explanation.

Passwords should be saved so nobody can turn the saved value back into the original words. When a person signs in, the application should check the entered password against the saved protected value. The checking process should be deliberately slow enough to make large numbers of guesses costly, while remaining acceptable for normal users. The saved value should include everything needed for later checking and should be replaceable with stronger protection over time. Failed sign-ins should not reveal whether an account exists, and passwords must never appear in logs.

Useful clarifying questions:

Useful Questions to Ask the Interviewer
  • Is Argon2id available in the PHP build used in production?
  • Does the application already contain hashes created by an older method?
  • What login response-time and memory limits must the application meet?
How should passwords be stored and verified in PHP? diagram
How to Explain It in an Interview

Use PHP's built-in password hashing API instead of designing a custom format. Create a password hash with password_hash() and verify it with password_verify(). A password hash is a one-way derived value. It should be practical to calculate once during registration or login but expensive for an attacker to calculate repeatedly for many guesses.

Use PASSWORD_ARGON2ID when the PHP build supports Argon2id. Argon2id is an adaptive, memory-hard password-hashing algorithm. Adaptive means its cost can be increased as hardware becomes faster. Memory-hard means each guess requires a configured amount of memory as well as processor time, which makes large-scale offline cracking more expensive.

Argon2id availability depends on how PHP was built. The application can check whether PASSWORD_ARGON2ID is defined or whether 'argon2id' appears in password_algos(). If Argon2id is unavailable, use PASSWORD_DEFAULT. In PHP 8.4 and PHP 8.5, PASSWORD_DEFAULT remains an alias for bcrypt, but the alias may change in a future major PHP release.

Do not manually generate or store a separate salt. PHP creates a cryptographically secure random salt for every call to password_hash(). The returned string contains the algorithm identifier, salt, cost parameters, and derived value. Store that complete string without changing or truncating it. Because PASSWORD_DEFAULT may produce a different-length format in the future, a VARCHAR(255) or equivalent binary-safe text column is a practical storage choice.

At login, retrieve the stored hash and call password_verify($submittedPassword, $storedHash). Do not call password_hash() again and compare the two strings because a new random salt normally produces a different hash each time. Do not write a custom equality or timing-safe comparison routine; password_verify() reads the algorithm and parameters from the stored hash and performs verification in a timing-attack-safe way.

After password_verify() succeeds, call password_needs_rehash() with the application's current algorithm and options. It returns true when the stored hash does not use those settings. If rehashing is needed, call password_hash() using the password that was just successfully verified and update the stored value. The update should be performed safely, preferably with a conditional database update or transaction so concurrent successful logins do not overwrite a newer hash incorrectly.

Never store plaintext passwords. Do not use reversible encryption because anyone who obtains the decryption key could recover every password. Do not use MD5, SHA-1, or a single SHA-256 or SHA-512 operation. These general-purpose hashes are intentionally fast, so an attacker with a stolen database can test very large numbers of guesses. Adding a salt to a fast hash prevents simple precomputed-table reuse but does not provide the deliberate cost of an adaptive password-hashing algorithm.

Authentication and authorization are different. Password verification authenticates the user's identity. Authorization is the later decision about which records, pages, or actions that authenticated user may access. A correct password check must not automatically grant unrestricted access.

Use the same general response for an unknown account and a wrong password, such as "Invalid credentials." This reduces direct account-enumeration information. Keep the public response generic, but record suitable internal security events such as repeated failures, operational errors, and request identifiers. Never log the plaintext password, reset token, full stored hash, or other authentication secrets.

Password hashing mainly protects against offline attacks after stored hashes are stolen. It does not by itself stop online guessing. Apply rate limiting, temporary backoff, monitoring, and appropriate multi-factor authentication separately where the application's risk requires them.

Benchmark the chosen algorithm and options on production-like hardware. Higher Argon2id memory or time settings increase resistance to cracking, but they also increase login latency, memory consumption per concurrent request, and denial-of-service exposure. Do not copy aggressive settings from another system without measuring them. Choose the strongest settings that the real login workload and infrastructure can safely support.

Verify the implementation with automated tests. Confirm that the correct password succeeds, an incorrect password fails, hashing the same password twice produces different encoded hashes because of different salts, both hashes still verify, outdated settings trigger password_needs_rehash(), current settings do not, malformed hashes fail safely, and passwords never appear in application logs.

Key Insight / Why This Solution Works
  1. Choose PASSWORD_ARGON2ID when it is available in the production PHP build; otherwise choose PASSWORD_DEFAULT.
  2. Benchmark the selected algorithm and options on production-like hardware before fixing custom costs.
  3. During registration or a password change, call password_hash() and store the complete returned string.
  4. Do not create a manual salt, encrypt the password, or apply a fast general-purpose hash.
  5. During login, retrieve the stored hash and call password_verify() with the submitted password.
  6. Return the same general failure message for an unknown account and an incorrect password.
  7. After successful verification, call password_needs_rehash() using the current algorithm and options.
  8. If rehashing is required, generate a replacement hash and update it safely in the database.
  9. Create the authenticated session and perform authorization as separate steps.
  10. Apply rate limiting and monitoring, and log failures without passwords, hashes, or other secrets.
  11. Test successful verification, rejection, unique salts, rehash detection, malformed input, concurrency behavior, and acceptable latency and memory use.
Code
<?php

declare(strict_types=1);

/**
 * @return array{algorithm: string|int, options: array<string, int>}
 */
function passwordConfiguration(): array
{
    if (defined('PASSWORD_ARGON2ID')) {
        return [
            'algorithm' => PASSWORD_ARGON2ID,
            'options' => [],
        ];
    }

    return [
        'algorithm' => PASSWORD_DEFAULT,
        'options' => [],
    ];
}

function createPasswordHash(string $plainPassword): string
{
    $configuration = passwordConfiguration();

    return password_hash(
        $plainPassword,
        $configuration['algorithm'],
        $configuration['options']
    );
}

/**
 * @return array{verified: bool, replacementHash: ?string}
 */
function verifyPassword(string $plainPassword, string $storedHash): array
{
    if (!password_verify($plainPassword, $storedHash)) {
        return [
            'verified' => false,
            'replacementHash' => null,
        ];
    }

    $configuration = passwordConfiguration();
    $replacementHash = null;

    if (password_needs_rehash(
        $storedHash,
        $configuration['algorithm'],
        $configuration['options']
    )) {
        $replacementHash = createPasswordHash($plainPassword);
    }

    return [
        'verified' => true,
        'replacementHash' => $replacementHash,
    ];
}

// Registration or password-change example.
$storedHash = createPasswordHash('Correct Horse Battery Staple!');

// Login example. In production, retrieve this hash from the account record.
$result = verifyPassword('Correct Horse Battery Staple!', $storedHash);

if (!$result['verified']) {
    // Use the same public response for an unknown account and a wrong password.
    echo "Invalid credentials.\n";
    exit;
}

if ($result['replacementHash'] !== null) {
    // Persist this with a parameterized and concurrency-safe database update.
    $storedHash = $result['replacementHash'];
}

// Session creation and authorization checks happen separately.
echo "Password verified.\n";
Why Interviewers Ask This

Interviewers want to confirm that the candidate understands the risks of a stolen password database and can use PHP's built-in password API correctly. The answer should show sound judgment about adaptive hashing, automatic salts, secure verification, algorithm availability, gradual rehashing, safe login failures, logging, performance tuning, and the difference between authentication and authorization.

Common interview mistakes

Common mistakes include storing plaintext passwords; using reversible encryption; using MD5, SHA-1, or a single SHA-256 or SHA-512 hash; adding a manual salt; truncating the encoded hash in the database; hashing the login input again and comparing strings; writing a custom verification comparison instead of using password_verify(); assuming Argon2id is available in every PHP build; hard-coding expensive Argon2id settings without benchmarking memory and concurrency; forgetting password_needs_rehash(); rehashing before the old password has been successfully verified; logging passwords or complete hashes; revealing whether an account exists; relying on hashing alone to stop online guessing; and treating authentication as authorization.

Interview tip

Start with password_hash(), password_verify(), and password_needs_rehash(). Explain that Argon2id is preferred when available and PASSWORD_DEFAULT is the portable fallback. Then cover automatic salts, why encryption and fast hashes are unsafe, safe failures, rehashing, benchmarking, logging without secrets, rate limiting, and the separation of authentication from authorization.

Interviewer may ask next
When should password_needs_rehash() be used?

Call it only after password_verify() succeeds, using the application's current algorithm and options. If it returns true, hash the successfully verified password again and safely replace the stored hash. This upgrades active accounts without storing plaintext passwords or forcing an immediate reset.

Should a PHP application add its own salt or pepper?

Do not add a manual salt because password_hash() generates a secure random salt and includes it in the encoded hash. A separately stored pepper may add protection in a specific threat model, but it introduces secret storage, rotation, availability, and recovery risks. It is optional defense in depth and never replaces an adaptive password hash.

109. How do prepared statements prevent SQL injection in PHP?SecurityEasy

Question Details

Explain placeholders and parameter binding, show why string concatenation is dangerous, and identify cases such as table or column names that still require allowlisting.

Short Interview Answer (30-60 seconds)

Prepared statements separate the SQL structure from user-supplied values. PHP sends values through placeholders, so the database treats them as data rather than executable SQL. They protect values only; dynamic table names, column names, and sort directions must be selected from strict allowlists.

Detailed Explanation

See the Code while reading this explanation.

This question asks how a PHP application can safely use information entered by a person when reading or changing stored records. The key idea is to keep the application's instruction separate from the entered information. If both are joined into one text string, a harmful entry may change the intended action. If they are supplied separately, the entry is handled only as information. Some choices, such as which field should control sorting, cannot be separated in the same way. Those choices must be limited to a fixed set approved by the developer.

Useful clarifying questions:

Useful Questions to Ask the Interviewer
  • Should I demonstrate PDO, MySQLi, or both?
  • Should I cover dynamic columns, tables, and sort directions?
  • Which database driver should I assume?
How do prepared statements prevent SQL injection in PHP? diagram
How to Explain It in an Interview

SQL injection occurs when untrusted input is inserted directly into an SQL string and is then interpreted as part of the SQL command. An attacker may use quotes, operators, comments, or additional SQL syntax to change the query's intended meaning.

A prepared statement separates:

  1. The fixed SQL structure, such as SELECT id FROM users WHERE email = :email.
  2. The data value supplied for the placeholder.

With PDO, PHP prepares the SQL statement and supplies the parameter value separately. When native prepared statements are used, the database receives the statement structure separately from its parameter values. The bound value is handled according to the placeholder position and parameter type. SQL-looking characters inside the value remain part of that value instead of becoming new SQL syntax.

Unsafe string concatenation looks like this:

$sql = "SELECT id FROM users WHERE email = '" . $email . "'";

If $email contains crafted input, it may terminate the quoted value and alter the SQL command.

The safe form uses a placeholder:

$statement = $pdo->prepare('SELECT id FROM users WHERE email = :email'); $statement->bindValue(':email', $email, PDO::PARAM_STR); $statement->execute();

PDO can emulate prepared statements for some drivers. Setting PDO::ATTR_EMULATE_PREPARES to false requests native preparation where the driver supports it. Correct parameterization remains essential either way, but native preparation more clearly preserves separation between the statement and its values and avoids some driver-specific emulation behavior.

Prepared-statement placeholders represent data values. They do not represent SQL identifiers or structural keywords. Therefore, a placeholder cannot safely choose a table name, column name, operator, or sort direction. For example, ORDER BY :column does not turn the supplied value into an identifier. The database generally treats it as a value, producing incorrect behavior rather than safely selecting a column.

For dynamic identifiers, map an external choice to a fixed developer-controlled value:

$allowedSortColumns = ['name' => 'name', 'created' => 'created_at'];

After confirming that the requested key exists, insert only the mapped constant into the SQL. Apply the same approach to table names and keywords such as ASC and DESC. Do not accept arbitrary identifier text and attempt to make it safe with filtering alone.

Input validation is still useful for business rules, such as requiring a valid email format or an integer within an expected range. However, validation is not a replacement for parameterized queries. A value can be valid for the application and still contain characters that would be dangerous if concatenated into SQL.

Use a least-privileged database account so the application can perform only the operations it requires. On failure, return a generic response to the client. Log an internal error code and safe diagnostic context, but do not log passwords, connection strings, session identifiers, access tokens, or unnecessary sensitive parameter values.

To verify the control, test normal values and hostile-looking values containing apostrophes, quotes, SQL comments, operators, and keywords. Confirm that each input is treated as one data value, does not change the query structure, and does not expose database error details. Also test that every unapproved identifier or sort direction is rejected before the SQL statement is built.

Key Insight / Why This Solution Works
  1. Write the SQL with placeholders for every untrusted data value.
  2. Prepare the statement with PDO or MySQLi.
  3. Bind each value using the appropriate parameter type, or pass values separately to execute.
  4. Never concatenate untrusted values into the SQL structure.
  5. For identifiers or SQL keywords that cannot use placeholders, map external choices to fixed values from a strict allowlist.
  6. Execute with a least-privileged database account.
  7. Return generic failure messages and log only non-sensitive diagnostic information.
  8. Test normal, malformed, and malicious-looking values, and verify that rejected identifiers never reach SQL construction.
Code
<?php

declare(strict_types=1);

header('Content-Type: application/json; charset=utf-8');

$email = $_GET['email'] ?? '';
$requestedSort = $_GET['sort'] ?? 'created';
$requestedDirection = strtolower($_GET['direction'] ?? 'desc');

if (!is_string($email) || filter_var($email, FILTER_VALIDATE_EMAIL) === false) {
    http_response_code(400);
    echo json_encode(['error' => 'Invalid request.'], JSON_THROW_ON_ERROR);
    exit;
}

$allowedSortColumns = [
    'name' => 'name',
    'created' => 'created_at',
];

$allowedSortDirections = [
    'asc' => 'ASC',
    'desc' => 'DESC',
];

if (!array_key_exists($requestedSort, $allowedSortColumns)
    || !array_key_exists($requestedDirection, $allowedSortDirections)) {
    http_response_code(400);
    echo json_encode(['error' => 'Invalid request.'], JSON_THROW_ON_ERROR);
    exit;
}

$sortColumn = $allowedSortColumns[$requestedSort];
$sortDirection = $allowedSortDirections[$requestedDirection];

$dsn = getenv('APP_DATABASE_DSN');
$username = getenv('APP_DATABASE_USER');
$password = getenv('APP_DATABASE_PASSWORD');

if ($dsn === false || $username === false || $password === false) {
    error_log('Database configuration is unavailable.');
    http_response_code(500);
    echo json_encode(['error' => 'Unable to process the request.'], JSON_THROW_ON_ERROR);
    exit;
}

try {
    $pdo = new PDO(
        $dsn,
        $username,
        $password,
        [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
            PDO::ATTR_EMULATE_PREPARES => false,
        ]
    );

    $sql = "SELECT id, name, email, created_at
            FROM users
            WHERE email = :email
            ORDER BY {$sortColumn} {$sortDirection}";

    $statement = $pdo->prepare($sql);
    $statement->bindValue(':email', $email, PDO::PARAM_STR);
    $statement->execute();

    echo json_encode(
        ['users' => $statement->fetchAll()],
        JSON_THROW_ON_ERROR | JSON_UNESCAPED_SLASHES
    );
} catch (PDOException $exception) {
    error_log('Database operation failed with code: ' . $exception->getCode());
    http_response_code(500);
    echo json_encode(
        ['error' => 'Unable to process the request.'],
        JSON_THROW_ON_ERROR
    );
} catch (JsonException $exception) {
    error_log('JSON encoding failed.');
    http_response_code(500);
    echo '{"error":"Unable to process the request."}';
}
Why Interviewers Ask This

Interviewers want to verify that the candidate understands the cause of SQL injection, can use PDO or MySQLi parameter binding correctly, does not rely on filtering or manual escaping as the primary defense, and recognizes that dynamic SQL identifiers require strict allowlisting because value placeholders cannot represent them.

Common interview mistakes

Common mistakes include concatenating or interpolating untrusted input into SQL, manually surrounding placeholders with quotes, treating input filtering or escaping as a complete defense, assuming validated data is safe to concatenate, attempting to bind table names or column names as parameters, accepting arbitrary ASC or DESC text, using an allowlist check but then inserting the original unchecked value, exposing database exception messages to clients, logging sensitive parameter values, and granting the application database account unnecessary permissions. Another mistake is claiming that prepared statements automatically secure SQL fragments that are still built through unsafe string concatenation.

Interview tip

Lead with the core rule: placeholders separate data values from SQL structure. Show one unsafe concatenation example and one parameterized PDO example. Then state the important limitation that identifiers and keywords cannot be bound and require fixed allowlists. Finish with validation, least privilege, safe failures, and a concrete verification test.

Interviewer may ask next
Can prepared-statement placeholders be used for table names, column names, or sort directions?

No. Placeholders represent data values, not SQL identifiers or structural keywords. Map each permitted external option to a fixed developer-controlled table name, column name, operator, or direction, reject every unknown option, and insert only the mapped constant into the SQL.

Do prepared statements make input validation and least-privileged database access unnecessary?

No. Prepared statements prevent values from changing the SQL structure, while validation enforces business rules such as format, range, and length. Least-privileged database access limits the damage possible from other defects or compromised application code. These controls serve different purposes and should be used together.

110. How do you prevent cross-site scripting when rendering user-controlled data in PHP?SecurityEasy

Question Details

Explain context-aware output encoding for HTML text and attributes, safe templating defaults, URL and JavaScript contexts, input validation, and Content Security Policy as defense in depth.

Short Interview Answer (30-60 seconds)

I prevent XSS by encoding untrusted data for its exact output context. I use htmlspecialchars() for HTML text and ordinary quoted attributes, validate URL schemes, avoid inline JavaScript data insertion, keep template auto-escaping enabled, and add a restrictive Content Security Policy as defense in depth.

Detailed Explanation

See the Code while reading this explanation.

This question asks how to stop information supplied by a visitor from becoming harmful instructions in another visitor's browser. The key is to handle the information safely at the place where it is displayed. Text, links, page properties, and browser instructions do not follow the same rules, so each needs suitable protection. Checking information when it enters the application is helpful, but that check cannot replace safe handling when displaying it. A complete answer should also cover safer page-building tools, extra browser restrictions, safe failure, careful records, and tests that prove the protection works.

Useful clarifying questions:

Useful Questions to Ask the Interviewer
  • In which contexts can user-controlled data appear: HTML text, attributes, URLs, JavaScript, CSS, or permitted HTML?
  • Does the application use plain PHP templates or a template engine with automatic escaping?
  • Are relative URLs or any schemes other than HTTPS required?
  • Must users be allowed to submit limited HTML formatting?
How do you prevent cross-site scripting when rendering user-controlled data in PHP? diagram
How to Explain It in an Interview

The main rule is to encode untrusted data when it is rendered, using the encoder required by that exact browser context. Input validation should enforce business rules, such as expected length, type, format, and allowed values, but filtering or validation alone is not complete XSS protection.

For HTML text and ordinary quoted attributes, I use htmlspecialchars($value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'). It converts characters such as <, >, &, single quotes, and double quotes into text that the HTML parser will not interpret as markup. ENT_SUBSTITUTE replaces invalid byte sequences safely. Attribute values must remain quoted. This rule does not make dangerous attributes such as onclick, style, or srcdoc safe, so I do not place untrusted data in those contexts.

A template engine should keep automatic escaping enabled by default. Developers should avoid raw-output features. When raw output is genuinely required, the value must come from a narrowly controlled source or be processed by a maintained, allowlist-based HTML sanitizer. A custom regular expression, strip_tags(), or a blacklist is not a reliable HTML sanitizer.

URLs require semantic validation as well as HTML encoding. I parse the URL and allow only the schemes the application needs, commonly https and, when justified, http. I reject unexpected schemes such as javascript: and data:. I then apply HTML attribute encoding when placing the accepted URL in a quoted href or src attribute. Encoding by itself cannot make a dangerous URL scheme safe. If relative URLs are allowed, I validate them with a separate policy instead of treating them as absolute URLs.

For JavaScript, I avoid placing user-controlled values directly inside inline scripts, event-handler attributes, or dynamically generated code. A safer design is to place serialized data in a non-executable data element and let trusted external JavaScript read it. When PHP must serialize data for an HTML script element, I use json_encode() with JSON_THROW_ON_ERROR, JSON_HEX_TAG, JSON_HEX_AMP, JSON_HEX_APOS, and JSON_HEX_QUOT. I never build JavaScript source by concatenating quoted user strings, and I do not pass untrusted strings to eval(), new Function(), or HTML-generating DOM APIs.

For CSS contexts, I avoid inserting user-controlled values into style attributes or style blocks. CSS has different parsing rules, and HTML encoding is not sufficient. I prefer selecting from predefined server-side values or applying a strict allowlist for a narrowly defined property.

Content Security Policy, or CSP, is a browser-enforced restriction on which scripts may run. A strong policy can block inline scripts, plugins, unsafe framing, and scripts from unapproved sources. Nonce-based or hash-based policies are stronger than allowing 'unsafe-inline'. CSP can reduce the impact of an encoding mistake, but it is defense in depth and never replaces context-aware output encoding.

When a value fails validation or cannot be encoded safely, the application should reject it or display a safe fallback instead of rendering the original value. Security logs can record the route, field name, validation reason, request identifier, and account identifier when appropriate. They should not contain passwords, session identifiers, authorization headers, private tokens, or unnecessarily complete attack payloads.

I verify the controls with automated tests containing HTML tags, closing tags, quote-breaking input, event handlers, dangerous URL schemes, malformed UTF-8, and strings containing </script>. I inspect the final browser DOM to confirm that values remain text or approved data, test the CSP response header, review CSP violation reports carefully, and use a security scanner as an additional check rather than as the only proof.

Key Insight / Why This Solution Works
  1. Inventory every location where user-controlled data is rendered.
  2. Classify each location as HTML text, ordinary quoted attribute, URL, JavaScript data, CSS, or permitted HTML.
  3. Validate the value against business rules and any context-specific allowlist.
  4. Prefer redesigning the page when the destination context is dangerous, such as an event-handler attribute, inline CSS, or executable JavaScript.
  5. Encode the value at output time with the correct context-specific method.
  6. Keep template automatic escaping enabled and restrict raw-output operations.
  7. Use a maintained allowlist-based sanitizer only when rendering user-authored HTML is an actual requirement.
  8. Add a restrictive CSP as defense in depth.
  9. Reject unsafe values or show a safe fallback, and log only non-secret diagnostic details.
  10. Test malicious boundary cases and inspect the final DOM and security headers.
Code
<?php

declare(strict_types=1);

function escapeHtml(string $value): string
{
    return htmlspecialchars(
        $value,
        ENT_QUOTES | ENT_SUBSTITUTE,
        'UTF-8'
    );
}

function validateAbsoluteHttpUrl(string $value): ?string
{
    if ($value === '' || strlen($value) > 2048) {
        return null;
    }

    $validated = filter_var($value, FILTER_VALIDATE_URL);
    if ($validated === false) {
        return null;
    }

    $scheme = strtolower((string) parse_url($validated, PHP_URL_SCHEME));
    if (!in_array($scheme, ['https', 'http'], true)) {
        return null;
    }

    return $validated;
}

function jsonForHtmlScript(mixed $value): string
{
    return json_encode(
        $value,
        JSON_THROW_ON_ERROR
        | JSON_HEX_TAG
        | JSON_HEX_AMP
        | JSON_HEX_APOS
        | JSON_HEX_QUOT
    );
}

$userName = (string) ($_GET['name'] ?? 'Guest');
$submittedUrl = (string) ($_GET['url'] ?? '');
$safeUrl = validateAbsoluteHttpUrl($submittedUrl);

$nonce = base64_encode(random_bytes(18));
header(
    "Content-Security-Policy: default-src 'self'; "
    . "script-src 'self' 'nonce-{$nonce}'; "
    . "object-src 'none'; base-uri 'self'; frame-ancestors 'none'"
);

$pageData = [
    'displayName' => $userName,
];
?>
<!doctype html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Safe output example</title>
</head>
<body>
    <h1>Hello, <?= escapeHtml($userName) ?></h1>

    <?php if ($safeUrl !== null): ?>
        <a href="<?= escapeHtml($safeUrl) ?>" rel="noopener noreferrer">
            Visit submitted link
        </a>
    <?php else: ?>
        <p>The submitted link is not allowed.</p>
    <?php endif; ?>

    <script type="application/json" id="page-data"><?= jsonForHtmlScript($pageData) ?></script>
    <script nonce="<?= escapeHtml($nonce) ?>">
        const element = document.getElementById('page-data');
        const data = JSON.parse(element.textContent);
        console.log(data.displayName);
    </script>
</body>
</html>
Why Interviewers Ask This

The interviewer is testing whether the candidate understands that XSS prevention depends on where untrusted data is rendered. A strong answer should distinguish HTML text, attributes, URLs, JavaScript, and permitted HTML; explain safe PHP and template behavior; avoid relying on filtering alone; include defense in depth; and describe safe failure, logging, and verification.

Common interview mistakes

Common mistakes include treating input validation or filtering as complete protection; encoding data when it is stored instead of for its final output context; double-encoding or later decoding an already safe value; omitting ENT_QUOTES; leaving attributes unquoted; using HTML encoding inside event handlers, CSS, or JavaScript source; validating a URL's format but not its scheme; allowing javascript: or unnecessary data: URLs; inserting PHP values directly into JavaScript strings; using innerHTML when textContent is sufficient; using strip_tags(), a blacklist, or regular expressions as an HTML sanitizer; disabling template auto-escaping; marking untrusted content as raw; relying only on CSP; weakening CSP with broad sources or 'unsafe-inline'; and logging secrets or complete malicious payloads unnecessarily.

Interview tip

Start with one sentence: encode at output time for the exact browser context. Then distinguish HTML text, URLs, JavaScript, and permitted HTML. State clearly that validation and CSP are supporting controls, not substitutes for context-aware encoding.

Interviewer may ask next
What should you do when users are allowed to submit formatted HTML?

Use a maintained, allowlist-based HTML sanitizer configured to permit only the required elements, attributes, and URL schemes. Do not rely on strip_tags(), regular expressions, or a blacklist. Keep the allowlist small, sanitize at a clearly defined trust boundary, prevent later unsafe transformations, test known bypass patterns, and retain CSP as defense in depth.

Why is htmlspecialchars() not sufficient for every output context?

htmlspecialchars() is appropriate for HTML text and ordinary quoted attributes because it follows HTML parsing rules. It does not validate URL schemes and is not a JavaScript, CSS, or HTML-sanitization function. Other contexts use different parsers, so the application must use a context-specific encoder, strict allowlist, safe serializer, or a design that avoids placing untrusted data there.

More questions load as you scroll

Disclaimer: This interview guide is for educational and informational purposes only. It is designed to help readers prepare, but it does not guarantee any interview result, hiring decision, offer, or outcome. Interview questions, hiring criteria, and preferred answers can vary by employer, interviewer, industry, location, and time. The examples and explanations reflect the authors' research and judgment, are provided without warranties of any kind, and should not be treated as the only correct approach. Diagrams are simplified illustrations intended to highlight the main components and their interactions; actual systems and implementations may be more complex. Alternative approaches may be equally valid or better suited to a particular question, context, or interviewer. To the fullest extent permitted by applicable law, the author, contributors, and publisher are not liable for decisions made, actions taken, or losses incurred based on this guide.