0day/00-CVE_EXP/CVE-2020-9273
2022-01-13 17:57:04 +08:00
..
2022-01-13 17:57:04 +08:00
2022-01-13 17:57:04 +08:00
2022-01-13 17:57:04 +08:00
2022-01-13 17:57:04 +08:00
2022-01-13 17:57:04 +08:00
2022-01-13 17:57:04 +08:00

CVE-2020-9273 PoC

ProFTPd Post-Auth Use-After-Free leading to Remote Code Execution

Demo

[%] Usage: python3 exploit.py <target IP> <target port> <callback IP> <callback port> <username> <password>

Vulnerability trigger requirements:

  • Valid credentials for a user

Exploitation requirements:

  • ProFTPd with mod_copy enabled

Introduction

There exists a Use-After-Free in ProFTPd that can be triggered when interrupting through command channel while a transference is active in the data channel.

Shout out to Antonio Morales (@nosoynadiemas) for discovering this vulnerability, you can find more information on the discovery in this post.

The result of triggering the Use-After-Free is controlling useful data from the program with custom values.

Unfortunately, the vulnerability requires pre-auth to be triggered

Leaking memory addresses

Due to the charasteristics of this vulnerability, a leak is really difficult as we require to disconnect from target server to trigger the bug, this means the exploitation process and execution flow of the program is the closing and cleanup process before finishing session.

So, to make this vulnerability possible to be exploited, and as we need predictable memory addresses I remembered that /proc/self/maps can be accessed from any process even with low privileges.

I tried to retrieve it using the data channel but something went wrong... stat returns 0 as it is a pseudo-file, so 0 bytes are transferred back to client

My solution to this was compiling the module with mod_copy, we can copy /proc/self/maps to /tmp and then perform a normal transfer

This the best leak an attacker could get as it gives you every segment address range, also, knowing filenames you can sometimes guess libc version by the filename (Eg.: libc-2.31.so)

Hijacking control flow

The exploitation part was somewhat limited, and primitives like arbitrary read, or write-what-where were not possible, instead, non-fully-controllable addresses could be written in arbitrary addresses.

Also, as we control pool-related structs we can move pools to pivot the pool somewhere else to arbitrary addresses, unfortunately, we have only once chance (one allocation) to do that as the rest of allocations against our controlled pool are non-controlled strings, most of them for responses

After finding different ways that could allow me to hijack execution flow, I found one that could be interesting which is corrupting cmd->tmp_pool, we can control cmd->tmp_pool->cleanups so when destroy_pool(cmd->tmp_pool) is called our fake cleanup_t struct would be interpreted.

Unfortunately, it turned out to be difficult to control as a string with value: displayable-str is appended after our arbitrary address, also due to the way bytes are copied (using string-based functions) we can just enter a single address.

The result is that crashes can happen when reaching destroy_pool().

@DUKPT_ who is also working on a PoC for this vulnerability, had the idea to overwrite gid_tab->pool, which is also passed to destroy_pool().

I decided to go this way as it is more reliable, so shout out to @DUKPT_ for his work on this.

The target with all these techniques is to make run_cleanups() interpret our custom fake cleanup_t struct, which allow us to get custom RIP and RDI values.

Once you get arbitrary RIP and RDI you can follow any method you would like to get RCE

Method 1: Stack pivot, ROP and shellcode execution

You can:

  1. Use RIP to jump to an stack pivot gadget like push rdi ; pop rsp ...
  2. As you control RDI aswell you will be able to control stack to your ROPchain
  3. Finally you will be able to execute a custom shellcode using mprotect() + jump to shellcode in heap (once RWX privileges set).

Method 2: ret2libc, ret2X

You can jump to any function that requires one argument, like system() does. Or reuse arguments at the time of run_cleanups() jumping to code

Example: execute netcat with reverse shell using system() and pointing rdi to the command string on heap.

Reproduce

  1. Initialize proftpd (compiled binary present too)
  2. Make sure proftpd.conf is the same as yours
  3. Execute this PoC against ProFTPd server (using any user credentials)
  4. Enjoy shell