From d18c05327e8a6e9e8f7ddb26021a4fb7860e4afd Mon Sep 17 00:00:00 2001 From: Norbert Pocs Date: Wed, 27 Dec 2023 20:32:18 +0100 Subject: [PATCH 04/12] misc: Add function to check username syntax Malicious code can be injected using the username with metacharacters, therefore the username must be validated before using it with any %u. Signed-off-by: Norbert Pocs Reviewed-by: Jakub Jelen --- include/libssh/misc.h | 1 + src/misc.c | 32 ++++++++++++++++++++++++++++++++ tests/unittests/torture_misc.c | 34 ++++++++++++++++++++++++++++++++++ 3 files changed, 67 insertions(+) diff --git a/include/libssh/misc.h b/include/libssh/misc.h index 3452251d..06c62678 100644 --- a/include/libssh/misc.h +++ b/include/libssh/misc.h @@ -103,6 +103,7 @@ int ssh_tmpname(char *template); char *ssh_strreplace(const char *src, const char *pattern, const char *repl); int ssh_check_hostname_syntax(const char *hostname); +int ssh_check_username_syntax(const char *username); FILE *ssh_strict_fopen(const char *filename, size_t max_file_size); diff --git a/src/misc.c b/src/misc.c index 0cca373a..0389ee70 100644 --- a/src/misc.c +++ b/src/misc.c @@ -2148,4 +2148,36 @@ FILE *ssh_strict_fopen(const char *filename, size_t max_file_size) return f; } +/** + * @brief Checks syntax of a username + * + * This check disallows metacharacters in the username + * + * @param username The username to be checked, has to be null terminated + * + * @return SSH_OK if the username passes syntax check + * SSH_ERROR otherwise or if username is NULL or empty string + */ +int ssh_check_username_syntax(const char *username) +{ + size_t username_len; + + if (username == NULL || *username == '-') { + return SSH_ERROR; + } + + username_len = strlen(username); + if (username_len == 0 || username[username_len - 1] == '\\' || + strpbrk(username, "'`\";&<>|(){}") != NULL) { + return SSH_ERROR; + } + for (size_t i = 0; i < username_len; i++) { + if (isspace(username[i]) != 0 && username[i + 1] == '-') { + return SSH_ERROR; + } + } + + return SSH_OK; +} + /** @} */ diff --git a/tests/unittests/torture_misc.c b/tests/unittests/torture_misc.c index 4470c358..7c134304 100644 --- a/tests/unittests/torture_misc.c +++ b/tests/unittests/torture_misc.c @@ -839,6 +839,39 @@ static void torture_ssh_check_hostname_syntax(void **state) assert_int_equal(rc, SSH_ERROR); } +static void torture_ssh_check_username_syntax(void **state) { + int rc; + (void)state; + + rc = ssh_check_username_syntax("username"); + assert_int_equal(rc, SSH_OK); + rc = ssh_check_username_syntax("Alice"); + assert_int_equal(rc, SSH_OK); + rc = ssh_check_username_syntax("Alice and Bob"); + assert_int_equal(rc, SSH_OK); + rc = ssh_check_username_syntax("n4me?"); + assert_int_equal(rc, SSH_OK); + + rc = ssh_check_username_syntax("alice&bob"); + assert_int_equal(rc, SSH_ERROR); + rc = ssh_check_username_syntax("backslash\\"); + assert_int_equal(rc, SSH_ERROR); + rc = ssh_check_username_syntax("&var|()us\" Date: Thu, 28 Dec 2023 12:16:29 +0100 Subject: [PATCH 05/12] Check any input username for validity Check possible inputs of username for malicious code. Signed-off-by: Norbert Pocs Reviewed-by: Jakub Jelen --- src/config_parser.c | 4 ++++ src/misc.c | 10 ++++++++-- src/options.c | 5 +++++ 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/config_parser.c b/src/config_parser.c index 5555f6fd..9163d374 100644 --- a/src/config_parser.c +++ b/src/config_parser.c @@ -203,6 +203,10 @@ int ssh_config_parse_uri(const char *tok, if (*username == NULL) { goto error; } + rc = ssh_check_username_syntax(*username); + if (rc != SSH_OK) { + goto error; + } } tok = endp + 1; /* If there is second @ character, this does not look like our URI */ diff --git a/src/misc.c b/src/misc.c index 0389ee70..b63b5521 100644 --- a/src/misc.c +++ b/src/misc.c @@ -183,6 +183,7 @@ char *ssh_get_local_username(void) { DWORD size = 0; char *user; + int rc; /* get the size */ GetUserName(NULL, &size); @@ -193,7 +194,10 @@ char *ssh_get_local_username(void) } if (GetUserName(user, &size)) { - return user; + rc = ssh_check_username_syntax(user); + if (rc == SSH_OK) { + return user; + } } return NULL; @@ -337,8 +341,10 @@ char *ssh_get_local_username(void) } name = strdup(pwd.pw_name); + rc = ssh_check_username_syntax(name); - if (name == NULL) { + if (rc != SSH_OK) { + free(name); return NULL; } diff --git a/src/options.c b/src/options.c index a8023fbf..a40459ff 100644 --- a/src/options.c +++ b/src/options.c @@ -662,6 +662,11 @@ int ssh_options_set(ssh_session session, enum ssh_options_e type, ssh_set_error_oom(session); return -1; } + rc = ssh_check_username_syntax(session->opts.username); + if (rc != SSH_OK) { + ssh_set_error_invalid(session); + return -1; + } } break; case SSH_OPTIONS_SSH_DIR: -- 2.54.0 From 26ae62df976dfd381b72f201ed4281530cd63d86 Mon Sep 17 00:00:00 2001 From: Norbert Pocs Date: Thu, 28 Dec 2023 12:27:31 +0100 Subject: [PATCH 06/12] torture: Add cases for username checks Signed-off-by: Norbert Pocs Reviewed-by: Jakub Jelen --- tests/unittests/torture_config.c | 3 +++ tests/unittests/torture_options.c | 6 ++++++ 2 files changed, 9 insertions(+) diff --git a/tests/unittests/torture_config.c b/tests/unittests/torture_config.c index 6dc59e48..09c769b9 100644 --- a/tests/unittests/torture_config.c +++ b/tests/unittests/torture_config.c @@ -2293,6 +2293,9 @@ static void torture_config_parse_uri(void **state) assert_null(username); assert_string_equal(hostname, "1:2:3::4"); SAFE_FREE(hostname); + + rc = ssh_config_parse_uri("user -name@", &username, NULL, NULL, true); + assert_int_equal(rc, SSH_ERROR); } /* Invalid configuration files diff --git a/tests/unittests/torture_options.c b/tests/unittests/torture_options.c index 2a7a04de..5c3d3f2c 100644 --- a/tests/unittests/torture_options.c +++ b/tests/unittests/torture_options.c @@ -89,6 +89,9 @@ static void torture_options_set_host(void **state) { assert_non_null(session->opts.username); assert_string_equal(session->opts.username, "at@login"); + /* disallow metacharacters in the username */ + rc = ssh_options_set(session, SSH_OPTIONS_HOST, "shallN()tP4ss -@hostname"); + assert_ssh_return_code_equal(session, rc, SSH_ERROR); } static void torture_options_set_ciphers(void **state) { @@ -396,6 +399,9 @@ static void torture_options_set_user(void **state) { assert_true(rc == 0); #endif /* _WIN32 */ + rc = ssh_options_set(session, SSH_OPTIONS_USER, "&shallN()tP4ss"); + assert_ssh_return_code_equal(session, rc, SSH_ERROR); + rc = ssh_options_set(session, SSH_OPTIONS_USER, "guru"); assert_true(rc == 0); assert_string_equal(session->opts.username, "guru"); -- 2.54.0 From 08a4f4c5151e75eda6d2105db493aa8c8df0dee7 Mon Sep 17 00:00:00 2001 From: Jakub Jelen Date: Thu, 2 Apr 2026 15:39:25 +0200 Subject: [PATCH 07/12] CVE-2026-59846 Block shell metacharacters from usernames MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When an attacker could sneak the dollar sign or backslash into the username expanded for example in proxy command, it can result in printing environment variables that might contain secrets. This is a fixup of CVE-2023-6004 which fixed this for hostnames, but these two metacharacters were left out from the username filter. This keeps the list in one place to simplify maintenance. Signed-off-by: Jakub Jelen Reviewed-by: Pavol Žáčik (cherry picked from commit 6309df220e3431deb41946f892f4bb5af8b59dba) --- include/libssh/priv.h | 2 ++ src/misc.c | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/include/libssh/priv.h b/include/libssh/priv.h index 876ea4ed..22f1e6da 100644 --- a/include/libssh/priv.h +++ b/include/libssh/priv.h @@ -223,6 +223,8 @@ int ssh_gettimeofday(struct timeval *__p, void *__t); # define LIBSSH_MEM_PROTECTION #endif +#define SSH_DANGEROUS_SHELL_CHARS "'`\";&<>|(){}$\\," + /* forward declarations */ struct ssh_common_struct; struct ssh_kex_struct; diff --git a/src/misc.c b/src/misc.c index b63b5521..f4f48a37 100644 --- a/src/misc.c +++ b/src/misc.c @@ -2174,7 +2174,7 @@ int ssh_check_username_syntax(const char *username) username_len = strlen(username); if (username_len == 0 || username[username_len - 1] == '\\' || - strpbrk(username, "'`\";&<>|(){}") != NULL) { + strpbrk(username, SSH_DANGEROUS_SHELL_CHARS) != NULL) { return SSH_ERROR; } for (size_t i = 0; i < username_len; i++) { -- 2.54.0