Skip to content

Commit 37042d8

Browse files
committed
Add spip_plugin_version function to retrieve plugin version from config.txt or Composed-By header
1 parent b8a1d40 commit 37042d8

File tree

2 files changed

+76
-1
lines changed

2 files changed

+76
-1
lines changed

lib/msf/core/exploit/remote/http/spip.rb

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,5 +41,71 @@ def spip_version
4141

4242
return version ? Rex::Version.new(version) : nil
4343
end
44+
45+
# Determine Spip plugin version by name
46+
#
47+
# @param [String] plugin_name Name of the plugin to search for
48+
# @return [Rex::Version, nil] Version of the plugin as Rex::Version, or nil if not found
49+
def spip_plugin_version(plugin_name)
50+
res = send_request_cgi(
51+
'method' => 'GET',
52+
'uri' => normalize_uri(target_uri.path, 'spip.php')
53+
)
54+
55+
return unless res
56+
57+
# Check the Composed-By header for plugin version or config.txt URL
58+
composed_by = res.headers['Composed-By']
59+
return unless composed_by
60+
61+
# Case 1: Look for config.txt URL in the header
62+
if composed_by =~ %r{(https?://[^\s]+/local/config\.txt)}i
63+
config_url = ::Regexp.last_match(1)
64+
vprint_status("Found config.txt URL: #{config_url}")
65+
66+
# Fetch and parse the config.txt file directly
67+
config_res = send_request_cgi(
68+
'method' => 'GET',
69+
'uri' => config_url
70+
)
71+
72+
if config_res&.code == 200
73+
return parse_plugin_version(config_res.body, plugin_name)
74+
end
75+
end
76+
77+
# Case 2: Check for plugin version directly in Composed-By
78+
composed_by.split(',').each do |entry|
79+
if entry =~ /#{plugin_name}\((\d+(\.\d+)+)\)/
80+
return Rex::Version.new(::Regexp.last_match(1))
81+
end
82+
end
83+
84+
# Case 3: Fallback to fetching /local/config.txt directly
85+
vprint_status('No version found in Composed-By header. Attempting to fetch /local/config.txt directly.')
86+
config_url = normalize_uri(target_uri.path, 'local', 'config.txt')
87+
config_res = send_request_cgi(
88+
'method' => 'GET',
89+
'uri' => config_url
90+
)
91+
92+
return parse_plugin_version(config_res.body, plugin_name) if config_res&.code == 200
93+
94+
nil
95+
end
96+
97+
# Parse the plugin version from config.txt or composed-by
98+
#
99+
# @param [String] body The body content to parse
100+
# @param [String] plugin_name Name of the plugin to find the version for
101+
# @return [Rex::Version, nil] Version of the plugin as Rex::Version, or nil if not found
102+
def parse_plugin_version(body, plugin_name)
103+
body.each_line do |line|
104+
if line =~ /#{plugin_name}\((\d+(\.\d+)+)\)/
105+
return Rex::Version.new(::Regexp.last_match(1))
106+
end
107+
end
108+
nil
109+
end
44110
end
45111
end

modules/exploits/multi/http/spip_porte_plume_previsu_rce.rb

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,9 +84,18 @@ def check
8484
]
8585

8686
vulnerable_ranges.each do |range|
87-
if rversion.between?(range[:start], range[:end])
87+
next unless rversion.between?(range[:start], range[:end])
88+
89+
print_status('SPIP version is in the vulnerable range.')
90+
91+
plugin_version = spip_plugin_version('porte_plume')
92+
93+
unless plugin_version
94+
print_warning('Could not determine the version of the porte_plume plugin.')
8895
return Exploit::CheckCode::Appears("The detected SPIP version (#{rversion}) is vulnerable.")
8996
end
97+
98+
return Exploit::CheckCode::Appears("The detected SPIP version (#{rversion}) and porte_plume version (#{plugin_version}) are vulnerable.") if plugin_version < Rex::Version.new('3.1.6')
9099
end
91100

92101
return Exploit::CheckCode::Safe("The detected SPIP version (#{rversion}) is not vulnerable.")

0 commit comments

Comments
 (0)