Fix agent to recognize calls from sshd service (#149)

https://github.com/PowerShell/Win32-OpenSSH/issues/734
This commit is contained in:
Manoj Ampalam 2017-05-24 21:52:20 -07:00 committed by GitHub
parent 2060a413d5
commit 4df71693c2
10 changed files with 209 additions and 77 deletions

View File

@ -82,13 +82,7 @@ function Adjust-HostKeyFileACL
("Everyone", "Read", "None", "None", "Allow")
$myACL.AddAccessRule($everyoneAce)
}
else
{
#this only is needed when the private host keys are not registered with agent
$sshdAce = New-Object System.Security.AccessControl.FileSystemAccessRule `
("NT service\sshd", "Read", "None", "None", "Allow")
$myACL.AddAccessRule($sshdAce)
}
Set-Acl -Path $FilePath -AclObject $myACL
}

View File

@ -165,6 +165,14 @@ WARNING: Following changes will be made to OpenSSH configuration
(Get-Content $_.FullName -Raw).Replace("`r`n","`n") | Set-Content $_.FullName -Force
Adjust-HostKeyFileACL -FilePath $_.FullName
}
#register host keys with agent
Get-ChildItem "$($script:OpenSSHBinPath)\sshtest*hostkey*"| % {
if (-not ($_.Name.EndsWith(".pub"))) {
$cmd = "cmd /c `"$env:ProgramData\chocolatey\lib\sysinternals\tools\psexec -accepteula -nobanner -s -w $($script:OpenSSHBinPath) ssh-add $_ 2> tmp.txt`""
iex $cmd
}
}
Restart-Service sshd -Force
#Backup existing known_hosts and replace with test version
@ -305,6 +313,13 @@ function Cleanup-OpenSSHTestEnvironment
Throw "Cannot find OpenSSH binaries under $script:OpenSSHBinPath. "
}
#unregister test host keys from agent
Get-ChildItem "$($script:OpenSSHBinPath)\sshtest*hostkey*.pub"| % {
$cmd = "cmd /c `"$env:ProgramData\chocolatey\lib\sysinternals\tools\psexec -accepteula -nobanner -s -w $($script:OpenSSHBinPath) ssh-add -d $_ 2> tmp.txt`""
iex $cmd
}
#Restore sshd_config
$backupConfigPath = Join-Path $Script:OpenSSHBinPath sshd_config.ori
if (Test-Path $backupConfigPath -PathType Leaf) {

View File

@ -31,7 +31,9 @@
*/
#include "agent.h"
#include "..\misc_internal.h"
#pragma warning(push, 3)
int scm_start_service(DWORD, LPWSTR*);
@ -133,7 +135,6 @@ wmain(int argc, wchar_t **argv)
/* - just start ssh-agent service if needed */
{
SC_HANDLE sc_handle, svc_handle;
DWORD err;
if ((sc_handle = OpenSCManagerW(NULL, NULL, SERVICE_START)) == NULL ||
(svc_handle = OpenServiceW(sc_handle, L"ssh-agent", SERVICE_START)) == NULL) {
@ -168,3 +169,4 @@ scm_start_service(DWORD num, LPWSTR* args)
return 0;
}
#pragma warning(pop)

View File

@ -110,9 +110,7 @@ agent_listen_loop()
if (GetLastError() == ERROR_PIPE_CONNECTED) {
debug("Client has already connected");
SetEvent(ol.hEvent);
}
if (GetLastError() != ERROR_IO_PENDING) {
} else if (GetLastError() != ERROR_IO_PENDING) {
debug("ConnectNamedPipe failed ERROR: %d", GetLastError());
SetEvent(event_stop_agent);
}

View File

@ -1,5 +1,7 @@
#include <Windows.h>
#include <stdio.h>
#define __attribute__(A)
#include "log.h"
#define MAX_MESSAGE_SIZE 256 * 1024
#define SSH_ROOT L"SOFTWARE\\SSH"
@ -25,10 +27,13 @@ struct agent_connection {
WRITING,
DONE
} state;
enum {
enum { /* retain this order */
UNKNOWN = 0,
USER, /* client is running as some user */
MACHINE /* clinet is running as machine - System, NS or LS */
NONADMIN_USER, /* client is running as a nonadmin user */
ADMIN_USER, /* client is running as admin */
SSHD_SERVICE, /* client is sshd service */
SYSTEM, /* client is running as System */
SERVICE, /* client is running as LS or NS */
} client_type;
HANDLE auth_token;
HANDLE hProfile;

View File

@ -48,13 +48,15 @@
#include <utf.h>
#pragma warning(push, 3)
Buffer cfg;
ServerOptions options;
struct passwd *privsep_pw = NULL;
static char *config_file_name = _PATH_SERVER_CONFIG_FILE;
int auth_sock = -1;
int
int
auth2_methods_valid(const char * c, int i) {
return 1;
}
@ -97,12 +99,16 @@ int
load_config() {
wchar_t basePath[PATH_MAX] = { 0 };
wchar_t path[PATH_MAX] = { 0 };
wchar_t* config_file = L"/sshd_config";
if (GetCurrentModulePath(basePath, PATH_MAX) == -1)
return -1;
wcsncpy(path, basePath, PATH_MAX);
wcsncat(path, L"/sshd_config", PATH_MAX);
if (wcslen(basePath) + wcslen(config_file) + 1 > PATH_MAX)
fatal("unexpected config file path length");
wcsncpy_s(path, PATH_MAX, basePath, PATH_MAX);
wcsncat_s(path, PATH_MAX, L"/sshd_config", PATH_MAX - wcslen(basePath));
if ((config_file_name = utf16_to_utf8(path)) == NULL)
return -1;
@ -129,4 +135,6 @@ pubkey_allowed(struct sshkey* pubkey, HANDLE user_token) {
return 0;
return user_key_allowed(pw, pubkey, 1);
}
}
#pragma warning(pop)

View File

@ -40,6 +40,11 @@
#include "key.h"
#include "inc\utf.h"
#pragma warning(push, 3)
int
pubkey_allowed(struct sshkey*, HANDLE);
static void
InitLsaString(LSA_STRING *lsa_string, const char *str)
{
@ -47,7 +52,7 @@ InitLsaString(LSA_STRING *lsa_string, const char *str)
memset(lsa_string, 0, sizeof(LSA_STRING));
else {
lsa_string->Buffer = (char *)str;
lsa_string->Length = strlen(str);
lsa_string->Length = (USHORT)strlen(str);
lsa_string->MaximumLength = lsa_string->Length + 1;
}
}
@ -146,7 +151,7 @@ generate_user_token(wchar_t* user_cpn) {
s4u_logon = (KERB_S4U_LOGON*)logon_info;
s4u_logon->MessageType = KerbS4ULogon;
s4u_logon->Flags = 0;
s4u_logon->ClientUpn.Length = wcslen(user_cpn) * 2;
s4u_logon->ClientUpn.Length = (USHORT)wcslen(user_cpn) * 2;
s4u_logon->ClientUpn.MaximumLength = s4u_logon->ClientUpn.Length;
s4u_logon->ClientUpn.Buffer = (WCHAR*)(s4u_logon + 1);
memcpy(s4u_logon->ClientUpn.Buffer, user_cpn, s4u_logon->ClientUpn.Length + 2);
@ -164,7 +169,7 @@ generate_user_token(wchar_t* user_cpn) {
s4u_logon = (MSV1_0_S4U_LOGON*)logon_info;
s4u_logon->MessageType = MsV1_0S4ULogon;
s4u_logon->Flags = 0;
s4u_logon->UserPrincipalName.Length = wcslen(user_cpn) * 2;
s4u_logon->UserPrincipalName.Length = (USHORT)wcslen(user_cpn) * 2;
s4u_logon->UserPrincipalName.MaximumLength = s4u_logon->UserPrincipalName.Length;
s4u_logon->UserPrincipalName.Buffer = (WCHAR*)(s4u_logon + 1);
memcpy(s4u_logon->UserPrincipalName.Buffer, user_cpn, s4u_logon->UserPrincipalName.Length + 2);
@ -184,7 +189,7 @@ generate_user_token(wchar_t* user_cpn) {
Network,
auth_package_id,
logon_info,
logon_info_size,
(ULONG)logon_info_size,
NULL,
&sourceContext,
(PVOID*)&pProfile,
@ -208,14 +213,39 @@ done:
return token;
}
static HANDLE
duplicate_token_for_client(struct agent_connection* con, HANDLE t) {
ULONG client_pid;
HANDLE client_proc = NULL, dup_t = NULL;
/* Should the token match client's session id?
ULONG client_sessionId;
if (GetNamedPipeClientSessionId(con->pipe_handle, &client_sessionId) == FALSE ||
SetTokenInformation(t, TokenSessionId, &client_sessionId, sizeof(client_sessionId)) == FALSE) {
error("unable to set token session id, error: %d", GetLastError());
goto done;
}*/
if ((FALSE == GetNamedPipeClientProcessId(con->pipe_handle, &client_pid)) ||
((client_proc = OpenProcess(PROCESS_DUP_HANDLE, FALSE, client_pid)) == NULL) ||
DuplicateHandle(GetCurrentProcess(), t, client_proc, &dup_t, TOKEN_QUERY | TOKEN_IMPERSONATE, FALSE, DUPLICATE_SAME_ACCESS) == FALSE ) {
error("failed to duplicate user token");
goto done;
}
done:
if (client_proc)
CloseHandle(client_proc);
return dup_t;
}
/* TODO - SecureZeroMemory password */
int process_passwordauth_request(struct sshbuf* request, struct sshbuf* response, struct agent_connection* con) {
char *user = NULL, *domain = NULL, *pwd = NULL;
size_t user_len, pwd_len;
wchar_t *user_utf16 = NULL, *udom_utf16 = NULL, *pwd_utf16 = NULL, *tmp;
int r = -1;
HANDLE token = 0, dup_token, client_proc = 0;
ULONG client_pid;
HANDLE token = 0, dup_token;
if (sshbuf_get_cstring(request, &user, &user_len) != 0 ||
sshbuf_get_cstring(request, &pwd, &pwd_len) != 0 ||
@ -243,13 +273,11 @@ int process_passwordauth_request(struct sshbuf* request, struct sshbuf* response
goto done;
}
if ((FALSE == GetNamedPipeClientProcessId(con->pipe_handle, &client_pid)) ||
((client_proc = OpenProcess(PROCESS_DUP_HANDLE, FALSE, client_pid)) == NULL) ||
(FALSE == DuplicateHandle(GetCurrentProcess(), token, client_proc, &dup_token, TOKEN_QUERY | TOKEN_IMPERSONATE, FALSE, DUPLICATE_SAME_ACCESS)) ||
(sshbuf_put_u32(response, (int)(intptr_t)dup_token) != 0)) {
debug("failed to duplicate user token");
if ((dup_token = duplicate_token_for_client(con, token)) == NULL)
goto done;
if (sshbuf_put_u32(response, (int)(intptr_t)dup_token) != 0)
goto done;
}
con->auth_token = token;
LoadProfile(con, user_utf16, udom_utf16);
@ -267,8 +295,6 @@ done:
free(user_utf16);
if (pwd_utf16)
free(pwd_utf16);
if (client_proc)
CloseHandle(client_proc);
return r;
}
@ -278,11 +304,10 @@ int process_pubkeyauth_request(struct sshbuf* request, struct sshbuf* response,
char *key_blob, *user, *sig, *blob;
size_t key_blob_len, user_len, sig_len, blob_len;
struct sshkey *key = NULL;
HANDLE token = NULL, dup_token = NULL, client_proc = NULL;
HANDLE token = NULL, dup_token = NULL;
wchar_t *user_utf16 = NULL, *udom_utf16 = NULL, *tmp;
PWSTR wuser_home = NULL;
ULONG client_pid;
LUID_AND_ATTRIBUTES priv_to_delete[1];
user = NULL;
if (sshbuf_get_string_direct(request, &key_blob, &key_blob_len) != 0 ||
@ -301,7 +326,7 @@ int process_pubkeyauth_request(struct sshbuf* request, struct sshbuf* response,
}
if ((token = generate_user_token(user_utf16)) == 0) {
debug("unable to generate token for user %ls", user_utf16);
error("unable to generate token for user %ls", user_utf16);
goto done;
}
@ -311,18 +336,16 @@ int process_pubkeyauth_request(struct sshbuf* request, struct sshbuf* response,
goto done;
}
if (key_verify(key, sig, sig_len, blob, blob_len) != 1) {
if (key_verify(key, sig, (u_int)sig_len, blob, (u_int)blob_len) != 1) {
debug("signature verification failed");
goto done;
}
if ((FALSE == GetNamedPipeClientProcessId(con->pipe_handle, &client_pid)) ||
( (client_proc = OpenProcess(PROCESS_DUP_HANDLE, FALSE, client_pid)) == NULL) ||
(FALSE == DuplicateHandle(GetCurrentProcess(), token, client_proc, &dup_token, TOKEN_QUERY | TOKEN_IMPERSONATE, FALSE, DUPLICATE_SAME_ACCESS)) ||
(sshbuf_put_u32(response, (int)(intptr_t)dup_token) != 0)) {
debug("failed to authorize user");
if ((dup_token = duplicate_token_for_client(con, token)) == NULL)
goto done;
if (sshbuf_put_u32(response, (int)(intptr_t)dup_token) != 0)
goto done;
}
con->auth_token = token;
token = NULL;
@ -346,8 +369,6 @@ done:
sshkey_free(key);
if (wuser_home)
CoTaskMemFree(wuser_home);
if (client_proc)
CloseHandle(client_proc);
if (token)
CloseHandle(token);
return r;
@ -361,6 +382,12 @@ int process_authagent_request(struct sshbuf* request, struct sshbuf* response, s
return -1;
}
/* allow only admins and NT Service\sshd to send auth requests */
if (con->client_type != SSHD_SERVICE && con->client_type != ADMIN_USER) {
error("cannot authenticate: client process is not admin or sshd");
return -1;
}
if (memcmp(opn, PUBKEY_AUTH_REQUEST, opn_len) == 0)
return process_pubkeyauth_request(request, response, con);
else if (memcmp(opn, PASSWD_AUTH_REQUEST, opn_len) == 0)
@ -369,4 +396,6 @@ int process_authagent_request(struct sshbuf* request, struct sshbuf* response, s
debug("unknown auth request: %s", opn);
return -1;
}
}
}
#pragma warning(pop)

View File

@ -31,6 +31,8 @@
#include "agent.h"
#include "agent-request.h"
#pragma warning(push, 3)
int process_request(struct agent_connection*);
#define ABORT_CONNECTION_RETURN(c) do { \
@ -114,20 +116,41 @@ agent_connection_disconnect(struct agent_connection* con)
DisconnectNamedPipe(con->pipe_handle);
}
static char*
con_type_to_string(struct agent_connection* con) {
switch (con->client_type) {
case UNKNOWN:
return "unknown";
case NONADMIN_USER:
return "restricted user";
case ADMIN_USER:
return "administrator";
case SSHD_SERVICE:
return "sshd service";
case SYSTEM:
return "system";
case SERVICE:
return "service";
default:
return "unexpected";
}
}
static int
get_con_client_type(struct agent_connection* con)
{
int r = -1;
char system_sid[SECURITY_MAX_SID_SIZE];
char ns_sid[SECURITY_MAX_SID_SIZE];
char ls_sid[SECURITY_MAX_SID_SIZE];
char sid[SECURITY_MAX_SID_SIZE];
wchar_t *sshd_act = L"NT SERVICE\\SSHD", *ref_dom = NULL;
DWORD reg_dom_len = 0, info_len = 0, sid_size;
DWORD sshd_sid_len = 0;
PSID sshd_sid = NULL;
SID_NAME_USE nuse;
HANDLE token;
TOKEN_USER* info = NULL;
HANDLE pipe = con->pipe_handle;
BOOL isMember = FALSE;
if (ImpersonateNamedPipeClient(pipe) == FALSE)
if (ImpersonateNamedPipeClient(con->pipe_handle) == FALSE)
return -1;
if (OpenThreadToken(GetCurrentThread(), TOKEN_QUERY, FALSE, &token) == FALSE ||
@ -136,26 +159,76 @@ get_con_client_type(struct agent_connection* con)
GetTokenInformation(token, TokenUser, info, info_len, &info_len) == FALSE)
goto done;
sid_size = SECURITY_MAX_SID_SIZE;
if (CreateWellKnownSid(WinLocalSystemSid, NULL, system_sid, &sid_size) == FALSE)
goto done;
sid_size = SECURITY_MAX_SID_SIZE;
if (CreateWellKnownSid(WinNetworkServiceSid, NULL, ns_sid, &sid_size) == FALSE)
goto done;
sid_size = SECURITY_MAX_SID_SIZE;
if (CreateWellKnownSid(WinLocalServiceSid, NULL, ls_sid, &sid_size) == FALSE)
/* check if its localsystem */
if (IsWellKnownSid(info->User.Sid, WinLocalSystemSid)) {
con->client_type = SYSTEM;
r = 0;
goto done;
}
if (EqualSid(info->User.Sid, system_sid) ||
EqualSid(info->User.Sid, ls_sid) ||
EqualSid(info->User.Sid, ns_sid))
con->client_type = MACHINE;
else
con->client_type = USER;
/* check if its SSHD service */
{
/* Does NT Service/SSHD exist */
LookupAccountNameW(NULL, sshd_act, NULL, &sshd_sid_len, NULL, &reg_dom_len, &nuse);
if (GetLastError() == ERROR_NONE_MAPPED)
debug3("Cannot look up SSHD account, its likely not installed");
else if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
error("LookupAccountNameW on SSHD account failed with %d", GetLastError());
goto done;
} else {
if ((sshd_sid = malloc(sshd_sid_len)) == NULL ||
(ref_dom = (wchar_t*)malloc(reg_dom_len * 2)) == NULL ||
LookupAccountNameW(NULL, sshd_act, sshd_sid, &sshd_sid_len, ref_dom, &reg_dom_len, &nuse) == FALSE)
goto done;
debug2("client type: %s", con->client_type == MACHINE? "machine" : "user");
if (EqualSid(info->User.Sid, sshd_sid)) {
con->client_type = SSHD_SERVICE;
r = 0;
goto done;
}
if (CheckTokenMembership(token, sshd_sid, &isMember) == FALSE)
goto done;
if (isMember) {
con->client_type = SSHD_SERVICE;
r = 0;
goto done;
}
}
}
/* check if its LS or NS */
if (IsWellKnownSid(info->User.Sid, WinNetworkServiceSid) ||
IsWellKnownSid(info->User.Sid, WinLocalServiceSid)) {
con->client_type = SERVICE;
r = 0;
goto done;
}
/* check if its admin */
{
sid_size = SECURITY_MAX_SID_SIZE;
if (CreateWellKnownSid(WinBuiltinAdministratorsSid, NULL, sid, &sid_size) == FALSE)
goto done;
if (CheckTokenMembership(token, sid, &isMember) == FALSE)
goto done;
if (isMember) {
con->client_type = ADMIN_USER;
r = 0;
goto done;
}
}
/* none of above */
con->client_type = NONADMIN_USER;
r = 0;
done:
debug("client type: %s", con_type_to_string(con));
if (sshd_sid)
free(sshd_sid);
if (ref_dom)
free(ref_dom);
if (info)
free(info);
RevertToSelf();
@ -214,13 +287,15 @@ done:
ZeroMemory(&con->io_buf, sizeof(con->io_buf));
if (r == 0) {
POKE_U32(con->io_buf.buf, sshbuf_len(response));
POKE_U32(con->io_buf.buf, (u_int32_t)sshbuf_len(response));
memcpy(con->io_buf.buf + 4, sshbuf_ptr(response), sshbuf_len(response));
con->io_buf.num_bytes = sshbuf_len(response) + 4;
con->io_buf.num_bytes = (DWORD)sshbuf_len(response) + 4;
}
if (response)
sshbuf_free(response);
return r;
}
}
#pragma warning(pop)

View File

@ -33,6 +33,8 @@
#include "agent-request.h"
#include <sddl.h>
#pragma warning(push, 3)
#define MAX_KEY_LENGTH 255
#define MAX_VALUE_NAME 16383
@ -48,7 +50,7 @@ get_user_root(struct agent_connection* con, HKEY *root)
LONG ret;
*root = HKEY_LOCAL_MACHINE;
if (con->client_type == USER) {
if (con->client_type <= ADMIN_USER) {
if (ImpersonateNamedPipeClient(con->pipe_handle) == FALSE)
return -1;
*root = NULL;
@ -71,7 +73,7 @@ convert_blob(struct agent_connection* con, const char *blob, DWORD blen, char **
int success = 0;
DATA_BLOB in, out;
if (con->client_type == USER)
if (con->client_type <= ADMIN_USER)
if (ImpersonateNamedPipeClient(con->pipe_handle) == FALSE)
return -1;
@ -102,7 +104,7 @@ convert_blob(struct agent_connection* con, const char *blob, DWORD blen, char **
done:
if (out.pbData)
LocalFree(out.pbData);
if (con->client_type == USER)
if (con->client_type <= ADMIN_USER)
RevertToSelf();
return success? 0: -1;
}
@ -123,6 +125,7 @@ process_add_identity(struct sshbuf* request, struct sshbuf* response, struct age
SECURITY_ATTRIBUTES sa;
/* parse input request */
memset(&sa, 0, sizeof(SECURITY_ATTRIBUTES));
blob = sshbuf_ptr(request);
if (sshkey_private_deserialize(request, &key) != 0 ||
(blob_len = (sshbuf_ptr(request) - blob) & 0xffffffff) == 0 ||
@ -142,9 +145,9 @@ process_add_identity(struct sshbuf* request, struct sshbuf* response, struct age
RegCreateKeyExW(user_root, SSH_KEYS_ROOT, 0, 0, 0, KEY_WRITE | KEY_WOW64_64KEY, &sa, &reg, NULL) != 0 ||
RegCreateKeyExA(reg, thumbprint, 0, 0, 0, KEY_WRITE | KEY_WOW64_64KEY, &sa, &sub, NULL) != 0 ||
RegSetValueExW(sub, NULL, 0, REG_BINARY, eblob, eblob_len) != 0 ||
RegSetValueExW(sub, L"pub", 0, REG_BINARY, pubkey_blob, pubkey_blob_len) != 0 ||
RegSetValueExW(sub, L"pub", 0, REG_BINARY, pubkey_blob, (DWORD)pubkey_blob_len) != 0 ||
RegSetValueExW(sub, L"type", 0, REG_DWORD, (BYTE*)&key->type, 4) != 0 ||
RegSetValueExW(sub, L"comment", 0, REG_BINARY, comment, comment_len) != 0 ) {
RegSetValueExW(sub, L"comment", 0, REG_BINARY, comment, (DWORD)comment_len) != 0 ) {
debug("failed to add key to store");
goto done;
}
@ -450,4 +453,6 @@ int process_keyagent_request(struct sshbuf* request, struct sshbuf* response, st
debug("unknown key agent request %d", type);
return -1;
}
}
}
#pragma warning(pop)

3
sshd.c
View File

@ -1677,7 +1677,8 @@ main(int ac, char **av)
* For windows, enable logging right away to capture failures while loading private host keys.
* On Unix, logging at configured level is not done until private host keys are loaded. Why??
*/
log_init(__progname, options.log_level, options.log_facility, log_stderr);
if (!debug_flag)
log_init(__progname, options.log_level, options.log_facility, log_stderr);
#endif // WINDOWS
/* challenge-response is implemented via keyboard interactive */