1
\$\begingroup\$

So I am making post requests to a page for search queries. I am turning them into a session array so I can paginate the results. The process kind of feels ugly and long winded. I need code review for two things.

  1. Shortening / making this code more elegant if possible
  2. Security - any glaringly obvious vulnerabilities I have missed. I am running the posts through mysqli_real_escape_string but is the way I am building the query vulnerable?

input appreciated

<?php
if ($_SERVER['REQUEST_METHOD'] == "POST") {
 //only accept posts from our domain
 if (strpos($_SERVER['HTTP_REFERER'], DOMAIN)) {
 if (isset($_POST['csrf_token']) && $_POST['csrf_token'] === $_SESSION['csrf_token']) {
 // some arrays to check against
 $stock_type = array(
 "beers",
 "wines",
 "spirits",
 "apops"
 );
 $trade_type = array(
 "buying",
 "selling"
 );
 // create session array from post variables
 $_SESSION['search'] = array(
 'stock_type' => mysqli_real_escape_string($connection, $_POST['stock_type']),
 'trade_type' => mysqli_real_escape_string($connection, $_POST['trade_type']),
 'brand' => mysqli_real_escape_string($connection, $_POST['brand']),
 'country' => mysqli_real_escape_string($connection, $_POST['country']),
 'min' => mysqli_real_escape_string($connection, $_POST['min']),
 'max' => mysqli_real_escape_string($connection, $_POST['max']),
 'user' => mysqli_real_escape_string($connection, $_POST['user'])
 );
 // the start of the search variable
 $where = "WHERE t.published = 1";
 // buld up the search variable based on whats been posted
 if (!empty($_SESSION['search']['stock_type']) && in_array($_SESSION['search']['stock_type'], $stock_type)) {
 $where .= " and t.stock_type = '" . $_SESSION['search']['stock_type'] . "'";
 }
 if (!empty($_SESSION['search']['trade_type']) && in_array($_SESSION['search']['trade_type'], $trade_type)) {
 $where .= " and t.buying_selling= '" . $_SESSION['search']['trade_type'] . "'";
 }
 if (!empty($_SESSION['search']['brand'])) {
 $where .= " and t.brand LIKE '%" .$_SESSION['search']['brand']. "%'";
 }
 if (!empty($_SESSION['search']['country']) && in_array($_SESSION['search']['country'], $country_list)) {
 $where .= " and u.country= '" . $_SESSION['search']['country'] . "'";
 }
 if (!empty($_SESSION['search']['min']) && ctype_digit($_SESSION['search']['min'])) {
 $where .= " and t.price >= '" . $_SESSION['search']['min'] . "'";
 }
 if (!empty($_SESSION['search']['max']) && ctype_digit($_SESSION['search']['max'])) {
 $where .= " and t.price <= '" . $_SESSION['search']['max'] . "'";
 }
 if (!empty($_SESSION['search']['user']) && ctype_digit($_SESSION['search']['user'])) {
 $where .= " and u.subscription = '" . $_SESSION['search']['user'] . "'";
 }
 $_SESSION['where'] = $where;
 }
 }
} else {
 $where = $_SESSION['where'];
}
if (isset($_SESSION['where'])) {
 mysqli_select_db($connection, $database_connection);
 $query = "SELECT SQL_CALC_FOUND_ROWS t.*,
 u.subType,
 Count(l.trade_id) AS leads 
 FROM trading t 
 LEFT JOIN leads l 
 ON l.trade_id = t.trade_id
 LEFT JOIN users u 
 ON u.user_id = t.user_id 
 $where
 GROUP BY t.trade_id 
 ORDER BY timestamp DESC 
 LIMIT $start, $perpage";
 $something = mysqli_query($connection, $query) or die(mysqli_error($connection));
 $totalRows = mysqli_num_rows($something);
}
?>
asked Jun 23, 2016 at 11:12
\$\endgroup\$

3 Answers 3

1
\$\begingroup\$

Security - any glaringly obvious vulnerabilities I have missed. I am running the posts through mysqli_real_escape_string but is the way I am building the query vulnerable?

Maybe. You didn't post where $start and $perpage come from, if they are user-supplied you may be vulnerable.

Also, you should switch to using prepared statements. Escaping is a weaker form of protection against SQL injections.

Returning early

You can avoid nested if statements by reversing it and returning early:

if ($_SERVER['REQUEST_METHOD'] != "POST") {
 // handle this case
}
if (!strpos($_SERVER['HTTP_REFERER'], DOMAIN)) {
 // return / die with CSRF error
}
if (!isset($_POST['csrf_token']) || $_POST['csrf_token'] !== $_SESSION['csrf_token']) {
 // return / die with CSRF error
}

Storing data in session

$_SESSION isn't really the right place to store temporary data like this, as it is global and only meant to store data across requests.

I think that you are doing this because it makes it easier to see which values are safe, and which are not. If you don't want to switch to prepared statements (which you should), you should use some other means of doing this, such as storing the values in your own array or appending "safe" to the variable names.

answered Jun 23, 2016 at 16:39
\$\endgroup\$
0
\$\begingroup\$

I recently switched from mysqli_ to PDO for the SQL injection reason. I'll give you a very basic example of connecting and searching a row:

<?php
 $host = "localhost";
 $name = "database name";
 $user = "root";
 $pass = "root";
 $variable1 = $_POST['variable1'];
 $variable2 = $_POST['variable2'];
 $db_connection = new PDO('mysql:host='. $host .';dbname=' . $name .';charset=utf8', $user, $pass);
 $database_search = $db_connection->prepare('SELECT * FROM table WHERE column1=:variable1 AND column2=:variable2');
 $database_search->bindValue(':variable1', $variable1, PDO::PARAM_STR);
 $database_search->bindValue(':variable2', $variable2, PDO::PARAM_STR);
 $database_search->execute();
 $database_search_results = $database_search->fetch();
 echo $database_search_results['column1'];

Note that when I use bindValue() on $variable1 and $variable2, the database already knows what the query is, which makes the query invulnerable to SQL injection.

answered Jun 23, 2016 at 15:28
\$\endgroup\$
0
\$\begingroup\$

I like the idea of returning early - hadn't thought of this. Below is the full snippet after a tidy up including the $start & $perpage get values. Ended up going with a foreach loop and switch statements.

<?php
if (!isset($_SESSION)) {
 session_start();
}
$perpage = 10;
if (isset($_GET["page"])) {
 if (!ctype_digit($_GET['page'])) {
 exit();
 } else {
 $page = intval($_GET["page"]);
 }
} else {
 $page = 1;
}
$calc = $perpage * $page;
$start = $calc - $perpage;
if ($_SERVER['REQUEST_METHOD'] == "POST") {
 //only accept posts from our domain
 if (strpos($_SERVER['HTTP_REFERER'], DOMAIN)) {
 if (isset($_POST['csrf_token']) && $_POST['csrf_token'] === $_SESSION['csrf_token']) {
 $ok_values = array(
 "beers",
 "wines",
 "spirits",
 "apops",
 "buying",
 "selling",
 "1"
 );
 $_SESSION['search'] = array(
 't.stock_type' => $_POST['stock_type'],
 't.buying_selling' => $_POST['trade_type'],
 'brand' => $_POST['brand'],
 'u.country' => $_POST['country'],
 'min' => $_POST['min'],
 'max' => $_POST['max'],
 'u.subscription' => $_POST['user']
 );
 $where = "WHERE t.published = 1";
 foreach ($_SESSION['search'] as $key => $value) {
 if (!empty($value)) {
 $value = mysqli_real_escape_string($connection, $value);
 switch ($key) {
 case "brand":
 $where .= " and t.brand LIKE '%" . $value . "%'";
 break;
 case "min":
 if (ctype_digit($value)) {
 $where .= " and t.price >= '" . $value . "'";
 }
 break;
 case "max":
 if (ctype_digit($value)) {
 $where .= " and t.price <= '" . $value . "'";
 }
 break;
 default:
 if (in_array($value, $ok_values)) {
 $where .= " and $key = '" . $value . "'";
 }
 }
 }
 }
 if (isset($where)) {
 $_SESSION['where'] = $where;
 }
 }
 }
} else {
 if (isset($_SESSION['where'])) {
 $where = $_SESSION['where'];
 }
}
mysqli_select_db($connection, $database_connection);
$query = "SELECT SQL_CALC_FOUND_ROWS t.*, 
 u.subtype, 
 count(l.trade_id) AS leads 
 FROM trading t 
 LEFT JOIN leads l 
 ON l.trade_id = t.trade_id 
 LEFT JOIN users u 
 ON u.user_id = t.user_id $where 
 GROUP BY t.trade_id 
 ORDER BY timestamp DESC 
 LIMIT $start, $perpage";
$something = mysqli_query($connection, $query) or die(mysqli_error($connection));
$totalRows = mysqli_num_rows($something);
// total rows before limit clause
$rows = sprintf("SELECT FOUND_ROWS() AS `found_rows`");
$total = mysqli_query($connection, $rows) or die(mysqli_error($connection));
$rows_total = mysqli_fetch_assoc($total);
?>
answered Jun 23, 2016 at 17:26
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.