Skip to content

Commit b36bc85

Browse files
authored
Update ssh folder permissions check in SSHD (#761)
* remove check on progdata/ssh/log folder permissions * add pester test * modify permissions check to log event without failing startup * modify perm check * update test * uncomment code * modify pester test * address review feedback * address review feedback * fix multi-line logging * cleanup allocations * address review feedback * address additional review feedback * store value in tmp var
1 parent 7baad0a commit b36bc85

File tree

7 files changed

+231
-76
lines changed

7 files changed

+231
-76
lines changed

contrib/win32/win32compat/misc.c

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1419,7 +1419,7 @@ is_absolute_path(const char *path)
14191419

14201420
/* return -1 - in case of failure, 0 - success */
14211421
int
1422-
create_directory_withsddl(wchar_t *path_w, wchar_t *sddl_w)
1422+
create_directory_withsddl(wchar_t *path_w, wchar_t *sddl_w, BOOL check_permissions)
14231423
{
14241424
if (GetFileAttributesW(path_w) == INVALID_FILE_ATTRIBUTES) {
14251425
PSECURITY_DESCRIPTOR pSD = NULL;
@@ -1444,12 +1444,9 @@ create_directory_withsddl(wchar_t *path_w, wchar_t *sddl_w)
14441444
return -1;
14451445
}
14461446
}
1447-
else {
1447+
else if (check_permissions) {
14481448
// directory already exists; need to confirm permissions are correct
1449-
if (check_secure_folder_permission(path_w, 1) != 0) {
1450-
error("Directory already exists but folder permissions are invalid");
1451-
return -1;
1452-
}
1449+
check_secure_folder_permission(path_w, 1);
14531450
}
14541451

14551452
return 0;

contrib/win32/win32compat/misc_internal.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ void to_lower_case(char *s);
6767
void to_wlower_case(wchar_t *s);
6868
HANDLE get_user_token(const char* user, int impersonation);
6969
int load_user_profile(HANDLE user_token, char* user);
70-
int create_directory_withsddl(wchar_t *path, wchar_t *sddl);
70+
int create_directory_withsddl(wchar_t *path, wchar_t *sddl, BOOL check_permissions);
7171
int is_absolute_path(const char *);
7272
int file_in_chroot_jail(HANDLE);
7373
PSID lookup_sid(const wchar_t* name_utf16, PSID psid, DWORD * psid_len);

contrib/win32/win32compat/w32-sshfileperm.c

Lines changed: 118 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,21 @@
3333
#include <Aclapi.h>
3434
#include <lm.h>
3535
#include <stdio.h>
36+
#include <stdlib.h>
37+
#include <string.h>
3638

3739
#include "inc\pwd.h"
3840
#include "sshfileperm.h"
3941
#include "debug.h"
4042
#include "misc_internal.h"
4143
#include "config.h"
4244

45+
#define NULL_TERMINATOR_LEN 1
46+
#define COMMA_SPACE_LEN 2
47+
#define BACKSLASH_LEN 1
48+
49+
extern int log_on_stderr;
50+
4351
/*
4452
* The function is to check if current user is secure to access to the file.
4553
* Check the owner of the file is one of these types: Local Administrators groups, system account, current user account
@@ -178,37 +186,38 @@ check_secure_file_permission(const char *input_path, struct passwd * pw, int rea
178186
* Check the owner of the file is one of these types: Local Administrators groups or system account
179187
* Check the users have access permission to the file don't violate the following rules:
180188
1. no user other than local administrators group and system account have write permission on the folder
181-
* Returns 0 on success and -1 on failure
189+
* Logs a message if the rules are violated, but does not prevent further execution
182190
*/
183-
int
191+
void
184192
check_secure_folder_permission(const wchar_t* path_utf16, int read_ok)
185193
{
186194
PSECURITY_DESCRIPTOR pSD = NULL;
187195
PSID owner_sid = NULL, ti_sid = NULL;
188196
PACL dacl = NULL;
189197
DWORD error_code = ERROR_SUCCESS;
190-
BOOL is_valid_sid = FALSE, is_valid_acl = FALSE;
198+
BOOL is_valid_sid = FALSE, is_valid_acl = FALSE, need_log_msg = FALSE, is_first = TRUE;
191199
wchar_t* bad_user = NULL;
192-
int ret = 0;
200+
size_t log_msg_len = (DNLEN + BACKSLASH_LEN + UNLEN) * 2 + COMMA_SPACE_LEN + NULL_TERMINATOR_LEN;
201+
wchar_t* log_msg = (wchar_t*)malloc(log_msg_len * sizeof(wchar_t));
202+
if (log_msg != NULL) {
203+
log_msg[0] = '\0';
204+
}
193205

194206
/*Get the owner sid of the file.*/
195207
if ((error_code = GetNamedSecurityInfoW(path_utf16, SE_FILE_OBJECT,
196208
OWNER_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION,
197209
&owner_sid, NULL, &dacl, NULL, &pSD)) != ERROR_SUCCESS) {
198210
printf("failed to retrieve the owner sid and dacl of file %S with error code: %d", path_utf16, error_code);
199211
errno = EOTHER;
200-
ret = -1;
201212
goto cleanup;
202213
}
203214
if (((is_valid_sid = IsValidSid(owner_sid)) == FALSE) || ((is_valid_acl = IsValidAcl(dacl)) == FALSE)) {
204215
printf("IsValidSid: %d; is_valid_acl: %d", is_valid_sid, is_valid_acl);
205-
ret = -1;
206216
goto cleanup;
207217
}
208218
if (!IsWellKnownSid(owner_sid, WinBuiltinAdministratorsSid) &&
209219
!IsWellKnownSid(owner_sid, WinLocalSystemSid)) {
210220
printf("Bad owner on %S", path_utf16);
211-
ret = -1;
212221
goto cleanup;
213222
}
214223
/*
@@ -224,7 +233,6 @@ check_secure_folder_permission(const wchar_t* path_utf16, int read_ok)
224233
if (!GetAce(dacl, i, &current_ace)) {
225234
printf("GetAce() failed");
226235
errno = EOTHER;
227-
ret = -1;
228236
goto cleanup;
229237
}
230238

@@ -247,15 +255,112 @@ check_secure_folder_permission(const wchar_t* path_utf16, int read_ok)
247255
continue;
248256
}
249257
else {
250-
ret = -1;
258+
/* collect all SIDs with write permissions */
259+
wchar_t resolved_trustee[UNLEN + NULL_TERMINATOR_LEN] = L"UNKNOWN";
260+
wchar_t resolved_trustee_domain[DNLEN + NULL_TERMINATOR_LEN] = L"UNKNOWN";
261+
DWORD resolved_trustee_len = _countof(resolved_trustee), resolved_trustee_domain_len = _countof(resolved_trustee_domain);
262+
SID_NAME_USE resolved_trustee_type;
263+
264+
need_log_msg = TRUE;
265+
266+
if (log_msg != NULL &&
267+
LookupAccountSidW(NULL, current_trustee_sid, resolved_trustee, &resolved_trustee_len,
268+
resolved_trustee_domain, &resolved_trustee_domain_len, &resolved_trustee_type) != 0) {
269+
if (is_first) {
270+
_snwprintf_s(log_msg, log_msg_len, _TRUNCATE, L"%ls\\%ls", resolved_trustee_domain, resolved_trustee);
271+
is_first = FALSE;
272+
}
273+
else {
274+
size_t currentLength = wcslen(log_msg);
275+
size_t userLength = resolved_trustee_domain_len + BACKSLASH_LEN + resolved_trustee_len + COMMA_SPACE_LEN;
276+
if (wcslen(log_msg) + userLength + NULL_TERMINATOR_LEN > log_msg_len) {
277+
log_msg_len *= 2;
278+
wchar_t* temp_log_msg = (wchar_t*)malloc(log_msg_len * sizeof(wchar_t));
279+
if (temp_log_msg == NULL) {
280+
break;
281+
}
282+
wcscpy_s(temp_log_msg, log_msg_len, log_msg);
283+
if (log_msg)
284+
free(log_msg);
285+
log_msg = temp_log_msg;
286+
}
287+
_snwprintf_s(log_msg + currentLength, log_msg_len - currentLength, _TRUNCATE,
288+
L", %ls\\%ls", resolved_trustee_domain, resolved_trustee);
289+
}
290+
}
251291
}
252292
}
293+
294+
if (need_log_msg) {
295+
log_folder_perms_msg_etw(path_utf16, log_msg);
296+
}
253297
cleanup:
254-
if (bad_user)
298+
if (bad_user) {
255299
LocalFree(bad_user);
256-
if (pSD)
300+
}
301+
if (log_msg) {
302+
free(log_msg);
303+
}
304+
if (pSD) {
257305
LocalFree(pSD);
258-
if (ti_sid)
306+
}
307+
if (ti_sid) {
259308
free(ti_sid);
260-
return ret;
309+
}
310+
}
311+
312+
/*
313+
* This function takes in the full path to the ProgramData\ssh folder
314+
* and a string of comma-separated domain\usernames. The function converts
315+
* the well-known built-in Administrators group sid and the Local System
316+
* sid to their corresponding names. With these names, and the input string,
317+
* it logs a message to the Event Viewer. If logging the detailed message fails,
318+
* a generic log message is written to the Event Viewer instead.
319+
*/
320+
void log_folder_perms_msg_etw(const wchar_t* path_utf16, wchar_t* log_msg) {
321+
PSID adminSid = NULL;
322+
WCHAR adminName[UNLEN + NULL_TERMINATOR_LEN];
323+
WCHAR adminDomain[DNLEN + NULL_TERMINATOR_LEN];
324+
DWORD adminNameSize = UNLEN + NULL_TERMINATOR_LEN;
325+
DWORD adminDomainSize = DNLEN + NULL_TERMINATOR_LEN;
326+
DWORD adminSidSize = SECURITY_MAX_SID_SIZE;
327+
PSID systemSid = NULL;
328+
WCHAR systemName[UNLEN + NULL_TERMINATOR_LEN];
329+
WCHAR systemDomain[DNLEN + NULL_TERMINATOR_LEN];
330+
DWORD systemNameSize = UNLEN + NULL_TERMINATOR_LEN;
331+
DWORD systemDomainSize = DNLEN + NULL_TERMINATOR_LEN;
332+
DWORD systemSidSize = SECURITY_MAX_SID_SIZE;
333+
SID_NAME_USE sidType;
334+
BOOL needLog = TRUE;
335+
int temp_log_on_stderr = log_on_stderr;
336+
log_on_stderr = 0;
337+
338+
adminSid = (PSID)malloc(SECURITY_MAX_SID_SIZE);
339+
if (log_msg != NULL && adminSid != NULL &&
340+
CreateWellKnownSid(WinBuiltinAdministratorsSid, NULL, adminSid, &adminSidSize) != 0 &&
341+
LookupAccountSidW(NULL, adminSid, adminName, &adminNameSize, adminDomain, &adminDomainSize, &sidType) != 0) {
342+
systemSid = (PSID)malloc(SECURITY_MAX_SID_SIZE);
343+
if (systemSid != NULL &&
344+
CreateWellKnownSid(WinLocalSystemSid, NULL, systemSid, &systemSidSize) != 0 &&
345+
LookupAccountSidW(NULL, systemSid, systemName, &systemNameSize, systemDomain, &systemDomainSize, &sidType) != 0) {
346+
logit("For '%S' folder, write access is granted to the following users: %S. "
347+
"Consider reviewing users to ensure that only %S\\%S, and the %S\\%S group, and its members, have write access.",
348+
path_utf16, log_msg, systemDomain, systemName, adminDomain, adminName);
349+
needLog = FALSE;
350+
}
351+
}
352+
353+
if (needLog) {
354+
/* log generic warning message in unlikely case that lookup for either well-known SID fails or user list is empty */
355+
logit("for '%S' folder, consider downgrading permissions for any users with unnecessary write access.", path_utf16);
356+
}
357+
358+
log_on_stderr = temp_log_on_stderr;
359+
360+
if (adminSid) {
361+
free(adminSid);
362+
}
363+
if (systemSid) {
364+
free(systemSid);
365+
}
261366
}

contrib/win32/win32compat/wmain_sshd.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ create_prgdata_ssh_folder()
135135
wchar_t ssh_cfg_dir[PATH_MAX] = { 0, };
136136
wcscpy_s(ssh_cfg_dir, _countof(ssh_cfg_dir), __wprogdata);
137137
wcscat_s(ssh_cfg_dir, _countof(ssh_cfg_dir), L"\\ssh");
138-
if (create_directory_withsddl(ssh_cfg_dir, L"O:BAD:PAI(A;OICI;FA;;;SY)(A;OICI;FA;;;BA)(A;OICI;0x1200a9;;;AU)") < 0) {
138+
if (create_directory_withsddl(ssh_cfg_dir, L"O:BAD:PAI(A;OICI;FA;;;SY)(A;OICI;FA;;;BA)(A;OICI;0x1200a9;;;AU)", TRUE) < 0) {
139139
printf("failed to create %S", ssh_cfg_dir);
140140
exit(255);
141141
}
@@ -144,7 +144,7 @@ create_prgdata_ssh_folder()
144144
wchar_t logs_dir[PATH_MAX] = { 0, };
145145
wcscat_s(logs_dir, _countof(logs_dir), ssh_cfg_dir);
146146
wcscat_s(logs_dir, _countof(logs_dir), L"\\logs");
147-
if (create_directory_withsddl(logs_dir, L"O:BAD:PAI(A;OICI;FA;;;SY)(A;OICI;FA;;;BA)") < 0) {
147+
if (create_directory_withsddl(logs_dir, L"O:BAD:PAI(A;OICI;FA;;;SY)(A;OICI;FA;;;BA)", FALSE) < 0) {
148148
printf("failed to create %S", logs_dir);
149149
exit(255);
150150
}

log.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,11 @@
5454
#include "match.h"
5555

5656
static LogLevel log_level = SYSLOG_LEVEL_INFO;
57+
#ifdef WINDOWS
58+
int log_on_stderr = 1;
59+
#else
5760
static int log_on_stderr = 1;
61+
#endif /* WINDOWS */
5862
static int log_stderr_fd = STDERR_FILENO;
5963
static int log_facility = LOG_AUTH;
6064
static const char *argv0;

0 commit comments

Comments
 (0)