mirror of https://github.com/wolfSSL/wolfssh.git
wolfsshd: apply ForceCommand to SFTP and SCP
A ForceCommand other than a bare "internal-sftp" now denies the SFTP subsystem, and any ForceCommand denies SCP, as sshd_config(5) describes it: a forced command applies to shell, command, and subsystem execution. The session dispatch checks the configured command alongside a certificate's, so either one on its own confines the session. - the subsystem arm and both SCP arms consult the configured ForceCommand, not only a certificate's force-command - "internal-sftp" followed by options denies SFTP, since no option is applied, while the shell still refuses it as "internal-sftp" - every site testing for "internal-sftp" now shares one predicate - root's transfer refusal no longer points at ForceCommand internal-sftp, which does not permit SCP - tests require a ForceCommand to deny SFTP and SCP, read the daemon's decisions from its log, and pair a configured internal-sftp with a cert Issue: ZD-22623 Issue: F-10597pull/1356/head
parent
d04bad3a6e
commit
0f4002bc4e
|
|
@ -21,7 +21,45 @@ source ./start_sshd.sh
|
|||
# root daemon holding the shared test port, so every later test in the suite
|
||||
# would talk to this config. stop_wolfsshd clears PID, so this is a no-op after
|
||||
# each explicit stop below.
|
||||
trap stop_wolfsshd EXIT
|
||||
cleanup() {
|
||||
stop_wolfsshd
|
||||
rm -f "$TESTDIR/scp_src.dat" "$TESTDIR/scp_dst.dat"
|
||||
rm -f "$TESTDIR/sshd_config_test_forcedcmd_args"
|
||||
rm -f "$TESTDIR/sshd_config_test_forcedcmd_prefix"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
# The daemon's decisions are read from log.txt, which the whole run shares:
|
||||
# log_mark notes its length and log_since prints what came after.
|
||||
log_mark() {
|
||||
sudo cat "$TESTDIR/log.txt" | wc -l
|
||||
}
|
||||
|
||||
log_since() {
|
||||
sudo cat "$TESTDIR/log.txt" | tail -n +`expr $1 + 1`
|
||||
}
|
||||
|
||||
# Native SCP is an exec, a path of its own. wolfscp masks its exit code, so
|
||||
# the decision is read from the log. Fails the test unless SCP is denied.
|
||||
check_scp_denied() {
|
||||
if [ ! -x "$TEST_SCP" ]; then
|
||||
return 0
|
||||
fi
|
||||
MARK=`log_mark`
|
||||
rm -f "$TESTDIR/scp_dst.dat"
|
||||
echo "scp payload" > "$TESTDIR/scp_src.dat"
|
||||
$TEST_SCP -u $USER -i $PRIVATE_KEY -j $PUBLIC_KEY \
|
||||
-S"$TESTDIR/scp_src.dat:$TESTDIR/scp_dst.dat" \
|
||||
-H $TEST_HOST -p $TEST_PORT > /dev/null 2>&1 || true
|
||||
if ! log_since $MARK | grep -q "denying SCP"; then
|
||||
echo "SCP should be denied with $1"
|
||||
exit 1
|
||||
fi
|
||||
if [ -f "$TESTDIR/scp_dst.dat" ]; then
|
||||
echo "SCP wrote its destination despite $1"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
cat <<EOF > sshd_config_test_forcedcmd
|
||||
Port $TEST_PORT
|
||||
|
|
@ -44,6 +82,7 @@ cd ../../..
|
|||
|
||||
TEST_CLIENT="./examples/client/client"
|
||||
TEST_SFTP="./examples/sftpclient/wolfsftp"
|
||||
TEST_SCP="./examples/scpclient/wolfscp"
|
||||
PRIVATE_KEY="./keys/hansel-key-ecc.der"
|
||||
PUBLIC_KEY="./keys/hansel-key-ecc.pub"
|
||||
|
||||
|
|
@ -59,12 +98,91 @@ fi
|
|||
set -e
|
||||
echo exit | $TEST_SFTP -u $USER -i $PRIVATE_KEY -j $PUBLIC_KEY -h $TEST_HOST -p $TEST_PORT
|
||||
|
||||
check_scp_denied "ForceCommand internal-sftp"
|
||||
|
||||
cd "$TESTDIR"
|
||||
stop_wolfsshd
|
||||
|
||||
# A configured ForceCommand that is not "internal-sftp" must still permit the
|
||||
# SFTP subsystem. Only a certificate force-command denies file transfer, so a
|
||||
# long-standing ForceCommand config keeps working.
|
||||
# sshd_config(5) permits options after "internal-sftp", but none is
|
||||
# applied: shell is still refused and SFTP is refused too.
|
||||
cat <<EOF > sshd_config_test_forcedcmd_args
|
||||
Port $TEST_PORT
|
||||
Protocol 2
|
||||
LoginGraceTime 600
|
||||
PermitRootLogin yes
|
||||
PasswordAuthentication yes
|
||||
PermitEmptyPasswords no
|
||||
UsePrivilegeSeparation no
|
||||
UseDNS no
|
||||
HostKey $TESTDIR/../../../keys/server-key.pem
|
||||
AuthorizedKeysFile $TESTDIR/authorized_keys_test
|
||||
|
||||
Match User $USER
|
||||
ForceCommand internal-sftp -u 0002
|
||||
EOF
|
||||
|
||||
start_wolfsshd "sshd_config_test_forcedcmd_args"
|
||||
cd ../../..
|
||||
|
||||
# The refused login exits non-zero and this runs under the "set -e" above.
|
||||
# Running "internal-sftp -u 0002" as a command would print no "bob" either,
|
||||
# so the refusal is read from the log.
|
||||
MARK=`log_mark`
|
||||
RESULT=$( $TEST_CLIENT -c 'echo bob' -u $USER -i $PRIVATE_KEY -j $PUBLIC_KEY -h $TEST_HOST -p $TEST_PORT ) || true
|
||||
if echo $RESULT | grep bob ||
|
||||
! log_since $MARK | grep -q "Only SFTP connections allowed"; then
|
||||
echo "Shell login should fail with internal-sftp and options"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
MARK=`log_mark`
|
||||
if echo exit | $TEST_SFTP -u $USER -i $PRIVATE_KEY -j $PUBLIC_KEY \
|
||||
-h $TEST_HOST -p $TEST_PORT; then
|
||||
echo "SFTP should fail with internal-sftp and options"
|
||||
exit 1
|
||||
fi
|
||||
if ! log_since $MARK | grep -q "internal-sftp options are not supported"; then
|
||||
echo "SFTP should be refused for its internal-sftp options"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cd "$TESTDIR"
|
||||
stop_wolfsshd
|
||||
|
||||
# A name that only starts with "internal-sftp" is some other command.
|
||||
sed 's/ForceCommand internal-sftp -u 0002/ForceCommand internal-sftpd/' \
|
||||
sshd_config_test_forcedcmd_args > sshd_config_test_forcedcmd_prefix
|
||||
|
||||
start_wolfsshd "sshd_config_test_forcedcmd_prefix"
|
||||
cd ../../..
|
||||
|
||||
# Refused as a forced command, not as internal-sftp with an option "d".
|
||||
MARK=`log_mark`
|
||||
if echo exit | $TEST_SFTP -u $USER -i $PRIVATE_KEY -j $PUBLIC_KEY \
|
||||
-h $TEST_HOST -p $TEST_PORT; then
|
||||
echo "SFTP should fail with a command named like internal-sftp"
|
||||
exit 1
|
||||
fi
|
||||
if ! log_since $MARK | grep -q "denying subsystem request" ||
|
||||
log_since $MARK | grep -q "options are not supported"; then
|
||||
echo "internal-sftpd was taken for internal-sftp"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# The shell runs it as the forced command, without the internal-sftp refusal.
|
||||
MARK=`log_mark`
|
||||
$TEST_CLIENT -c 'echo bob' -u $USER -i $PRIVATE_KEY -j $PUBLIC_KEY \
|
||||
-h $TEST_HOST -p $TEST_PORT > /dev/null 2>&1 || true
|
||||
if log_since $MARK | grep -q "Only SFTP connections allowed"; then
|
||||
echo "Shell refused internal-sftpd as internal-sftp"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cd "$TESTDIR"
|
||||
stop_wolfsshd
|
||||
|
||||
# A ForceCommand other than "internal-sftp" denies the SFTP subsystem and
|
||||
# native SCP: the client may only run the forced command.
|
||||
cat <<EOF > sshd_config_test_forcedcmd_sftp
|
||||
Port $TEST_PORT
|
||||
Protocol 2
|
||||
|
|
@ -84,7 +202,13 @@ EOF
|
|||
start_wolfsshd "sshd_config_test_forcedcmd_sftp"
|
||||
cd ../../..
|
||||
|
||||
echo exit | $TEST_SFTP -u $USER -i $PRIVATE_KEY -j $PUBLIC_KEY -h $TEST_HOST -p $TEST_PORT
|
||||
if echo exit | $TEST_SFTP -u $USER -i $PRIVATE_KEY -j $PUBLIC_KEY \
|
||||
-h $TEST_HOST -p $TEST_PORT; then
|
||||
echo "SFTP should fail with a forced command other than internal-sftp"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
check_scp_denied "a forced command"
|
||||
|
||||
cd "$TESTDIR"
|
||||
stop_wolfsshd
|
||||
|
|
|
|||
|
|
@ -228,8 +228,8 @@ HostKey $HOSTKEY
|
|||
TrustedUserCAKeys $WORK/trusted-cas.pub
|
||||
EOF
|
||||
|
||||
# Second config, adding a ForceCommand that is not "internal-sftp". Used to
|
||||
# separate the config-sourced command from the certificate's.
|
||||
# Second config, adding a ForceCommand that is not "internal-sftp", which
|
||||
# denies SFTP on its own.
|
||||
cat > "$WORK/sshd_config_ossh_fc" <<EOF
|
||||
Port $PORT
|
||||
Protocol 2
|
||||
|
|
@ -243,6 +243,22 @@ Match User $LOGINUSER
|
|||
ForceCommand /bin/echo
|
||||
EOF
|
||||
|
||||
# Third config, a ForceCommand "internal-sftp", which permits SFTP. Only here
|
||||
# does a certificate's force-command change the outcome, so it shows the
|
||||
# configured command does not mask the certificate's.
|
||||
cat > "$WORK/sshd_config_ossh_isftp" <<EOF
|
||||
Port $PORT
|
||||
Protocol 2
|
||||
UsePrivilegeSeparation no
|
||||
UseDNS no
|
||||
PasswordAuthentication no
|
||||
HostKey $HOSTKEY
|
||||
TrustedUserCAKeys $WORK/trusted-cas.pub
|
||||
|
||||
Match User $LOGINUSER
|
||||
ForceCommand internal-sftp
|
||||
EOF
|
||||
|
||||
CONFIG="$WORK/sshd_config_ossh"
|
||||
FAIL=0
|
||||
DRIVER=""
|
||||
|
|
@ -453,13 +469,16 @@ run_suite() { # driver
|
|||
sftp_check "internal-sftp sftp" "$ED" \
|
||||
"$CERTS/$LOGINUSER-ossh-internalsftp-cert.pub" 0
|
||||
|
||||
# A configured ForceCommand is not a certificate force-command: on its
|
||||
# own it must not deny SFTP, and it must not mask one carried by a
|
||||
# certificate.
|
||||
# A configured ForceCommand denies SFTP on its own.
|
||||
CONFIG="$WORK/sshd_config_ossh_fc"
|
||||
sftp_check "config forcecmd sftp" "$ED" \
|
||||
"$CERTS/$LOGINUSER-ossh-cert.pub" 1
|
||||
|
||||
# A configured "internal-sftp" does not mask a certificate's.
|
||||
CONFIG="$WORK/sshd_config_ossh_isftp"
|
||||
sftp_check "config isftp sftp" "$ED" \
|
||||
"$CERTS/$LOGINUSER-ossh-cert.pub" 0
|
||||
sftp_check "config+cert sftp deny" "$ED" \
|
||||
sftp_check "config isftp+cert sftp deny" "$ED" \
|
||||
"$CERTS/$LOGINUSER-ossh-forcecmd-cert.pub" 1
|
||||
CONFIG="$WORK/sshd_config_ossh"
|
||||
else
|
||||
|
|
@ -476,6 +495,12 @@ run_suite() { # driver
|
|||
"$CERTS/$LOGINUSER-ossh-forcecmd-cert.pub" 1
|
||||
scp_check "internal-sftp scp" "$ED" \
|
||||
"$CERTS/$LOGINUSER-ossh-internalsftp-cert.pub" 1
|
||||
|
||||
# A configured ForceCommand denies it too.
|
||||
CONFIG="$WORK/sshd_config_ossh_fc"
|
||||
scp_check "config forcecmd scp" "$ED" \
|
||||
"$CERTS/$LOGINUSER-ossh-cert.pub" 1
|
||||
CONFIG="$WORK/sshd_config_ossh"
|
||||
fi
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1734,6 +1734,47 @@ static const char* GetCertForcedCmd(WOLFSSHD_CONNECTION* conn)
|
|||
#endif
|
||||
}
|
||||
|
||||
|
||||
static const char internalSftp[] = "internal-sftp";
|
||||
#define INTERNAL_SFTP_SZ ((word32)(sizeof(internalSftp) - 1))
|
||||
|
||||
/* 1 when 'forcedCmd' is "internal-sftp". Only the first token is matched;
|
||||
* sshd_config(5) permits options after it. */
|
||||
static int ForcedCmdIsInternalSftp(const char* forcedCmd)
|
||||
{
|
||||
return forcedCmd != NULL &&
|
||||
WSTRNCMP(forcedCmd, internalSftp, INTERNAL_SFTP_SZ) == 0 &&
|
||||
(forcedCmd[INTERNAL_SFTP_SZ] == '\0' ||
|
||||
WISSPACE((unsigned char)forcedCmd[INTERNAL_SFTP_SZ]));
|
||||
}
|
||||
|
||||
|
||||
/* 1 when 'forcedCmd' is set to anything but a bare "internal-sftp". No
|
||||
* internal-sftp option is applied, so one given denies SFTP rather than
|
||||
* being ignored. NULL forbids nothing. */
|
||||
static int ForcedCmdDeniesSftp(const char* forcedCmd)
|
||||
{
|
||||
const char* opt;
|
||||
|
||||
if (forcedCmd == NULL) {
|
||||
return 0;
|
||||
}
|
||||
if (!ForcedCmdIsInternalSftp(forcedCmd)) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
for (opt = forcedCmd + INTERNAL_SFTP_SZ; *opt != '\0'; opt++) {
|
||||
if (!WISSPACE((unsigned char)*opt)) {
|
||||
wolfSSH_Log(WS_LOG_ERROR,
|
||||
"[SSHD] internal-sftp options are not supported: %s",
|
||||
forcedCmd);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef WOLFSSH_SCP
|
||||
static int SCP_Subsystem(WOLFSSHD_CONNECTION* conn, WOLFSSH* ssh,
|
||||
WPASSWD* pPasswd, WOLFSSHD_CONFIG* usrConf)
|
||||
|
|
@ -2147,7 +2188,7 @@ static int SHELL_Subsystem(WOLFSSHD_CONNECTION* conn, WOLFSSH* ssh,
|
|||
|
||||
/* @TODO check for conpty support LoadLibrary()and GetProcAddress(). */
|
||||
|
||||
if (forcedCmd != NULL && WSTRCMP(forcedCmd, "internal-sftp") == 0) {
|
||||
if (ForcedCmdIsInternalSftp(forcedCmd)) {
|
||||
wolfSSH_Log(WS_LOG_ERROR,
|
||||
"[SSHD] Only SFTP connections allowed for user "
|
||||
"%s", wolfSSH_GetUsername(ssh));
|
||||
|
|
@ -2818,7 +2859,7 @@ static int SHELL_Subsystem(WOLFSSHD_CONNECTION* conn, WOLFSSH* ssh,
|
|||
forcedCmd = subCmd;
|
||||
}
|
||||
|
||||
if (forcedCmd != NULL && WSTRCMP(forcedCmd, "internal-sftp") == 0) {
|
||||
if (ForcedCmdIsInternalSftp(forcedCmd)) {
|
||||
wolfSSH_Log(WS_LOG_ERROR,
|
||||
"[SSHD] Only SFTP connections allowed for user "
|
||||
"%s", wolfSSH_GetUsername(ssh));
|
||||
|
|
@ -3755,10 +3796,10 @@ static int UserAuthResult(byte result,
|
|||
}
|
||||
|
||||
#if defined(WOLFSSH_SFTP) || defined(WOLFSSH_SCP)
|
||||
/* Returns 1 if root's forced-commands-only ForceCommand isn't
|
||||
* "internal-sftp": SFTP/SCP never honor ForceCommand (only SHELL_Subsystem
|
||||
* does), so this blocks the confinement bypass. No-op on Windows
|
||||
* (pPasswd is NULL there). */
|
||||
/* 1 when root is under forced-commands-only and its ForceCommand isn't a
|
||||
* bare "internal-sftp". The dispatch denies SCP under any ForceCommand
|
||||
* before this, so root never gets SCP there. No-op on Windows (pPasswd is
|
||||
* NULL there). */
|
||||
static int IsRootTransferBlocked(WPASSWD* pPasswd, WOLFSSHD_CONFIG* usrConf)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
|
|
@ -3777,7 +3818,7 @@ static int IsRootTransferBlocked(WPASSWD* pPasswd, WOLFSSHD_CONFIG* usrConf)
|
|||
}
|
||||
|
||||
forcedCmd = wolfSSHD_ConfigGetForcedCmd(usrConf);
|
||||
return (forcedCmd == NULL || WSTRCMP(forcedCmd, "internal-sftp") != 0);
|
||||
return forcedCmd == NULL || ForcedCmdDeniesSftp(forcedCmd);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
|
@ -3787,8 +3828,8 @@ static int RejectRootTransfer(WPASSWD* pPasswd, WOLFSSHD_CONFIG* usrConf,
|
|||
const char* what)
|
||||
{
|
||||
if (IsRootTransferBlocked(pPasswd, usrConf)) {
|
||||
wolfSSH_Log(WS_LOG_ERROR, "[SSHD] %s for root requires ForceCommand "
|
||||
"internal-sftp under forced-commands-only", what);
|
||||
wolfSSH_Log(WS_LOG_ERROR, "[SSHD] %s for root is not allowed "
|
||||
"under forced-commands-only", what);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
|
|
@ -3929,7 +3970,8 @@ static void* HandleConnection(void* arg)
|
|||
WPASSWD* pPasswd = NULL;
|
||||
WOLFSSHD_CONFIG* usrConf;
|
||||
char* usr;
|
||||
const char* forcedCmd = NULL;
|
||||
const char* certCmd = NULL;
|
||||
const char* confCmd = NULL;
|
||||
|
||||
/* get configuration for user */
|
||||
usr = wolfSSH_GetUsername(ssh);
|
||||
|
|
@ -3971,14 +4013,15 @@ static void* HandleConnection(void* arg)
|
|||
break;
|
||||
|
||||
case WOLFSSH_SESSION_SUBSYSTEM:
|
||||
/* A certificate force-command overrides the requested
|
||||
* subsystem. "internal-sftp" still permits SFTP; any other
|
||||
* command denies file transfer (fail closed). */
|
||||
forcedCmd = GetCertForcedCmd(conn);
|
||||
if (forcedCmd != NULL &&
|
||||
WSTRCMP(forcedCmd, "internal-sftp") != 0) {
|
||||
/* A force-command, configured or from a certificate,
|
||||
* overrides the requested subsystem. Only
|
||||
* "internal-sftp" still permits SFTP. */
|
||||
certCmd = GetCertForcedCmd(conn);
|
||||
confCmd = wolfSSHD_ConfigGetForcedCmd(usrConf);
|
||||
if (ForcedCmdDeniesSftp(certCmd) ||
|
||||
ForcedCmdDeniesSftp(confCmd)) {
|
||||
wolfSSH_Log(WS_LOG_ERROR,
|
||||
"[SSHD] Cert force-command set for user %s; "
|
||||
"[SSHD] Force-command set for user %s; "
|
||||
"denying subsystem request",
|
||||
wolfSSH_GetUsername(ssh));
|
||||
ret = WS_FATAL_ERROR;
|
||||
|
|
@ -4005,11 +4048,11 @@ static void* HandleConnection(void* arg)
|
|||
|
||||
case WS_SCP_INIT:
|
||||
#ifdef WOLFSSH_SCP
|
||||
/* any certificate force-command, including
|
||||
* internal-sftp, denies SCP */
|
||||
if (forcedCmd != NULL) {
|
||||
/* any force-command, including internal-sftp,
|
||||
* denies SCP */
|
||||
if (certCmd != NULL || confCmd != NULL) {
|
||||
wolfSSH_Log(WS_LOG_ERROR,
|
||||
"[SSHD] Cert force-command set for user "
|
||||
"[SSHD] Force-command set for user "
|
||||
"%s; denying SCP",
|
||||
wolfSSH_GetUsername(ssh));
|
||||
ret = WS_FATAL_ERROR;
|
||||
|
|
@ -4054,11 +4097,12 @@ static void* HandleConnection(void* arg)
|
|||
/* SCP can be an exec type */
|
||||
if (ret == WS_SCP_INIT) {
|
||||
#ifdef WOLFSSH_SCP
|
||||
/* any certificate force-command denies SCP */
|
||||
forcedCmd = GetCertForcedCmd(conn);
|
||||
if (forcedCmd != NULL) {
|
||||
/* any force-command denies SCP */
|
||||
certCmd = GetCertForcedCmd(conn);
|
||||
confCmd = wolfSSHD_ConfigGetForcedCmd(usrConf);
|
||||
if (certCmd != NULL || confCmd != NULL) {
|
||||
wolfSSH_Log(WS_LOG_ERROR,
|
||||
"[SSHD] Cert force-command set for user %s; "
|
||||
"[SSHD] Force-command set for user %s; "
|
||||
"denying SCP", wolfSSH_GetUsername(ssh));
|
||||
ret = WS_FATAL_ERROR;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue