David Sky David Sky - 4 years ago 91
Linux Question

Python: How to scan stderr or stdout of a subprocess and perform an action if the string is found?

The below code fails to exit after 3/3 even though the stderr text

[ERROR] ssh target does not support password auth
pops up in my linux terminal. I believe the text is stderr output due to the following line from Hydra's source code:

hydra module "ssh.c" source code snip:
fprintf(stderr, "[ERROR] ssh target..


It appears that my program is not properly scanning stderr for the string. The variable
output
is supposed to capture this output, which I then want to "grep" for an occurrence of the [ERROR] string. If it's found I want the program to exit (instead of running 20k login attempts against an invulnerable service).

Any help is appreciated.

My code

#!/usr/bin/env python
import subprocess
import sys

if len(sys.argv) != 3:
print "Usage: sshrecon.py <ip address> <port>"
sys.exit(0)

ip_address = sys.argv[1].strip()
port = sys.argv[2].strip()

print "INFO: Performing <<TEST>> hydra ssh scan against " + ip_address

#3 steps for test phase:
#1 Run Hydra
#2 Gather std_err std_out output into a var
#3 analyze for String. Exit if found, else continue with full scan.

#[1/3] TEST if target allows PW auth
HYDRA_TEST_COMMAND = "hydra -l test -p test -f -o /var/www/html/recon_scan/results/labs/%s_test.txt -u %s -s %s ssh" % (ip_address, ip_address, port)
results_Test = subprocess.Popen(HYDRA_TEST_COMMAND, stdout=subprocess.PIPE, shell=True)

#[2/3]Gather stderr, stdout output into var
output = results_Test.stdout.read()
print output

#[3/3]analyze std_out for string. Exit if found.
if "ssh target does not support password auth" in output:
sys.exit()

Answer Source

This does not capture the result of stderr.

results_Test = subprocess.Popen(
    HYDRA_TEST_COMMAND,
    stdout=subprocess.PIPE,
    shell=True)

Because stderr is not redirected, only stdout, your program will not be able to read it. Your two choices are to mix stderr and stdout into one single stream, or to capture them separately.

If you want to combine both stderr and stdout into one stream,

results_Test = subprocess.Popen(
    HYDRA_TEST_COMMAND,
    stdout=subprocess.PIPE,
    stderr=subprocess.STDOUT,
    shell=True)

If you want separate streams,

results_Test = subprocess.Popen(
    HYDRA_TEST_COMMAND,
    stdout=subprocess.PIPE,
    stderr=subprocess.PIPE,
    shell=True)

Use shell=False

Using shell=True is not recommended in general, because it can lead to potential unexpected behavior or attack vectors. Alternatively, you can pass the arguments as a list instead of a string, and then you can use the default (safer, faster) shell=False.

HYDRA_TEST_COMMAND = [
    'hydra', '-l', 'test', '-p', 'test', '-f',
    '-o', '/var/www/html/recon_scan/results/labs/%s_test.txt' % ip_address,
    '-u', ip_address, '-s', port, 'ssh']

If port is an int you will need to use str(port) instead.

There are cases where shell=True is more convenient but they are rare in practice.

This might not affect how your program works, but it eliminates problems with quoting or weird characters in filenames. If you don't like how verbose it is you can make it a bit shorter with .split().

Use .communicate()

Instead of proc.stdout.read(), use proc.communicate().

# instead of results_Test.stdout.read()
stdout, stderr = results_Test.communicate()

If you used PIPE for both stdout and stderr, this gives you the contents of both in separate variables. If you use .stdout.read() instead, the process can become blocked waiting for you to read from .stderr.read(). That's the primary job of .communicate() -- it reads from both stderr and stdout at the same time so the process doesn't block.

If you use stderr=subprocess.STDOUT, then stderr will be None because the contents are mixed into stdout.

In either case, it is recommended to use .communicate().

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download