I am trying to collect show tech-support from multiple devices using telnetlib only. And it works with a tiny issue. It collects the full output of the show tech-support command and exports it to a text file (export_text_support() is a simple with open() statement).
The entire output of the two switches in my lab is 32,768 and 16,325 lines for the show tech-support command. I get the entire output, BUT the tiny issue is that the session is not exited calling tn.write(b"exit\n") upon completion. It exits when exec-timeout is hit on the switch (when the session becomes idle), which is 10 minutes for both switches, not when there is nothing else to read from the tn.
I tried the same code below for shorter outputs like show running-config and I see the #exit (with a blank line at the end) in the file I export.
Short output
...
365 !
366 end
367
368 Switch#
369 Switch#exit
370
Huge output
....
16321 423 entries printed
16322
16323
16324
16325 Switch#
(As you can notice exit is not seen in the huge output sample and there is no blank line in the end)
This is my snippet
from telnetlib import Telnet
host = ""
username = ""
password = ""
def get_tech_support(host: str) -> None:
with Telnet(host=host, port=23) as tn:
# Check for credentials
if username:
tn.read_until(b"Username: ")
tn.write(username.encode("ascii") + b"\n")
if password:
tn.read_until(b"Password: ")
tn.write(password.encode("ascii") + b"\n")
# Some commands
commands = ["\n", "terminal width 115", "terminal length 0"]
[tn.write(command.encode("ascii") + b"\n") for command in commands]
# The main command
cmd = "show tech-support"
# Send the main command
tn.write(cmd.encode("ascii") + b"\n")
hostname = tn.read_until(b"#")
tn.write(b"\nexit\n")
command_output = tn.read_all().decode("ascii")
result = dict(
ip=host, hostname=hostname, command=cmd, command_output=command_output
)
export_tech_support(command_output=result) # Export the show command output
How can I make the session exits automatically upon completion for verbose outputs and avoid waiting for the exec-timeout to be hit (10 minutes in my case)
There is an issue with telnetlib and paramiko when we are extracting large output and configs.
This happens due to the console connection closing while running the script, so you have to look for a solution to maintain the console connection. I would suggest netmiko ,as netmiko version >= 1.0 also has Telnet support now.
For the example , you can check this script:
from pprint import pprint
import yaml
from netmiko import (
ConnectHandler,
NetmikoTimeoutException,
NetmikoAuthenticationException,
)
def send_show_command(device, commands):
result = {}
try:
with ConnectHandler(**device) as ssh:
ssh.enable()
for command in commands:
output = ssh.send_command(command)
result[command] = output
return result
except (NetmikoTimeoutException, NetmikoAuthenticationException) as error:
print(error)
if __name__ == "__main__":
device = {
"device_type": "cisco_ios_telnet", #Refer netmiko device type
"host": "192.168.100.1",
"username": "cisco",
"password": "cisco123",
"secret": "cisco", #if not required remove this
}
result = send_show_command(device, ["sh clock", "sh ip int br"])
pprint(result, width=120)
Related
I'm reading a book called ( Black Hat Python: Python Programming for Hackers and Pentesters
Book by Justin Seitz)
and I'm doing a project from the book called Replacing Netcat , basically an attempt to replace netcat with a python script ,
I wrote the code identical to the book but its not running properly,,,
** I mean I had to make some obvious changes , cause the book is using python 2.7 , and I'm using Python 3.9.12**
but the code isn't working as it should , according to the book,,,,
this is how the book is telling me to run the code ::- how the book tells me to run the script
me trying to run the code 1st step
me trying to run the code 2nd step
as u can see it gives no output ,, if I try to run it according to the book
and just print's the strings and stops when I write "python3 file_name.py" 2
and stops , it doesn't even execute a single function after that, what am I doing wrong ?
import sys
import socket
import getopt
import threading
import subprocess
# global variables that we fooking need .
listen = False
command = False
upload = False
execute = ""
target = ""
upload_dest = ""
port = 0
def usage():
print ("BHP Net Tool")
print()
print ("Usage: bhpnet.py -t target_host -p port")
print ("""-l --listen - listen on [host]:[port] for ¬
incoming connections""")
print ("""-e --execute=file_to_run - execute the given file upon ¬
receiving a connection""")
print ("-c --command - initialize a command shell")
print ("""-u --upload=destination - upon receiving connection upload a ¬
file and write to [destination]""")
print()
print()
print ("Examples: ")
print ("bhpnet.py -t 192.168.0.1 -p 5555 -l -c")
print ("bhpnet.py -t 192.168.0.1 -p 5555 -l -u=c:\\target.exe")
print ("bhpnet.py -t 192.168.0.1 -p 5555 -l -e=\"cat /etc/passwd\"")
print ("echo 'ABCDEFGHI' | ./bhpnet.py -t 192.168.11.12 -p 135")
sys.exit(0)
def main():
global listen
global port
global execute
global command
global upload_dest
global target
if not len(sys.argv[1:]):
usage()
# read commandline options
try:
opts,args = getopt.getopt(sys.argv[1:],"hle:t:p:cu:",
["help", "listen", "execute", "target", "port", "command", "upload"])
except getopt.GetoptError as err:
print(str(err))
usage()
for o,a in opts:
if o in ("-h", "--help"):
usage()
elif o in ("-l", "--listen"):
listen = True
elif o in ("-e", "--execute"):
execute = a
elif o in ("-c", "commandshell"):
command = True
elif o in ("-u", "--upload"):
upload_dest = a
elif o in ("-t", "--target"):
target = a
elif o in ("-p", "--port"):
port = int(a)
else:
assert False, "Unhandled Option"
#are we going to listen or just send data from stdin ?
if not listen and len(target) and port > 0 :
#read in buffer from the commandline
#this will block , so send CTRL-D if not sending input
#to stdin
buffer = sys.stdin.read()
# send data off
client_sender(buffer)
# we are going to listen and potentially
# upload things , execute commands , and drop a shell back
# depending on our command line options above
if listen:
server_loop()
main()
def client_sender(buffer):
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
# connect to out target host
client.connect((target,port))
if len(buffer):
client.send(buffer)
while True:
# now wait for back
recv_len = 1
response = ""
while recv_len:
data = client.recv(4096)
recv_len = len(data)
response += data
if recv_len < 4096:
break
print(response)
# wait for more input
buffer = input("")
buffer += "\n"
# send it off
client.send(buffer)
except:
print("[*] Exception ! Exiting.")
# tear down the connection
client.close()
def server_loop():
global target
#if no target is defined, we listen on al interfaces
if not len(target):
target = "0.0.0.0"
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind((target,port))
server.listen(5)
while True:
client_socket, addr = server.accept()
# spin off a thread to handel our new client
client_thread = threading.Thread(target=client_handler, args = (client_socket,))
client_thread.start()
def run_command(command):
# trim the newline
command = command.rstrip()
# run the command and get the output back
try:
output = subprocess.check_output(command,stderr = subprocess.STDOUT, shell=True)
except:
output = "Failed to execute command.\r\n"
#send the output back to the client
return output
def client_handler(client_socket):
global upload
global exceute
global command
# check for upload
if len(upload_dest):
# read in all of the bytes and write to our destination
file_buffer = ""
#keep reading data until none is available
while True:
data = client_socket.recv(1024)
if not data:
break
else:
file_buffer += data
# now we take these bytes and try to write them out
try:
file_descriptor = open("upload_dest", "wb")
file_descriptor.write(file_buffer)
file_descriptor.close()
# acknowledge that we wrote the file out
client_socket.send(f"Succesfully saved file to {upload_dest}")
except:
client_socket.send(f"Failed to save file to \r\n{upload_dest}")
# check for command execution
if len(execute):
# run the command
output = run_command(execute)
client_socket.send(output)
# now we go into another loop if a command shell was requested
if command:
while True:
# show a simple prompt
client_socket.send("<BHP:#>")
# now we recive until we see a linefeed ( enter key )
cmd_buffer = ""
while "\n" not in cmd_buffer:
cmd_buffer += client_socket.recv(1024)
# send back the command output
response = run_command(cmd_buffer)
# send back the response
client_socket.send(response)
How is your debugging skill?
I was experiencing the same issue afew days ago & i fixed it by debugging entry points into functions to keep track of the eip while the program is running by printing arguments before any operation as they are being passed between functions .. this helps to check whether the code is producing expected values as it runs ..
in short as this case , the book was written in regards to python2 so most of the errors are going to force you to work on your debugging skills..
i would also advice you to take a short crash course in C programming before taking on the BHP book since alot of system calls like socket getopts are really C apis , check on man 2 and man 3 of whatever you're importing in python to have a broader look on things as you debug most errors ..
managed to fix it up until here ..
download source code link
https://filebin.net/i40e2oisabxqmbys
I'm trying to run various linux commands via python's subprocess and ssh. I'd like to be able to run the command and check the stderr and stdout lengths to determine if the command was successful or not. For example if there's no error it was successful. The problem is that after the initial connection all the output is going to stdout.
I did try using paramiko but kept getting unreliable authentication behaviour. This approach seems more robust, if I could just get it to work.
import subprocess
import os
import pty
from select import select
class open_ssh_helper(object):
def __init__(self, host="127.0.0.1", user="root"):
self.host = host
self.user = user
self.cmd = ['ssh',
user +'#'+host,
"-o", "StrictHostKeyChecking=no",
"-o", "UserKnownHostsFile=/dev/null"]
self.prompt = '~#'
self.error = ''
self.output = ''
self.mtty_stdin, self.stty_stdin = pty.openpty()
self.mtty_stdout, self.stty_stdout = pty.openpty()
self.mtty_stderr, self.stty_stderr = pty.openpty()
self.ssh = subprocess.Popen(self.cmd,
shell=False,
stdin=self.stty_stdin,
stdout=self.stty_stdout,
stderr=self.stty_stderr)
self._read_stderr()
self._read_stdout()
def _reader(self, read_pty):
char = ""
buf = ""
while True:
rs, ws, es = select([read_pty], [], [], 0.5)
if read_pty in rs:
char = os.read(rs[0], 1)
buf += char
else:
break
return buf
def _read_stderr(self):
self.error = self._reader(self.mtty_stderr)
def _read_stdout(self):
self.output = self._reader(self.mtty_stdout)
def execute(self, command):
os.write(self.mtty_stdin, command)
self._read_stderr()
self._read_stdout()
if __name__=='__main__':
ss = open_ssh_helper('10.201.202.236', 'root')
print "Error: \t\t: " + ss.error
print "Output: \t: " + ss.output
ss.execute("cat /not/a/file\n")
print "Error: \t\t: " + ss.error
print "Output: \t: " + ss.output
Which outputs something like:
Error: : Warning: Permanently added '10.201.202.236' (ECDSA) to the list of known hosts.
Debian GNU/Linux 8
BeagleBoard.org Debian Image xxxx-xx-xx
Support/FAQ: http://elinux.org/Beagleboard:BeagleBoneBlack_Debian
Output: : Last login: Thu Oct 5 07:32:06 2017 from 10.201.203.29
root#beaglebone:~#
Error: :
Output: : cat /not/a/file
cat: /not/a/file: No such file or directory
root#beaglebone:~#
My hope was that the line cat: /not/a/file: No such file or directory would be printed as the error line above it. However it seems that for some reason the stdout is printing both the output and the error.
ssh pulls back both stdout and stderr on the remote system as a single stream so they both come through on the ssh stdout.
If you want to separate the two streams on the remote system you will have to do that by redirecting one or both of them to a remote file and then reading the files. Or less reliably but possibly easier, you could redirect stderr through a filter that puts something like 'STDERR:' on the start of each line and split the streams again that way.
Related questions that are essentially asking the same thing, but have answers that don't work for me:
Make python enter password when running a csh script
How to interact with ssh using subprocess module
How to execute a process remotely using python
I want to ssh into a remote machine and run one command. For example:
ssh <user>#<ipv6-link-local-addr>%eth0 sudo service fooService status
The problem is that I'm trying to do this through a python script with only the standard libraries (no pexpect). I've been trying to get this to work using the subprocess module, but calling communicate always blocks when requesting a password, even though I supplied the password as an argument to communicate. For example:
proc = subprocess.Popen(
[
"ssh",
"{testUser1}#{testHost1}%eth0".format(**locals()),
"sudo service cassandra status"],
shell=False,
stdin=subprocess.PIPE)
a, b = proc.communicate(input=testPasswd1)
print "a:", a, "b:", b
print "return code: ", proc.returncode
I've tried a number of variants of the above, as well (e.g., removing "input=", adding/removing subprocess.PIPE assignments to stdout and sterr). However, the result is always the same prompt:
ubuntu#<ipv6-link-local-addr>%eth0's password:
Am I missing something? Or is there another way to achieve this using the python standard libraries?
This answer is just an adaptation of this answer by Torxed, which I recommend you go upvote. It simply adds the ability to capture the output of the command you execute on the remote server.
import pty
from os import waitpid, execv, read, write
class ssh():
def __init__(self, host, execute='echo "done" > /root/testing.txt',
askpass=False, user='root', password=b'SuperSecurePassword'):
self.exec_ = execute
self.host = host
self.user = user
self.password = password
self.askpass = askpass
self.run()
def run(self):
command = [
'/usr/bin/ssh',
self.user+'#'+self.host,
'-o', 'NumberOfPasswordPrompts=1',
self.exec_,
]
# PID = 0 for child, and the PID of the child for the parent
pid, child_fd = pty.fork()
if not pid: # Child process
# Replace child process with our SSH process
execv(command[0], command)
## if we havn't setup pub-key authentication
## we can loop for a password promt and "insert" the password.
while self.askpass:
try:
output = read(child_fd, 1024).strip()
except:
break
lower = output.lower()
# Write the password
if b'password:' in lower:
write(child_fd, self.password + b'\n')
break
elif b'are you sure you want to continue connecting' in lower:
# Adding key to known_hosts
write(child_fd, b'yes\n')
else:
print('Error:',output)
# See if there's more output to read after the password has been sent,
# And capture it in a list.
output = []
while True:
try:
output.append(read(child_fd, 1024).strip())
except:
break
waitpid(pid, 0)
return ''.join(output)
if __name__ == "__main__":
s = ssh("some ip", execute="ls -R /etc", askpass=True)
print s.run()
Output:
/etc:
adduser.conf
adjtime
aliases
alternatives
apm
apt
bash.bashrc
bash_completion.d
<and so on>
I have a Python routine which invokes some kind of CLI (e.g telnet) and then executes commands in it. The problem is that sometimes the CLI refuses connection and commands are executed in the host shell resulting in various errors. My idea is to check whether the shell prompt alters or not after invoking the CLI.
The question is: how can I get the shell prompt string in Python?
Echoing PS1 is not a solution, because some CLIs cannot run it and it returns a notation-like string instead of the actual prompt:
SC-2-1:~ # echo $PS1
\[\]\h:\w # \[\]
EDIT
My routine:
def run_cli_command(self, ssh, cli, commands, timeout = 10):
''' Sends one or more commands to some cli and returns answer. '''
try:
channel = ssh.invoke_shell()
channel.settimeout(timeout)
channel.send('%s\n' % (cli))
if 'telnet' in cli:
time.sleep(1)
time.sleep(1)
# I need to check the prompt here
w = 0
while (channel.recv_ready() == False) and (w < timeout):
w += 1
time.sleep(1)
channel.recv(9999)
if type(commands) is not list:
commands = [commands]
ret = ''
for command in commands:
channel.send("%s\r\n" % (command))
w = 0
while (channel.recv_ready() == False) and (w < timeout):
w += 1
time.sleep(1)
ret += channel.recv(9999) ### The size of read buffer can be a bottleneck...
except Exception, e:
#print str(e) ### for debugging
return None
channel.close()
return ret
Some explanation needs here: the ssh parameter is a paramiko.SSHClient() instance. I use this code to login to a server and from there I call another CLI which can be SSH, telnet, etc.
I’d suggest sending commands that alter PS1 to a known string. I’ve done so when I used Oracle sqlplus from a Korn shell script, as coprocess, to know when to end reading data / output from the last statement I issued. So basically, you’d send:
PS1='end1>'; command1
Then you’d read lines until you see "end1>" (for extra easiness, add a newline at the end of PS1).
I want to run a python script say test.py on a Linux target machine (which has a python interpreter) and capture the output of the command in a text file as the part of another python script invoke.py using paramiko module.
The statement in the script
stdin, stdout, sterr = ssh.exec_command("python invoke.py > log.txt")
generates a blank file log.txt.
Please suggest corrections / alternate way to do this. to write the output to the file correctly.
test.py when run locally outputs sane text (which is expected to be logged in log.txt).
There are some relevant posts here and here, but no one deals with output of python script
instead of calling client.exec_command() you can use client.open_channel() and use channel session's recv() and recv_stderr() streams to read write stdout/std err:
def sshExecute(hostname, username, password, command, logpath = None):
buffSize = 2048
port = 22
client = paramiko.Transport((hostname, port))
client.connect(username=username, password=password)
if logpath == None:
logpath = "./"
timestamp = int(time.time())
hostname_prefix = "host-%s-%s"%(hostname.replace(".","-"),timestamp)
stdout_data_filename = os.path.join(logpath,"%s.out"%(hostname_prefix))
stderr_data_filename = os.path.join(logpath,"%s.err"%(hostname_prefix))
stdout_data = open(stdout_data_filename,"w")
stderr_data = open(stderr_data_filename,"w")
sshSession = client.open_channel(kind='session')
sshSession.exec_command(command)
while True:
if sshSession.recv_ready():
stdout_data.write(sshSession.recv(buffSize))
if sshSession.recv_stderr_ready():
stderr_data.write(sshSession.recv_stderr(buffSize))
if sshSession.exit_status_ready():
break
stdout_data.close()
stderr_data.close()
sshSession.close()
client.close()
return sshSession.recv_exit_status()
Hope this fully working function helps you