This is my first time participating in an Intigriti challenge. Personally, I find this to be a very interesting challenge involving PHP and SQLi.
Enter the challenge details, we will be provided with a website at: https://challenge-0923.intigriti.io/challenge.php
Brief analysis reveals that this is a website that allows listing the users in the database and has a button to view the server-side source code.
When clicking on the "show source" button, we get the following PHP code:
<?php
if (isset($_GET['showsource'])) {
highlight_file(__FILE__);
exit();
}
require_once("config.php");
$dsn = "mysql:host=$host;dbname=$db;charset=$charset";
$options = [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
];
try {
$pdo = new PDO($dsn, $user, $pass, $options);
} catch (\\PDOException $e) {
exit("Unable to connect to DB");
}
$max = 10;
if (isset($_GET['max']) && !is_array($_GET['max']) && $_GET['max']>0) {
$max = $_GET['max'];
$words = ["'","\\"",";","`"," ","a","b","h","k","p","v","x","or","if","case","in","between","join","json","set","=","|","&","%","+","-","<",">","#","/","\\r","\\n","\\t","\\v","\\f"]; // list of characters to check
foreach ($words as $w) {
if (preg_match("#".preg_quote($w)."#i", $max)) {
exit("H4ckerzzzz");
} //no weird chars
}
}
try{
//seen in production
$stmt = $pdo->prepare("SELECT id, name, email FROM users WHERE id<=$max");
$stmt->execute();
$results = $stmt->fetchAll();
}
catch(\\PDOException $e){
exit("ERROR: BROKEN QUERY");
}
/* FYI
CREATE TABLE users (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255) NOT NULL,
email VARCHAR(255) UNIQUE NOT NULL,
password VARCHAR(255) NOT NULL
);
*/
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Utenti</title>
<link href="<https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css>" rel="stylesheet">
</head>
<div class="container mt-5">
<h2>Users</h2>
<table class="table table-bordered">
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>Email</th>
</tr>
</thead>
<tbody>
<?php foreach ($results as $row): ?>
<tr>
<td><?= htmlspecialchars(strpos($row['id'],"INTIGRITI")===false?$row['id']:"REDACTED"); ?></td>
<td><?= htmlspecialchars(strpos($row['name'],"INTIGRITI")===false?$row['name']:"REDACTED"); ?></td>
<td><?= htmlspecialchars(strpos($row['email'],"INTIGRITI")===false?$row['email']:"REDACTED"); ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<div class="text-center mt-4">
<!-- Show Source Button -->
<a href="?showsource=1" class="btn btn-primary">Show Source</a>
</div>
</div>
<!-- including Bootstrap e jQuery -->
<script src="<https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js>"></script>
<script src="<https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js>"></script>
</body>
</html>
Proceed with the analysis, and it is observed that the max
parameter can be used to increase the number of returned records through the following code snippet:
$max = 10;
if (isset($_GET['max']) && !is_array($_GET['max']) && $_GET['max']>0) {
$max = $_GET['max'];
$words = ["'","\\"",";","`"," ","a","b","h","k","p","v","x","or","if","case","in","between","join","json","set","=","|","&","%","+","-","<",">","#","/","\\r","\\n","\\t","\\v","\\f"]; // list of characters to check
foreach ($words as $w) {
if (preg_match("#".preg_quote($w)."#i", $max)) {
exit("H4ckerzzzz");
} //no weird chars
}
}
try{
//seen in production
$stmt = $pdo->prepare("SELECT id, name, email FROM users WHERE id<=$max");
$stmt->execute();
$results = $stmt->fetchAll();
}
catch(\\PDOException $e){
exit("ERROR: BROKEN QUERY");
}
It is observed that when performing query creation, the server will create a query by concatenating strings from the variable $max
. We can control the value of the variable $max
through the max
parameter with a GET request ⇒ SQL injection can be exploited.
However, before being passed into the query, the variable $max
will be filtered in the if-else statement. Specifically, as follows:
max
appear in the GET request?max
an array?max
exceed 0?max
to the variable $max
.$words
containing blacklisted characters.$words
array and check if that character appears in $max
.$words
appears in $max
, it will stop executing the code and print the message "H4ckerzzzz".First, based on the blacklist, it can be seen that we will not be able to use certain functions and keywords such as substring
, substr
, if
, between
, in
, .... and certain speical characters like '
, space
, /
, \\
in MySQL.
Next, analyze the code segment for printing the query result:
<tbody>
<?php foreach ($results as $row): ?>
<tr>
<td><?= htmlspecialchars(strpos($row['id'],"INTIGRITI")===false?$row['id']:"REDACTED"); ?></td>
<td><?= htmlspecialchars(strpos($row['name'],"INTIGRITI")===false?$row['name']:"REDACTED"); ?></td>
<td><?= htmlspecialchars(strpos($row['email'],"INTIGRITI")===false?$row['email']:"REDACTED"); ?></td>
</tr>
<?php endforeach; ?>
</tbody>
The flag has the form INTIGRITI{.*}, but when reading the records returned from the database, the server will check if the string INTIGRITI exists in those records. If it does, it will only print out the string "REDACTED". Therefore, we need to remove the string INTIGRITI from the result when performing union-based SQLi or use time-based SQLi.