4

if I try to run the following PHP code, I get a

Call to a member function fetch() on a non-object.

Do you know why? I use the same code on another site, where it works just fine.

<?php
$username = ($_GET ['user']);
try {
    $dbh = new PDO("mysql:host=localhost;dbname=***", '***', '***');    
} catch (PDOException $e) {
    echo $e->getMessage();
}
$sth = $dbh->query( "SELECT user, captcha 
    FROM xf_captcha WHERE user='$username'" );
print_r($sth->fetch());
?>

Edit:

$sth = $dbh->query( "SELECT username, user_state, last_activity, alerts_unread, conversations_unread, message_count 
    FROM xf_user WHERE username='$user'" );
$row = $sth->fetch();

Edit2:

Does this look safe, should I do more ?

<?php
$username = ($_GET ['user']);
try {
    $dbh = new PDO("mysql:host=localhost;dbname=***", '***', '***');
} catch (PDOException $e) {
    echo $e->getMessage();
}
$sth = $dbh->prepare("SELECT username, captcha, timestamp 
    FROM xf_captcha 
    WHERE username = :username", array(PDO::ATTR_CURSOR => PDO::CURSOR_FWDONLY));
$sth->execute(array(':username' => $username));
print_r($sth->fetch());
?>
dhh
  • 4,289
  • 8
  • 42
  • 59
user2693017
  • 1,750
  • 6
  • 24
  • 50
  • 1
    Ugh. PDO, yet inlined variables. Parameterise a prepared statement, for heavens sake! – eggyal Sep 26 '13 at 01:07
  • 2
    You should surround `$sth = $dbh->query(...)` in a try catch block as well, you have no guarantee that it will succeed. This is likely the problem, either that - or its possible that it completes and returns NULL. – Zack Newsham Sep 26 '13 at 01:07
  • than, why is the edited code working ? – user2693017 Sep 26 '13 at 01:10
  • I would suggest that you take a read of [this answer I wrote](http://stackoverflow.com/questions/12859942/why-shouldnt-i-use-mysql-functions-in-php/18872292#18872292) which will show you why @eggyal has froth around his lips, and why your code is dangerously insecure :) – Fluffeh Sep 26 '13 at 01:15
  • @Fluffeh: what do you mean with dangerously, the get ? which is post in the real script ? It is get here to make the testing easier. – user2693017 Sep 27 '13 at 05:47
  • @user2693017 This `WHERE user='$username'` section of code allows a user to enter in data that could be insecure. Take a [read of this answer I wrote](http://stackoverflow.com/questions/12859942/why-shouldnt-i-use-mysql-functions-in-php/18872292#18872292) which shows you just how trivial it is to bypass seemingly safe verifications - and how to secure it properly against that sort of attack. – Fluffeh Sep 27 '13 at 05:59
  • @Fluffeh isn´t "PDO::ATTR_EMULATE_PREPARES, false" enough ? – user2693017 Sep 27 '13 at 07:30
  • @user2693017 I made an edit to my answer - and seriously, if that isn't enough to make you say "Oh SH...." or something along those lines, there is something rather wrong. – Fluffeh Sep 27 '13 at 09:03
  • @user2693017 I made another edit, added some more malicious code off the top of my head. I seriously hope it is enough to scare the bejezus out of you, because that's how damned easy it is to get past your code at the moment. – Fluffeh Sep 27 '13 at 09:26
  • @Fluffeh Am I understanding this one wrong: http://stackoverflow.com/questions/134099/are-pdo-prepared-statements-sufficient-to-prevent-sql-injection/12202218#12202218 The wrapping up section at the bottom of the post. I thought one of these is enough. – user2693017 Sep 27 '13 at 17:18
  • so I should use "prepared" and "execute" ? – user2693017 Sep 27 '13 at 17:40
  • @user2693017 Yes, you should use prepared statements and you should execute, but make sure that you pass the data as params rather than putting them directly into the statement you prepare. Have a look at the [bottom of my answer here](http://stackoverflow.com/questions/12859942/why-shouldnt-i-use-mysql-functions-in-php/18872292#18872292) to see how I pass variables to SQL using PDO. – Fluffeh Sep 27 '13 at 21:49
  • @Fluffeh thanks for your help, don´t give up showing us noobs how serious this is. – user2693017 Sep 28 '13 at 12:23
  • @Fluffeh Hey, I updated my question, could you please tell me if this is now secure ? Should I do anything more to make it more secure ? (the get will switch to post) And thank you so much for your support. – user2693017 Sep 28 '13 at 16:13
  • @user2693017 As small a change as that may seem, yes the code is now pretty damn secure :) – Fluffeh Sep 28 '13 at 23:19

3 Answers3

3

Your code has the variable $username in the top part of your question, but you then have $user in the bottom section.

Are you perhaps meaning to use the same variable?

$username = ($_GET ['user']);
$sth = $dbh->query( "SELECT username, user_state, last_activity, alerts_unread, conversations_unread, message_count 
  FROM xf_user WHERE username='$user'" );
  //                           ^^ Should this ALSO be $username ?   
$row = $sth->fetch();

Edit: Okay, now you are just being cute with your PDO::ATTR_EMULATE_PREPARES. Observe this:

Database and table structure:

Database changed
mysql> show tables
    -> ;
+----------------+
| Tables_in_prep |
+----------------+
| users          |
+----------------+
1 row in set (0.00 sec)

mysql> select * from users;
+----+---------+--------+
| id | userid  | pass   |
+----+---------+--------+
|  1 | Fluffeh | mypass |
+----+---------+--------+
1 row in set (0.00 sec)

And some PHP code that is copied from yours, with the added PDO attribute:

<?php
    //$username = ($_GET ['user']);
    $username="Fluffeh";

    $dbh = new PDO('mysql:host=localhost;dbname=prep', 'prepared', 'example');
    $dbh->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_OBJ);

    $sth = $dbh->query( "SELECT userid, pass FROM users WHERE userid='$username'" );
    echo "Trying to use $username.\n";
    print_r($sth->fetch());
    echo "----------------------------------------\n\n";
?>

<?php
    //$username = ($_GET ['user']);
    $username="user2693017";

    $dbh = new PDO('mysql:host=localhost;dbname=prep', 'prepared', 'example');
    $dbh->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_OBJ);

    $sth = $dbh->query( "SELECT userid, pass FROM users WHERE userid='$username'" );
    echo "Trying to use $username.\n";
    print_r($sth->fetch());
    echo "----------------------------------------\n\n";
?>

<?php
    //$username = ($_GET ['user']);
    $username="Oh my' or 1=1 or 'm=m";

    $dbh = new PDO('mysql:host=localhost;dbname=prep', 'prepared', 'example');
    $dbh->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_OBJ);

    $sth = $dbh->query( "SELECT userid, pass FROM users WHERE userid='$username'" );
    echo "Trying to use $username.\n";
    print_r($sth->fetch());
    echo "----------------------------------------\n\n";
?>

<?php
    //$username = ($_GET ['user']);
    $username="(select id from users limit 1)";

    $dbh = new PDO('mysql:host=localhost;dbname=prep', 'prepared', 'example');
    $dbh->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_OBJ);

    $sth = $dbh->query( "SELECT userid, pass FROM users WHERE id='$username'" );
    echo "Trying to use $username.\n";
    print_r($sth->fetch());
    echo "----------------------------------------\n\n";
?>

<?php
    //$username = ($_GET ['user']);
    // Changed this one to be a non-string, you might be checking an ID instead.
    $username="(select id from users limit 1)";

    $dbh = new PDO('mysql:host=localhost;dbname=prep', 'prepared', 'example');
    $dbh->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_OBJ);

    $sth = $dbh->query( "SELECT userid, pass FROM users WHERE id=$username" );
    echo "Trying to use $username.\n";
    print_r($sth->fetch());
    echo "----------------------------------------\n\n";
?>

<?php
    //$username = ($_GET ['user']);
    $username="bob'; drop table users; \  
    ";
    // This one is tricker to do in PHP code. I could easily enter this into a text field however.

    $dbh = new PDO('mysql:host=localhost;dbname=prep', 'prepared', 'example');
    $dbh->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_OBJ);

    //$sth = $dbh->query( "SELECT userid, pass FROM users WHERE id='$username'" );
    echo "Trying to use $username.\n";
    print_r($sth->fetch());
    echo "----------------------------------------\n\n";
?>

And the output:

    Trying to use Fluffeh.
stdClass Object
(
    [userid] => Fluffeh
    [pass] => mypass
)
----------------------------------------


    Trying to use user2693017.
----------------------------------------


    Trying to use Oh my' or 1=1 or 'm=m.
stdClass Object
(
    [userid] => Fluffeh
    [pass] => mypass
)
----------------------------------------


    Trying to use (select id from users limit 1).
----------------------------------------


    Trying to use (select id from users limit 1).
stdClass Object
(
    [userid] => Fluffeh
    [pass] => mypass
)
----------------------------------------


    Trying to use bob'; drop table users; \  
        .
----------------------------------------

Oh, the reason I left the last one till LAST is this output now in my database:

mysql> show tables;
Empty set (0.00 sec)

Yes, that's right, I just dropped a table. Let me say that again, I had a select statement, and with a little trickery I entered in a value that ANYONE with half a brain and some malicious intent could do into a text field, and DROPPED YOUR TABLE.

Now, granted, if you are setting things up properly, you might well set up a different user for the select statements, and only grant them select rights from your database, to stop this sort of thing happening - but lets be honest... you aren't are you?

Clearly setting that emulation is not enough. Seriously, now PLEASE do go read that answer, use prepared statements and use params if you want to be secure in your code.

Community
  • 1
  • 1
Fluffeh
  • 33,228
  • 16
  • 67
  • 80
  • @user2693017 Now please, link this answer to your friends who you see have code that is similar to your original code. Tell them why they need to write GOOD code, so that I haven't spent all this time putting this together just to help one person. Don't get me wrong, even if only you improved, it would be worth it, but lets pass it around some more :) – Fluffeh Sep 27 '13 at 09:30
  • Would you please take all the try-catch blocks out from your code? – Your Common Sense Sep 27 '13 at 09:43
  • @YourCommonSense What do you mean? Remove them for readability? I had them in there as I simply copied the code from the OP and added the emulation. – Fluffeh Sep 27 '13 at 09:44
  • To make this code sane and secure. **Especially** if you ask someone to distribute the link among friends. – Your Common Sense Sep 27 '13 at 09:46
  • @YourCommonSense Yeah, no probs. I haven't really used try catch since.... Goodness, VBScript I think :) Also, I wasn't suggesting that they use THIS code, but rather am showing just how vulnerable it is. The link I gave uses nice prepared statements with params passed properly. – Fluffeh Sep 27 '13 at 09:47
  • I don't have objections for retelling in the story of poor Bobby Tables for thousandth time, but please make your code at least bearable and secure. – Your Common Sense Sep 27 '13 at 09:48
  • 1
    Well, it might be thousandth time for you and me, but there are folks out there who simply haven't heard it before. I was taking the time to put that together to help them out hopefully. – Fluffeh Sep 27 '13 at 09:51
0

Where is your Execute statement being called? Is this also inside your ->query ? If not think of using the following for also a better query build up.:

<?php
            $username = ($_GET['user']);
            try {
                $dbh = new PDO("mysql:host=localhost;dbname=***", '***', '***');    
            } catch (PDOException $e) {
                echo $e->getMessage();
            }
            $statement = "SELECT user, captcha FROM xf_captcha WHERE user=:username";
            //If you have query as a method(Which I don't think so but if you can change "prepare" to your "query"
            $sth = $dbh->prepare($statement);
            $sth->execute(array(":username" => $username));
            $row = $sth->fetch(PDO::FETCH_ASSOC);
    ?>

In the execute parentheses you can use an array to fill the parameter :username for the variable $username

I think looking into PDO Class examples might also be good for a better understanding of PDO and methods(You can also refer to the PHP PDO Manual)

Orion
  • 227
  • 1
  • 4
  • 14
-1

It happens when the table doesn't exist also. make sure it actually exists, and isn't just a holder in the database due to hard drive errors.

When that happens I suggest you recreate the database/table if phpMyAdmin gets an error when trying to read it "manually".

a coder
  • 546
  • 4
  • 23
  • what's wrong with the answer? – a coder Jun 18 '15 at 18:53
  • You're describing a single reason why it can fail - and not actually explaining why a table not existing causes it. Compare to this answer - which explains the theory... http://stackoverflow.com/questions/3349612/pdo-call-to-a-member-function-fetch-on-a-non-object "I suggest you recreate the database/table.", trying stuff at random is not a good strategy. – Danack Jun 18 '15 at 18:58
  • Can you either add to my explanation, or correct your view? – a coder Jun 18 '15 at 19:07