Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SSH – Force Command execution on login even without Shell

Tags:

ssh

sshd

I am creating a restricted user without shell for port forwarding only and I need to execute a script on login via pubkey, even if the user is connected via ssh -N user@host which doesn't asks SSH server for a shell.

The script should warn admin on connections authenticated with pubkey, so the user connecting shouldn't be able to skip the execution of the script (e.g., by connecting with ssh -N).

I have tried to no avail:

  • Setting the command at /etc/ssh/sshrc.
  • Using command="COMMAND" in .ssh/authorized_keys (man authorized_keys)
  • Setting up a script with the command as user's shell. (chsh -s /sbin/myscript.sh USERNAME)
  • Matching user in /etc/ssh/sshd_config like: Match User MYUSERNAME ForceCommand "/sbin/myscript.sh"

All work when user asks for shell, but if logged only for port forwarding and no shell (ssh -N) it doesn't work.

like image 534
Iacchus Avatar asked Nov 14 '15 21:11

Iacchus


3 Answers

The ForceCommand option runs without a PTY unless the client requests one. As a result, you don't actually have a shell to execute scripts the way you might expect. In addition, the OpenSSH SSHD_CONFIG(5) man page clearly says:

The command is invoked by using the user's login shell with the -c option.

That means that if you've disabled the user's login shell, or set it to something like /bin/false, then ForceCommand can't work. Assuming that:

  1. the user has a sensible shell defined,
  2. that your target script is executable, and
  3. that your script has an appropriate shebang line

then the following should work in your global sshd_config file once properly modified with the proper username and fully-qualified pathname to your custom script:

Match User foo
    ForceCommand /path/to/script.sh
like image 187
Todd A. Jacobs Avatar answered Oct 16 '22 18:10

Todd A. Jacobs


I am the author of the OP. Also, you can implement a simple logwatcher as the following written in python3, which keeps reading for a file and executes a command when line contains pattern.

logwatcher.python3

#!/usr/bin/env python3

# follow.py
#
# Follow a file like tail -f.

import sys
import os
import time

def follow(thefile):
    thefile.seek(0,2)
    while True:
        line = thefile.readline()
        if not line:
            time.sleep(0.5)
            continue
        yield line

if __name__ == '__main__':
    logfilename = sys.argv[1]
    pattern_string = sys.argv[2]
    command_to_execute = sys.argv[3]

    print("Log filename is: {}".format(logfilename))

    logfile = open(logfilename, "r")
    loglines = follow(logfile)

    for line in loglines:
        if pattern_string in line:
            os.system(command_to_execute)

Usage

  1. Make the above script executable:

chmod +x logwatcher.python3

  1. Add a cronjob to start it after reboot

crontab -e

Then write this line there and save it after this:

@reboot /home/YOURUSERNAME/logwatcher.python3 "/var/log/auth.log" "session opened for user" "/sbin/myscript.sh"

The first argument of this script is the log file to watch, and the second argument is the string for which to look in it. The third argument is the script to execute when the line is found in file.

It is best if you use something more reliable to start/restart the script in case it crashes.

like image 24
Iacchus Avatar answered Oct 16 '22 19:10

Iacchus


If you only need to run a script you can rely on pam_exec.

Basically you reference the script you need to run in the /etc/pam.d/sshd configuration:

session optional pam_exec.so seteuid /path/to/script.sh

After some testing you may want to change optional to required.

Please refer to this answer "bash - How do I set up an email alert when a ssh login is successful? - Ask Ubuntu" for a similar request.

Indeed in the script only a limited subset on the environment variables is available:

LANGUAGE=en_US.UTF-8
PAM_USER=bitnami
PAM_RHOST=192.168.1.17
PAM_TYPE=open_session
PAM_SERVICE=sshd
PAM_TTY=ssh
LANG=en_US.UTF-8
LC_ALL=en_US.UTF-8
PWD=/

If you want to get the user info from authorized_keys this script could be helpful:

#!/bin/bash
# Get user from authorized_keys
# pam_exec_login.sh
# * [ssh - What is the SHA256 that comes on the sshd entry in auth.log? - Server Fault](https://serverfault.com/questions/888281/what-is-the-sha256-that-comes-on-the-sshd-entry-in-auth-log)
# * [bash - How to get all fingerprints for .ssh/authorized_keys(2) file - Server Fault](https://serverfault.com/questions/413231/how-to-get-all-fingerprints-for-ssh-authorized-keys2-file)

# Setup log
b=$(basename $0| cut -d. -f1)
log="/tmp/${b}.log"

function timeStamp () {
  echo "$(date '+%b %d %H:%M:%S') ${HOSTNAME} $b[$$]:"
}

# Check if opening a remote session with sshd
if [ "${PAM_TYPE}" != "open_session" ] || [ $PAM_SERVICE != "sshd" ] || [ $PAM_RHOST == "::1" ]; then
  exit $PAM_SUCCESS
fi

# Get info from auth.log
authLogLine=$(journalctl -u ssh.service |tail -100 |grep "sshd\[${PPID}\]" |grep "${PAM_RHOST}")
echo ${authLogLine} >> ${log}

PAM_USER_PORT=$(echo ${authLogLine}| sed -r 's/.*port (.*) ssh2.*/\1/')
PAM_USER_SHA256=$(echo ${authLogLine}| sed -r 's/.*SHA256:(.*)/\1/')

# Get details from .ssh/authorized_keys
authFile="/home/${PAM_USER}/.ssh/authorized_keys"
PAM_USER_authorized_keys=""

while read l; do
  if [[ -n "$l" && "${l###}" = "$l" ]]; then
    authFileSHA256=$(ssh-keygen -l -f <(echo "$l"))
    if [[ "${authFileSHA256}" == *"${PAM_USER_SHA256}"* ]]; then
      PAM_USER_authorized_keys=$(echo ${authFileSHA256}| cut -d" " -f3)
      break
    fi
  fi
done < ${authFile}

if [[ -n ${PAM_USER_authorized_keys} ]]
then
  echo "$(timeStamp) Local user: ${PAM_USER}, authorized_keys user: ${PAM_USER_authorized_keys}" >> ${log}
else
  echo "$(timeStamp) WARNING: no matching user in authorized_keys" >> ${log}
fi
like image 3
emaV Avatar answered Oct 16 '22 19:10

emaV