36C3 preroll music
Herald Angel: On my left, I have Omer
Gull, and Omer Gull is gonna tell us - A
SELECT code_execution FROM * USING SQLite.
Omer, Omer Gull, that's your talk, your stage, have fun!
Omer Gull: Thank you. Thank you. Hello,
everyone. Welcome to my talk, SELECT code
execution from just about anything using
SQLite, where we will gain code execution
using malicious SQLite databases. So my
name is Omer Gull. I'm a vulnerability
researcher from Tel Aviv. I've been
working in Check Point Research for the
past three years, and I've recently moved
on to a new startup called Hunters.AI.
Our agenda for today: So we'll start with a
little motivation and the backstory for
this research. Then we'll have a brief
SQLite introduction, and we'll examine the
attack surface given to a malicious
database. Then we will discuss some
previous work done in the field of SQLite
exploitation and think about exploiting
memory corruption bugs, using nothing but
pure SQL. We'll then demonstrate our own
innovative technique, called "Query
Oriented Programing", or QOP, and take it
for a spin in a couple of demos. We'll
wrap things up with some future work
possibilities and some conclusion. So the
motivation for this research is quite
obvious. SQLite is one of the most
deployed pieces of software out there.
Whether it's PHP 5, PHP 7, Android, iOS,
Mac OS, it is now built into Windows 10.
It's in Firefox and Chrome. This list
could continue forever. Yet querying an
SQLite database is considered safe.
Hopefully, by the end of this talk, you
will realize why this is not necessarily
the case. So, it all began with password
stealers, which is pretty strange, and
there are many, many password stealers in
the wild, but the story is usually the
same. First of all, a computer gets
infected. Then some malware collects the
storage credentials, as they are
maintained by various clients. Now, some
of these clients actually store your
secrets within SQLite databases. So the
malware just ships these SQLite databases
to its C2 server, where the secrets are
extracted and stored within a collective
database with the rest of the loot. So,
one day, my colleague and I, Omri, we were
looking at the leaked sources of a very
well known password stealers. Then we
thought to ourself: "These guys are just
harvesting a bunch of our databases and
parse them in their own backend. Can we
actually leverage the load and query of an
untrusted database to our advantage?" And
if we could, this could have much bigger
implications, just because SQLite is used
in countless scenarios. And so began the
longest CTF challenge of my life so far.
So, SQLite. Unlike most SQL databases,
SQLite does not have that client server
architecture. Instead, it simply reads and
write files directly to the file system.
So, you have one complete database, with
multiple tables, and indices, and
triggers, and views, and everything is
contained within the single file. So let's
examine the attack surface given to a
potentially malicious SQLite database. So
again, this is a snippet of code, of a
very well known password stealer, and we
have two main points of interest here:
First of all, we have sqlite3_open, where
our potentially malicious database is
loaded, and some parsing is going on. And,
obviously, we have the query itself,
right? The SELECT statement. Now, do note
that we have no control over that
statement, right. It is hardcoded within
our target. It tries to extract the
secrets out of our database. Yet we do
control the content, so we might have some
effect on what's going on there, right.
So, starting with the first point, the
sqlite3_open. This is just a bunch of
setup and configuration code. Then we move
on to really straightforward header
parsing, and the header itself is not that
long, just 100 bytes. And thirdly, it was
already fuzzed to death by AFL. So it's
probably not a very promising path to
pursue. But the SEL- the sqlite3_query
might be a bit more interesting, because
using SQLite author's words, "The SELECT
statement is the most complicated command
in the SQL language". Now, you might be
aware that behind the scenes, SQLite is a
virtual machine. So every query must first
be compiled to some proprietary bytecode.
And this is also known as the preparation
step. So sqlite3_prepare would walk and
expand the query. So, for example, every
time you select an asterisk, it simply
rewrite this asterisk as all column names.
So, sqlite3LocateTable will actually
verified that all the relevant objects
that you are querying actually exist, and
will locate them in memory. Where does it
locate them? So, every SQLite database has
a table called sqlite_master, and this is
actually the schema that is defining the
database. And this is its structure. So,
for every object in the database, you have
an entry, so its type, like whether it's a
table or view, and its name, and at the
very bottom you can see something called
SQL. And SQL is actually the DDL that is
describing the object. And DDL stands for
data definition language, and you can sort
of look at it like header files in C. So,
they are used to define the structures,
and names, and types of the objects within
the database. Furthermore, they appear in
plaintext within the file. So, let me show
you an example. Here I opened the SQLite
interpreter, I create a table, and I
insert some values into it. Then I quit
the interpreter, and now I hex dump the
file that was created. And you can see,
highlighted in yellow, the DDL statement
that is part of the master schema. And at
the very bottom, you can also see the
values. So, let's go back to query
preparation. We have sqlite3LocateTable
that attempts to find the structure that
is describing the table that we are
interested in querying. So it goes on and
reads the schema available in
sqlite_master that we just described. And
if it's the first time that it is doing
so, it has some callback function for
every of these DDL statements. The
callback function would actually validate
the DDL, and then it will go on and build
the internal structures of the object in
question. So then we thought about the
concept of DDL patching. What if I simply
replace the SQL query within the DDL? So
it turns out that there is a slight
problem with it. And this is the callback
function that I mentioned earlier, and as
you can tell, the DDL is first verified to
begin with "CREATE ". And only if it does
then we continue with the preparation. So,
this is definitely a constraint, right?
Our DDL must begin with "CREATE ". Yet it
does leave some room for flexibility,
because judging by SQLite documentation,
many things can be created. We can create
indexes, and tables, and triggers, and
views, and something we still don't quite
understand, called virtual tables. So,
then we thought about "CREATE VIEW",
because view is simply a pre-packaged
SELECT statement, and views are queried
very similarly to tables. So, selecting a
column out of a table is semantically
equivalent to selecting a column out of a
view. Then when we thought about the
concept of query hijacking. We are going
to patch sqlite_master DDL with views
instead of tables. Now our patched views
can actually have any SELECT subquery that
we wish. And now with this subquery I can
suddenly interact with the SQLite
interpreter. And this is a huge step
forward! We just turned an uncontrollable
query to something that we have some
control over. So let me show you query
hijacking by example. So let's say that
some original database had a single table,
and this is the DDL that is defining it.
So it's called "dummy" and it has two
columns. So, obviously any target software
would try to query it in the following
way: It would just try to select these
columns out of the table, right. Yet the
following view can actually hijack this
query. I create a view, it has just the
same name, and just the same amount of
columns, and each column is named just the
same way, and you can see that now every
column can have any sub query that I wish,
highlighted in blue at the bottom. So
again, let me show you a practical example
of it. Here, I created a view called
"dummy" with cola and colb, and the first
column is utilizing the sqlite_version()
function, and that's a built in function
that simply returns the SQLite version,
obviously. The second column is utilizing
SQLite's own implementation of printf.
That's right, they have all these really
surprising features and capabilities. So,
let's see that from the supposedly - from
the target side. So, anyone trying to
select out of these column, is suddenly
executing our functions. So, at the left
you can see the SQLite version, and on the
right you can see the printf that was
executed on the target side. So again,
this is a huge step forward. We just
gained some control over that query,
right. And the question is, what can we do
with this control? Does SQLite have any
system commands? Can maybe - maybe we can
read and write to some other files on the
file system. So, this was a good point to
stop and look at some previous work done
in the field, because obviously, we are
not the first to notice SQLite's huge
potential, in terms of exploitation. A
reasonable place to start is SQLite
injection, because this is sort of a
similar scenario, right? Someone malicious
has some control on an SQL query. So,
there are a couple of known tricks in SQL
injection with SQLite. The first one has
something to do with attaching another
database, and then creating a table, and
inserting some strings into it. And
because, as I mentioned earlier, every -
every database is just a file, so this is
somewhat of an arbitrary file, right, on
the file system. Yet we do have this
constraint, if you remember, that we can't
ATTACH, because our DDL must begin with
"CREATE". Another cool trick is abusing
the load extension function. And here you
can see how you can potentially load a
remote DLL. In this case, the
meterpreter.dll, but obviously this very
dangerous function is disabled by default.
So again, no go. What about memory
corruption in SQLite, because SQLite is
really complex and it's all written in C.
So in his amazing blog post "Finding Bugs
in SQLite, the easy way", Michal Zalewski,
the author of AFL, described how he found
22 bugs in just under 30 minutes of
fuzzing. And actually, since then, since
that was version 3.8.10, that was in 2015,
SQLite actually started using AFL as an
integral part of the remarkable test
suite. Yet these memory corruption bugs
all proved to be really difficult to
exploit without some convenient
environment. Yet, the security research
community soon found the perfect target,
and it was called WebSQL. So, WebSQL is
essentially an API for storing data in
databases, and it is queried from
JavaScript and it has an SQLite backend.
Also, it is available in Chrome and
Safari. So here you can see a very simple
example of how to interact with WebSQL
from JavaScript. But in other words, what
I'm hearing here is that we have some
untrusted input to SQLite and it is
reachable from any website on the Internet
in two of the world's most popular
browsers. And suddenly these bugs, these
memory corruptions can now be leveraged
with the knowledge and - with the
knowledge and comfort of JavaScript
exploitation, right. The JavaScript
interpreter exploitation that we got, we
got pretty good over the years. So there
have been really several really impressive
research that were published regarding
WebSQL from really low hanging fruits like
CVE-2015-7036, that was an untrusted
pointer dereference in the fts3_tokenizer(),
to some more complex exploit as presented
in Blackhat 2017 by the awesome Chaitin
team, that found a type confusion in the
FTS optimizer, to the very recent Magellan
bugs found and exploited by Tencent, that
found an integer overflow in the FTS
segment reader. And if you are paying even
a tiny bit of attention by now, you must
see an interesting pattern arises. All
these vulnerable functions start with FTS.
So what is FTS? I have never heard of it.
And actually googling it just left me more
confused. After some time, I came to the
realization that FTS stands for "full text
search", and it is something called a
virtual table module and it allows for
some really cool textual search on a set
of documents. Or like the SQLite authors
described it, It's "like Google for your
SQlite databases". So virtual tables allow
for some pretty cool functionality in
SQLite, whether it's this free text search
or a virtual table module called RTREE,
that does some really clever geographical
indexing, or a virtual table called CSV,
that lets you treat your database as a CSV
file. And these virtual tables are
actually queried just like regular tables.
Yet behind the scenes, some dark magic
happens. And after every query, there is
some callback function that is invoked and
it works on something called shadow
tables. Now, shadow tables would be best
explained by example. So let's say that I
create a virtual table using that FTS
virtual table module and I insert a string
into it. Now, obviously, to allow for some
efficient search, I need to have some
metadata, right? I need to have some
offsets or indexes or tokens or stuff like
that. So - and obviously they're all text,
right? So that one virtual table is
actually, it's raw text, and metadata is
stored among three shadow tables. So the
raw text would go to vt_content and the
metadata would go to vt_segments and
vt_segdir. And in time, these shadow
tables actually have interfaces passing
information between them, right. Because
the metadata is storing all these
pointers, so you need to pass them between
each other. And these interfaces proved to
be really, really trusting in their
nature. And it makes them a really fertile
ground for bug hunting. So let me show you
a bug that I found in the RTREE virtual
table module. So, RTREE virtual table
module is available now in MacOS and iOS,
and it's really cool because now it's also
built into Windows 10. And as I've
mentioned, it does some really clever
geographical indexing. Now, the DDL is
supposed to be the following. Any RTREE
virtual table is supposed to begin with
"id", that needs to be an integer. Then
you have some X and Y coordinates. So
obviously every RTREE interface would
expect "id" to be an integer. But if I
create a virtual table and I insert into
"id" something that is definitely not an
integer, then I use one of these RTREE
interfaces, rtreenode at the very bottom,
you see that we got this crash, this out-
of-bounds read on the heap. And this
example is a crash in Windows 10. So
that's pretty good. We have established
that virtual table has bugs. And now,
using query hijacking technique, we can
suddenly trigger these bugs on our target,
which is a C2 of the password stealer, and
will cause it to segfault. And this is
nice, but actually gaining flow control
over our target requires us to have some
form of scripting, right? We want to
bypass ASLR and do all these crazy things.
Yet we don't have JavaScript, we don't
have JavaScript arrays and variables and,
like, logic statements, like if and and
loops and stuff like that. However, we do
vaguely recall hearing somewhere that SQL
is turing complete. So we decided to put
it to the test from exploitation
perspective, and we started creating our
own primitive wish list for exploitation.
So if it would create a full exploit
exploiting memory corruption bugs with
nothing but SQL, what capabilities do we
want? So, obviously, to bypass ASLR and
these kind of things, we want to leak some
memory. We need to have an info leak. And
if you've done any pwning in your past,
you must be familiar with really common
tasks like unpacking 64-bit pointers and
doing some pointer arithmetics, right?
Because we had an info leak, we converted
- we read this pointer, and it's a little
endian and so we need to flip it, and now
we want to calculate, let's say where's
the base of libsqlite so we can find some
more functions maybe. So we need some
pointer arithmetics. Obviously, after
reading pointers and manipulating them, we
want to pack them again and write them
somewhere. Obviously, writing a single
pointer is never enough. We want to create
fake objects in memory, like more complex
objects than this one pointer. And
finally, we would like to heap spray
because this might be really useful. So,
the question remains, can all this
exploitation be done with nothing but SQL?
So, the answer is "yes, it is". And I
proudly present to you Query Oriented
Programing, or QOP. And to demonstrate
QOP, we are going to exploit the unfixed
CVE-2015-7036. And you might ask yourself
"What? How come a four year old bug is
still unfixed?" And this is a great point
to our argument. This CVE was only ever
considered dangerous in the context of
untrusted WebSQL. So it was mitigated
accordingly, right. It is blacklisted
unless SQLite is compiled with is certain
flag. So, obviously browsers are not
compiled with this flag anymore. But let
me show you who is compiled with this
flag. So, we have PHP 5 and PHP 7, in
charge on most of the Internet, and iOS
and MacOS and probably so many other
targets that we just didn't have the time
to go over. So, let's explain this
vulnerability a little bit. I've mentioned
that it's in the FTS tokenizer. So, a
tokenizer is just a set of rules to
extract terms from documents or queries.
And the default tokanizer, that is named
"simple", just split the strings by
whitespaces. However, if you like, you can
register your own custom tokenizer. You
can just pass a C function and you
actually register this custom tokenizer
with the function fts3_tokenizer() in an
SQL query. This is a bit where it's all
repeated slowly. You pass a row pointer
to a C function in an SQL query. This is
absolutely insane. To be honest, after
studying this for quite a while, I still
don't understand how to use this feature
outside of my exploit.
audience laughing
clapping
So fts3_tokenizer()
is actually an overloaded function, and if
you call it with one argument that is the
name of a tokenizer, you get back the
address of that tokenizer, and to make it
a bit more human readable, or somewhat
human, we'll use the hex decoder. And you
can now see that we actually got an info
leak to libsqlite3. Now, because it's
little endian, so it's the other way
around, so we need to reverse it, but this
is already pretty cool. If you call
fts3_tokenizer() with two arguments, the
first one being a name of a tokenizer, and
the second one, again, is a row pointer,
this is absolutely insane, you rewrite the
address of that tokenizer. So now,
whenever someone will try to use a virtual
table, so it will instantiate our default
tokenizer, right. It will crash and burn.
And this is pretty amazing. Let's have a
short recap. So, we've established that
SQLite is a wonderful one-shot for many
targets, right? It's absolutely
everywhere. And it is a complex machine
that is written in C. Now, with query
hijacking, we can start triggering these
bugs, and we aim to write a full exploit,
implementing all necessary primitives
using SQL queries. Our exploitation game
plan is as follows: We will leak some
pointers, and then we'll calculate some
function addresses. We'll then create a
fake tokenizer object with some pointer to
system(). We will override the default
tokenizer and trigger our malicious
tokenizer. Then something will happen, and
obviously by the end of the process we
should be able to profit somehow, right?
So, starting with memory leak, an info
leak to libsqlite. So, you already know
how to do it, right? We've seen
fts3_tokenizer(), but we still have a tiny
problem of the little-endian pointer. So
we need to flip it. Now, surely we can use
the SUBSTR function and read this pointer
two characters at a time in a reverse
fashion, and then simply concatenate
everything throughout the pointer. So, we
get a SELECT query that looks the
following, but now we have our pointer.
This is great. What about an info leak to
the heap? We want to know where the heap
is located. So, to do that trick, I'm
going to do something pretty similar to
the RTREE bug that we've found. So, again,
I'm going to confuse some shadow table
interface. So, we created a virtual table
and inserted some values into it. And now
we're about to confuse the match
interface. So, the match interface does
many things, but behind the scenes, it
just finds - it serves the pointer in
memory to where the text is located,
right. It's this metadata, cool things
that virtual table has. So we're going to
confuse it, and instead of passing it to
another virtual table interface, we'll
simply pass it to the hex decoder, so we
will decode this raw pointer. And you can
see that, again in little-endian, but now
we have a link to the heap. We can cross
that off the list. And before we go on to
unpacking this pointer, we have a very
basic problem. How do we even save these
things? Because unlike browser WebSQL, we
don't have JavaScript variables or arrays
to use and then abuse them later, but we
need to create some complex logic, right?
We need to calculate the function address
and create things in memory, but how can
we do it? Obviously, with SQLite, when you
want to save some values, you need to have
insert statements, but we can only create
tables and views and index and triggers.
Then we thought about chaining this view
together to use them sort of like a
pseudo-variable. So again, let me show you
an example. Here, I create a view, it's
called "little-endian leak", right? And
again, I abuse the fts3_tokenizer()
function. Now I create another view on top
of it, and this one is called "leak", and
it's flipping it using the SUBSTR trick
that you know from before. But notice how
I referred to the first view, I referred
to "little-endian leak". So again, I do it
throughout the pointer, and eventually
what I have is a pseudo-variable that's
called "leak". And when I select from it,
I get the expected result. So now we can
really move forward. Now we can start
building some more complex things based on
this logic. And now we can go to unpacking
the pointers. So, we want to calculate a
base of an image, for example, or maybe
find the beginning of the heap. So first
of all, we want to convert our pointers to
integers. So, again, we're going to start
and read these pointers one character at a
time and in reverse fashion using SUBSTR.
And to get the value of this hex
character, we're going to use INSTR, that
is just like strchar, and using the
following string we'll get the value of
the hex character. Now, because it is one-
based, you have on the right the minus
one. Then I need to have some shifting,
like, dark magic, and then I go and just
concatenate everything throughout the
pointer. So the result is this monster
query. But eventually, when all of this is
done, I get an integer that is the
unpacked version of our initial leak. So I
successfully unpacked this pointer and we
now have integers at hand, so we can cross
that off the list as well. We know how to
convert pointers to integers. Now, pointer
arithmetics, right, because we want to
have the addresses of some functions in
memory and actually, with integer, this is
super simple. All we need to do is use
some more sub-queries. So, on the left you
can see that I'm referring to the, now,
pseudo-variables that we have, the
unpacked leak, and on the right I can
either have, like, subtract a silly
constant like I did here, or I can
actually use another pseudo-variable to
make it a bit more dynamic, can make some
a bit more reliable. So, eventually, what
I'm ending up with is the libsqlite base
in an integer form. So, we've read some
pointers and we manipulated them. Now it's
a good time to write them back. And
obviously we're all used to "char" being
the exact opposite of "hex". And you can
see that it works fairly well on most of
the values, but bigger integers were
actually translated to their two-byte
code-points. So this was a huge obstacle
for us. So, after bashing our head against
the documentation for quite a while, we
suddenly had the strangest epiphany. We
realized that our exploit is actually a
database. And if I want any conversion to
take place, I can simply create, ahead of
time, this key-value map and simply query
it to translate whatever value I want to
another value with sub-queries. So this is
the python function that I've used and you
can see that there's a very simple for
loop going from 0 to FF and just inserting
values to a table called "hex_map" with
its key-map value. And now our conversions
are using sub-queries. So again, let me
show you by example. You can see that I'm
selecting "val" from hex map, this key-
value map, where "int" is equal to, and
then I go and then doing some more like
shifting and modulo dark magic, but
eventually, what I'm ending up with is a
packed version of our libsqlite base. Now
it's, we have a packed little-endian
pointer, so we can cross that off the list
as well. As I've mentioned, writing a
single pointer is definitely useful, but
it's not enough. We want to be faking
complete objects, right? All the cool kids
are doing it and it's a pretty powerful
primitive. And if you actually recall, we
have to do it because fts3_tokenizer()
requires us to assign a tokenizer module.
Now, what is a tokenizer module? How does
it look? So, this is the beginning of its
structure and there is an "iVersion",
that's an integer at the beginning, we
don't really care about it, but following
it are three function pointers. We have
"xCreate", which is the constructor of the
tokenizer, and "xDestroy", which is the
destructor. We need to have both of them
valid so we don't crash during our
exploitation. The third function pointer
is really interesting, because this is
what actually tokenizes the string. So we
have a function pointer that we are
passing a controllable string into. This
would be a perfect place to put our system
gadget, right. So by now, I've used a fair
share of my SQL knowledge, right. But I do
have one more trick up my sleeve and
that's JOIN queries, right? Because I
learned about it at a time in the past. So
we're now going to create a fake tokenizer
view and we're going to concatenate a
bunch of "A"'s and then, using a JOIN
query, we'll concatenate it with a pointer
to simple_create and a pointer to
simple_destroy and then a bunch of "B"'s.
Now let's verify it from a low-level
debugger and you can actually see that at
some place in memory, we have a bunch of
"A"'s followed by a pointer to
simple_create, followed by a pointer to
simple_destroy, and a bunch of "B"'s. So,
we're almost done. But we need one more
primitive for this exploit. And this is
because we already have our malicious
tokenizer, and we know where the heap is
located, but we are not quite sure where
our tokenizer is. So this is a great time
for some heap spraying, right. And ideally
this would be some repetitive form of our
fake object primitive. So we've thought
about repeat, but sadly SQLite did not
implement it like mySQL. So, like anyone
else, we went to Stack Overflow, and we
found this really elegant solution. So
we're going to use the zeroblob function
that simply returns a blob of N zeros. And
then we will replace each of that zeros
with our fake tokenizer. And we're going
to do it ten thousand times, as you can
see above. Again, let's verifiy it with a
debugger. So, you see a bunch of "A"'s and
it's kind of hard to see, because these
are really bad colors, but we also got
perfect consistency, because these
structures repeat themselves every 20 hex
bytes. So we created a pretty good heap
spraying capabilities and we are done with
our exploitation primitive wish list, so
we can go back to our initial target.
Again, this is the code snippet of a very
well known password stealer. And at the
bottom, you can see that it's trying to
SELECT to extract the secrets by selecting
a column called "BodyRich" from a table
called "Notes". So, we're going to prepare
a little surprise for him, right? We're
going to create a view that is called
"Notes". And it has three sub-queries in a
column called "BodyRich". And each of
these sub-queries is actually a QOP chain
on its own. So if you remember my
exploitation game plan, we're going to
start with heap spray, and then we will
override the default tokenizer, and then
we will trigger our malicious tokenizer.
And you might ask yourself, what is
heap_spray? Obviously, heap_spray is a QOP
chain that utilizes our heap spraying
capabilities, right. We are spraying ten
thousand instances of our fake tokenizer,
that is a JOIN query of a bunch of "A"'s
and then some pointers like
p64_simple_create. And the party goes on,
because p64_ simple_create is actually
derived from u64_simple_create, right,
with our pointer-packing capabilities. And
this is just turtles all the way down,
because you have u64_simple_create, that
is derived from libsqlite_base plus some
constant. And this goes back to the
unpacked leak version, right, minus some
constant. So again, we're using our
pointer arithmetics capabilities. We can
continue with u64_leak being derived from
the almost initial leak. And we'll wrap up
by showing how the leak is actually
derived from the initial vulnerability
using fts3_tokenizer. And this was just
one out of three QOP chains that we used
in this exploit. And by now, every time
that I described this exploit, this is how
I must look. And to be honest, this is how
I feel. But luckily for you guys, you
don't have to look and feel like me
because we created QOP.py, and it is
available on Checkpoint Research GitHub.
And suddenly these crazy long chains can
now be created with four easy lines of
python. And it feels like pwntools if you're
familiar with it, so you can go ahead and
play with it and not look crazy on stage.
So, we'll go to our first demo. We'll own
a password stealer backend running the
latest PHP 7. So obviously that's a model
that we created with the leaked sources
and you can see all the infected victims,
right. Cool. Now we'll try to go to our
webshell, to p.php. Obviously it still
does not exist, we get a 404. Moving to
the attacker's computer. So, we see that
we have two scripts here. First, we're
going to use QOP.py, that will generate a
malicious database. Let's see, the
database was created. Now we're going to
emulate an infection. We're going to send
our malicious database to the C2 server as
if we were infected by a password stealer.
And because this process takes a bit of
time, we can look at all the cool DDL
statements, right. So you see that we
started with some bin leak and heap leak
and then we unpacked them. And at the very
bottom, you can see that our end gadget is
echoing the simplest webshell to p.php,
right. And this is the same page that we
just tried to reach. So hopefully, yeah -
great, it's done. Now we go back to the
password stealer backend. We go to p.php
and we got 200. Now, let's execute some
code on it. whoami. www-data. And
obviously we need to go for /etc/password.
Yay.
applause
So, what just happened is that we've shown
that, given just the query to our
malicious a database, we can execute code
on the querying process. Now, given the
fact that SQLite is so popular, this
really opens up the door to a wide range
of attacks. Let's explore another use case
that is completely different. And our next
target is going to be iOS persistency. So,
iOS uses SQLite extensively, and
persistency is really hard to achieve,
because all executable files must be
signed. Yet, SQLite databases are not
signed, right, they're data-only. There is
no need to sign them. And iOS and MacOS
are both compiled with
ENABLE_FTS3_TOKENIZER. That's the
dangerous compile-time flag. So, my plan
is to regain code execution after reboot
by replacing an arbitrary SQLite DB. And
to do this, I'm going to target that
contacts database, and its name is
"AddressBook.sqlite". So these are two
tables in the original database. They have
no significant meaning, right. Just for
example. And I'm going to create malicious
contacts DB, and I'm going to start with
two malicious DDL statements that you're
already familiar by now. First of all,
we'll override the default organizer
"simple" to a bunch of "A"'s. Then, our
second DDL statement would actually
instantiate this malicious trigger, so it
will actually crash the program, right,
because every time you create a virtual
table module, someone is trying to go to
the constructor of the tokenizer. So then
it will crash. Now, what I'm doing next is
I'm going to go over each and every of the
original table and rewrite them using our
query hijacking technique, right. So,
instead of the columns that it expected,
we are going to redirect the execution to
the override statement and the crash
statement. So, we did it for one table, we
also do it for the others. Now we reboot
and voila, we get the following CVE and
secure boots was bypassed. And if you pay
close attention, this is really cool,
because we see that the crash happened at
0x414141...49. And this is exactly what we
expected to happen, right, because this is
where our constructor should be, at an
offset of eight after the first integer of
the version. But actually, there is more.
We get a bonus here. Because the contacts
DB is actually used by many, many
different processes. So Contacts and
Facetime and Springboard and WhatsApp and
Telegram and XPCProxy and so many others.
And a lot of these processes are way more
privileged than others. And we've
established that we can now execute code
on the process that queries our malicious
database. So we also got a privilege
escalation in this process, right. And
this is really cool. And there's nothing
special about the contacts database.
Actually, any shared database can be used.
All we need is something to be writeable
by a weak user and then being queried by a
stronger user, right. And obviously all
these techniques and bugs were reported to
Apple and they're all fixed. And these are
the CVEs if you want to go and read about
it later. Now, to wrap things up, if
you'll take anything away from this talk,
I don't want it to be the crazy SQL
gymnastics or a bunch of CVE numbers. I
want it to be the following. Querying it
database might not be safe, whether if
it's across reboots or between users or
processes, querying a database might not
be safe with query hijacking. And now,
with query hijacking and query oriented
programing, these memory corruptions that
we can trigger can actually be reliably
exploited with nothing but SQL. We don't
need JavaScript anymore. We don't need
WebSQL. And we truly think that this is
just the tip of the iceberg. So far,
SQLite, super popular, yet it was only
assessed from the very narrow lands of
WebSQL, and as much as browser pwning is
exciting, SQLite has so much more
potential. So we do have some thoughts
about possible future work with this.
Obviously, something really cool to do
would be expanding our primitives to some
stronger primitives, right. We want to
gain things like absolute read and write.
And my sketchy POC exploit was pretty
silly because it had many constants in it,
like you've seen, but actually if you used
the internal function of SQLite, like, if
during the exploitation you use functions
like sqlite3_version(), you ask the
interpreter, what version are you, what
compile options where you compiled with,
you can dynamically build these QOP chains
as you go and actually target the specific
target environment that you are
exploiting. Like, actually utilizing the
fact that our exploit is a database, we
can create this really cool exploit
polyglot, and we think that these
techniques can be used to privilege
escalate so many situations, because the
developers never had it in mind that now,
we can take any database that is writeable
by a weak user and queried by a strong
user, and suddenly we can try to privilege
our escalation. Another cool thing to do
would be to note that many of the
primitives that I've shown you are not
exclusive to SQLite, right?
The pointer packing and unpacking and
heap spraying, and all these things are
not exclusive to SQLite. So it would be
really interesting to take these
primitives and see if we can go ahead and
exploit other memory corruption bugs in
different database engines.
Thank you very much.
Applause
Herald Angel: Omer Gull, thank you very
much. That gives us a lot of time for
questions. So we do have three microphones
here in the hall: Number one, Number two and
Number three, if you have questions,
please line up. Do we have some questions
from the net already? No. All right. Then
we're gonna start with microphone number
two.
Microphone two: Yeah. So the question is
regarding the hijacking of the CREATE
something. So you mentioned that at the
beginning of the research was assuming
that CREATE was the first order of
checking and then a space following the
CREATE word and then it could create all
of other things. Now, my question is, if
that was changed following your report,
because this seems like a way to expose
larger attack surface then. Well, most of
the other bugs. So I just wonder if they
changed. And I mean, what basically what
was the mitigation if and if that was all
of it?
Omer Gull: Yeah. So the escalate people,
we're really more concerned with specific
bugs and not exploitation techniques. And
this is really sad because we all know
that you can kill bugs, but exploitation
techniques is what sticks. So no, they
didn't change this verification. And
actually this validation of create space
was actually added not so long ago. Before
that, you can have any DDL statement
that you want. So the situation is not
really good over there.
Microphone two: Good for them and good
luck in the future. And that was the
question.
Herald Angel: All right. We head over to
microphone one, please.
Microphone one: Did you, maybe by
accident, attack a server which was used
for password stealing stealing?
Omer Gull: No, obviously, I would never do
that. I would never attack anyone. This is
just in our lab on POC. Right.
Microphone one: Thank you.
Omer Gull: Your passwords are safe with
the Stealers.
laughter
Herald Angel: Right. Nobody is queueing
anymore. Do we have questions from the
net? Nothing over there. Well, here we go.
Omer Gull: Thank you.
Herald Angel: Omer Gull, thank you very much.
applause
36c3 outro music
subtitles created by c3subtitles.de
in the year 2019. Join, and help us!