-1

I'm trying to take a query string param such as ?table=products and have mysql return all the rows for the "products" table in mysql. I tried running the code below in my browser, but I just get a blank white page. I know the mysql server/username/pass information is correct, I've tested the query in mysql and it works fine.

I guess I have two question:

  1. What am I doing wrong?
  2. How come I can't see any error messages when php has an issue?

e.g. code:

<?php

    // Get query string parameter value
    $keys = array_keys($_GET);
    $key = $keys[0];
    $value = $_GET[$key];

    // Setup connection to mysql database
    $serverName = "localhost";
    $username = "root";
    $password = "password";
    $dbname = "webserver";

    $conn = new mysqli($serverName, $username, $password, $dbname);

    // SQL query
    $sql = "SELECT * FROM $value";
    $result = $conn->query($sql);

    // Print results
    echo $result;

?>
pss1suporte
  • 139
  • 1
  • 10
Evan
  • 139
  • 3
  • 3
    Your code is vulnerable to SQL injections. Please learn to use [prepared statements](https://www.youtube.com/watch?v=nLinqtCfhKY) instead. Also, is this a trick question? – tereško Nov 26 '17 at 21:03
  • you need to enable errors in php.ini config – Anand Siddharth Nov 26 '17 at 21:04
  • This approach is just asking to get hacked. At least whitelist the tables you'll accept as input, instead of just running whatever SQL the user decides to put into the $value string without checking it. You can't use parameterised queries since you're trying to inject the table name, so a whitelist is probably the safest option you've got. And don't let your web app log in as root either, just create an account with the privileges it actually needs. – ADyson Nov 26 '17 at 21:11
  • Anyway, even if your query works, all you're doing by echoing $result is the mysqli result object, it's not actually the data itself. For that you need to fetch each row and loop until you've fetched them all, extracting the data as you go. Pretty much any basic PHP/MySQL tutorial will show you this standard technique - I'm surprised you haven't discovered it already? – ADyson Nov 26 '17 at 21:12
  • @tereško: is your only contribution here to keep telling people that their code is vulnerable to SQL injection? In this case, the code is vulnerable but in the absence of a filtering predicate the impact is inconsequential. – symcbean Nov 26 '17 at 21:14
  • 1
    @symcbean yes, deal with it. – tereško Nov 26 '17 at 21:15
  • 1
    @symcbean how is the vulnerability inconsequential, in your view? To me it seems the user could put almost any SQL into $value and make it run, including DROP DATABASE commands and so on (which will work because the app is needlessly logging in as root) – ADyson Nov 26 '17 at 21:16
  • 1
    @symcbean Until people stop writing vulnerable MySQL, people like tereško, myself and doubtless many others are going to keep banging on about it. There is no excuse. – Niet the Dark Absol Nov 26 '17 at 21:19
  • @ADyson: No - you can add any string you like but the database WILL NOT EXECUTE additional SQL commands - RTFM - http://php.net/manual/en/mysqli.quickstart.multiple-statement.php the only way to subvert this code is to make it show fewer rows or include rows from another table. – symcbean Nov 26 '17 at 21:29

3 Answers3

1

Follow the instuctions on below link to enable php.ini errors

How do I get PHP errors to display?

VULNERABLE IMPLEMENTATION WARNING

The above comments clearly mention the side effects of this implementation.

Since knowing the actual bug is a developer's right! Continue reading the answer keeping the safety of software and its users in mind.


You are trying to print $result which is not valid since its an object.

You can do the following instead:

$response = array();
$sql = "SELECT * FROM $value"; 
$result = $conn->query($sql);

// Print results
if ($result) {
    while($row = $result->fetch_array(MYSQL_ASSOC)) {
        $response[] = $row;
    }

}
echo json_encode($response);
Anand Siddharth
  • 873
  • 1
  • 8
  • 26
  • 2
    I am downvoting this answer because of the SERIOUS MySQL injection vulnerability that has been left unaddressed. It is ***TOTALLY irresponsible*** to post an answer on a site like StackOverflow that contains such a serious issue. – Niet the Dark Absol Nov 26 '17 at 21:17
  • 1
    @NiettheDarkAbsol I appreciate your consciousness regarding the safer web, but please read the entire answer, at the last, I have mentioned the warning. – Anand Siddharth Nov 26 '17 at 21:19
  • 4
    That's not acceptable. Thousands of "developers" copy-paste code from StackOverflow answers and bodge it into their code without actually caring for anything other than "does it (seem to) work?" You should not post code samples with such a staggering security vulnerability *at all*, because it will cause problems elsewhere. – Niet the Dark Absol Nov 26 '17 at 21:22
  • @NiettheDarkAbsol Acknowledged! Thanks! I have edited the post which now clearly tells of the implementation effects. Will keep in mind next time :) – Anand Siddharth Nov 26 '17 at 21:28
  • I appreciate the effort, but I'm afraid the problem is worse than that. People do not read when they're just looking for code that "works". It doesn't matter how many warnings you put, just having the code there is an issue. – Niet the Dark Absol Nov 26 '17 at 21:30
  • 1
    I guess that's how it works! Every software comes with a legal warning. It depends on users whether they follow or not. – Anand Siddharth Nov 26 '17 at 21:32
  • 1
    There are answers I have written, I believe, especially on high-level problems, which contain a warning that my *high-level* answer may contain unrelated issues. Examples are SQL Injection vulnerabilities, use dependency injection here, other ones that contain **--snip--**. Firstly, @NiettheDarkAbsol your attitude here is not acceptable, and secondly, it's not up to the answerers to provide every single use-case in case a trial were to find an edge case and hang them for it. A simple warning is enough here. Your campaign will not work, although I agree with the principle. – Jimbo Nov 27 '17 at 12:38
0

What am I doing wrong?

Sadly, pretty much everything.

// Get query string parameter value
$keys = array_keys($_GET);
$key = $keys[0];
$value = $_GET[$key];

You are dereferencing a named value based on its position. And its totally unnecessary. Consider:

$value=$_GET['table'];

...

$conn = new mysqli($serverName, $username, $password, $dbname);

Where is your error checking to see if $conn was initialized?

$result = $conn->query($sql);

again, no error checking.

echo $result;

$result here is a mysqli_result object. You need to call some methods on it to get the data out.

while ($row = $result->fetch_array(MYSQLI_ASSOC)) {
     var_export($row);
}

How come I can't see any error messages when php has an issue?

Have you tested that the default handlers produce output in your browser? You're not overriding the config in php.ini in the code you've shown us. Did you check your logs?

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

I just get a blank white page

Would it be so hard to put

 print "finished";

at the end of the code? Then you'd at least know if the code executed.

symcbean
  • 45,607
  • 5
  • 49
  • 83
0

The main issue you have right now is you need to get the results

while ($row = $result->fetch_assoc()) {
    //do something with row
} 

See ( for mysqli->query method ) http://php.net/manual/en/mysqli.query.php

false on failure and mysqli_query() will return a mysqli_result object on success

See ( for the result objects definition ) http://php.net/manual/en/class.mysqli-result.php

Now as others mentioned I would never just concatenate user data into your query. Imagine a hacker knows the name of a valid table, not hard considering your sending it through the request. All they would have to do is send a value like this:

$value = 'real_table; DROP DATABASE';

And your query becomes.

$sql = "SELECT * FROM real_table; DROP DATABASE";

I won't say that this would actually work as there are ( maybe ) some restrictions on running multiple queries in a single request,user permissions etc... That might save your bacon, but I certainly wouldn't risk it.

So you have 2 choices.

  • Use a white list of tables
  • Query the DB for the schema

The first one is easy to do, make a list of tables

$whitelist = [
  'table1',
  'table2'
];

Then compare your user input

$safeTable = false;
if( false !== ($index = array_search($table, $whitelist))) {
    $safeTable = $whitelist[$index];
}else{
   //log error and
   exit();
}

 // SQL query
$sql = "SELECT * FROM $safeTable";
$result = $conn->query($sql);

For the second one,

$schema = $conn->query('SELECT `TABLE_NAME` FROM `information_schema`.`TABLES` WHERE `TABLE_SCHEMA` LIKE "database"');

$whitelist = [];
while ($row = $result->fetch_assoc()) {
    $whitelist[] = $row['TABLE_NAME'];
}

$safeTable = false;
if( false !== ($index = array_search($table, $whitelist))) {
    $safeTable = $whitelist[$index];
}else{
   //log error and
   exit();
}

 // SQL query
$sql = "SELECT * FROM $safeTable";
$result = $conn->query($sql);

This will return a list of all the tables in that database, from which you can build an array and then compare. The nice thing about the second one is that if you add a table then you don't have to change the code, which may or may not be a good thing. You have to have a user with permission to read from information_schema database. And you have to do an additional query.

-note- I am not directly using the users input, I'm using their input to find my data. It's less prone to breaking when there is a coder error. Consider this:

 ///all my codes are broken;
--if(!in_array($_GET['table'], $whitelist))) {
--  //log error and
--   exit();
--}

 // SQL query
$sql = "SELECT * FROM {$_GET['table']}";
$result = $conn->query($sql);

Against this:

$safeTable = false;
 // all my codes are broken
-- if( false !== ($index = array_search($_GET['table'], $whitelist))) {
--     $safeTable = $whitelist[$index];
-- }else{
--    //log error and
--    exit();
-- }

 // SQL query
$sql = "SELECT * FROM $safeTable"; //$safeTable is undefined or false;
$result = $conn->query($sql);

Were using our code for inclusion, instead of exclusion. So if it breaks, it's never included. The other way, if it breaks it's never excluded. Which is not a situation we want to be even remotely possible.

I hope that helps you understand some of the pitfalls. The #1 rule for SQL (or anything on the web), is Never Trust the User. Never put their data into your SQL.

ArtisticPhoenix
  • 20,683
  • 2
  • 18
  • 34