For this challenge we had a website that had three functions:
– showing top 5 products
– displaying details of one product (requires name and secret)
– adding a new product
Apart from that we had access to the source code. From the quick overview everything seemed perfect, not vulnerable to SQL injection (bind_param used), secret hashed, protection against adding a new product when one with the same name already exists etc.
Here are some parts of the source code:
$product = get_product($name); if ($product !== null) { return "Product name already exists, please enter again"; } insert_product($name, hash('sha256', $secret), $description);
function get_product($name) { global $db; $statement = $db->prepare( "SELECT name, description FROM products WHERE name = ?" ); check_errors($statement); $statement->bind_param("s", $name); check_errors($statement->execute()); $res = $statement->get_result(); check_errors($res); $product = $res->fetch_assoc(); $statement->close(); return $product; } function insert_product($name, $secret, $description) { global $db; $statement = $db->prepare( "INSERT INTO products (name, secret, description) VALUES (?, ?, ?)" ); check_errors($statement); $statement->bind_param("sss", $name, $secret, $description); check_errors($statement->execute()); $statement->close(); } function check_name_secret($name, $secret) { global $db; $valid = false; $statement = $db->prepare( "SELECT name FROM products WHERE name = ? AND secret = ?" ); check_errors($statement); $statement->bind_param("ss", $name, $secret); check_errors($statement->execute()); $res = $statement->get_result(); check_errors($res); if ($res->fetch_assoc() !== null) { $valid = true; } $statement->close(); return $valid; }
You can’t show the product if you don’t have a secret and you can’t overwrite it if it already exists. Also on the challenges page there was an information that for brute forcing is ban so that’s out of the way as well. Tough case.
Solution
Have you ever wondered how bind_param with INSERT statement handle cases where the parameter finishes with a space? Wonder no more. Turns out that if you name your new product “facebook ” (note the space at the end) SELECT statement will return no results as it takes that space into account effectively bypassing the check but INSERT will remove the trailing spaces and add a new record to a database with name “facebook” and secret of your choice. After that all it takes is just viewing a product “facebook” (cause that’s how it got added to the system) with our new secret and check_name_secret will authorize us with our new credentials while get_product will return the original row of the database containing the flag.