Skip Menu |

This queue is for tickets about the DBD-mysql CPAN distribution.

Report information
The Basics
Id: 126014
Status: open
Priority: 0/
Queue: DBD-mysql

People
Owner: Nobody in particular
Requestors: MLEHMANN [...] cpan.org
Cc: marco [...] nethype.de
pali [...] cpan.org
AdminCc:

Bug Information
Severity: Normal
Broken in: 4.046
Fixed in: (no value)



CC: marco [...] nethype.de
Subject: security issue: placeholders will string-interpolate without quoting, executing parameters as sql
Hi! We currently run into a DBD::mysql where placeholders (?) in prepared statements get replaced with the string value of the parameter without any quoting, leading to parameter values being executed as sql. I don't have a testcase, but looking at the code in dbdimp.C parse_params, it's quite obvious that this is the case under certain conditions. If you follow the logic for case '?', then the parameter value will be interpolated without any quoting when bind_type_guessing is false and the column is numeric - SQL_NUMERIC will cause is_num to be set, nothing will re-set it, and the code will then take the string(!) value of the parameter value and interpolate it as sql commands instead of quoting it. For example, the following code will probably trigger this issue: $st = $dbh->prepare ("delete from users where uid = ?"); $st->bind_param (1, undef, { TYPE => DBI::SQL_NUMERIC }); $st->execute ("uid"); Which would result in: delete from users where uid = uid I haven't tested the code above, but following the logic for case '?' is quite easy and somehow it must be possible to have bind_type_guessing off, while having a numeric type for a parameter. The code is overall quite weirdly inefficient (why does it call parse_number when is_num is false for example), so this is probably the result of some refactoring, having been undetected because bind_param is rarely used. I think this is a security bug because ? placeholders should NEVER EVER treat the bound value as sql code and execute it.
Just FYI, moving the is_num = 0 from after the "if (bind_type_guessing)" to before seems to fix this, although the performance could probably be further improved by calling parse_number only when is_num is true. Beware, though, I haven't looked at what parse_number actually does - there could be further security bugs inside.
On Uto Aug 07 08:42:15 2018, MLEHMANN wrote: Show quoted text
> Hi! > > We currently run into a DBD::mysql where placeholders (?) in prepared > statements get replaced with the string value of the parameter without > any quoting, leading to parameter values being executed as sql. > > I don't have a testcase, but looking at the code in dbdimp.C > parse_params, it's quite obvious that this is the case under certain > conditions. > > If you follow the logic for case '?', then the parameter value will be > interpolated without any quoting when bind_type_guessing is false and > the column is numeric - SQL_NUMERIC will cause is_num to be set, > nothing will re-set it, and the code will then take the string(!) > value of the parameter value and interpolate it as sql commands > instead of quoting it. > > For example, the following code will probably trigger this issue: > > $st = $dbh->prepare ("delete from users where uid = ?"); > $st->bind_param (1, undef, { TYPE => DBI::SQL_NUMERIC }); > $st->execute ("uid"); > > Which would result in: > > delete from users where uid = uid > > I haven't tested the code above, but following the logic for case '?' > is quite easy and somehow it must be possible to have > bind_type_guessing off, while having a numeric type for a parameter. > > The code is overall quite weirdly inefficient (why does it call > parse_number when is_num is false for example), so this is probably > the result of some refactoring, having been undetected because > bind_param is rarely used. > > I think this is a security bug because ? placeholders should NEVER > EVER treat the bound value as sql code and execute it.
Hi! This problem should be already fixed in DBD::MariaDB driver: https://github.com/gooddata/DBD-MariaDB There were fixed also other issues with parsing parameters and placeholders.
On 2018-09-24 08:14:12, PALI wrote: Show quoted text
> On Uto Aug 07 08:42:15 2018, MLEHMANN wrote:
> > I think this is a security bug because ? placeholders should NEVER > > EVER treat the bound value as sql code and execute it.
> > Hi! This problem should be already fixed in DBD::MariaDB driver: > https://github.com/gooddata/DBD-MariaDB
This function is a disaster, I found another buffer overfflow in it, and another likely buffer overflow. First, my patch fixes it for some cases, but there is a special case for LIMIT clauses, which forces is_num to TRUE again. This causes a buffer overflow, as the is_num case depends on "end" being set to the end of the alue, but end is never set in that case, so it points at a semi-random memory location. Moving this case before the parse_number case probably fixes this: /* we're at the end of the query, so any placeholders if */ /* after a LIMIT clause will be numbers and should not be quoted */ if (limit_flag == 1) is_num = TRUE; /* (note this sets *end, which we use if is_num) */ if (is_num && parse_number(valbuf, vallen, &end) != 0) { is_num = 0; /* security fix for https://rt.cpan.org/Ticket/Display.html?id=126014 */ if (bind_type_guessing) { /* .. not a number, so apparently we guessed wrong */ ph->type = SQL_VARCHAR; } } The second problem is memory allocation, which works in ways I honestly don't understand, but if it works, then by chance only. The first part of parse_params calculates the buffer length for the full statement, but it miscalculates the length for strings(!): valbuf= SvPV(ph->value, vallen); alen+= 2+vallen+1; The expression seems to be "2 characters for quotes, one for each value character, than an extra 2 characters" (extra 2, because "?" is replaced, so if it were 2 + vallen - 1, it would be correct. However, the since every character can be escaped later, the correct formular would be "2*vallen + 2 - 1", or in other words "2*vallen + 1", which is almost exactly what is used - so maybe this is just a typo. This might be partially shadowed by the actual allocation: /* Allocate memory, why *2, well, because we have ptr and statement_ptr */ New(908, salloc, alen*2, char); I don't understand why the *2 is there, but since it massively overallocates the buffer, this might be the reason why it rarely causes issues. However, a short statement and a long string vlaue will still overflow memory. These new bugs are much easier to exploit for attackers. I will fix these problems in the perlmulticore-patched DBD::mysql version found at http://perlmulticore.schmorp.de/registry
Full diff at http://perlmulticore.schmorp.de/pkg/DBD-mysql-4.050.diff Relevant excerpt: diff -pur /tmp/orig/DBD-mysql-4.050/dbdimp.c /tmp/multi/DBD-mysql-4.050/dbdimp.c --- orig/DBD-mysql-4.050/dbdimp.c 2019-01-09 07:45:48.000000000 +0100 +++ multi/DBD-mysql-4.050/dbdimp.c 2020-03-12 17:22:40.641860570 +0100 @@ -567,14 +567,15 @@ static char *parse_params( else { valbuf= SvPV(ph->value, vallen); - alen+= 2+vallen+1; + /* schmorp: 2* instead of 2+, security fix for https://rt.cpan.org/Ticket/Display.html?id=126014 */ + alen+= 2*vallen+1; /* this will most likely not happen since line 214 */ /* of mysql.xs hardcodes all types to SQL_VARCHAR */ if (!ph->type) { if (bind_type_guessing) { - valbuf= SvPV(ph->value, vallen); + /*valbuf= SvPV(ph->value, vallen);*/ /* schmorp: commented out, already done above */ ph->type= SQL_INTEGER; if (parse_number(valbuf, vallen, &end) != 0) @@ -747,22 +748,23 @@ static char *parse_params( break; } + /* schmorp: moved before parse_number block, security fix for https://rt.cpan.org/Ticket/Display.html?id=126014 */ + /* we're at the end of the query, so any placeholders if */ + /* after a LIMIT clause will be numbers and should not be quoted */ + if (limit_flag == 1) + is_num = TRUE; + /* (note this sets *end, which we use if is_num) */ - if ( parse_number(valbuf, vallen, &end) != 0 && is_num) + if (is_num && parse_number(valbuf, vallen, &end) != 0) { + is_num = 0; /* schmorp: security fix for https://rt.cpan.org/Ticket/Display.html?id=126014 */ if (bind_type_guessing) { /* .. not a number, so apparently we guessed wrong */ - is_num = 0; ph->type = SQL_VARCHAR; } } - /* we're at the end of the query, so any placeholders if */ - /* after a LIMIT clause will be numbers and should not be quoted */ - if (limit_flag == 1) - is_num = TRUE; - if (!is_num) { *ptr++ = '\'';
Subject: Re: [rt.cpan.org #126014] security issue: placeholders will string-interpolate without quoting, executing parameters as sql
Date: Thu, 12 Mar 2020 17:59:48 +0100
To: bug-DBD-mysql [...] rt.cpan.org
From: pali [...] cpan.org
On Thursday 12 March 2020 11:50:08 Marc_Lehmann via RT wrote: Show quoted text
> <URL: https://rt.cpan.org/Ticket/Display.html?id=126014 > > > On 2018-09-24 08:14:12, PALI wrote:
> > On Uto Aug 07 08:42:15 2018, MLEHMANN wrote:
> > > I think this is a security bug because ? placeholders should NEVER > > > EVER treat the bound value as sql code and execute it.
> > > > Hi! This problem should be already fixed in DBD::MariaDB driver: > > https://github.com/gooddata/DBD-MariaDB
> > This function is a disaster, I found another buffer overfflow in it, and another likely buffer overflow...
Hi! You have quoted and reacted on my DBD::MariaDB message. So do you mean that function in DBD::MariaDB? Because there problems should be already fixed.
On 2020-03-12 13:08:26, PALI wrote: Show quoted text
> Hi! You have quoted and reacted on my DBD::MariaDB message. So do you > mean that function in DBD::MariaDB? Because there problems should be > already fixed.
I haven't looked at DBD::MariaDB, so I don't know if these problems are all fixed there, I only looked at DBD::mysql so far.
On Thu Mar 12 15:09:56 2020, MLEHMANN wrote: Show quoted text
> On 2020-03-12 13:08:26, PALI wrote:
> > Hi! You have quoted and reacted on my DBD::MariaDB message. So do you > > mean that function in DBD::MariaDB? Because there problems should be > > already fixed.
> > I haven't looked at DBD::MariaDB, so I don't know if these problems > are all fixed there, I only looked at DBD::mysql so far.
Ok. In this case, I suggest you to look at DBD::MariaDB, it is fork of DBD::mysql and exactly this problem which you described was already fixed. And I do not think that it make sense to spend time on anaylizing and fixing problem which was already analyzed and fixed. Because this just aim to writing again same / similar patch which already exists.
On 2020-03-12 15:15:50, PALI wrote: Show quoted text
> DBD::mysql and exactly this problem which you described was already > fixed.
The problem is, it's not fixed - this ticket is about DBD::mysql.