SSH Hijacking
ssh hijacking
sshd
ssh-agent
ssh
Client communicates with ssh-agent, which
has a cached credential in memory.
sshd
ssh-agent
ssh
ssh-agent performs auth to sshd across network
sshd
ssh-agent
ssh
ssh-agent acts as a pipe between two sockets for the client/server
sshd
ssh-agent
ssh
client can now execute code
sshd
ssh-agent
ssh
Attackers can ask ssh-agent to auth for them, and since the creds are cached this works
evil.sh
sshd
ssh-agent
ssh
But if we enforce 2FA on sshd, attacker is stopped
evil.sh
sshd
ssh-agent
ssh
with control master the ssh-agent never releases the connection to the server, so clients don't have to re-auth if there's MFA
one time mfa
sshd
ssh-agent
ssh
now attacker can leverage the existing connection, bypassing 2fa
evil.sh
shit alright so what do we do, we were banking on mfa
drinking grapl, obviously
notice how all of the GRAPHics were actually graphs, coincidence?
sshd
ssh-agent
ssh
evil.sh
What's suspicious about this whole scenario?
sigs
- some proc other than ssh is talking to ssh-agent
- fucking ban control master entirely, that should be a policy
it actually gets worse
sshd
so the client agent opens a socket to the server sshd, and then that server sshd uses the client to auth *more* connections to *other* servers
sshd
sshd
legit ssh from eng
evil ssh from ian
sshd
so the client agent opens a socket to the server sshd, and then that server sshd uses the client to auth *more* connections to *other* servers
sshd
sshd
legit ssh from eng
evil ssh from ian
ian says "auth me" to eng
sshd
ian uses 'eng' agent to auth to some other server that previously he didnt have access to
basically he owns EVERY SINGLE USER who forwards their key to that purple ssh
sshd
sshd
legit ssh from eng
evil ssh from ian
ian says "auth me" to eng
class SshIpc(Analyzer):
def get_queries(self) -> IpcQuery:
return (
IpcQuery()
.with_ipc_from(
ProcessQuery().with_process_name(eq=Not("/bin/ssh"))
)
.with_ipc_to(
ProcessQuery().with_process_name(eq=Not("/usr/bin/ssh-agent"))
)
)
class RareParentOfSsh(Analyzer):
def get_queries(self) -> IpcQuery:
return (
SshQuery()
.parent_process().with_process_name()
)
def on_response(self, response, chn):
if count(response.process_name, response.parent.process_name) <= 1:
pass # emit the sig
# Some SSH client ssh'd, and then performed IPC, and then SSH'd again
class ChainedSsh(Analyzer):
def get_queries(self) -> IpcQuery:
return (
SshQuery()
.with_ssh_connection_to(
SshQuery()
.with_ipc_to()
.with_ssh_connection_to(
SshQuery()
)
)
)
Testing, etc
By Colin
Testing, etc
- 624