Secure Code Review: A journey from flaws to fortifying code — Part 1 (SQLi, XSS, Error Disclosure)

Aakash Mudigonda
8 min readFeb 14, 2025

--

Imagine spending hours writing code, pushing it live to show off your skills — only to stumble upon an article one day, using your project as an example of security vulnerabilities. Ouch. 🥲🥲

I have come forward to stop this from happening to you!

Technology is evolving faster than ever, and so is the software we use every day. As code-bases grow in complexity and size, so do their vulnerabilities. More lines of code mean more opportunities for security flaws to creep in.

When I first started exploring secure code review, I found plenty of challenges on GitHub, YouTube, and other platforms. But what was missing? A solid foundation — a beginner-friendly introduction that could bridge the gap between writing code vs writing secure code. That’s why I’m writing this article: to break down the basics of secure code review and help YOU get started the right way.

(If you’ve come across great beginner-friendly resources on this topic, drop a comment and share them with the community!)

In this article, we’ll dive into three key vulnerabilities you might encounter during a code review:

  • SQL Injection
  • XSS (Cross-Site Scripting)
  • Error Disclosure

Before we dive in, I intend keep this simple and basic as not everyone might not be on the same level. I will later in the post suggest some resources where you can practice your freshly acquired code review skills.

SQL Injection:

Occurs when you give some input to the website, and then the code takes in the input and processes it. Sounds normal eh? Now, imagine you are on a banking website and you give the input to dump out everyone’s password.

Try to check out this piece of code:

Vulnerable Code (SQL Injection):

<?php
// Retrieve user input from the URL query parameter
$username = $_GET['username'];


$query = "SELECT * FROM users WHERE username = '$username'";

// Execute the query
$result = mysqli_query($conn, $query);

// Process result...
?>

Try exploiting this code without scrolling down yet. Try figuring out what’s happening in the code.

Exploitation (SQL Injection):

Suppose the attacker accesses the page with the following query string:

?username=' OR '1'='1

The resulting Query in the above code would be:

SELECT * FROM users WHERE username ='' OR '1'='1'

The username is now checking for a condition and since '1'='1' is always true, this query returns all records from the users table rather than just the intended user.

The problem here being, $username is being added directly into the SQL Query.

Mitigated Code (SQL Injection):

<?php
// Retrieve user input from the URL query parameter
$username = $_GET['username'];

// Create a prepared statement with a placeholder for the username
$stmt = $conn->prepare("SELECT * FROM users WHERE username = ?");

// Bind the input parameter to the prepared statement
// "s" specifies that the parameter is of type string
$stmt->bind_param("s", $username);

// Execute the prepared statement
$stmt->execute();

// Retrieve the result set from the executed statement
$result = $stmt->get_result();

// Process result...
?>

$username = $_GET[‘username’]; :

  • Retrieves the user input from the query string.
  • One thing to note is that, the input is still raw at this point, but it will be safely handled by the following “prepared statement

$stmt = $conn->prepare(“SELECT * FROM users WHERE username = ?”); :

  • We prepare an SQL statement with a placeholder (?) instead of directly embedding the user input.
  • Since the structure of the query is fixed, it prevents the alteration of the SQL command by malicious input.

$stmt->bind_param("s", $username);

  • We bind the variable $username to the placeholder in the prepared statement.
  • The "s" indicates that the parameter is a string.
  • This process ensures that the user input is treated solely as data

$stmt->execute();

  • We now execute the prepared SQL statement with the bound parameter.

$result = $stmt->get_result();

  • The executed statement now retrieves the result and sends it for further operations or processing.

Trying to Exploit the Mitigated Code (SQL Injection):

Attacker again gives the input:

?username=' OR '1'='1

In the mitigated code, the input string(' OR '1'='1) is bound to be the placeholder as a literal string. So, basically this whole thing is considered a string : “ 'OR '1'='1

Now when this is put into the SQL Query, it looks like this:

SELECT * FROM users WHERE username = " 'OR '1' = '1 "

The SQL query is now looking for a username “ 'OR '1'='1” in its database.

Why didn’ t the exploit work?

  • The use of prepared statements ensures that the user input is treated strictly as data rather than part of the SQL command.
  • The malicious input is interpreted literally, so the database does not execute any additional SQL logic (like the always-true condition '1'='1').
  • Because the query structure is fixed at the time of preparation, the input cannot modify the intended command flow, effectively neutralizing the SQL injection attack.

Now, let’s move to XSS.

XSS:

A vulnerability that allows attackers to inject malicious scripts into websites, which then run in the browsers of unsuspecting users.

Vulnerable Code (XSS):

Try to check out this piece of code:

<?php
// Retrieve user input from the query parameter "message"
$message = $_GET['message'];


echo "<p>$message</p>";
?>

Try exploiting this code without scrolling down yet. Try figuring out what’s happening in the code.

Exploitation (XSS):

An attacker tries to inject malicious JS(JavaScript) into our webpage/function:

?message=<script>alert('XSS');</script>

The browser renders the input as:

<p><script>alert('XSS');</script></p>

This causes the malicious JavaScript (alert(‘XSS’);) to execute, demonstrating an XSS attack.

This happened because, the input taken from the user, was not sanitized and was directly included into the webpage code.

Mitigated Code (XSS):

<?php
// Retrieve user input from the query parameter "message"
$message = $_GET['message'];

// Mitigation: Sanitize the output using htmlspecialchars to convert special characters
$safe_message = htmlspecialchars($message, ENT_QUOTES, 'UTF-8');

// Output the sanitized message safely into the HTML
echo "<p>$safe_message</p>";
?>

$safe_message = htmlspecialchars($message, ENT_QUOTES, 'UTF-8');

  • We convert special HTML characters (like <, >, and quotes) into their corresponding HTML entities. Parameters:
  • ENT_QUOTES: Converts both double and single quotes.
  • 'UTF-8': Specifies the character encoding.
  • This now prevents browsers from interpreting user input as executable code by rendering it as plain text.

echo "<p>$safe_message</p>";

  • We now safely output the sanitized message into an HTML paragraph element.

Trying to exploit Mitigated Code (XSS):

Attacker again gives the input:

<script>alert('XSS');</script>

When this input is passed into the Mitigated code, the input is passed into htmlspecialchars , which converts it to:

&lt;script&gt;alert('XSS');&lt;/script&gt;

The final output in the HTML becomes:

<p>&lt;script&gt;alert('XSS');&lt;/script&gt;</p>

The browser displays the text literally instead of interpreting it as an executable script.

Now for the final one.

Error Disclosure:

This happens when the error provides you with too much details about application or system internals potentially outputting sensitive data.

For example, let’s say you give username and password, and then you get an error wrong password. This is low level error disclosure where you are getting the error of password being wrong, which means the username exists.

One more example would be, when you give an SQL Injection query in the username or password field, the error output says SQL query error and could display what SQL flavor is being used.

Vulnerable Code (Error Disclosure):

Try to check out this piece of code:

<?php

ini_set('display_errors', 1);
error_reporting(E_ALL);

// Example code that might trigger an error (e.g., accessing an undefined variable)
echo $undefined_variable;
?>

Try exploiting this code without scrolling down yet. Try figuring out what’s happening in the code.

Exploitation (XSS):

An attacker visits the page and triggers an error (for example, by providing unexpected input like an SQLi or XSS, or simply due to the code accessing an undefined variable).

The attacker sees a detailed error message like:

Notice: Undefined variable: undefined_variable in /var/www/html/index.php on line 5
Stack trace:
#0 /var/www/html/index.php(5): {main}()

This error disclosure might reveal:

  • The file system structure (e.g., /var/www/html/)
  • The internal structure of the code (line numbers, file names)
  • Potential hints for further attacks (e.g., paths to configuration files, libraries in use)

Mitigated Code (XSS):

<?php
// Mitigation: Disable error display and enable error logging
ini_set('display_errors', 0); // Do not show errors to the end user
ini_set('log_errors', 1); // Log errors to a file or error logging system
error_reporting(E_ALL); // Report all errors internally

// Ensure that your php.ini or server configuration defines a secure location for error logs
// For example: error_log = /var/log/php_errors.log

// Example code
echo $undefined_variable;
?>

ini_set('display_errors', 0);

  • Disables the direct display of errors and warnings on the web page, preventing users (and attackers) from seeing internal system details.

ini_set('log_errors', 1);

  • Enables error logging. Instead of displaying errors, they are recorded in a log file where developers can review them securely.

error_reporting(E_ALL);

  • Continues to report all types of errors. This setting ensures that errors are still captured, but with the display turned off, only authorized personnel with access to the log files can view them.

Note: In production, ensure that the error log file is stored in a secure location (for example, outside of the web root) and that file permissions prevent unauthorized access.

Trying to Exploit the Mitigated Code (Error Disclosure):

Attacker again gives the malicious input to the website.

  • Because display_errors is set to 0, no error messages are shown on the web page.
  • The error is silently logged to a file (e.g., /var/log/php_errors.log) that is not accessible via the web server.
  • The attacker does not gain any information about the application’s internals or file structure.

Why the Exploit doesn’t work

  • No Direct Error Disclosure: With display_errors turned off, end users (and attackers) are not exposed to sensitive error messages.
  • Secure Logging: Errors are logged in a controlled manner, so developers can diagnose issues without compromising security.
  • Reduced Information Leakage: Attackers cannot gain insights into file paths, code structure, or other internal details that could help them craft further attacks.

Alright then! That was fun!

Remember, these code snippets are just to get you started, there might be ways you can still exploit these mitigated codes too (Let me know by dropping a comment!)

Before you go, the resources which I found very useful for my secure code preparation:

https://owasp.org/www-project-secure-coding-practices-quick-reference-guide/assets/docs/OWASP_SCP_Quick_Reference_Guide_v21.pdf

Alright then! See you again soon! Keep looking out for those bugs — in the code!

bye!

--

--

No responses yet