How to do interactive "su -c command -" with AsyncSSH

337 Views Asked by At

I have successfully executed su -c whoami - using paramiko like such:

def amiroot(ssh: paramiko.client.SSHClient, root_pass: str) -> bool:
    session = ssh.get_transport().open_session()
    session.set_combine_stderr(True)
    session.get_pty()
    # print("Sending su -c whoami -")
    session.exec_command("su -c whoami -")
    stdin = session.makefile('wb', -1)
    stdout = session.makefile('rb', -1)
    while not re.search(b"[Pp]assword", session.recv(1024)):
        time.sleep(1)
    # print("Sending password")
    stdin.write(root_pass + "\n")
    stdin.flush()
    start = time.monotonic()
    while not stdout.channel.eof_received:
        time.sleep(1)
        if (time.monotonic() - start) > 10:
            # print("STDOUT timeout")
            stdout.channel.close()
            break
    # lines = list(filter(None, (lb.decode("utf-8").strip() for lb in stdout.readlines())))
    # print(lines)
    return stdout.read().decode("utf-8").strip() == "root"

So conceptually it works, but I need it to be done asynchronously against many targets.

I've chosen to use AsyncSSH, and have gone so far as such:

async def amiroot(address: str, username: str, password: str, rootpass: str):
    async with asyncssh.connect(
        address,
        username=username,
        password=password,
        known_hosts=None,
        connect_timeout=10,
        login_timeout=10,
    ) as conn:
        print(f"Connected to {address}, sending su")
        async with conn.create_process("su -c whoami -", term_type="xterm") as process:
            print(f"{address}: su sent, waiting response")  ###
            while "password" not in (await process.stdout.read()):
                await asyncio.sleep(1)
            await process.stdin.write(rootpass + "\n")
            print(f"{address}: rootpass sent, waiting response")
            print(await process.stdout.read())

However I got stuck at the line marked ### and never even reached the rootpass sent line. For example:

Connected to 1.2.3.4, sending su
1.2.3.4: su sent, waiting response

And the program just stuck there.

How do I invoke su -c ... - in AsyncSSH similarly to how I did it using paramiko?

1

There are 1 best solutions below

0
On

Okay finally figured out how to interact. This is the function that works:

async def amiroot(address: str, username: str, password: str, rootpass: str):
    async with asyncssh.connect(
        address,
        username=username,
        password=password,
        known_hosts=None,
        connect_timeout=10,
        login_timeout=10,
    ) as conn:
        print(f"Connected to {address}, sending su")
        async with conn.create_process("su -c whoami -", stderr=asyncssh.STDOUT) as process:
            print(f"{address}: su sent, waiting response")
            response = ""
            while True:
                recv = await process.stdout.read(512)
                print(recv)
                if recv is not None:
                    response += recv.casefold()
                    if "password" in response:
                        break
                await asyncio.sleep(0.1)
            # while "password" not in await process.stdout.read(1024).casefold():
            #     await asyncio.sleep(1)
            print(f"{address}: sending rootpass")
            process.stdin.write(rootpass + "\n")
            process.stdin.write_eof()
            print(f"{address}: rootpass sent, waiting response")
            whoami = (await process.stdout.read()).strip()
            print(">>", whoami)
            return whoami == "root"

The output when run:

Connected to 1.2.3.4, sending su
1.2.3.4: su sent, waiting response
Password: 
1.2.3.4: sending rootpass
1.2.3.4: rootpass sent, waiting response
>> root