0

I have a command to obtain the last logged in time of a certain ubuntu user and I need the output to store it elsewhere so when I run this in my python script, I get a syntax error but when I ssh into remote server and execute the command, there's no problem.

# last logged in time of ubuntu user

user_login = os.popen('ssh -i /Users/abcxyz/keypair ubuntu@1#.###.##.# lastlog -u 'ubuntu' | grep -v Latest | awk '{$1="";$2="";$3="";print $0 }'').read()

print(user_output)

when I just run this in my terminal, it works fine and gives me the output:

ssh -i /Users/abcxyz/keypair ubuntu@1#.###.##.# lastlog -u 'ubuntu' | grep -v Latest | awk '{$1="";$2="";$3="";print $0 }'

Output: Sat Nov 17 16:32:10 +0000 2018

2 Answers 2

1

Since your string has both single and double quotes inside of it, I'd triple-quote the whole thing

user_login = os.popen("""ssh -i /Users/abcxyz/keypair ubuntu@1#.###.##.# lastlog -u 'ubuntu' | grep -v Latest | awk '{$1="";$2="";$3="";print $0 }'""").read()

Otherwise as written some of the single quotes inside your string are terminating the full length string in the middle

Sign up to request clarification or add additional context in comments.

1 Comment

Works perfectly well for the immediate problem; the only reason I don't suggest this is that it leaves other issues open (f/e, shell injection attacks) as soon as the user wants to parameterize a command that they specify in this way.
1

Ideally, you should construct this in a manner that doesn't expose you to the problem at all -- which is to say, let the Python interpreter do the work of generating a correctly shell-quoted string (and then re-quoting it to be safely passed to ssh).

try:
  from shlex import quote # Python 3
except ImportError:
  from pipes import quote # Python 2
import subprocess

# specify your commands the way they're actually seen by the operating system -- with
# lists of strings as argument vectors.
rmt_pipeline = [[ 'lastlog', '-u', 'ubuntu' ],
                [ 'grep', '-v', 'Latest' ],
                [ 'awk', '{$1="";$2="";$3="";print $0 }' ]]

# ...then, let shlex.quote() or pipes.quote() determine how to make those lists be valid
# shell syntax, as expected by the remote copy of sh -c '...' invoked by ssh
rmt_pipeline_str = ' | '.join(' '.join(quote(word) for word in piece)
                              for piece in rmt_pipeline)

# ...finally, generate the argument vector for our local copy of ssh...
ssh_cmd = [ 'ssh', '-i', '/Users/abcxyz/keypair', rmt_pipeline_str ]

# and actually invoke it.
user_output = subprocess.Popen(ssh_cmd, stdout=subprocess.PIPE).stdout

If you really want to use os.popen() -- which you shouldn't, as Python documentation explicitly suggests using subprocess instead -- you can replace the last line with:

ssh_cmd_str = ' '.join(quote(word) for word in ssh_cmd)
user_output = os.popen(ssh_cmd_str)

On UNIX-family operating systems, all program execution happens through the execve() syscall, which passes a list of C strings around. Specifying that list yourself gives you the most possible control over how execution takes place, and prevents shell injection attacks (where a user authorized to provide a parameter to one of the programs you're running passes content that's interpreted by the shell as syntax rather than data, and thus runs a completely different program or an indirection operation instead).

1 Comment

thanks so much for sharing the code & detailed explanation :) appreciate it.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.