-
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!