3

I just saw in my webstats that someone appended a lot of SQL code to one url parameter. The URLs look like this:

http://www.example.com/page.php?id=672%3f%20and%28select%201%20from%28select%20count%28*%29%2cconcat%28%28select%20%28select%20concat%280x7e%2c0x27%2cunhex%28hex%28cast%28database%28%29%20as%20char%29%29%29%2c0x27%2c0x7e%29%29%20from%20%60information_schema%60.tables%20limit%200%2c1%29%2cfloor%28rand%280%29*2%29%29x%20from%20%60information_schema%60.tables%20group%20by%20x%29a%29%20and%201%3d1

http://www.example.com/page.php?id=convert%28int%2cdb_name%28%29%29--

http://www.example.com/page.php?id=999999.9%20union%20all%20select%200x31303235343830303536%2c0x31303235343830303536%2c0x31303235343830303536%2c0x31303235343830303536%2c0x31303235343830303536%2c0x31303235343830303536%2c0x31303235343830303536%2c0x31303235343830303536%2c0x31303235343830303536%2c0x31303235343830303536%2c0x31303235343830303536%2c0x31303235343830303536%2c0x31303235343830303536%2c0x31303235343830303536%2c0x31303235343830303536%2c0x31303235343830303536%2c0x31303235343830303536%2c0x31303235343830303536%2c0x31303235343830303536%2c0x31303235343830303536%2c0x31303235343830303536%2c0x31303235343830303536--

and some more...

My code looks like this:

$myid = intval($_GET['id']);
$stmt = $con->prepare("SELECT *
FROM mytable AS r
WHERE r.ID =:ID");
$stmt->bindValue(':ID', $myid, PDO::PARAM_INT);

My questions are: Is my code secure? And how can I check what the result of these queries was? I mean my page only echos the variables I asked for. But the attacker of course wants to see the things he/she queried for.

Gumbo
  • 643,351
  • 109
  • 780
  • 844
user1204121
  • 385
  • 1
  • 3
  • 15
  • 1
    Since you use prepared statements, it is secure – bksi Nov 02 '13 at 12:38
  • Whether your code is secure depends on what `$con` and `$stmt` are and what `$con->prepare()` and `$stmt->bindValue()` do. – Oswald Nov 02 '13 at 12:38
  • Well $stmt is defined above. $con is this: $con = new PDO( DB_DSN, DB_USERNAME, DB_PASSWORD ); – user1204121 Nov 02 '13 at 12:40
  • How did he/she know your table names? –  Nov 02 '13 at 12:46
  • @Houssni Where can you see the table names? – user1204121 Nov 02 '13 at 12:47
  • @user1204121 Decode that HTML URL after the `?id=` part and you can see the whole query he attempted to execute –  Nov 02 '13 at 12:52
  • @Houssni I can't see any table names apart from "information_schema" - and that is standard on a MySQL server I guess?! Can you see other table names? – user1204121 Nov 02 '13 at 13:07
  • @user1204121 Ah right of course! I forgot that, for the rest of the part I can clearly see he has no knowledge about your database further. –  Nov 02 '13 at 13:34

3 Answers3

3

It is secure. In a prepared statement, the parameter value is never actually interpolated into the query string. The query is sent to the database server before the parameters. Thus, no chance of an injection. In your example:

Sending to the database server:

$stmt = $con->prepare("SELECT * FROM mytable AS r WHERE r.ID =:ID");

Sending the parameter(s) to the database server:

$stmt->bindValue(':ID', $myid, PDO::PARAM_INT);

This is unless you're using emulated prepared statements. To enable prepared statements:

$con = new PDO( DB_DSN, DB_USERNAME, DB_PASSWORD );
$con->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
Wayne Whitty
  • 19,513
  • 7
  • 44
  • 66
  • 1
    To clarify: even with emulated prepared statements you're still supposedly safe, unless something is going really wrong in the emulation. – deceze Nov 02 '13 at 12:44
  • Thanks, but what do you mean by "This is UNLESS you're using emulated prepared statements." What would be the difference? – user1204121 Nov 02 '13 at 12:44
  • 2
    @user1204121 PDO uses emulated prepared statements by default. This means that your query isn't really being sent to the server beforehand. You can change this by setting PDO::ATTR_EMULATE_PREPARES to false (as shown in my updated code). – Wayne Whitty Nov 02 '13 at 12:47
  • @WayneWhitty Ok, thanks! You mean this would make it even more save? – user1204121 Nov 02 '13 at 12:48
  • 2
    @user1204121 I'd always recommend using prepared statements that are native to the DBMS. It will also allow you to use exceptions with PDO::prepare (if you have exceptions enabled). – Wayne Whitty Nov 02 '13 at 12:50
  • Most of your arguments are quite pointless. emulation mode is as safe as native one. And exception thrown on execute is as informative as on prepare. – Your Common Sense Nov 02 '13 at 14:09
  • @YourCommonSense Never said that prepare exceptions were more informative. However, I'd still rather not send extra data at the database server if I can catch an exception on prepare. Plus, the native prepared statements give you the benefit of being able to execute the same prepared statement multiple times with different values. With emulated prepares, you're sending the same query to be executed each time, with the drawback being that the DBMS won't know its the same query. – Wayne Whitty Nov 02 '13 at 20:10
  • @YourCommonSense You're kind of losing sight. With one line of code, you can guard against that 1:10000000000000 chance (I reckon the chances are lower than that, but hey, its not like any of us has any actual metrics here). Also, multiple execution is far more widespread than you're letting on, and I think that you're being a little disingenuous here. You've been on StackOverflow long enough to know that many people will use queries inside loops if it means that it "gets the job done" without having to read up on joins etc. – Wayne Whitty Nov 02 '13 at 20:25
  • I am losing nothing. "sending extra data to database" **which would be sent there anyway** if no exception raised, is just a fictional reason you invented right out of nowhere. It is not something extraordinary. Sending data to db is rather most ordinary thing in the world. So, there is nothing to be afraid of THAT much. – Your Common Sense Nov 02 '13 at 20:31
  • @YourCommonSense I like the way you're purposely ignoring my point about multiple execution, as if it isn't a common thing. Also, no it wouldn't be sent there anyway, if a prepared statement fails, either because of a syntax issue being pushed live or because a high load is causing interruptions. All in all, all I see are benefits in using the native prepares. – Wayne Whitty Nov 02 '13 at 20:48
  • No doubt you see them. The only problem they are all imaginary ones. But for some reason you just can't follow very simple logic. Ok, I'll try once more, the last try. We have two cases. "extra" data is sent to DB and it is not. In most cases "extra" data is sent. Just because everything goes all right. So, nothing to worry about at all. In such extremely rare case when we have an error in query, in emulation mode we have data sent. The point is, **IT IS NOT BEING ANY PROBLEM AT ALL** It is not something like too big a load. No. It is regular data that have to be sent anyway. – Your Common Sense Nov 02 '13 at 20:57
  • @YourCommonSense You need to be a little less salty. Basically, I have positives for using native prepares, whereas you have absolutely no positives for using emulated prepares. You've ignored the multiple execution comment, even though we both know that we're writing code in a language that sees **a lot** of it. I mean, really, what is so difficult for you to grasp? Saying that exceptions on prepare are not a rare case just goes to show your lack of real-world experience, where syntax errors DO get pushed to live and things like connection interruptions DO occur, even if they're rare. – Wayne Whitty Nov 02 '13 at 21:09
  • As I said above, exception on execute is as informative as on prepare. So, in the real world you are talking about I will get the same exception even in emulation mode -- no difference. – Your Common Sense Nov 02 '13 at 21:11
  • @YourCommonSense You're trying to counter an argument that I never made. Not once did I say that prepare exceptions were more informative than execute exceptions. You're also repeatedly ignoring the issue of multiple execution, as well as the issues that come hand-in-hand with a high load. You have failed to offer **any** benefit to NOT using native prepared statements. – Wayne Whitty Nov 02 '13 at 21:21
  • All right. Here are your words: "It will also allow you to use exceptions with PDO::prepare". You are welcome to provide an argument, why this is an essential benefit of native mode. – Your Common Sense Nov 02 '13 at 21:24
  • @YourCommonSense And I quote: "You're also repeatedly ignoring the issue of multiple execution, as well as the issues that come hand-in-hand with a high load." – Wayne Whitty Nov 02 '13 at 21:25
  • All right, I take it as you finally abandon that false argument of having exception on prepare at last. Now we can turn to that multiple execution superstition. – Your Common Sense Nov 02 '13 at 21:26
  • 1. You are advocating bad practice of running the same query multiple times. Although it's up to you, I wouldn't call it a "benefit" then, but rather disadvantage as it evidently encourage a bad practice. 2. The difference is not that noticeable, as you imagine. There are no *real* tests showing any difference for the average web-application and **there is not a single complaint** for the emulation mode performance. Actually, it's just yet another buzzword. – Your Common Sense Nov 02 '13 at 21:33
2

You are lifting this log from the wrong end.

It doesn't matter, where the data has come from, be it URL, or JSON object, or a file of whatever.

But it's only destination that matters. So, everything that goes into query via prepared statement is perfectly safe. Just because it's the very purpose of prepared statements.

So, most of your precautions are too redundant and whole code can be just 2 lines

$stmt = $con->prepare("SELECT * FROM mytable WHERE ID = ?");
$row  = $stmt->execute([$_GET['id']])->fetch();
Your Common Sense
  • 156,878
  • 40
  • 214
  • 345
  • 2
    The purpose of prepared statements is to execute a single statement repeatedly with different parameters. That those parameters are passed properly is just a means of proper operation. – Gumbo Nov 02 '13 at 14:46
  • May be it was intended this way at first. But, as it often happens in a real life, a real benefit emerged. So, this useless feature scarcely worth mentioning. – Your Common Sense Nov 02 '13 at 18:26
0

You actually have two lines of defense, which is very good.

The first one is intval(), which will return 0 for non-numerical input.

The second is the actual prepared statement. The one which you're using is good, just keep it that way.

However, just using prepare() does not automatically make your code immune to SQL injection. what you have to do is to prepare a constant SQL string with placeholders. For example, consider code like this:

$stmt = $con->prepare("SELECT * FROM mytable WHERE $cond");  // NEVER DO THIS!

where $cond is a string that may contain arbitrary data, including dangerous one.

(Sometimes, it is useful to be able to dynamically construct part of an SQL query, which is why writing something like the example above can be so tempting. But even in this case you have to create a constant SQL out of hardcoded query parts in your script, adding placeholders for the arbitrary data literals, but it's not a trivial task. Some database abstraction frameworks may provide helper methods to let you do this more easily and safely.)

What actually does make your code immune to SQL injection is to never insert arbitrary data literals into SQL code, but via placeholder only. The very purpose of prepared statements is to allow you to run a query this way, by letting you use placeholders and bind values to them instead. That's what makes them so useful from a security viewpoint.

So, to recap: as long as no arbitrary data literal ever makes it into your SQL statements, you should be safe from SQL injection. In your example, your SQL statement is a constant string (with a PDO placeholder for the arbitrary data), so you're OK.

Your Common Sense
  • 156,878
  • 40
  • 214
  • 345
kravietz
  • 10,667
  • 2
  • 35
  • 27
  • 2
    care to provide an example of such a horribly incorrect manner? – Your Common Sense Nov 02 '13 at 16:02
  • 1
    Typical: inserting a whole, concatenated logical condition as string into parameter of WHERE statement. – kravietz Nov 02 '13 at 17:39
  • So, what's wrong with **immunity** here? – Your Common Sense Nov 02 '13 at 17:51
  • Absolutely nothing, if arbitrary queries run users are not in your risk profile. – kravietz Nov 02 '13 at 18:10
  • 1
    @YourCommonSense: Kravietz is right; it's possible to "use prepared statements" in such a horribly incorrect way that it negates all their supposed security benefits. For example, consider code like `$stmt = $con->prepare("SELECT * FROM mytable WHERE $cond");` where `$cond` includes unsanitized user input. The fact that this is, technically, using prepared statements does nothing to reduce the gaping SQL injection hole here. So no, prepared statements are not "magic dust" that one can just sprinkle over insecure SQL to make it secure; one has to actually understand _how_ to use them properly. – Ilmari Karonen Nov 02 '13 at 18:58
  • @IlmariKaronen you are spupposed to read the conversation before trying to patronize. **Nowhere** Kravietz is right, talking of inserting a string into **parameter** of WHERE statement. And even your artificial example doesn't make an excuse for his wrong statement – Your Common Sense Nov 02 '13 at 19:18
  • 1
    @YourCommonSense: I wasn't trying to be patronizing, and I don't think Kravietz was either. I'm sure _you_ know better than to do something silly like that, but I _have_ seen less experienced developers do exactly that, and then express surprise when they're told that their code is _still_ vulnerable to SQL injection, even though _they are using prepared statements_ just like they were told to. – Ilmari Karonen Nov 03 '13 at 12:13
  • 1
    @YourCommonSense: Anyway, I would suggest that you also try to be less condescending and more careful at reading yourself, and at least _consider_ the possibility that this Kravietz guy might actually know what a prepared statement is, even though they have less rep than you and may not always use perfectly idiomatic English (or use the word "parameter" in exactly the same sense as you take it to mean, whatever that sense is). The statement of his that you edited out was literally correct, as example I gave demonstrated. It would've been even better with an example, but it was still correct. – Ilmari Karonen Nov 03 '13 at 12:23
  • @IlmariKaronen You have to comprehend his whole answer, instead of picking on random phrases. His whole answer advocates "two lines of defense" for the very reason of "prepared statements do not guarantee immunity". This is plainly misleading statement. Whatever reader who come across it, would know that 1. Prepared statement is broken. 2. One have to take some other precautions beside using placeholders. While both statements are wrong. Instead, he had to advocate proper prepared statement usage, like you do. – Your Common Sense Nov 03 '13 at 12:33
  • @IlmariKaronen I appreciate your skill in picking on words, but here you failed. In context of prepared statements, the word "parameter" has certain and unambiguous meaning, and nowhere it's "idiomatic". – Your Common Sense Nov 03 '13 at 12:36
  • 1
    @YourCommonSense: Yes, and Kravietz may have used the word incorrectly in his first comment above. That hardly justifies claiming that he has "no idea what a prepared statement is", given that what he _meant_ was clear enough, and a valid concern. Anyway, I just edited the answer to restore the warning and explain the issue in more detail. (Kravietz: please feel free to revert or modify anything that you disagree with or would prefer to be said differently. It's still your answer, even if it seems to have turned temporarily into a battleground, for which I apologize.) – Ilmari Karonen Nov 03 '13 at 12:50
  • @IlmariKaronen this answer is essentially flawed from the very first words. Having two unreliable lines of defense is worse than having one solid. this answer is misleading and confusing. I am trying my best with correcting your misleading and dangerous statements as well, but to delete this answer would be best solution – Your Common Sense Nov 03 '13 at 12:52
  • @YourCommonSense: No line of defense is ever _perfectly_ solid, since programmers are human and make mistakes (and so are library / platform developers, so even having _your_ code be 100% error free may not always be enough). The best thing, therefore, is to have as many solid lines of defense as you can. In this case, the OP has two: intval() and PDO placeholders. Either one alone should be enough to block the attacks the OP is concerned about; having both is even better. – Ilmari Karonen Nov 03 '13 at 13:00
  • 1
    @IlmariKaronen like many other PHP users you fail with formal logic. Either line of defense is solid, or it shouldn't be used ever. But if it's solid, no other have to be used. And Prepared statements **ought to be solid** because numbers aren't the only data types we have to deal with. – Your Common Sense Nov 03 '13 at 13:04
  • @YourCommonSense: It's called "defense in depth", and is a standard element of defensive programming. Relying on a single line of defense makes for brittle programs; it's safer to have a single solid line of defense _and_ then add extra defensive layers (like intval() here) on top of it. Your argument above only holds if you trust both your own code _and_ your libraries, language, OS and even CPU to never have bugs that could compromise your single line of defense. (And if you trust _PHP_, of all things, never to have built-in security-critical bugs, I've got a bridge to sell you.) – Ilmari Karonen Nov 03 '13 at 13:24
  • 1
    @YourCommonSense: Anyway, I _know_ [you know all this](http://stackoverflow.com/a/8255054) already, so I'm not sure why we're even still arguing about this. Anyway, the version after your edits seems acceptable to me (Kravietz could disagree, of course), even if I might be tempted to make the distinction between "literals" in SQL vs. "literals" in PHP a bit clearer, so I'm going to just butt out of this discussion. In hindsight, we probably should've taken it to chat long ago anyway. – Ilmari Karonen Nov 03 '13 at 13:34
  • @IlmariKaronen go on, sell me a bridge. With complete reproduceable code against my single line of defense. – Your Common Sense Nov 03 '13 at 13:38
  • 1
    @YourCommonSense: If I had a current 0-day exploit for PDO, I'd be reporting it to the devs, not posting it here. But since you asked, a couple of years ago [this](http://stackoverflow.com/a/12202218) would've worked. That's assuming you were using emulated prepares and an obscure character set, that is, but still, it should be enough to demonstrate that such bugs do happen. And it only took me a minute or two of Googling to find. – Ilmari Karonen Nov 03 '13 at 13:56
  • @IlmariKaronen so, you don't have one. That's the problem with all you security theoreticians. Okay, another question. Would your "second line" help against that 2-minute-of-googleing expoit? – Your Common Sense Nov 03 '13 at 14:03
  • 1
    @YourCommonSense: You mean intval()? Yes, it would. – Ilmari Karonen Nov 03 '13 at 14:14
  • @IlmariKaronen nope it wouldn't. Apparently you cannot use intval on string data. – Your Common Sense Nov 03 '13 at 14:16
  • The example Ilmari given is exactly what I was talking about and I have seen tens of such examples during real application code reviews. Not sure what you both mean by "incorrect usage of words" and I'll appreciate if you clarify. As for doubled defense: YCS is living in a perfectly spherical world, where a single perfectly educated developer with perfect memory develops an application. Welcome to real world, where random developers write or refactor different modules at random moments of time, frequently replacing safe code with unsafe one. Been there, seen that. – kravietz Nov 03 '13 at 18:22
  • Ok, after reading the whole thread I seem to understand the problem with naming is. YCS seems to understand "using prepared statements" literally as 1) define template, 2) substitute parameters with, and only with, the designated function. However majority of developers will understand "using prepared statements" as "doing anything with function like prepare()". If you ask them "are you using p.s." they will answer "yes, we are". When you check the code, you'll see they really meant something like Ilmari given or even `prepare("SELECT * FROM table WHERE id=" + _GET['id'])`. – kravietz Nov 03 '13 at 18:38
  • If you care so much for the proper understanding prepared statements usage, you should have explain this very topic, instead of making ambiguous statements like "prepared statement is essentially broken", and promote false theories of 2 lines of defense. – Your Common Sense Nov 03 '13 at 20:21
  • I have **never** written that p.s. is "essentially broken". What I have written before you edited my answer was that just using p.s. does not guarantee security because I know very well how different our understanding of "using p.s." is from that of developers. If you had less of "you have no idea" attitude in the first place, we might have focused on refining that part instead of continuing this pointless "I know better" discussion. – kravietz Nov 03 '13 at 20:40
  • As for "false theories", your example of "perfectly safe" prepared statement won't be such if the query would be slightly more complicated than it is. Adding `intval()` costs nothing, but at least you get guaranteed integer and not anything else at that point: string, object, whatever that the hacker can imagine, and SQL parser will happily insert. It's different story with type-enforcing statements, such as Java's `prep.setInt()`, but it was not in scope here. – kravietz Nov 03 '13 at 20:49
  • Unlike intval, prepared statement offers 100% safety for any data. While intval helps with numbers only. So, it's your idea of two lines is broken, not prepared statement. I have no idea why so much talk for this silly intval, while there is a string hangs around, for which you obviously have no "strval". – Your Common Sense Nov 03 '13 at 21:26
  • Your ideas are rather confusing for a developer. Instead of plain and simple parameterized query you tell him to use whatever else measures for 2-nd line, but it works only for part of data. So, you left him with uneasy feeling - prepared statement do not guarantee immunity, and extra line of defence is required. But there is no line for strings! Stalemate? Unavoidable vulnerability? – Your Common Sense Nov 03 '13 at 21:31
  • Prepared statement is 100% safe **if** and only if it's 100% correctly implemented. If it's not, and this is common, then you're vulnerable. This vulnerability can be exploitable or not. Things like `intval` are what would eventually save your ass here and that's why I recommend them. – kravietz Nov 03 '13 at 21:51
  • What about strings which you cannot intval? – Your Common Sense Nov 03 '13 at 22:21
  • Regex, or whatever you can in given situation. If you cannot, then then double check your p.s., place big warning in comments for future developers and that will be perfectly OK too. – kravietz Nov 03 '13 at 23:43
  • So, all your idea of protection is as reliable as a big warning in comments. I expected something like this. – Your Common Sense Nov 04 '13 at 08:29