# HG changeset patch # User Matt Johnston # Date 1647422243 -28800 # Node ID 5d8dbb6fdab7a6d33c0da772532f6ce85f75f692 # Parent 75d6a9faf9199bac612d1be3df58299484a5eb84 Fix SSH_PUBKEYINFO, limit characters, add tests We fix a bad_bufptr() failure from a previous commit. We now limit the allowed characters to those that will definitely be safe in a shell. Some scripts/programs may use arbitrary environment variables without escaping correctly - that could be a problem in a restricted environment. The current allowed set is a-z A-Z 0-9 .,_-+@ This also adds a test for SSH_PUBKEYINFO, by default it only runs under github actions (or "act -j build"). diff -r 75d6a9faf919 -r 5d8dbb6fdab7 .github/workflows/build.yml --- a/.github/workflows/build.yml Wed Mar 16 10:43:24 2022 +0800 +++ b/.github/workflows/build.yml Wed Mar 16 17:17:23 2022 +0800 @@ -86,6 +86,8 @@ # for fuzzing CXX: clang++ RANLIB: ${{ matrix.ranlib || 'ranlib' }} + # some pytests depend on special setup from this file. see authorized_keys below. + DBTEST_IN_ACTION: true steps: - name: deps @@ -128,7 +130,14 @@ - name: keys run: | mkdir -p ~/.ssh + # remove old files so we can rerun in-place with "act -r" during test development + rm -vf ~/.ssh/id_dropbear* ~/inst/bin/dropbearkey -t ecdsa -f ~/.ssh/id_dropbear | grep ^ecdsa > ~/.ssh/authorized_keys + + # to test setting SSH_PUBKEYINFO, replace the trailing comment + ~/inst/bin/dropbearkey -t ecdsa -f ~/.ssh/id_dropbear_key2 | grep ^ecdsa | sed 's/[^ ]*$/key2 extra/' >> ~/.ssh/authorized_keys + ~/inst/bin/dropbearkey -t ecdsa -f ~/.ssh/id_dropbear_key3 | grep ^ecdsa | sed 's/[^ ]*$/key3%char/' >> ~/.ssh/authorized_keys + ~/inst/bin/dropbearkey -t ecdsa -f ~/.ssh/id_dropbear_key4 | grep ^ecdsa | sed 's/[^ ]*$/key4,char/' >> ~/.ssh/authorized_keys chmod 700 ~ ~/.ssh ~/.ssh/authorized_keys ls -ld ~ ~/.ssh ~/.ssh/authorized_keys diff -r 75d6a9faf919 -r 5d8dbb6fdab7 svr-authpubkey.c --- a/svr-authpubkey.c Wed Mar 16 10:43:24 2022 +0800 +++ b/svr-authpubkey.c Wed Mar 16 17:17:23 2022 +0800 @@ -261,6 +261,7 @@ const char* algo, unsigned int algolen, const unsigned char* keyblob, unsigned int keybloblen) { buffer *options_buf = NULL; + char *info_str = NULL; unsigned int pos, len, infopos, infolen; int ret = DROPBEAR_FAILURE; @@ -339,16 +340,36 @@ goto out; } - /* truncate the line at the space after the base64 data */ + /* find the length of base64 data */ pos = line->pos; for (len = 0; line->pos < line->len; len++) { - if (buf_getbyte(line) == ' ') break; - } - /* findout the length of the public key info */ + if (buf_getbyte(line) == ' ') { + break; + } + } + + /* find out the length of the public key info, stop at the first space */ infopos = line->pos; for (infolen = 0; line->pos < line->len; infolen++) { - if (buf_getbyte(line) == ' ') break; + const char c = buf_getbyte(line); + if (c == ' ') { + break; + } + /* We have an allowlist - authorized_keys lines can't be fully trusted, + some shell scripts may do unsafe things with env var values */ + if (!(isalnum(c) || strchr(".,_-+@", c))) { + TRACE(("Not setting SSH_PUBKEYINFO, special characters")) + infolen = 0; + break; + } } + if (infolen > 0) { + info_str = m_malloc(infolen + 1); + buf_setpos(line, infopos); + strncpy(info_str, buf_getptr(line, infolen), infolen); + } + + /* truncate to base64 data length */ buf_setpos(line, pos); buf_setlen(line, line->pos + len); @@ -359,26 +380,24 @@ /* free pubkey_info if it is filled */ if (ses.authstate.pubkey_info) { m_free(ses.authstate.pubkey_info); - ses.authstate.pubkey_info = NULL; } + if (ret == DROPBEAR_SUCCESS) { if (options_buf) { ret = svr_add_pubkey_options(options_buf, line_num, filename); } - /* save the (optional) public key information */ - if (infolen) { - ses.authstate.pubkey_info = m_malloc(infolen + 1); - if (ses.authstate.pubkey_info) { - strncpy(ses.authstate.pubkey_info,(const char *) buf_getptr(line, infopos), infolen); - ses.authstate.pubkey_info[infolen]='\0'; - } - } + /* take the (optional) public key information */ + ses.authstate.pubkey_info = info_str; + info_str = NULL; } out: if (options_buf) { buf_free(options_buf); } + if (info_str) { + m_free(info_str); + } return ret; } diff -r 75d6a9faf919 -r 5d8dbb6fdab7 test/test_svrauth.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/test_svrauth.py Wed Mar 16 17:17:23 2022 +0800 @@ -0,0 +1,30 @@ +from test_dropbear import * +import signal +import queue +import socket +import os +from pathlib import Path + +# Tests for server side authentication + +# Requires keyfile and authorized_keys set up in github action build.yml +@pytest.mark.skipif('DBTEST_IN_ACTION' not in os.environ, reason="DBTEST_PUBKEYINFO not set") +def test_pubkeyinfo(request, dropbear): + kf = str(Path.home() / ".ssh/id_dropbear_key2") + r = dbclient(request, "-i", kf, "echo -n $SSH_PUBKEYINFO", capture_output=True) + # stop at first space + assert r.stdout.decode() == "key2" + +@pytest.mark.skipif('DBTEST_IN_ACTION' not in os.environ, reason="DBTEST_PUBKEYINFO not set") +def test_pubkeyinfo_special(request, dropbear): + kf = str(Path.home() / ".ssh/id_dropbear_key3") + r = dbclient(request, "-i", kf, "echo -n $SSH_PUBKEYINFO", capture_output=True) + # comment contains special characters so the SSH_PUBKEYINFO should not be set + assert r.stdout.decode() == "" + +@pytest.mark.skipif('DBTEST_IN_ACTION' not in os.environ, reason="DBTEST_PUBKEYINFO not set") +def test_pubkeyinfo_okchar(request, dropbear): + kf = str(Path.home() / ".ssh/id_dropbear_key4") + r = dbclient(request, "-i", kf, "echo -n $SSH_PUBKEYINFO", capture_output=True) + # comment contains special characters so the SSH_PUBKEYINFO should not be set + assert r.stdout.decode() == "key4,char"