← Back to Blog

June 30, 2026

Inside AvengerCon X: A Day in an Agentic Offensive Security Range

11 min read

Notes from Day 1 of AvengerCon X, a hands-on workshop where AI coding agents drove a 40-challenge offensive security range, and what the experience taught me about human-agent teaming.

aicybersecurityctfconference

AvengerCon X, a two-day cyber conference organized by the Army’s 780th Military Intelligence Brigade (CYBER), took place this year at Johns Hopkins University’s Applied Physics Laboratory in Laurel, Maryland. Day 2 featured talks and panels, while Day 1 focused on hands-on workshops, specifically bring-your-own-laptop sessions. Out of four parallel tracks, I chose Offensive AI Tools and Techniques, taught by Mark Klink.

The workshop’s objective was straightforward: spend the morning exploring AI concepts and then the afternoon deploying AI coding agents on a live range. I’ve spent months building my own agent workflows (as detailed in my Hermes Agent series), so I was eager to see how these ideas translate into an offensive security context designed by professionals in the field. Curiosity led me in, and genuine admiration followed.

The range: SPARKLAB

The workshop took place at SPARKLAB, and the infrastructure choices were worth analyzing in detail. Everything was hosted behind a private Tailscale network specifically set up for the event. Participants connected using a temporary authentication key, and once connected, a single host provided a CTFd portal for launching challenges and a model gateway for the AI component.

The gateway stood out as the most intriguing aspect. It exposed an OpenAI-compatible endpoint that could route to either a local model running on dedicated hardware or a frontier model. Switching between these models was as simple as a command, allowing everyone in the room to compare their performance on the same challenges rather than relying solely on assumptions.

Additionally, there was an optional “agentic workstation,” a preconfigured terminal equipped with multiple AI coding-agent CLIs side by side. Instead of selecting a single tool and teaching it, the workshop encouraged participants to drive the range using their preferred agent or compare several. This framing, where the agent is seen as a teammate rather than an autopilot, influenced every aspect of the session.

What we were expected to do

The instructions were refreshingly explicit about the process, rather than just the outcomes. We were instructed to work from evidence rather than making assumptions based on the challenge’s name, to maintain running notes that a teammate could reproduce, and to treat the environment with the same operational discipline as in a real engagement: stay within your own launched instance, avoid hammering shared infrastructure, refrain from brute-forcing by default, and use the bounded wordlist only when a challenge explicitly pointed you toward guessing.

The debrief goal made the priorities crystal clear. The focus was on demonstrating a single working command chain, identifying one incorrect assumption, highlighting a moment where the agent required correction, and providing an honest comparison between the local and frontier models. This is a vastly different evaluation criterion compared to “how many flags did you obtain,” and I believe it is the appropriate one for a workshop aimed at learning to collaborate with an agent rather than merely letting it run.

Forty challenges, one ladder

The range consisted of forty challenges, along with an unscored zero-index sandbox designed to help you get your agent tooling working before engaging with any scored challenges. I won’t walk through exact solve steps here, partly out of respect for the people who’ll run this range again for future cohorts, and partly because the value wasn’t in any single flag. But the shape of the ladder itself is worth describing in more detail because it’s a genuinely good model for teaching the entire intrusion lifecycle in a single afternoon.

The opening tier was pure hygiene. Nothing here required an exploit so much as noticing what shouldn’t have been left in plain sight: SSH banners that disclosed default service credentials before you’d even authenticated, a .env file sitting in a public web root with a database connection string in it, a stale backup directory a plain directory listing walked you straight into, a robots.txt that told crawlers to stay out of exactly the folder holding the flag, SNMP still answering to the default public community string, and a raw, netcat-reachable “maintenance relay” that handed out fallback credentials to anyone who simply connected. None of these needed cleverness so much as the discipline to check the boring places first, which turned out to be most of the lesson.

From there, it moved into the vulnerability classes that show up on every top-ten list, and for good reason. A login form vulnerable to classic SQL injection. A file-viewer endpoint that would happily read any path on the server, local file inclusion in miniature. A cousin of that bug reachable through a URL-fetching “include” feature, this time via a file:// wrapper instead of a raw path. An XML intake endpoint that resolved external entities without complaint, textbook XXE. A “job queue” that deserialized whatever you handed it, Python pickle included, a reminder that deserialization isn’t inert until proven otherwise. A signed session token secured with a guessable HMAC secret, forgeable into an admin role in a few lines of Python. A template-preview endpoint that evaluated user input as a Jinja template rather than treating it as a plain string, server-side template injection with a direct path to reading files off the box. A network diagnostic tool that shelled out to ping with the host parameter concatenated straight into the command line. And one challenge that stacked two of these together on purpose: an unauthenticated config-disclosure endpoint that leaked credentials into an authenticated “run a command” feature on that same service, no separate SSH foothold required at all.

Past that, the ladder shifted from getting in to going further, entirely through Linux privilege escalation. A find utility installed with the SUID bit set, a classic GTFOBins-style escape hatch. A sudo configuration that lets a completely ordinary binary run as root with no password. Operator notes sitting in a home directory that leaked a second account’s credentials outright. A backup script, world-writable, that is executed by a scheduled root job on a timer, letting you rewrite what root would run next. A privileged helper that shelled out to python3 without an absolute path, so planting a fake binary earlier in PATH caused it to execute as root instead. A non-standard Python interpreter carrying the cap_setuid Linux capability, enough to call setuid(0) without a SUID bit anywhere in sight. A world-writable /etc/passwd, letting you append a UID-0 account directly, and just as important, restoring the file cleanly afterward, since privilege escalation that leaves a box worse off than you found it isn’t actually the skill being tested. A GNU tar job run against a world-writable directory, where filenames crafted to look like tar options got interpreted as real flags, turning file creation into command execution. And an LD_PRELOAD trick against a SUID helper that forwarded an environment variable into the dynamic loader before dropping privileges, letting an attacker-supplied shared library execute as root.

The back half dropped to the binary level, where the bug isn’t a missing check on a web form but a specific way a program manages its own memory. A stack buffer overflow that overwrote a saved function pointer, redirecting execution into a privileged win() function that already existed in the binary and just needed to be called. A format-string vulnerability that leaked a secret straight out of a running SUID process’s memory using nothing more than positional format specifiers, no write primitive needed at all. A ret2libc-style challenge that leaked a libc address as an obvious distraction, when the more reliable move on a non-PIE binary was redirecting into the executable’s own fixed-address helper function instead. A heap-allocated struct where the “authorization” flag lived inside the very same chunk as the attacker-controlled input, so reaching it was a matter of sending enough bytes rather than a separate overflow. And a genuine TOCTOU race condition, a check-then-sleep-then-use gap in a SUID binary, where winning the race also meant understanding that the kernel’s symlink protections block a staged symlink from working inside a sticky, world-writable directory like /tmp, so the race had to be staged somewhere else entirely.

A handful of challenges didn’t fit into any single category, and those were among the most memorable. One was a straightforward “find the thing,” a flag hidden in a world-readable dotfile in a temporary filesystem, planted with conventional low-effort placement rather than any real protection, which was its own lesson: not everything escalates; some challenges just test whether you actually looked. Another was a small forensics puzzle: a hand-crafted, mostly empty image file concealing an XOR-encoded message in its metadata, solvable with a hex dump and some patience rather than any steganography tooling. And a couple of challenges chained several of the categories above end-to-end, with a leaked web credential leading to an SSH foothold in one of the binary bugs, which is a far more realistic simulation of an actual engagement than any single isolated puzzle could offer.

Strung together, it’s a genuinely well-designed teaching arc: recon, exploitation, escalation, and low-level exploitation, in that order, each building the necessary intuition for the next.

Where I actually learned something

A few things resonated with me after the workshop.

The agent is a collaborator, not a black box. The workshop’s framing (manual, co-pilot, full auto) holds significance because these represent genuinely different working relationships, and the skill of effectively directing an agent is distinct from the skill of exploitation itself. Observing when an agent’s initial instinct was misguided and guiding it back with more compelling evidence proved more instructive than any challenge it solved flawlessly on the first attempt.

Local models are a viable option, albeit with certain limitations. The ability to seamlessly switch between a local model and a frontier model on identical infrastructure made the trade-off tangible rather than theoretical. The local option offers privacy and doesn’t leave the network, which is crucial in many real operational contexts. Additionally, in this setup, it’s less reliable under load. This is a fair trade-off in some situations and a disadvantage in others, and now I’ve experienced the difference firsthand instead of merely reading about it.

A brand-new frontier model landed mid-workshop, and the difference was obvious immediately. Anthropic had shipped Claude Sonnet 5 that very day, and I couldn’t resist pointing my own agent at it instead of sticking to the two options already wired into the gateway, with its reasoning effort dialed all the way up to the highest “Extra” setting. The difference showed up fast: it was noticeably better at holding a hypothesis, testing it against the actual evidence in front of it, and revising course the moment that evidence didn’t line up, which is exactly the judgment that separates a useful agent teammate from one that just guesses confidently. It didn’t replace the workshop’s own model choices, since comparing those was the actual point of the exercise, but running a model on its first day of public availability side by side against the local and frontier options already in the range was a rare kind of live A/B test, and Sonnet 5 on Extra effort held up to every challenge I pointed it at.

Small mistakes compound quickly when an agent is executing them. I made an environment-hygiene mistake partway through the afternoon that locked up my own challenge instance and forced a restart. Nothing damaging, and entirely inside my own sandbox, but it was a good reminder that “the agent can run this command for me” doesn’t remove the need to think through what that command actually does first, especially with anything that touches process environment or memory. An agent will execute a bad idea exactly as fast as it executes a good one.

Process discipline is the actual skill being taught. The emphasis on reproducible notes, stated rationale, and scope discipline wasn’t administrative overhead bolted onto the “real” content. It was the content. Anyone can eventually stumble into a flag. Being able to explain why you tried what you tried, in an order someone else could follow, is what separates a fun afternoon from a transferable skill.

I came into AvengerCon X expecting a solid technical workshop. I left with a clearer mental model of what good agent-assisted offensive security practice actually looks like, and a few humbling reminders about the discipline it still requires from the human in the loop.