From 1e4d64d300eb4d1cb15f7f2dca9aee720b0c0e98 Mon Sep 17 00:00:00 2001 From: Tuomas Haikarainen Date: Thu, 7 Jul 2022 11:37:31 +0300 Subject: Added permitopen option to authorized_keys It is now possible to limit local port forwarding to specified destination(s) by using the permitopen option in authorized_keys. Resolves #181 --- auth.h | 13 ++++++++ dropbear.8 | 8 +++++ svr-authpubkeyoptions.c | 81 +++++++++++++++++++++++++++++++++++++++++++++++++ svr-tcpfwd.c | 5 +++ 4 files changed, 107 insertions(+) diff --git a/auth.h b/auth.h index 2063cad..264c1f1 100644 --- a/auth.h +++ b/auth.h @@ -28,6 +28,7 @@ #include "includes.h" #include "signkey.h" #include "chansession.h" +#include "list.h" void svr_authinitialise(void); @@ -45,6 +46,7 @@ int svr_pubkey_allows_agentfwd(void); int svr_pubkey_allows_tcpfwd(void); int svr_pubkey_allows_x11fwd(void); int svr_pubkey_allows_pty(void); +int svr_pubkey_allows_local_tcpfwd(const char *host, unsigned int port); void svr_pubkey_set_forced_command(struct ChanSess *chansess); void svr_pubkey_options_cleanup(void); int svr_add_pubkey_options(buffer *options_buf, int line_num, const char* filename); @@ -54,6 +56,9 @@ int svr_add_pubkey_options(buffer *options_buf, int line_num, const char* filena #define svr_pubkey_allows_tcpfwd() 1 #define svr_pubkey_allows_x11fwd() 1 #define svr_pubkey_allows_pty() 1 +static inline int svr_pubkey_allows_local_tcpfwd(const char *host, unsigned int port) + { (void)host; (void)port; return 1; } + static inline void svr_pubkey_set_forced_command(struct ChanSess *chansess) { } static inline void svr_pubkey_options_cleanup(void) { } #define svr_add_pubkey_options(x,y,z) DROPBEAR_SUCCESS @@ -93,6 +98,7 @@ void cli_auth_pubkey_cleanup(void); #define AUTH_METHOD_INTERACT "keyboard-interactive" #define AUTH_METHOD_INTERACT_LEN 20 +#define PUBKEY_OPTIONS_ANY_PORT UINT_MAX /* This structure is shared between server and client - it contains @@ -139,6 +145,13 @@ struct PubKeyOptions { int no_pty_flag; /* "command=" option. */ char * forced_command; + /* "permitopen=" option */ + m_list *permit_open_destinations; +}; + +struct PermitTCPFwdEntry { + char *host; + unsigned int port; }; #endif diff --git a/dropbear.8 b/dropbear.8 index 3073dd0..30df631 100644 --- a/dropbear.8 +++ b/dropbear.8 @@ -144,6 +144,14 @@ same functionality with other means even if no-pty is set. .B restrict Applies all the no- restrictions listed above. +.TP +.B permitopen=\fR"\fIhost:port\fR" +Restrict local port forwarding so that connection is allowed only to the +specified host and port. Multiple permitopen options separated by commas +can be set in authorized_keys. Wildcard character ('*') may be used in +port specification for matching any port. Hosts must be literal domain names or +IP addresses. + .TP .B command=\fR"\fIforced_command\fR" Disregard the command provided by the user and always run \fIforced_command\fR. diff --git a/svr-authpubkeyoptions.c b/svr-authpubkeyoptions.c index 447f4b7..e25bcf1 100644 --- a/svr-authpubkeyoptions.c +++ b/svr-authpubkeyoptions.c @@ -46,6 +46,7 @@ #include "dbutil.h" #include "signkey.h" #include "auth.h" +#include "runopts.h" #if DROPBEAR_SVR_PUBKEY_OPTIONS_BUILT @@ -88,6 +89,29 @@ int svr_pubkey_allows_pty() { return 1; } +/* Returns 1 if pubkey allows local tcp fowarding to the provided destination, + * 0 otherwise */ +int svr_pubkey_allows_local_tcpfwd(const char *host, unsigned int port) { + if (ses.authstate.pubkey_options + && ses.authstate.pubkey_options->permit_open_destinations) { + m_list_elem *iter = ses.authstate.pubkey_options->permit_open_destinations->first; + while (iter) { + struct PermitTCPFwdEntry *entry = (struct PermitTCPFwdEntry*)iter->item; + if (strcmp(entry->host, host) == 0) { + if ((entry->port == PUBKEY_OPTIONS_ANY_PORT) || (entry->port == port)) { + return 1; + } + } + + iter = iter->next; + } + + return 0; + } + + return 1; +} + /* Set chansession command to the one forced * by any 'command' public key option. */ void svr_pubkey_set_forced_command(struct ChanSess *chansess) { @@ -113,6 +137,16 @@ void svr_pubkey_options_cleanup() { if (ses.authstate.pubkey_options->forced_command) { m_free(ses.authstate.pubkey_options->forced_command); } + if (ses.authstate.pubkey_options->permit_open_destinations) { + m_list_elem *iter = ses.authstate.pubkey_options->permit_open_destinations->first; + while (iter) { + struct PermitTCPFwdEntry *entry = (struct PermitTCPFwdEntry*)list_remove(iter); + m_free(entry->host); + m_free(entry); + iter = ses.authstate.pubkey_options->permit_open_destinations->first; + } + m_free(ses.authstate.pubkey_options->permit_open_destinations); + } m_free(ses.authstate.pubkey_options); } if (ses.authstate.pubkey_info) { @@ -205,6 +239,53 @@ int svr_add_pubkey_options(buffer *options_buf, int line_num, const char* filena dropbear_log(LOG_WARNING, "Badly formatted command= authorized_keys option"); goto bad_option; } + if (match_option(options_buf, "permitopen=\"") == DROPBEAR_SUCCESS) { + int valid_option = 0; + const unsigned char* permitopen_start = buf_getptr(options_buf, 0); + + if (!ses.authstate.pubkey_options->permit_open_destinations) { + ses.authstate.pubkey_options->permit_open_destinations = list_new(); + } + + while (options_buf->pos < options_buf->len) { + const char c = buf_getbyte(options_buf); + if (c == '"') { + char *spec = NULL; + char *portstring = NULL; + const int permitopen_len = buf_getptr(options_buf, 0) - permitopen_start; + struct PermitTCPFwdEntry *entry = + (struct PermitTCPFwdEntry*)m_malloc(sizeof(struct PermitTCPFwdEntry)); + + list_append(ses.authstate.pubkey_options->permit_open_destinations, entry); + spec = m_malloc(permitopen_len); + memcpy(spec, permitopen_start, permitopen_len - 1); + spec[permitopen_len - 1] = '\0'; + if ((split_address_port(spec, &entry->host, &portstring) == DROPBEAR_SUCCESS) + && entry->host && portstring) { + if (strcmp(portstring, "*") == 0) { + valid_option = 1; + entry->port = PUBKEY_OPTIONS_ANY_PORT; + TRACE(("local port forwarding allowed to host '%s'", entry->host)); + } else if (m_str_to_uint(portstring, &entry->port) == DROPBEAR_SUCCESS) { + valid_option = 1; + TRACE(("local port forwarding allowed to host '%s' and port '%u'", + entry->host, entry->port)); + } + } + + m_free(spec); + m_free(portstring); + break; + } + } + + if (valid_option) { + goto next_option; + } else { + dropbear_log(LOG_WARNING, "Badly formatted permitopen= authorized_keys option"); + goto bad_option; + } + } next_option: /* diff --git a/svr-tcpfwd.c b/svr-tcpfwd.c index 4aa3152..7967cfa 100644 --- a/svr-tcpfwd.c +++ b/svr-tcpfwd.c @@ -289,6 +289,11 @@ static int newtcpdirect(struct Channel * channel) { goto out; } + if (!svr_pubkey_allows_local_tcpfwd(desthost, destport)) { + TRACE(("leave newtcpdirect: local tcp forwarding not permitted to requested destination")); + goto out; + } + snprintf(portstring, sizeof(portstring), "%u", destport); channel->conn_pending = connect_remote(desthost, portstring, channel_connect_done, channel, NULL, NULL, DROPBEAR_PRIO_NORMAL); -- cgit v1.2.1