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.

Friday, August 14, 2009

Pentester trick #8: command-line sniffing made easy

(Preamble: this post applies to Windows operating system only. Linux has tcpdump, Solaris has snoop, etc.)

Sometimes sniffing the network from a compromised remote target might become handy.

For instance, it proved useful to me in the following pentest cases:
  • Recovering POP/IMAP/SMTP passwords, when classical tools are blocked by antivirus software (use of POPS/IMAPS/SMTPS is still not widespread, especially on enterprise LANs).
  • Gathering HTTP session cookies or even passwords.
But sometimes you have only command-line access to the remote target (through PSEXEC, Metasploit and such).

Getting access to the GUI (through VNC, Remote Desktop or DameWare Mini Remote Control) is not practical, since the targetted user is actively working on the console (there are workarounds for this situation, but I am not going to discuss them right now).

Installing network sniffing software, such as WireShark/Winpcap, is not practical because you have to setup the software (which makes change to the target system configuration) and you might end up in rebooting the system. Not to mention the x64 case, which requires signed drivers (latest x64 Winpcap drivers are signed, though).

A lot of people are pretending to offer "rebootless command line sniffers", but they are often unmaintained proof-of-concept tools, and professional pentesters cannot afford to crash a remote target.

The most reliable and lightweight tool I know is ... the one made by Microsoft, a.k.a. Microsoft Network Monitor. It relies on Windows built-in packet capture features, therefore leaving minimal footprint on the target system. It can run without install. It works on all Microsoft-supported Windows versions, in x86, x64 and even IA64 flavors.

How to use it ?
  1. Download and install Microsoft Network Monitor on a standalone computer.
  2. Upload nmconfig.exe and nmcap.exe on the target computer.
  3. Enable the Microsoft Network Monitor Driver: nmconfig /install
  4. Test: nmcap /displaynetworks
  5. Sniff all TCP traffic on every local interface: nmcap /network * /capture tcp /File tcp.cap
  6. Disable the Microsoft Network Monitor Driver: nmconfig /uninstall
(Caveat: the capture file format is not Winpcap-compatible. However, Wireshark (and others) know how to read it.)

Wednesday, May 27, 2009

There is no Notepad trick

You might have heard that Notepad will fail to display correctly a file holding this single line: "this app can break" (or any sentence built on the same 4/3/3/5 scheme).



This issue could be tracked down to ANSI vs. Unicode text autodetection.

However, there is a much cooler Notepad trick in the latest issue of 2600 magazine. If the first text line happens to be ".LOG", Notepad will automatically append last modification time at the end of the file (as documented in KB81067). This feature is available from Windows 2.03.



This is not a pentester's trick by itself. But I still love it :)

Monday, January 19, 2009

Pentester trick #7: re-enabling CMD & REGEDIT

There are 2 settings that are commonly used by system administrators in "restricted", kiosk-like environments: DisableCMD and DisableRegistryTools, which are both to be found under:

HKCU\Software\Microsoft\Windows\CurrentVersion\Policies\System

As their names imply, those settings disable the use of CMD.EXE and REGEDIT.EXE.

Those settings are enforced by CMD and REGEDIT themselves. Therefore, alternatives such as Console and Registry Workshop will still run fine. However, it might not always be handy to bring new applications on the target system. So, how do we recover CMD and REGEDIT applications locally ?

It would be easy to find binary checks inside both applications and to patch them, but a good pentester is lazier than that.

After making a copy of both applications, it is enough to replace a single character within "DisableCMD" or "DisableRegistryTools" strings. I really love those stupid tricks :) The question is "how ?" ... and surprisingly, the answer is not obvious.
  • DEBUG/EDLIN: they won't handle files over 64KB.
  • ".COM" application written in pure assembly using DEBUG: cool, but a bit tedious.
  • QBASIC application: there is no QBASIC shipped with Windows any more :(
  • Notepad/Wordpad: they mess up binary files on write back.
  • VBScript: is poor at handling binary files.
  • VBA inside an Office application: cool, but you need to have Office installed beforehand.
  • NTSD: does not support the .readmem/.writemem commands.
In most cases, the best course of action is to run CMD inside NTSD (hint: you can drag-and-drop CMD over NTSD, which is sometimes handy in very restricted "kiosk" modes):

C:\> ntsd cmd.exe
(...)
0:000> lm
start end module name
4ad00000 4ad64000 cmd (deferred)
77be0000 77c38000 msvcrt (deferred)
77ef0000 77f37000 gdi32 (deferred)
7c800000 7c905000 kernel32 (deferred)
7c910000 7c9c7000 ntdll (export symbols) ntdll.dll
7e390000 7e420000 user32 (deferred)

0:000> s 4ad00000 L 64000 44 00 69 00 73 00 61 00 62 00 6C 00 65 00 43 00 4D 00

4ad14944 44 00 69 00 73 00 61 00-62 00 6c 00 65 00 43 00 D.i.s.a.b.l.e.C.


0:000> e 4ad14944 41


0:000> g

(...)
Microsoft Windows XP [version 5.1.2600]
(C) Copyright 1985-2001 Microsoft Corp.

C:\temp>


I'll be glad if someone comes with another solution :)

Note: surprisingly, the "DisableCMD" string lies within the code (".text") section.

Note for kiosk designers: to prevent users from running arbitrary applications, Software Restriction Policies would scale more easily.

Monday, January 05, 2009

Lessons learned from MS08-005

[ This post has been 80% complete for 1 year. And I swear the cleanup my "todo list" in 2009 :) ]

MS08-005 (KB 942831) is a local privilege escalation bug affecting:
  • IIS 5.0 (Windows 2000)
  • IIS 5.1 (Windows XP)
  • IIS 6.0 (Windows 2003)
  • IIS 7.0 (Vista)
This particular bug caught my attention for several reasons:
  • Local bugs tend to be more easily and reliably exploitable ;
  • The bugfix is very small in size ;
  • This bug made up his way into Vista, despite manual and automated code analysis.
Let's play with Windows XP SP2 version of this bug!

BinDiff-ing

First step is to install the patch, and to recover backuped files. In our case, there is only 1 file (infocomm.dll), that can be found in:
C:\windows\$NtUninstallKB942831$

Second step is to diff both original and patched files. If you happen to have a legit IDA Pro copy, Tenable PatchDiff2 is the best free plugin available out there. Otherwise, you'll have to fall back on eEye Binary Diffing Suite. Screenshots below are taken from BinDiff2.

Only 2 functions were modified by the patch:
int __stdcall CVRootDirMonitorEntry::FileChanged(char *lpString2, int) int __thiscall CVRootDirMonitorEntry::ActOnNotification(unsigned long, unsigned long)

Understanding the change

We will focus on FileChanged() function, in which strlen()/strcpy() operations were fixed (as shown in the graph below). Note: both APIs were inlined.



Code changes can also be spotted using the excellent Hex-Rays decompilation plugin.

Unpatched version:

if ( _strchr(input_filename, '~') )
{
result = ConvertToLongFileName(*((char **)long_filename + 3), input_filename, &FindFileData);
if ( !
result )
return
result;
v7 = _strrchr(input_filename, '\\');
if (
v7 )
{
v9 = v7 - input_filename;
v15 = v7 - input_filename + 1;
v16 = v7 - input_filename + 1;
v15 >>= 2;
memcpy(&v25, input_filename, 4 * v15);
v17 = &input_filename[4 * v15];
v18 = &v25 + 4 * v15;
v19 = v16 & 3;
v8 = FindFileData.cFileName;
memcpy(v18, v17, v19);
do
v20 = *v8++;
while (
v20 );
memcpy(&v26[v9], FindFileData.cFileName, v8 - &FindFileData.cFileName[1] + 1);
input_filename = &v25;
}

Patched version:

if ( _strchr(v4, '~') )
{
result = ConvertToLongFileName(*(char **)(v3 + 12), v4, &FindFileData);
if ( !
result )
return
result;
v7 = _strrchr(v4, '\\');
if (
v7 )
{
v9 = v7 - v4 + 1;
memcpy(v24, v4, v7 - v4 + 1);
v8 = FindFileData.cFileName;
v10 = 261 - v9;
do
v17 = *v8++;
while (
v17 );
if (
v8 - &FindFileData.cFileName[1] + 1 < v10 )
{
v11 = FindFileData.cFileName;
do
v18 = *v11++;
while (
v18 );
v10 = v11 - &FindFileData.cFileName[1] + 1;
}
v3 = v22;
memcpy(&v24[v9], FindFileData.cFileName, v10);
v25 = 0;
v4 = v24;
}


Runtime analysis

At this point, a little bit of runtime analysis using WinDbg could help us to get the whole picture.

Being attached to the inetinfo.exe process (where infocomm.dll is loaded, according to Sysinternals handle utility), we set a breakpoint on FileChanged():

0:016> .reload /f
Reloading current modules ..................................................................
0:016> bp CVRootDirMonitorEntry::FileChanged
0:016> bl

0 e 71ba7ca0 0001 (0001) 0:**** INFOCOMM!CVRootDirMonitorEntry::FileChanged
0:016> g


Given the name of the function under scrutiny, we suspect it will be called during file operations inside the Web root:

echo "hello" > c:\inetpub\wwwroot\test.txt

It worked! Moreover, we can confirm that the first argument passed to FileChanged(), which is of type char* according to debug symbols, is the filename.

Breakpoint 0 hit
eax=00000000 ebx=00000008 ecx=00712430 edx=007238c4 esi=007238a8
edi=007238a8
eip=71ba7ca0 esp=009cfed8 ebp=009cff0c
iopl=0 nv up ei pl zr na pe nc

cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246 INFOCOMM!CVRootDirMonitorEntry::FileChanged:
71ba7ca0 8bff mov edi,edi

0:004> da poi(esp+4)

009cfee4 "test.txt"


Summary of our findings

Not everything makes sense for now, but we have gathered much interesting information during this preliminary analysis phase:
  • CVRootDirMonitorEntry::FileChanged() does (possibly insecure) string manipulation.
  • This function is called whenever a file under the Web root is "touched". The filename is passed as the first argument.
  • The offending code will be reached only if the filename contains the "~" character, and the "\" character (thus being inside a subdirectory).
  • ConvertToLongFileName() will be called in between on the filename.
A bug, really ?

At this point, we need to have a closer look at ConvertToLongFileName() internals.

According to debug symbols, the function prototype is:
int __stdcall ConvertToLongFileName(char *, LPCSTR lpString2, LPWIN32_FIND_DATAA lpFindFileData)

Implementation of this function is trivial: it takes the filename as an argument and uses FindFirstFileA() on it. The corresponding WIN32_FIND_DATA structure is passed back to the caller for future use.

MSDN documentation relative to FindFirstFile() is pretty straightforward. The WIN32_FIND_DATA structure is more interesting:

typedef struct _WIN32_FIND_DATA {
DWORD dwFileAttributes;
FILETIME ftCreationTime;
FILETIME ftLastAccessTime;
FILETIME ftLastWriteTime;
DWORD nFileSizeHigh;
DWORD nFileSizeLow;
DWORD dwReserved0;
DWORD dwReserved1;
TCHAR cFileName[MAX_PATH];
TCHAR cAlternateFileName[14];
}
WIN32_FIND_DATA,
*PWIN32_FIND_DATA, *LPWIN32_FIND_DATA;


The caller will copy
FindFileDate.cFileName into a fixed size buffer of 264 bytes. Since MAX_PATH has a value of 260 on Windows platform, this is probably MAX_PATH+1 aligned to a DWORD. Where is the trick ?

Where Unicode comes into play

The trick is called Unicode. Quoting MSDN documentation:
In the Windows API (with some exceptions discussed in the following paragraphs), the maximum length for a path is MAX_PATH, which is defined as 260 characters.
(...)
The Windows API has many functions that also have Unicode versions to permit an extended-length path for a maximum total path length of 32,767 characters.
What about CreateFile ?
The Unicode versions of several functions permit a maximum path length of approximately 32,000 characters composed of components up to 255 characters in length.
Therefore, it is possible to build a very long Unicode path, as long as each path token is less than 255 characters long. There is a little quirk in FindFirstFile() documentation here: cFileName cannot be longer than MAX_PATH, but the full path to this file can go far beyond MAX_PATH.

Do it yourself

Here are the steps to trigger the bug:
  • Using the mkdir command, create a directory inside C:\Inetpub\wwwroot with a long name (200 times 'A', for instance).
  • Using CreateFile("\\?\C:\Inetpub\wwwroot\AAA...AAA\BBB...BBB"), create inside this directory a file with a long name (200 times 'B', for instance). This API call must be Unicode-style, because the resulting full path will be longer than MAX_PATH.
  • Now access this file using its short name, as reported by the dir /x command. In this example, this would be something like echo toto > bbbbbb~1.
Et voilĂ  ! IIS should crash, because it expanded "aaa...aaa\bbbbbb~1" into "aaa...aaa" and "bbb...bbb" strings, that are thereafter concatenated into a stack-based buffer of size MAX_PATH.

Since infocomm.dll has been compiled with /GS option, a stack cookie prevents direct exploitation of this bug. Exploitation on IIS 5 is left as an exercise to the reader ;)

Conclusion

That was a very nice bug to study (even if it ended up in a trivial stack overflow) because it requires good knowledge of Windows internals.

As usual, it would be nice to know "how" this bug has been found by the original author. However, using Unicode filenames breaks so many applications out there that it could have been found by accident ;)

PS. Happy New Year to all readers !

Thursday, January 01, 2009

Having fun with certificates

Unless you went on vacation without WiFi access, your iPhone and your BlackBerry, you certainly have heard of the latest "Internet is dead" issue.

All details are available here and there. A comprehensive analysis is available on O'Reilly blog. A summary is available on ISC blog.

Now this is where C# beauty comes into play. Here is a code snippet that will check from local certificate store(s) the signature algorithm used. Everything that is not sha1RSA is displayed, because it should be bad (according to Microsoft analysis).

using System;
using System.Security.Cryptography.X509Certificates;

namespace SearchCerts
{
class Program
{
static void Main(string[] args)
{
// *** select appropriate store below ***
//var store = new X509Store(StoreName.My);
//var store = new X509Store(StoreName.AuthRoot);
//var store = new X509Store(StoreName.CertificateAuthority);
//var store = new X509Store(StoreName.Root);
//var store = new X509Store(StoreName.TrustedPeople);
//var store = new X509Store(StoreName.TrustedPublisher);

store.Open(OpenFlags.OpenExistingOnly | OpenFlags.ReadOnly);
foreach (var cert in store.Certificates)
{
if (cert.SignatureAlgorithm.FriendlyName != "sha1RSA")
{
System.Console.WriteLine("------------------------------");
System.Console.WriteLine("[FriendlyName]\t" + cert.FriendlyName);
System.Console.WriteLine("[Issuer]\t" + cert.Issuer);
System.Console.WriteLine("[Subject]\t" + cert.Subject);
System.Console.WriteLine("[Signature]\t" + cert.SignatureAlgorithm.FriendlyName);
}
}
store.Close();
System.Console.WriteLine("finished");
System.Console.ReadLine();
}
}
}


Here are some results from my certificate stores. Your mileage may vary.

Freephonie PKI is using MD5. End-users cannot submit CSRs by themselves, so the risk remains low. I'd be glad to know if the Freebox itself can send CSRs.

[FriendlyName]
[Issuer] O=Free, L=Paris, S=France, C=FR
[Subject] CN=1234567, O=Free, L=Paris, S=France, C=FR
[Signature] md5RSA


CNRS-Standard and CNRS-Plus PKI are using MD5. This is more concerning, because those are widely used authorities, and users can request certificates "at will".

[FriendlyName]
[Issuer] CN=CNRS, O=CNRS, C=FR
[Subject] CN=CNRS-Plus, O=CNRS, C=FR
[Signature] md5RSA

[FriendlyName]
[Issuer] CN=CNRS, O=CNRS, C=FR
[Subject] CN=CNRS-Standard, O=CNRS, C=FR
[Signature] md5RSA


Microsoft drivers signing PKI (at least on Windows XP SP2). Since drivers developers can ask for signatures, this is concerning too. But I feel that this authority might not be used by Microsoft anymore for newer signatures, given its old age.

[FriendlyName]
[Issuer] CN=Microsoft Root Authority, OU=Microsoft Corporation, OU=Copyright (c) 1997 Microsoft Corp.
[Subject] CN=Microsoft Windows Hardware Compatibility, OU=Microsoft Corporation, OU=Microsoft Windows Hardware Compatibility Intermediate CA, OU=Copyright (c) 1997 Microsoft Corp.
[Signature] md5RSA


Some random foreign authorities, which are using obscure certification policies.

[FriendlyName] NetLock Uzleti (Class B) Tanusitvanykiado
[Issuer] CN=NetLock Uzleti (Class B) Tanusitvanykiado, OU=Tanusitvanykiadok, O=NetLock Halozatbiztonsagi Kft., L=Budapest, C=HU
[Subject] CN=NetLock Uzleti (Class B) Tanusitvanykiado, OU=Tanusitvanykiadok, O=NetLock Halozatbiztonsagi Kft., L=Budapest, C=HU
[Signature] md5RSA


And last but not least, this VeriSign authority is using ... MD2 (this is not the only one, unfortunately).

[FriendlyName] VeriSign
[Issuer] OU=VeriSign Commercial Software Publishers CA, O="VeriSign, Inc.", L=Internet
[Subject] OU=VeriSign Commercial Software Publishers CA, O="VeriSign, Inc.", L=Internet
[Signature] md2RSA


PS. impots.gouv.fr is NOT vulnerable ;)