News for hackers. Stuff that matters.
Re:Finding an authorization bypass on my own website by the_gipsy on Saturday March 05, 2022 @06:29PM (#30573080)
The root mistake is taking in user input without any checking.

It's not the author's fault, it's JavaScript that leads this way. Even worse is TypeScript, as its compile-time checking adds a false sense of security.

Re:Finding an authorization bypass on my own website by hn_throwaway_99 on Saturday March 05, 2022 @06:57PM (#30573310)
I disagree. I would qualify this as a clear bug in mysqljs.

First off, most other DB drivers will use real prepared statements, so what is passed down to the DB is actually the templated query and a set of values. It looks like mysqljs actually parses and interpolates the string before sending it to the DB engine.

Perhaps more importantly, though, this bug is basically the exact type of bug that caused the log4shell fiasco: trying to be too "smart" with user provided values that should just be treated as "dumb" scalars. The fundamental way of filling out template parameters with something that can refer to DB column names is a major flaw IMO.

Re:Finding an authorization bypass on my own website by progforlyfe on Saturday March 05, 2022 @07:05PM (#30573376)
Bingo. Any time software is made to try parsing & escaping SQL queries before sending to the server is going to find out the hard way why that is risky.

With prepared statements it's like saying to the DB, here's the query: SELECT * FROM users WHERE email = ? AND password = ?. And here's the data: ["whatever", "whatever"]. You can literally send whatever your heart desires for those two strings, unescaped, to the DB server and let them deal with it.

Re:Finding an authorization bypass on my own website by galaxyLogic on Saturday March 05, 2022 @07:34PM (#30573616)
Prepared Statements are great. Is there any way to force the programmer to use (only) them?
Re:Finding an authorization bypass on my own website by singron on Saturday March 05, 2022 @08:52PM (#30574202)
From the DB, no. E.g. you could always have `query("... a = ? and b = "+b, [a])`.

From within a language, you somehow need to restrict how you build up the query. E.g. a query builder or ORM usually makes it hard to use anything besides parameters, but they usually have escape hatches (e.g. a where method that takes arbitrary sql strings).

One trick you can do in Go is to create a type like `type sqlLiteral string`. The type is private, so other packages can't directly instantiate it, but a string literal will automatically coerce to it. Basically, you can't compute sqlLiteral from arbitrary expressions. It must be a single hardcoded string literal.

Re:Finding an authorization bypass on my own website by nicoburns on Saturday March 05, 2022 @08:59PM (#30574251)
No, because constants and column names are of course legitimate parts of SQL queries (even those using prepared statements), and because queries with no parameters are also legitimate.
Re:Finding an authorization bypass on my own website by namibj on Saturday March 05, 2022 @10:57PM (#30574900)
Code review and static analysis work great, by allowing obviously-safe dynamic SQL where static SQL is insufficient.
Re:Finding an authorization bypass on my own website by Merad on Saturday March 05, 2022 @07:18PM (#30573485)
Both are true. When the request input is blatantly the wrong type like this, you shouldn't even be attempting to execute the sql query. But of course JavaScript encourages developers to be lazy and not even think about such things.
Re:Finding an authorization bypass on my own website by hn_throwaway_99 on Saturday March 05, 2022 @07:51PM (#30573776)
Developers shouldn't have to think about these things. If I'm doing any sort of string interpolation, whether it be in a SQL statement like this or in something like a log or print formatter, I should be able to put whatever the f I want into the value, without checking it. That's the whole point of using interpolated statements like this in the first place: it should be safe to run (but may throw something like a type error in the DB) regardless of what is stuffed in the value.

This is solely a mysqljs bug.

Re:Finding an authorization bypass on my own website by Merad on Saturday March 05, 2022 @08:29PM (#30574036)
If you use any strongly typed language with a halfway decent framework you _don't_ have to think about this because the request will automatically be validated and rejected for invalid input before your code is ever hit.
Re:Finding an authorization bypass on my own website by nicoburns on Saturday March 05, 2022 @09:04PM (#30574295)
That’s also true of a decent JS library. The problem here being that the library wasn’t decent.

A library in a strongly typed language would still need to use parameterised queries or escape the strings just like a JS library. And it would be just as easy or difficult to have an incorrect sanitising function.

In fact, the specified string is not invalid input, it would be perfectly valid to store backticks in a string field in MySQL. They just need to be correctly escaped before being submitted to the database.

Re:Finding an authorization bypass on my own website by hn_throwaway_99 on Saturday March 05, 2022 @09:42PM (#30574517)
That's not really true. Pretty much every DB driver I know, in every language, will take prepared statement values that are any valid DB datatype: strings, ints, decimals, etc., and, importantly, JSON values because most DBs now support JSON. Many JS DB frameworks will take any JS object and convert it to a JSON string because that's obviously a trivial operation (it is "JavaScript Object Notation" after all).

Again, the only bug here is the completely incorrect serialization that mysqljs does.

Re:Finding an authorization bypass on my own website by Merad on Saturday March 05, 2022 @11:04PM (#30574934)
You're missing my point because you're focused on the database query. I'm talking about the web application framework. If this was written in Asp.Net, for example, the password field would be declared as a string and the data binding code within Asp.Net would validate that the field was in fact a string as part of instantiating a model for the strongly typed request body. If someone passed in an object for that field your code for the endpoint, including all of the database interaction, would never be called because the framework would automatically return a 400 response when data binding failed.
Re:Finding an authorization bypass on my own website by thinkharderdev on Sunday March 06, 2022 @06:31AM (#30576788)
I don't think it would actually be possible to use proper prepared statements for a "feature" like this. The whole point is that query is built dynamically from any JSON object. So you could, strictly speaking, build a prepared statement dynamically but you would have the exact same bug.
Re:Finding an authorization bypass on my own website by zamalek on Saturday March 05, 2022 @09:38PM (#30574490)
Nope. Primarily, this stance is hostile to international users. We have all the tools required to transparently move data from the front-end to the backend, and back to the UI. Furthermore, simply because my password begins with '{', contains '{', or is 4367 characters long shouldn't be a concern whatsoever.

Sanitization is an ignorantly dangerous practice, because it obscures much safer practices.

Validation? That's for business rules. No more order items than there are things in stock, and stuff like that. If you need to do that because something in your stack needs it, then it's far past the time to reconsider your stack.

Re:Finding an authorization bypass on my own website by ovex on Saturday March 05, 2022 @07:31PM (#30573594)
The problem is not only that types are not checked but that strings are simply replaced in structured input. Without having looked in detail, I would bet that the implementation also fails at queries which contain question marks inside strings, i.e. question marks that are not placeholders. Even if string escaping was the right way, which it is not, the proper way to do it would be to translate the SQL statement into an AST and then replace the leaves of the tree that are placeholders with the respective escaped strings.
Re:Finding an authorization bypass on my own website by the_gipsy on Sunday March 06, 2022 @03:22AM (#30575958)
> Perhaps more importantly, though, this bug is basically the exact type of bug that caused the log4shell fiasco: trying to be too "smart" with user provided values that should just be treated as "dumb" scalars.

Which is exactly the equivalent of using JSON.parse(). You will find more library combinations, and for many of them the options can't be considered bugs like you do for mysqljs here, and you definitely won't be able to catch it.

The solution is parsing beyond JSON.parse(). You can't have arbitrary user provided structures floating around your program.

Re:Finding an authorization bypass on my own website by ovex on Saturday March 05, 2022 @07:04PM (#30573370)
I'd argue that the root mistake is that the server-side feature of prepared statements through the client/server binary protocol is not used as described in [1]. Instead, the question marks are replaced using a dirty hack. The whole point of prepared statements is that the client/server protocol takes care of inserting the values into the statement instead of having to fiddle with functions like mysql_real_escape_string.

[1]: https://dev.mysql.com/doc/refman/8.0/en/sql-prepared-stateme...

Re:Finding an authorization bypass on my own website by evanelias on Saturday March 05, 2022 @07:39PM (#30573658)
This is actually not unusual though in practice. Large-scale applications quite frequently do client-side (application-side) handling of bind variables -- including many large websites/apps that you use, I can absolutely guarantee this from personal experience.

The reason is that using a real prepared statement in MySQL (as well as most other DBMS) requires an extra round-trip, which adds latency. It also potentially adds complexity if a proxy/middleware layer is in use, since prepared statements are per-connection, at least in MySQL.

The core problem in this specific case is that this js mysql client library simply did not implement client-side interpolation correctly or securely. This is quite bad; I'm not aware of a similar problem in any other major language's most popular mysql driver in recent years.

Re:Finding an authorization bypass on my own website by ovex on Saturday March 05, 2022 @08:05PM (#30573871)
Interesting. I would still insist on not doing it that way, especially when writing a library for universal use. First of all, string replacement on structured input is an immediate red flag. Second, even if you get handling the structured input right by parsing the statement into an AST and replacing the leaves that are placeholders with the escaped strings, there are still potential vulnerabilities. For example, mysql_real_escape_string is still vulnerable when being used with some exotic character sets [1].

I don't know the internals of the (binary) protocol used for communication with the MySQL server though. Couldn't one just save the extra round-trip with length-prefixed strings by sending the query together with the parameters in a single message?

[1]: http://www.gosecure.it/blog/art/483/sec/mysql_escape_string-...

Re:Finding an authorization bypass on my own website by bgrainger on Saturday March 05, 2022 @10:27PM (#30574759)
> Couldn't one just save the extra round-trip with length-prefixed strings by sending the query together with the parameters in a single message?

No, this wouldn't work, because you have to send COM_STMT_PREPARE (https://dev.mysql.com/doc/internals/en/com-stmt-prepare.html) first, which takes the SQL and returns a "statement ID". Then you can send COM_STMT_EXECUTE (https://dev.mysql.com/doc/internals/en/com-stmt-execute.html) which contains the statement ID and the parameters. Finally, you would ideally send COM_STMT_CLOSE (https://dev.mysql.com/doc/internals/en/com-stmt-close.html) to free the server-side resources for the prepared statement, although this could be "pipelined" with the EXECUTE packet.

Re:Finding an authorization bypass on my own website by evanelias on Saturday March 05, 2022 @09:23PM (#30574413)
> mysql_real_escape_string is still vulnerable when being used with some exotic character sets

Indeed -- mysql_real_escape_string "mostly" fixes this problem by requiring a db connection as one of its args. Since the driver is usually aware of the connection state, mysql_real_escape_string can check to see if one of those exotic charsets is in-use. The issue is that there are multiple ways to change the connection charset, some of which the driver is aware of (e.g. in PHP mysqli set_charset) but some it is not (running textual statements like SET NAMES or SET CHARACTER SET).

However, generally an attacker won't have the ability to set an arbitrary exotic character set for the connection anyway... unless they already have some other sql injection mechanism, in which case it's a moot point :)

Driver documentation also typically mentions this problem. For example, here's the doc for doing client-side param interpolation in the most popular MySQL driver for Golang: https://github.com/go-sql-driver/mysql#interpolateparams (see warning in italics)

It also explicitly detects if your initial connection settings attempt to use one of those charsets along with param interpolation, and throws an error if so: https://github.com/go-sql-driver/mysql/blob/21f789cd/dsn.go#...

> Couldn't one just save the extra round-trip with length-prefixed strings by sending the query together with the parameters in a single message?

AFAIK, no, not with the traditional MySQL binary protocol. The newer "X protocol" introduced in MySQL 5.7 does allow this, but it is not widely implemented in drivers.

Re:Finding an authorization bypass on my own website by nicoburns on Saturday March 05, 2022 @09:10PM (#30574335)
I actually think it’s fine to do string sanitising here. The string formats for the databases are well specified, and writing a function to that spec shouldn’t be difficult. I believe there’s no need to parse to AST for postgres as you can just wrap the string in quotes, replace any existing quotes in the string with two quotes and you’re done. If other databases have funkier string formats then you may have a point.

at least that’s true for Postgres, I can’t speak for MySQL.

Re:Finding an authorization bypass on my own website by vlovich123 on Saturday March 05, 2022 @09:26PM (#30574436)
I’m kind of shocked that there’s an extra round trip.

One would expect you can do the preparation lazily as there shouldn’t even be a hypothetical performance consideration for using prepared statements.

In other words you send an execute prepared statement request, you can provide the truly prepared statement OR the templated sql string. In the latter case the server would send you the results of preparing the statement before sending you the result of execution so that you can provide it on subsequent requests.

Or use an RPC system that supports promise pipelining so that you could send the prepare request and the execute is sent with the promise of the prepare request.

Re:Finding an authorization bypass on my own website by evanelias on Saturday March 05, 2022 @09:38PM (#30574491)
I think the core mistake in a lot of these older protocols is conflating the concept of a prepared statement with the concept of a parameterized query.

With a prepared statement, there's an implication that you want to re-use it multiple times, so the DB needs to give you back some sort of handle/identifier on it. But this requires bookkeeping on both the client and server side, and that overhead is often not worthwhile on modern CPUs, especially when running fairly simple CRUD-style queries which are quick to parse. (and at least in modern versions of MySQL, the DB will track a "digest" of the query which removes params anyway, even if you used client-side param interpolation!)

So ideally DB binary protocols should offer less expensive ways of doing parameters for "fire and forget" queries, instead of having to track real prepared statements, but often they don't.

MySQL's newer X Protocol provides a way to avoid the extra round-trip, but it isn't very widely used yet AFAIK, and it still involves prepared statement bookkeeping: https://dev.mysql.com/doc/internals/en/x-protocol-use-cases-...

Re:Finding an authorization bypass on my own website by vlovich123 on Saturday March 05, 2022 @10:31PM (#30574789)
Yes, you should also be able to do a stand-alone parametrized query. The only DB system I’ve seriously programmed against was SQLite where this isn’t an issue.
Re:Finding an authorization bypass on my own website by namibj on Saturday March 05, 2022 @11:02PM (#30574924)
PostgreSQL Extended Query Protocol won't cause the round trip issue; you can even send multiple parametrized queries back-to-back without any RTT penalties. This requires consideration in the API, though, because errors are delayed until the end of such a sequence, loosing attribution as to which individual query caused the error (at least at a low-level protocol level).

Not that this is a problem for a well-designed library, of course. E.g. tokio-postgres in Rust does it if you use a promise-combinator for the first poll of multiple queries inside the same transaction.

Re:Finding an authorization bypass on my own website by thinkharderdev on Sunday March 06, 2022 @06:38AM (#30576830)
The root mistake is that the actual feature makes using real prepared statements impossible. The whole point of a prepared statement is that you know the query AST ahead of time so you can just plug user input into the AST. I'm guessing since this seems to be intentionally designed this way, the goal is to allow a "parameterized query" like "SELECT * FROM foos WHERE ?" and I can pass { "foo": 1, "bar": 2 } to get "SELECT * FROM foos WHERE foo = 1 AND bar = 2"