Vai al contenuto
Home » Recuperare informazioni via SNMP (con ruby)

Recuperare informazioni via SNMP (con ruby)

Se come sysadmin ti stai domandando chi mai potrà essere interessato a
recuperare informazioni su un host per un attacco successivo, allora ti manca
una profonda dose di awareness. Secondo Gartner, ma il dato che ricordo è
vecchiotto di un 6 – 7 anni, più dell’80% delle intrusioni informatiche avviene
dall’interno dell’infrastruttura aziendale.

Veicoli d’attacco classici sono virus, malware, chiavette USB che arrivano da
amici di amici di amici e dai sempre classici utenti in VPN o impiegati
scontenti o semplicemente burloni. Si ci sono anche le persone diversamente
oneste nella vita (e questa purtroppo non è fantasy-banfa-security).

Oggi vediamo come si possa usare ruby ed il
protocollo
SNMP
per
avere qualche informazione in più rispetto ad un portscan.

Attenzione, leggimi bene un paio di volte

Lo scopo di codiceinsicuro.it è nella mia testa
molto chiaro. Parlare di application security, in italiano, con esempi concreti
e codice alla mano. Ci sono delle situazioni boarder line dove per parlare di
un particolare aspetto si può dare allo script kiddie l’idea che adesso può
bucare l’universo intero dalla sua console di IRB che usa perché lo fa sentire
31337.

Potrei anche buttare lì errori nel codice d’esempio a caso in modo che il
nostro Neo in erba non possa vivere di solo copia e incolla.

No. Il codice che verrà presentato e funzionante e, precisazione che farò
ora e non ripeterò più, se volete giocare all’accher 31337 che ascolta musica
electro da uno stereo in una stanzetta buia bhé buon per voi, il mondo è bello
perché è vario ma non sentitevi incentivati a farlo leggendo quello che scrivo
io. Perché la mia opinione su di voi è pari a quella che ho per #sha7.

SNMP

Il protocollo SNMP (Simple Network Management Protocol) è un protocollo
applicativo, layer 7 della pila ISO/OSI, la cui funzione è quella di offrire
servizi di diagnostica per amministratori di rete e sistemisti. I device
vengono attestati in rete con una community string che permette di inviare
query per conoscere le proprietà e lo stato in cui versa un host, uno switch,
un router, una stampante di rete o qualsiasi cosa pubblichi il demone SNMP in
ascolto sulla porta UDP 161.

Protocollo UDP, quindi non aspettatevi sessioni, non aspettatevi ritrasmissione
o error recovery. Se c’è una congestione in rete e i vostri pacchetti andranno
persi… bhé sono andati persi.

E’ possibile usare TCP per il protocollo SNMP ma a mia memoria, ho visto solo
installazioni su UDP. Prendete questa come una statistica personale.

Il modello di sicurezza, all’interno di una comunicazione così inaffidabile,
non può essere molto evoluto. In particolare un gioco fondamentale lo ricopre
la stringa della community con la quale si configura il demone SNMP, una sorta
di password con la quale “autenticarsi” per poter interrogare un dispositivo.

SNMP versione 3 supporta anche l’encryption e meccanismi più complessi come
username e password per profilare chi sta facendo cosa. In questo caso ci si
affida al protocollo DTLS che assicura l’encription di dati su comunicazione
non affidabile.
Anche in questo caso, io ho visto la maggior parte dei sistemi usare per
retrocompatibilità e mille altri constraint, ignoranza inclusa, SNMP versione
2.

Esistono due community (che ricordiamoci valgono un po’ come le password,
perché senza la community giusta non potremo interrogare quel device) standard:

  • public
  • private

Spesso l’incauto sysadmin lascia la community invariata e qui entriamo in scena
noi.

Tutto parte dal portscan, e vi possono avere già scoperti

Allora, attenzione perché se nella rete dove vi trovate c’è un IDS1
probabilmente se lanciate un portscan, se le regole sono scritte bene, se fanno
scattare un alert e se c’è qualcuno che guarda l’alert, allora potreste essere
scoperti. Ci sono tanti se, concordo, tuttavia spesso molti di questi fattori
vengono a mancare, quindi diciamo che potreste essere anche fortunati.

Anche se la fortuna aiuta gli audaci, lanciare nmap in modalità aggressive è
un po’ come sparare con un bazooka contro una zanzara. Siamo interessati alla
sola porta UDP 161, quindi possiamo fare una richiesta più mirata.
Attenzione per lanciare questa scansione dovete essere root sulla macchina.

$ sudo nmap -sU -p161 127.0.0.0/24

Avrete una serie di host per le quali la porta sarà open, questi file sono
quello che state cercando.

Minatori, al lavoro

Entra in scena ruby ora, in particolare la gemma
snmp. Installarla è triviale, basta
un semplice:

$ gem install snmp

Questa gemma non ha dipendenze strane in quanto è una pura implementazione in
ruby del protocollo SNMP.

Proviamo una semplice query, che restituisca la descrizione, il contatto
dell’amministratore, il nome macchina e la location del datacenter dove la
macchina è attestata:

#!/usr/bin/ruby
require 'snmp'

include SNMP

lookup_values = ["sysDescr", "sysContact", "sysName", "sysLocation"]

Manager.open(:Host => 'localhost', :Community => 'public') do |manager|
  manager.walk(lookup_values) do |row|
    row.each { |vb| puts "t#{vb.value}" }
  end
end

All’output ho sostituito il nome reale con ‘localhost’, nella mia rete l’ho
lanciato contro un po’ di IP a caso ottenuti dalla scansione delle porte UDP
161 fino a quando ho trovato un host con la community ‘public’:

$ ./snmp_walk.rb
  SunOS localhost 5.10 Generic_147441-02 i86pc
  "System administrator"
  localhost
  "System administrators office"

Ora, a parte il dettaglio sul dove si trova la macchina e la descrizione del
contatto, ho delle informazioni molto utili. So l’architettura, i86pc, so il
sistema operativo e la sua versione (Sun Solaris) e anche il patch level.

Per risalire alla sola versione (quindi non a tutto questo dettaglio), avrei
dovuto lasciare lavorare nmap in maniera molto più aggressiva e sperare che le
condizioni di scansione fossero quelle ottimali per ottenere una rilevazione
con un buon grado di confidenza. Spesso nmap (soprattutto nel mondo Microsoft)
si trova a darti alcune probabilità su quale sia la reale release.

Adesso proviamo a modificare il nostro script, snmp_walk.rb, in modo tale da
renderlo indipendente dall’output di nmap. Per fare questo useremo la gemma
ipaddressche ci permette di gestire in
maniera agevole indirizzi IP e indirizzi di rete in notazione CIDR.

Posso quindi passare la mia intera rete /24 allo script senza più passare da
nmap. Sarà lo script a cercare di usare la community SNMP public su un certo
indirizzo IP per ottenere informazioni.

Adesso provo a recuperare qualche informazione in più sullo stato delle schede
di rete. Perché questa informazione è utile? Perché un server può avere più
schede di rete di quanto non mi dica il DNS della rete interna, magari su VLAN
di management o magari su VLAN dedicate ad altri servizi. L’idea è che
particolari demoni siano in ascolto non su tutte le interfacce ma solamente su
un specifico IP e per un attaccante sono tutti punti di ingresso potenziali.

#!/usr/bin/ruby
require 'snmp'
require 'ipaddress'

include SNMP

lookup_values = ["sysDescr", "sysContact", "sysName", "sysLocation", "ifIndex", "ifDescr", "ifInOctets", "ifOutOctets"]

# ARGV[0] is an IP address or a network in CIDR notation
raise "Missing IP address or network" if ARGV[0].nil?

@list = IPAddress.parse(ARGV[0])
@list.each do |ip|
  puts "Asking #{ip.to_s} for info using 'public' SNMP community"
  Manager.open(:Host => ip.to_s, :Community => 'public') do |manager|
    manager.walk(lookup_values) do |row|
      row.each { |vb| puts "t#{vb.value}" }
    end
  end
end

Adesso vediamo di rendere le cose un po’ più interessanti. SNMP non da nessun
dettaglio a livello di protocollo su quali debbano essere le informazioni messe
a disposizioni da un sistema.

Quando è stato disegnato l’SNMP, è stato creato in maniera tale che le
informazioni disponibili fossero definite da MIB (Management Information Bases)
preposti a descrivere la struttura dei dati di un sistema. Per modellare questa
struttura dati è stata creata una gerarchia di identificatori, OID (Object
IDentifier)
che si presentano
come una stringa composta da interi separati da un ‘.’.

Ogni OID rappresenta una variabile collegata ad un’informazione di sistema che
può essere letta o scritta a seconda delle permission legate alla
community.

In particolare modifichiamo il nostro script affinché chieda la descrizione
della macchina (OID = “1.3.6.1.2.1.1.1.0”) e l’uptime (OID =
“1.3.6.1.2.1.1.3.0”).

#!/usr/bin/ruby
require 'snmp'
require 'ipaddress'

include SNMP

# ARGV[0] is an IP address or a network in CIDR notation
raise "Missing IP address or network" if ARGV[0].nil?

@list = IPAddress.parse(ARGV[0])
@list.each do |ip|
  puts "Asking #{ip.to_s} for info using 'public' SNMP community"
  Manager.open(:Host => ip.to_s, :Community => 'public') do |manager|
    response = manager.get([ObjectId.new("1.3.6.1.2.1.1.1.0"), ObjectId.new("1.3.6.1.2.1.1.3.0")])
    response.each_varbind do |vb|
      puts "#{vb.name.to_s}  #{vb.value.to_s}  #{vb.value.asn1_type}"
    end
  end
end

Eseguiamo lo script.

$ ruby snmp_walk.rb localhost
Asking localhost for info using 'public' SNMP community
SNMPv2-MIB::sysDescr.0  SunOS localhost 5.10 Generic_147441-02 i86pc  OCTET STRING
SNMPv2-MIB::sysUpTime.0  4 days, 18:39:34.30  TimeTicks

La macchina è stata riavviata 4 giorni fa. L’indicazione lato attaccante che
abbiamo da questo dato è quello di un sistema comunque vivo, che può essere
stato appena aggiornato. Non è una macchina dimenticata nel datacenter. E’
anche vero che sarebbe meglio parlare di indizio più che di informazione,
perché ci permette di fare solo supposizioni, non certo di trarre conclusioni.

Sapendo che l’host target è una Sun Solaris, possiamo andare a vedere qual è
l’OID relativo alla tabella dei processi per questo sistema operativo. Sì, la
gerarchia degli OID è molto puntuale.
La tabella dei processi è memorizzata nell’OID
1.3.6.1.4.1.42.3.12.1.
Proviamo a chiedere ora la lista dei processi di quella macchina.

#!/usr/bin/ruby
require 'snmp'
require 'ipaddress'

include SNMP

# ARGV[0] is an IP address or a network in CIDR notation
raise "Missing IP address or network" if ARGV[0].nil?

@list = IPAddress.parse(ARGV[0])
@list.each do |ip|
  puts "Asking #{ip.to_s} for info using 'public' SNMP community"
  Manager.open(:Host => ip.to_s, :Community => 'public') do |manager|
    response = manager.get([ObjectId.new("1.3.6.1.2.1.1.1.0"), ObjectId.new("1.3.6.1.2.1.1.3.0")])
    response.each_varbind do |vb|
      puts "#{vb.name.to_s}  #{vb.value.to_s}  #{vb.value.asn1_type}"
    end

    # Sun Process Table - http://www.alvestrand.no/objectid/1.3.6.1.4.1.42.3.12.1.html
    processTable = ObjectId.new("1.3.6.1.4.1.42.3.12.1")
    manager.walk(processTable) { |varbind|  puts "#{varbind.value.to_s}" if varbind.name.to_s.start_with?("SNMPv2-SMI::enterprises.42.3.12.1.10")}
  end
end

L’output del nostro script è più interessante ora:

$ ruby snmp_walk.rb localhost
Asking localhost for info using 'public' SNMP community
SNMPv2-MIB::sysDescr.0  SunOS localhost 5.10 Generic_147441-02 i86pc  OCTET STRING
SNMPv2-MIB::sysUpTime.0  4 days, 19:38:27.64  TimeTicks
sched
init
pageout
fsflush
zpool-rpool
vmtasks
svc.startd
svc.configd
devfsadm
zpool-zpool1
syseventd
powerd
...

Da qui in avanti è solo un andare a vedere quale informazioni recuperare dalla
macchina target e quali OID sono associati.

Lo script è disponibile a questo gist su github:

#!/usr/bin/ruby
require 'snmp'
require 'ipaddress'

include SNMP

# ARGV[0] is an IP address or a network in CIDR notation
raise "Missing IP address or network" if ARGV[0].nil?

@list = IPAddress.parse(ARGV[0])
@list.each do |ip|
  puts "Asking #{ip.to_s} for info using 'public' SNMP community"
  Manager.open(:Host => ip.to_s, :Community => 'public') do |manager|
    response = manager.get([ObjectId.new("1.3.6.1.2.1.1.1.0"), ObjectId.new("1.3.6.1.2.1.1.3.0")])
    response.each_varbind do |vb|
      puts "#{vb.name.to_s}  #{vb.value.to_s}  #{vb.value.asn1_type}"
    end

    # Sun Process Table - http://www.alvestrand.no/objectid/1.3.6.1.4.1.42.3.12.1.html
    processTable = ObjectId.new("1.3.6.1.4.1.42.3.12.1")
    manager.walk(processTable) { |varbind|  puts "#{varbind.value.to_s}" if varbind.name.to_s.start_with?("SNMPv2-SMI::enterprises.42.3.12.1.10")}
  end
end


Off by one

Configurare un demone SNMP con una community public e farlo rispondere a
qualsiasi query è da sconsiderati. E’ possibile risalire a informazioni
critiche come i processi in esecuzione, i demoni in ascolto, gli utenti
presenti sulla macchina (o almeno alcuni di essi).

Il suggerimento, lato sistemistico, è quello di usare la versione di SNMPv3 con
protocollo DTLS ed usare il modello role based per profilare chi può fare le
query e cosa può interrogare.

Enjoy it!

  1. Intrusion Detection System 

Tag:

Lascia un commento

Il tuo indirizzo email non sarà pubblicato.