Authorizer bypass "vulnerability"

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
4 messages Options
Reply | Threaded
Open this post in threaded view
|

Authorizer bypass "vulnerability"

Matthias-Christian Ott
I'm unsure what the threat and security model of SQLite's authorizer
callback is. I think it would only be an effective authorization
mechanism if the attacker was able to only execute SQL statements on a
database and the database was otherwise not accessible to the attacker.
So I'm not sure whether the following behaviour should be seen as a
vulnerability. Nonetheless, I wanted to point out SQLite's behaviour and
document it.

One of the parameters of the authorizer is the name of the database of
the operation that is to be authorized. The documentation gives you the
impression that "main" and "temp" are two reserved database names and
cannot be changed. However, the name of the main database can be changed
by sqlite3_db_config. So if the name of the main database is relevant to
the authorizer, it must be impossible to change it through
sqlite3_db_config (in the context of the application). The following
code demonstrates the "problem":

#include <assert.h>
#include <stddef.h>
#include <string.h>
#include <sqlite3.h>

int auth(void *p, int op, const char *name1, const char *name2,
       const char *dbname, const char *caller)
{
    return dbname != NULL && strcmp(dbname, "main") == 0 ?
        SQLITE_DENY : SQLITE_OK;
}

int main()
{
    int rc;
    sqlite3 *db;

    rc = sqlite3_open(":memory:", &db);
    assert(rc == SQLITE_OK);
    rc = sqlite3_exec(db, "CREATE TABLE t (i)", NULL, NULL, NULL);
    assert(rc == SQLITE_OK);
    rc = sqlite3_set_authorizer(db, &auth, NULL);
    assert(rc == SQLITE_OK);
    rc = sqlite3_exec(db, "SELECT * FROM t", NULL, NULL, NULL);
    assert(rc == SQLITE_AUTH);
    rc = sqlite3_db_config(db, SQLITE_DBCONFIG_MAINDBNAME, "main2");
    assert(rc == SQLITE_OK);
    rc = sqlite3_exec(db, "SELECT * FROM t", NULL, NULL, NULL);
    assert(rc == SQLITE_OK);
    rc = sqlite3_close(db);
    assert(rc == SQLITE_OK);

    return 0;
}

It seems it is not possible to call sqlite3_db_config from SQL, so the
attacker would need to have access to the address space of the database
connection and the database file to bypass the authorizer.

Perhaps it makes sense to either always pass "main" as the database name
of the main database to the authorizer or to introduce an additional
function that allows to retrieve the main database name, for example
sqlite3_db_config_get.

- Matthias-Christian
_______________________________________________
sqlite-users mailing list
[hidden email]
http://mailinglists.sqlite.org/cgi-bin/mailman/listinfo/sqlite-users
Reply | Threaded
Open this post in threaded view
|

Re: Authorizer bypass "vulnerability"

Richard Hipp-3
On 4/13/17, Matthias-Christian Ott <[hidden email]> wrote:
>
> It seems it is not possible to call sqlite3_db_config from SQL,

Exactly.  And since the programmers who implement the authorizer
callback know whether or not the database name has changed, they know
whether or not to check for "main" or some other name.  I do not
consider this to be a serious issue - certainly not worth adding a new
API or adding a new branch in a critical path that will slow down the
code in the common case.

--
D. Richard Hipp
[hidden email]
_______________________________________________
sqlite-users mailing list
[hidden email]
http://mailinglists.sqlite.org/cgi-bin/mailman/listinfo/sqlite-users
Reply | Threaded
Open this post in threaded view
|

Re: Authorizer bypass "vulnerability"

Keith Medcalf
In reply to this post by Matthias-Christian Ott

On Thursday, 13 April, 2017 06:14, Matthias-Christian Ott <[hidden email]> wrote:

> I'm unsure what the threat and security model of SQLite's authorizer
> callback is. I think it would only be an effective authorization
> mechanism if the attacker was able to only execute SQL statements on a
> database and the database was otherwise not accessible to the attacker.
> So I'm not sure whether the following behaviour should be seen as a
> vulnerability. Nonetheless, I wanted to point out SQLite's behaviour and
> document it.
 
> One of the parameters of the authorizer is the name of the database of
> the operation that is to be authorized. The documentation gives you the
> impression that "main" and "temp" are two reserved database names and
> cannot be changed. However, the name of the main database can be changed
> by sqlite3_db_config. So if the name of the main database is relevant to
> the authorizer, it must be impossible to change it through
> sqlite3_db_config (in the context of the application). The following
> code demonstrates the "problem":
 

> #include <assert.h>
> #include <stddef.h>
> #include <string.h>
> #include <sqlite3.h>
>
> int auth(void *p, int op, const char *name1, const char *name2,
>        const char *dbname, const char *caller)
> {
>     return dbname != NULL && strcmp(dbname, "main") == 0 ?
>         SQLITE_DENY : SQLITE_OK;
> }

The implementation above is what is called "default permit".  This means that everything is permitted except that which has been defined by enumeration to be specifically denied.  This is the model used, for example, by most Antivirus programs.  It requires a complete enumeration of all possible badness in order to work and anything that is not specifically enumerated (such as a new malware variant) cannot be denied until it is added to the enumeration.

The proper model to use is "default deny", in which case everything is denied except for those things which are by enumeration permitted.  This is how most Discretionary Access Control Systems work (Login, Firewalls, etc).  That means that you cannot bypass the denial by becoming something unanticipated.  Whitelisting software, for example, works this way too.

It is possible to use a "hybrid" model.  For example, you would check first to ensure that the database name is one of the ones that you are enumerating.  It not, then deny.  You can then, secure in the knowledge that the remainder of your authorization code is now limited only "enumerated" possibilities, so that if someone changes the database name to something unexpected the default is to deny.

However, in the case of SQLite (and many other authorization systems such as most DRM and copy protection), it is simply sufficient to use software that does not implement the authorizer in order to bypass it.  So in the case of SQLite just using the standard shell compiled without authorization hooks in place is sufficient to do what you will to the database (barring some other outer layer of protection limiting you to using only one specific set of code containing the authorizer functions to access the database file, such as application level encryption with the key being application specific).

The original Lotus 123 version 1a, for example, implemented a call to its authorizer (to make sure it was running from the original diskette) early in the startup code.  You simply replaced the machine code of the call with an XOR A INC A NOP NOP and the protection was bypassed entirely.





_______________________________________________
sqlite-users mailing list
[hidden email]
http://mailinglists.sqlite.org/cgi-bin/mailman/listinfo/sqlite-users
Reply | Threaded
Open this post in threaded view
|

Re: Authorizer bypass "vulnerability"

Richard Hipp-3
On 4/13/17, Keith Medcalf <[hidden email]> wrote:
>
> So in the case of
> SQLite just using the standard shell compiled without authorization hooks in
> place is sufficient to do what you will to the database

Yes.  The sqlite3_set_authorizer() feature is designed to allow a
restricted subset of SQL to be used in web-applications where the
remote user does not have access to the original database.  For
example, in Fossil (https://www.fossil-scm.org/) it is possible to
allow anonymous users to enter SQL to query bug reports.  But we want
to prevent the anonymous users from access sensitive data, such as
user passwords.  Hence:
https://www.fossil-scm.org/fossil/artifact/ee53ffbf762?ln=161-232

Background: The sqlite3_set_authorizer() interface was first added for
CVSTrac (http://www.cvstrac.org).  The Fossil report logic was copied
from CVSTrac.

--
D. Richard Hipp
[hidden email]
_______________________________________________
sqlite-users mailing list
[hidden email]
http://mailinglists.sqlite.org/cgi-bin/mailman/listinfo/sqlite-users