From 05d19757432391c9ce881cc647a1ddd1e82763a9 Mon Sep 17 00:00:00 2001 From: Manoj Ampalam Date: Tue, 20 Feb 2018 23:00:14 -0800 Subject: [PATCH] =?UTF-8?q?Added=20logic=20to=20spawn=20unauthenticated=20?= =?UTF-8?q?sshd=20workers=20to=20run=20in=20isolated=20=E2=80=A6=20(#275)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Issue: All SSHD unauthenticated workers are currently running as "sshd". Any compromised worker can snoop into the process space of one other worker, steal authentication payload and elevate itself. Fix: Added logic to spawn the unauthenticated workers under the context of run time unique security identities that will provide complete isolation between these worker processes. --- contrib/win32/openssh/paths.targets | 2 +- contrib/win32/win32compat/lsa_missingdefs.h | 60 +++++ .../win32/win32compat/win32_usertoken_utils.c | 212 +++++++++++++++++- contrib/win32/win32compat/wmain_sshd.c | 2 +- 4 files changed, 271 insertions(+), 5 deletions(-) create mode 100644 contrib/win32/win32compat/lsa_missingdefs.h diff --git a/contrib/win32/openssh/paths.targets b/contrib/win32/openssh/paths.targets index 46b4e4eaa..f2c034644 100644 --- a/contrib/win32/openssh/paths.targets +++ b/contrib/win32/openssh/paths.targets @@ -12,7 +12,7 @@ true libcrypto.lib; 8.1 - bcrypt.lib;Userenv.lib;Crypt32.lib;Ws2_32.lib;Secur32.lib;Shlwapi.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;Netapi32.lib;Rpcrt4.lib + bcrypt.lib;Userenv.lib;Crypt32.lib;Ws2_32.lib;Secur32.lib;Shlwapi.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;Netapi32.lib;Rpcrt4.lib;ntdll.lib false \ No newline at end of file diff --git a/contrib/win32/win32compat/lsa_missingdefs.h b/contrib/win32/win32compat/lsa_missingdefs.h new file mode 100644 index 000000000..838c1b316 --- /dev/null +++ b/contrib/win32/win32compat/lsa_missingdefs.h @@ -0,0 +1,60 @@ +/* +* Missing public definitions from Ntsecapi.h +*/ + + +typedef enum _LSA_SID_NAME_MAPPING_OPERATION_TYPE { + LsaSidNameMappingOperation_Add, + LsaSidNameMappingOperation_Remove, + LsaSidNameMappingOperation_AddMultiple, +} LSA_SID_NAME_MAPPING_OPERATION_TYPE, *PLSA_SID_NAME_MAPPING_OPERATION_TYPE; + +typedef enum _LSA_SID_NAME_MAPPING_OPERATION_ERROR { + LsaSidNameMappingOperation_Success, + LsaSidNameMappingOperation_NonMappingError, + LsaSidNameMappingOperation_NameCollision, + LsaSidNameMappingOperation_SidCollision, + LsaSidNameMappingOperation_DomainNotFound, + LsaSidNameMappingOperation_DomainSidPrefixMismatch, + LsaSidNameMappingOperation_MappingNotFound, +} LSA_SID_NAME_MAPPING_OPERATION_ERROR, *PLSA_SID_NAME_MAPPING_OPERATION_ERROR; + +typedef struct _LSA_SID_NAME_MAPPING_OPERATION_ADD_INPUT { + UNICODE_STRING DomainName; + UNICODE_STRING AccountName; + PSID Sid; + ULONG Flags; +} LSA_SID_NAME_MAPPING_OPERATION_ADD_INPUT, *PLSA_SID_NAME_MAPPING_OPERATION_ADD_INPUT; + +typedef struct _LSA_SID_NAME_MAPPING_OPERATION_REMOVE_INPUT { + UNICODE_STRING DomainName; + UNICODE_STRING AccountName; +} LSA_SID_NAME_MAPPING_OPERATION_REMOVE_INPUT, *PLSA_SID_NAME_MAPPING_OPERATION_REMOVE_INPUT; + +typedef union _LSA_SID_NAME_MAPPING_OPERATION_INPUT { + LSA_SID_NAME_MAPPING_OPERATION_ADD_INPUT AddInput; + LSA_SID_NAME_MAPPING_OPERATION_REMOVE_INPUT RemoveInput; +} LSA_SID_NAME_MAPPING_OPERATION_INPUT, *PLSA_SID_NAME_MAPPING_OPERATION_INPUT; + +typedef struct _LSA_SID_NAME_MAPPING_OPERATION_GENERIC_OUTPUT { + LSA_SID_NAME_MAPPING_OPERATION_ERROR ErrorCode; +} LSA_SID_NAME_MAPPING_OPERATION_GENERIC_OUTPUT, *PLSA_SID_NAME_MAPPING_OPERATION_GENERIC_OUTPUT; + +typedef LSA_SID_NAME_MAPPING_OPERATION_GENERIC_OUTPUT LSA_SID_NAME_MAPPING_OPERATION_ADD_OUTPUT, *PLSA_SID_NAME_MAPPING_OPERATION_ADD_OUTPUT; +typedef LSA_SID_NAME_MAPPING_OPERATION_GENERIC_OUTPUT LSA_SID_NAME_MAPPING_OPERATION_REMOVE_OUTPUT, *PLSA_SID_NAME_MAPPING_OPERATION_REMOVE_OUTPUT; + +typedef union _LSA_SID_NAME_MAPPING_OPERATION_OUTPUT { + LSA_SID_NAME_MAPPING_OPERATION_ADD_OUTPUT AddOutput; + LSA_SID_NAME_MAPPING_OPERATION_REMOVE_OUTPUT RemoveOutput; +} LSA_SID_NAME_MAPPING_OPERATION_OUTPUT, *PLSA_SID_NAME_MAPPING_OPERATION_OUTPUT; + +NTSTATUS WINAPI LsaManageSidNameMapping( + LSA_SID_NAME_MAPPING_OPERATION_TYPE OpType, + PLSA_SID_NAME_MAPPING_OPERATION_INPUT OpInput, + PLSA_SID_NAME_MAPPING_OPERATION_OUTPUT *OpOutput +); + +VOID WINAPI RtlInitUnicodeString( + PUNICODE_STRING DestinationString, + PCWSTR SourceString +); \ No newline at end of file diff --git a/contrib/win32/win32compat/win32_usertoken_utils.c b/contrib/win32/win32compat/win32_usertoken_utils.c index 3f0350ca5..e455088f3 100644 --- a/contrib/win32/win32compat/win32_usertoken_utils.c +++ b/contrib/win32/win32compat/win32_usertoken_utils.c @@ -40,8 +40,11 @@ #include "inc\utf.h" #include "logonuser.h" #include +#include +#include #include #include "misc_internal.h" +#include "lsa_missingdefs.h" #include "Debug.h" #pragma warning(push, 3) @@ -118,7 +121,7 @@ done: #define MAX_PW_LEN 64 static HANDLE -generate_user_token(wchar_t* user_cpn) { +generate_s4u_user_token(wchar_t* user_cpn) { HANDLE lsa_handle = 0, token = 0; LSA_OPERATIONAL_MODE mode; ULONG auth_package_id; @@ -329,6 +332,8 @@ done: return token; } +HANDLE generate_sshd_virtual_token(); + HANDLE get_user_token(char* user) { HANDLE token = NULL; @@ -339,12 +344,18 @@ get_user_token(char* user) { goto done; } - if ((token = generate_user_token(user_utf16)) == 0) { + if (wcscmp(user_utf16, L"sshd") == 0) { + if ((token = generate_sshd_virtual_token()) != 0) + goto done; + debug3("unable to generate sshd virtual token, falling back to s4u"); + } + + if ((token = generate_s4u_user_token(user_utf16)) == 0) { error("unable to generate token for user %ls", user_utf16); /* work around for https://github.com/PowerShell/Win32-OpenSSH/issues/727 by doing a fake login */ LogonUserExExWHelper(L"FakeUser", L"FakeDomain", L"FakePasswd", LOGON32_LOGON_NETWORK_CLEARTEXT, LOGON32_PROVIDER_DEFAULT, NULL, &token, NULL, NULL, NULL, NULL); - if ((token = generate_user_token(user_utf16)) == 0) { + if ((token = generate_s4u_user_token(user_utf16)) == 0) { error("unable to generate token on 2nd attempt for user %ls", user_utf16); goto done; } @@ -382,4 +393,199 @@ done: return r; } + +/* *** virtual account token generation logic ***/ + +char* LSAMappingErrorDetails[] = { + "LsaSidNameMappingOperation_Success", + "LsaSidNameMappingOperation_NonMappingError", + "LsaSidNameMappingOperation_NameCollision", + "LsaSidNameMappingOperation_SidCollision", + "LsaSidNameMappingOperation_DomainNotFound", + "LsaSidNameMappingOperation_DomainSidPrefixMismatch", + "LsaSidNameMappingOperation_MappingNotFound" +}; + +#define VIRTUALUSER_DOMAIN L"VIRTUAL USERS" +#define VIRTUALUSER_GROUP_NAME L"ALL VIRTUAL USERS" + +/* returns 0 on success -1 on failure */ +int +AddSidMappingToLsa(PUNICODE_STRING domain_name, + PUNICODE_STRING account_name, + PSID sid) +{ + LSA_SID_NAME_MAPPING_OPERATION_INPUT input = { 0 }; + PLSA_SID_NAME_MAPPING_OPERATION_OUTPUT p_output = NULL; + LSA_SID_NAME_MAPPING_OPERATION_ERROR op_result = + LsaSidNameMappingOperation_NonMappingError; + NTSTATUS status = STATUS_SUCCESS; + int ret = 0; + + input.AddInput.DomainName = *domain_name; + if (account_name) + input.AddInput.AccountName = *account_name; + input.AddInput.Sid = sid; + + status = LsaManageSidNameMapping(LsaSidNameMappingOperation_Add, + &input, + &p_output); + if (status != STATUS_SUCCESS) { + ret = -1; + if (p_output) { + op_result = p_output->AddOutput.ErrorCode; + if (op_result == LsaSidNameMappingOperation_NameCollision || op_result == LsaSidNameMappingOperation_SidCollision) + ret = 0; /* OK as it failed due to collision */ + else + error("LsaManageSidNameMapping failed with : %s \n", LSAMappingErrorDetails[op_result]); + } + else + error("LsaManageSidNameMapping failed with ntstatus: %d \n", status); + } + + if (p_output) + LsaFreeMemory(p_output); + + return ret; +} + + +int RemoveVirtualAccountLSAMapping(PUNICODE_STRING domain_name, + PUNICODE_STRING account_name) +{ + int ret = 0; + + LSA_SID_NAME_MAPPING_OPERATION_INPUT input = { 0 }; + PLSA_SID_NAME_MAPPING_OPERATION_OUTPUT p_output = NULL; + PLSA_SID_NAME_MAPPING_OPERATION_REMOVE_INPUT remove_input = &input.RemoveInput; + + remove_input->DomainName = *domain_name; + if (account_name) + remove_input->AccountName = *account_name; + + NTSTATUS status = LsaManageSidNameMapping(LsaSidNameMappingOperation_Remove, + &input, + &p_output); + if (status != STATUS_SUCCESS) + ret = -1; + + if (p_output) + LsaFreeMemory(p_output); + + return ret; +} + +HANDLE generate_sshd_virtual_token() +{ + SID_IDENTIFIER_AUTHORITY nt_authority = SECURITY_NT_AUTHORITY; + UNICODE_STRING domain, group, account; + WCHAR va_name[32]; /* enough to accomodate sshd_123457890 */ + + PSID sid_domain = NULL, sid_group = NULL, sid_user = NULL; + HANDLE va_token = 0, va_token_restricted = 0; + + StringCchPrintfW(va_name, 32, L"%s_%d", L"sshd", GetCurrentProcessId()); + + RtlInitUnicodeString(&domain, VIRTUALUSER_DOMAIN); + RtlInitUnicodeString(&group, VIRTUALUSER_GROUP_NAME); + RtlInitUnicodeString(&account, va_name); + + /* Initialize SIDs */ + /* domain SID - S-1-5-111 */ + if (!(AllocateAndInitializeSid(&nt_authority, + 1, + 111, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + &sid_domain))) + goto cleanup; + + /* group SID - S-1-5-111-0 */ + if (!(AllocateAndInitializeSid(&nt_authority, + 2, + 111, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + &sid_group))) + goto cleanup; + + /* + * account SID + * this is derived from higher RIDs in sshd service account SID to ensure there are no conflicts + * S-1-5-80-3847866527-469524349-687026318-516638107-1125189541 (Well Known group: NT SERVICE\sshd) + * Ex account SID - S-1-5-111-3847866527-469524349-687026318-516638107-1125189541-123 + */ + if (!(AllocateAndInitializeSid(&nt_authority, + 7, + 111, + 3847866527, + 469524349, + 687026318, + 516638107, + 1125189541, + GetCurrentProcessId(), + 0, + &sid_user))) + goto cleanup; + + /* Map the domain SID */ + if (AddSidMappingToLsa(&domain, NULL, sid_domain) != 0) + goto cleanup; + + /* Map the group SID */ + if (AddSidMappingToLsa(&domain, &group, sid_group) != 0) + goto cleanup; + + /* Map the user SID */ + if (AddSidMappingToLsa(&domain, &account, sid_user) != 0) + goto cleanup; + + /* Logon virtual and create token */ + if (!LogonUserExExWHelper( + va_name, + VIRTUALUSER_DOMAIN, + L"", + LOGON32_LOGON_INTERACTIVE, + LOGON32_PROVIDER_VIRTUAL, + NULL, + &va_token, + NULL, + NULL, + NULL, + NULL)) { + debug3("LogonUserExExW failed with %d \n", GetLastError()); + goto cleanup; + } + + /* remove all privileges */ + if (!CreateRestrictedToken(va_token, DISABLE_MAX_PRIVILEGE, 0, NULL, 0, NULL, 0, NULL, &va_token_restricted )) + debug3("CreateRestrictedToken failed with %d \n", GetLastError()); + + CloseHandle(va_token); + +cleanup: + RemoveVirtualAccountLSAMapping(&domain, &account); + + if (sid_domain) + FreeSid(sid_domain); + if (sid_user) + FreeSid(sid_user); + if (sid_group) + FreeSid(sid_group); + + return va_token_restricted; +} + + + #pragma warning(pop) \ No newline at end of file diff --git a/contrib/win32/win32compat/wmain_sshd.c b/contrib/win32/win32compat/wmain_sshd.c index b90d7120f..1072839c8 100644 --- a/contrib/win32/win32compat/wmain_sshd.c +++ b/contrib/win32/win32compat/wmain_sshd.c @@ -129,7 +129,7 @@ generate_host_keys() ui.usri1_priv = USER_PRIV_USER; ui.usri1_home_dir = NULL; ui.usri1_comment = NULL; - ui.usri1_flags = UF_SCRIPT; + ui.usri1_flags = UF_SCRIPT | UF_DONT_EXPIRE_PASSWD; ui.usri1_script_path = NULL; NetUserAdd(NULL, 1, (LPBYTE)&ui, &dwError);