Hacker News new | past | comments | ask | show | jobs | submit login
Format string vulnerability found in 'sudo' (sudo.ws)
171 points by sweis on Jan 30, 2012 | hide | past | favorite | 32 comments



In the source of sudo-1.8.3p1, the error is in src/sudo.c at line 1219. Relevant code snippet:

  easprintf(&fmt2, "%s: %s\n", getprogname(), fmt);
  va_start(ap, fmt);
  vfprintf(stderr, fmt2, ap);
  va_end(ap);
  efree(fmt2);
Obviously, fmt2 can still contain a format string, yet it is printed anyway with vfprintf. I'm really surprised this type of bug still exists. Doesn't every decent compiler warn the developer about this?


It just goes to show that all project owners should be responsible for turning on maximum compiler warnings which make sense for their project. There are lots of great gcc warnings/errors which are not enabled by default. In particular, this one is reported by -Wformat-nonliteral or -Wformat-security. From the gcc docs:

  -Wformat is included in -Wall. For more control over some 
  aspects of format checking, the options -Wformat-y2k,
  -Wno-format-extra-args, -Wno-format-zero-length,
  -Wformat-nonliteral, -Wformat-security, and -Wformat=2
  are available, but are not included in -Wall.


"fmt2" is supposed to contain a format string. It's supposed to contain the format string from "fmt". That's how it prints the args from "ap".

The bug is when getprogname() also returns a string containing a format specifier (e.g. "%s"), which is not expected.

Clearly the fix is:

  fprintf(stderr, "%s: ", getprogname());
  va_start(ap, fmt);
  vfprintf(stderr, fmt, ap);
  va_end(ap);
  fprintf(stderr, "\n");
(Edit for formatting, clarity)


The real crime is creating a library function like printf without also creating a library function to escape format strings. Leaving safe escaping as an exercise for the application programmer clearly doesn't work.


This is like asking for SQL databases to have a SQL-Injection escaping function. You 'escape' format string attacks by using the C language in a correct fashion.


This is provided by MySQL, as an example:

http://dev.mysql.com/doc/refman/5.0/en/mysql-real-escape-str...

I'm sure what you meant was a server-side escaping function which is obviously pointless, but I did want to point out that MySQL's client driver does ship with one that you can use so you don't have to write your own.


Actually, isn't the escaping done serversiden when using placeholders?

At least in Perl, there's no need to escape placeholder values explicitly. I believe the prepared statement is stored on the server, and then the values sent later. Of course the escaping could be the work of the driver.. anyone know?


See "Prepared Statement Support" from http://search.cpan.org/perldoc?DBD::mysql#Class_Methods

Prepared statements are disabled by default and require MySQL >= 4.1.3. Instead, the driver does the placeholder replacement on the client, and prepare() does nothing clever at all.


hm.. could be useful information.. thanks


Building dynamic SQL strings for comparison can sometimes be handy (e.g. with LIKE), so it's really too bad there's no server-side SQL escape function, at least in the sql dialects I'm familiar with.


Well, just because it really exists doesn't mean it wasn't a silly thing to ask for ;-)


Yes, client libraries for SQL databases should absolutely have an escaping function. The fact that some don't is responsible for a significant fraction of all security failures.


Your suggestion makes no sense.

If you don't want to use a format string, use a function that takes a regular string, like puts, instead of a vprintf variant. Why would anyone go to the trouble of escaping a format string so they can use it with a function whose sole purpose is to parse format strings? (Which you can already do, by the way, by using vprintf correctly.)


This vulnerability is reassuringly difficult to exploit under normal conditions. On this dreary morning in San Francisco, against my better judgement I've taken a crack at it (On most systems, due to a combination of old compiler AST-optimizing hacks and security methods, using techniques like information leaks through %X%X stack printing and Null pointer dereference bugs through the infamous %n specifier are challenging and virtually unexploitable on modern systems). Luckily (for blackhats and the curious), due to the way memcpy() works, I think, like the famously exploitable 'unexploitable buffer overflow' that existed solely in 32-bit Opera, this vulnerability is easier to exploit than it would be in real-world environments where system administrators don't even know what Data Execution Prevention and write protected memory pages are, much less turn them on.

Here's how it breaks down

For reference, here's the basic idea of Memcpy on x86

        movl    LEN(%esp), %ecx
        movl    SRC(%esp), %eax
        movl    DEST(%esp), %edx
So you the general premise of exploiting format string bugs if you've never seen them before (and if you're a young person like myself, It's entirely possible this is the first C format specifier bug you've ever seen in a major piece of software, yet again, Finite Field analysis scores another victory for the Static Analysis Team. Take that fuzzers!) is to either use clever format specifiers (%X, and %N as mentioned above are some old school faves) or to take advantage of lower system calls like brk() that can be indirectly abused through calls that accept format specifiers as an argument, but I digress.

So, the user arguments are stored in the above registers. To exploit this vulnerability, you must have control of the length argument which is stored in ECX general purpose register as you can see from this code snippet.

        cmp     %eax, %edx
        jx      link(copy_forward)
        je      L(fwd_write)
        cmp     $32, %ecx
        jge     L(fmt)
        jmp     L(get_prog_name_offset)
So, the user arguments are stored in the above registers. Before things like ROP, to exploit these vulnerabilities the attacker must have control of other, more directly meaningful arguments such as EIP (Which if you control EIP, why bother setting up some complex staged format string exploit?) and hoped that an external task would reboot this binary if it crashed, and keep trying until you beat address space layout randomization at the entropy game, again, I digress. Regardless, The format specifier returned from getprogname() allows us to do some funky stuff like use "safe" format string specifiers like %c (Don't you just love that classic Infosec industry model where %x is 'dangerous' but %c is 'safe' and where breaking MD5 is a big deal but nobody checks the integrity of their login scrips?) to pop bytes off the stack, write an address either to a stored instruction pointer that will be invoked later in the runtime of the program (and presumably will repair the stack before anything gets noticed by the OS) or to just get trashy and munge your local stack with a new EIP pointing to your shellcode (You can even have Null bytes in it, We're moving on up!). Of course, you're also going to have to be attacking a system running a filesystem with very, very, very long filenames, but I most modern ones support around 255 byte filenames, which is definitely not enough if you're using %c, but you could definitely exploit this bug with %o with a limit of 255 bytes.

The remaining, perhaps more difficult question still has to be asked -- Why is nobody taking compiler warnings seriously? Do you think compilers are just kidding when they say "Someone will use this to take control of any system that has this software"?


Affected versions are 1.8.0 through 1.8.3p1 inclusive. Ubuntu 11.10 is not affected (it's running 1.7.4). Ubuntu 12.04 (alpha) might be, which appears to be running 1.8.3p1, for a short while anyway.


Ubuntu 10.04 LTS seems to be OK:

  $ lsb_release -a
  No LSB modules are available.
  Distributor ID: Ubuntu
  Description:    Ubuntu 10.04.3 LTS
  Release:        10.04
  Codename:       lucid

  $ sudo -V
  Sudo version 1.7.2p1


Super. My gentoo boxes are 1.8.2, and are broken as expected.

  $ ln -s /usr/bin/sudo ./%s
  $ ./%s -D9
  [some debug output with garbage]
  Segmentation fault
  $
Bug is here:

https://bugs.gentoo.org/show_bug.cgi?id=401533

Looks like it's patched on x86/amd64 already.

OpenIndiana looks unaffected. It's using 1.7.4p4.


Yep patch is in Gentoo, I just did a sync and 1.8.3_p2 will be built.


Debian stable is unaffected (1.7.4p4).


Debian _un_stable, on the other hand may be (depending on when you last updated). There is a new package available and so an `apt-get install sudo' or `apt-get upgrade' will sort it.


CentOS 6.2 (and hence RHEL 6.2, the current version) is on sudo-1.7.4p5.


RHEL compiled out the debugging support and is NOT vulnerable.

Fedora compiles everything with -DFORTIFY_SOURCE which means this still crashes, but is not (thought to be) vulnerable. In any case there is an update available for Fedora right now. https://admin.fedoraproject.org/updates/sudo-1.8.3p1-2.fc16


On OS X 10.7.2: Sudo version 1.7.4p6, on the latest 10.6: 1.7.0.


segfaults on opensuse 12.1 (1.8.2).


ArchLinux, in its current state, is vulnerable. I suspect it will be patched soon enough.

/me sends a note to the package maintainers anyway.


arch compiles with FORTIFY_SOURCE also so it's still vulnerable, just a pain to exploit


ArchLinux doesn't have package signing, so patching sudo isn't going to get you much.


Sure it does, as of earlier this year. http://www.archlinux.org/news/pacman-4-moves-to-core/

Still in beta though. Not very stable.


Yes they do.

It is still pretty new, and from what I understand upgrading can be a bit of a pain, but pacman 4.0 has package signing.

Been using Fedora for a bit now, so haven't actually tried it yet...


Lion runs 1.7.4p6


OpenBSD 4.9 and 5.0 are ok (both on 1.7.2p8)


c is terrifying.^H^H^H^H^H^H^H^H^H^H^H^H^H^H^H^HComputers are terrifying.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: