Repository navigation
Releases: euriconicacio/pwnloop
Release list
pwnloop 1.22.0
Seven pending write-ups published — Voleur (Medium, Windows / AD),
GiveBack (Medium, Linux / k8s), Artificial (Easy, Linux), DevHub
(Medium, Linux), Era (Medium, Linux), SmartHIRE (Medium, Linux) and
Reset (Easy, Linux) — taking the index from 60 to 67 chains. The
rooted-machines count is reconciled 61 → 69: the 67 published chains plus two
rooted-only machines held back from publication (darkzeroreturns, rooted but
not retired, and one further box held back).
Two through-lines run across the batch. The first is a model upload is code
execution, not data: Artificial runs an uploaded Keras .h5 whose Lambda layer
deserializes to RCE, and SmartHIRE lands the same way through a model-loading
endpoint reached with default creds — treat any ML "upload a model / run a model"
surface as a deserialization sink, not a file store. The second is the thing
that runs as root is a backup or ops convenience: Voleur falls to a
world-readable ntds.dit + SYSTEM/SECURITY backup left on a share (an offline
DCSync that needs no SeBackupPrivilege at all), DevHub to an "operations" MCP
service running as root that hands out root's key, Artificial to a localhost
Backrest backup daemon with a user-editable command field, and GiveBack to a
sudo runc "debug" wrapper whose host-mount filter is a single string compare
walked around with /..
The reference tree gains the write-back these engagements produced.
ad.md: on a Kerberos-only (NTLM-disabled) domain a recovered hash or AES key
cannot pass-the-hash — convert it to a TGT and overpass-the-hash instead, and
prefer the AES256 key because a hardened domain often refuses RC4 (Voleur).
privesc-windows.md: an AD backup (ntds.dit + SYSTEM) left readable on disk
is a full DCSync regardless of any AD right, and WSL is an in-box Linux pivot
whose /mnt/c (DrvFs) keeps Windows ACLs — root in the subsystem is a loot view
for world-readable secrets, not Windows privilege (Voleur). privesc-linux.md
gains the audit-log-as-credential-store class (Reset's adm-readable
auditd/laurel) and the writable-directory-on-root's-import-path class (SmartHIRE's
sudo-run MLflow control script). web.md and llm-apps.md gain the
model-deserialization and PHP stream-wrapper sinks behind Artificial, SmartHIRE
and Era; kubernetes.md and containers.md the no-egress pod pivot and the
container-runtime-wrapper string-filter bypass behind GiveBack.
pwnloop 1.21.0
[1.21.0] — 2026-09-19
Six pending write-ups published — DarkZero (Hard, Windows / AD), JOB
(Medium, Windows), media (Medium, Windows), Outbound (Easy, Linux),
race (Hard, Linux) and watcher (Medium, Linux) — taking the index from
54 to 60 chains. The rooted-machines count had drifted behind reality: six boxes
were rooted but never counted, so this release also reconciles the metric
55 → 61 (60 published chains plus one rooted-only machine held back from
publication).
The through-line across the Windows boxes is a stripped service token is not a
missing privilege. On both DarkZero and media the service account runs without
SeImpersonate in the shell you land in — the obvious potato is off the table —
but the privilege is only absent from the restricted/network token; the
account's service-logon token still carries it. media restores it with
FullPowers; DarkZero recovers it by making the SMB redirector re-authenticate
the account to a local named pipe and impersonating the client. Either way the
lesson is the same: when whoami /priv is short on a service account, check
whether the privilege was removed or merely filtered before abandoning the
potato path.
references/services.md gains the run's self-found addition to the MSSQL
linked-server section: a linked server can self-map into a separate forest,
so a guest on one DC's SQL becomes sysadmin RCE on another forest's DC — and
enabling xp_cmdshell across the link needs four-part naming
(EXEC [LINK].master.dbo.sp_configure …) because the nested-quote EXEC(…) AT
form mangles the doubled quotes; driving it from a small impacket tds script
beats escaping through the shell.
The rest of the references/ tree gains the write-back these six engagements
produced. web.md: an upload that a person or bot later opens is a client-side
NTLM coercion (media's WMP/ASX UNC), and a CMS's own deny rules plus stock
features leak what you need (the Grav 403/404 existence oracle and
backup-as-read-primitive from race). privesc-linux.md: a root process that
follows a symlink you planted to write its own output, and a root job that
checksum-validates a file then executes it (a TOCTOU the race box is named
after), plus reading a privileged script that authenticates through a writable
web root (watcher/outbound). privesc-windows.md: arbitrary file write as SYSTEM
turned into execution (JOB's PHISH handler, media's junction). evasion.md:
AppLocker that blocks PowerShell but still runs native LOLBins (JOB).
devops.md: a CI server's build runner is command execution as whatever the
agent runs as (watcher's root TeamCity agent). cracking.md: a cracker that
returns instantly having "recovered" nothing may never have run — check for a
held instance lock (JOB).
memory/local.md records three method notes from the DarkZero run: do not stack
blocking helper processes on a low-RAM target (it DoSes the box and severs your
own channel — this run wedged a 2 GB DC and needed a reset); get one clean
interactive context early instead of iterating blind multi-step commands through
a slow linked-server xp_cmdshell; and for an unconstrained-delegation TGT
capture, the trigger decides whether the forwarded ticket persists — an
incidental file-touch (xp_dirtree) can tear the session down before the ticket
caches, while a real coercion primitive (MS-EFSRPC / MS-DFSNM) leaves it long
enough for the monitor to catch.
pwnloop 1.20.0
Four write-ups published — Imagery (Medium, Linux), Manage (Easy,
Linux), RetroTwo (Easy, Windows / AD) and Editor (Easy, Linux) — taking
the index from 50 to 54 chains and the rooted-machines count 51 → 55.
The through-line this cycle is an allow-list names a thing; it does not bind
one. Three of these four boxes fell to the same reasoning error on the
defender's side: a control enumerates permitted names and then lets something
else decide what each name resolves to. Once you look for that gap the exploit is
never the hard part — the work is noticing that the list and the binding are two
different mechanisms.
references/privesc-linux.md carries both Linux halves. First, sudo lets you
create a principal the policy already trusts — the name is the vulnerability: a
rule like NOPASSWD: /usr/sbin/adduser ^[a-zA-Z0-9]+$ is genuinely hardened
against option injection, traversal and the two-argument group form, and still
loses, because sudoers routinely grants rights to an account nobody has created
yet and creating it inherits the grant. The entry includes how to reason toward
the trusted name when /etc/sudoers is unreadable, which cheap alternatives to
rule out explicitly so the lead does not loop, and the chicken-and-egg cleanup (a
self-granting account cannot userdel itself; schedule it detached with
systemd-run --on-active). Second, the privileged helper with an allow-list of
subcommands joins the PATH-hijack class: a setuid shim exists so an unprivileged
agent can run a fixed set of tools as root, and its hardcoded table constrains the
program name while only an absolute path constrains which program that
name reaches — so a supplementary group in id that exists solely to reach such
a shim is itself the lead.
The same file gains the run's own mistake as a rule: a shell script is a bad
payload for any setuid primitive. bash, dash and sh reset euid to the
real uid at startup unless given -p, so the vector fires perfectly and leaves
artifacts owned by the unprivileged user — a silent failure that reads exactly
like a patched target and sends you off doubting the CVE. The entry gives the
one-line diagnostic (id > /tmp/who as the whole payload) and the rule that
follows: judge a setuid payload by the owner of what it created, not by its
exit status.
references/ad.md states the Windows form of the same class: a broad group you
can join is the source of an ACE, not just a target of one. Any authenticated
user with MachineAccountQuota>0 can create a computer, which is automatically a
member of Domain Computers; whatever that group can do, your new computer can
do. The chain worth walking is the second hop most people skip — seize a machine
account via ForceChangePassword, then query what that account can do, because
a machine account with AddMember over a group that nests into a logon group is
interactive access to a DC.
references/web.md adds the enumeration discipline that opened two of these
boxes. Server-rendered products leak their own version in asset paths, not
headers — a Java webapp emits its build version inside every static URL it
renders, so the gap between the container version nmap reports (Jetty, Tomcat)
and the application version the CVEs are written against closes with one curl
and a grep, not with a scanner. And a sanitiser applied to N−1 fields: an
application that imports DOMPurify is not an application that sanitises, it is an
application with a sanitiser, and the question is how many of its sinks call it.
Counting sanitise calls against interpolated fields in each render loop names the
vulnerable field — and therefore the endpoint that writes it — before a single
payload is sent.
references/privesc-windows.md documents the Print Spooler as a SYSTEM primitive
for the case that actually recurs: no SeImpersonate, PowerUp clean, spooler
running. The local PrintNightmare variant is the one to rank when egress
tests come back blocked, since the driver DLL is loaded from the target's own
disk. The entry is mostly a map of the errors, because none of them mean
"patched" except a consistent access-denied with the warned-driver flag set, and
because the working call needs a real driver DLL copied out of the driver store
(the in-use file gives a sharing violation) and a plain C# Add-Type P/Invoke
(PSReflect fails on the PowerShell v2 you will actually meet on 2008 R2). It also
carries the cost of getting this wrong: PrintNightmare is destructive to the
spooler, repeated tuning calls wedge it, and this cycle that cost a box reset —
so prove the primitive once, then fire the real payload once.
references/services.md expands RMI/JMX now that a JMX port was the whole
foothold on one box: beanshooter enum answers "does it require auth" with no
credentials, and the step to take before any exploit is reading MBeans for
secrets — a Tomcat JVM's UserDatabase MBean hands back realm passwords in
cleartext even when the Manager app is IP-restricted, so a 403 there is not the
end of the port.
references/artifacts.md treats a Microsoft Access database as a credential
store including its macros: a native ACE db is not an OLE/OOXML container, so
msoffcrypto-tool/olevba refuse it outright and the path is office2john plus
jackcess-encrypt — and the VBA project lives as OLE streams in the hidden
MSysAccessStorage table, where a service account's LDAP bind password sits in
cleartext in the source rather than in strings output.
references/cracking.md adds the case where neither tool fits: no john format
and no GPU for the hashcat mode reads as "not crackable here" and usually is not
true. Every password-based container keeps its KDF, its verifier and its
parameters in the header in the clear, so reimplementing the guess-and-check loop
against OpenSSL is a ten-minute job that makes the wordlist the limit again.
pwnloop 1.19.0
Eight write-ups published — Ten (Hard, Linux), Guardian (Hard, Linux),
Delegate (Medium, Windows / AD), Previous (Medium, Linux), Rainbow
(Medium, Windows), Build (Medium, Linux), CodePartTwo (Easy, Linux) and
Lock (Easy, Windows) — taking the index from 42 to 50 chains and the
rooted-machines count 43 → 51.
The through-line this cycle is the privileged config is the payload: on more
than half of these boxes root did not come from a memory bug or a kernel exploit
but from a value an unprivileged user controls being rendered, unescaped, into a
file or an argument list that a root process then executes.
references/privesc-linux.md grows the two general cases. First, the sudo
target's configuration is part of the trust boundary — read the Defaults line,
not just the command: !env_reset that keeps HOME turns any sudo-able tool that
reads a dotfile into root (~/.terraformrc dev_overrides fork/execs an attacker
terraform-provider-* before the go-plugin handshake, so judge it by a
side-effect oracle, not the exit status). Second, a sudo wrapper that hands a
server its own config file — the config language is the payload, and for Apache
the reliable primitive is a piped log (CustomLog "|/path/prog"), spawned as
root when Apache opens its logs. references/devops.md states the class from the
other end: a value that is a command list — backup and job-runner configs
(npbackup pre_exec_commands) and self-hosted-git push-to-deploy hooks — is RCE
the moment you can write it. The same shape reaches the top of the chain on Ten,
where a signup value is dropped into etcd and a root remco daemon templates it
straight into an Apache vhost.
references/ad.md and references/relay.md add the delegation-definition edge:
SeEnableDelegationPrivilege is the right to mark an account trusted for
delegation, separate from any DACL — a low-privileged group that holds it can flag
a computer account for unconstrained delegation, coerce the DC, and take the
domain; the two failure codes (ERROR_PRIVILEGE_NOT_HELD vs
ERROR_DS_INSUFF_ACCESS_RIGHTS) tell you which half you are missing.
references/binary.md gains the SEH-overwrite fingerprint: a null-terminated
string copy into a module whose addresses carry a null MSB means you place one
address into the SE-handler slot rather than chasing a jmp esp, so read the
mitigation flags and the sink's terminator behaviour before choosing the
technique. references/privesc-windows.md covers the interactive installer/repair
LPE (PDF24, CVE-2023-49147) driven headless from the container and held on its log
file with an exclusive oplock, so the SYSTEM helper blocks instead of flashing
past.
pwnloop 1.18.0
Four more machines rooted (39 → 43) and nine write-ups published, taking the index from 30 to 42 chains. This release also backfills what 1.17.0 shipped without — the bamboo/conversor/dump index rows, its changelog entry, and a track-record line that had been stale since 1.16.0.
Write-ups
- hacknet (Medium, Linux) — the
/likes/<id>fragment concatenates the raw username into a template string andengine.from_string(...).render()s it, so the field is SSTI rather than XSS;{{ users.values }}dumps every account with cleartext passwords → SSH → DjangoFileBasedCachepickling into a world-writable directory → RCE as the web user → GPG passphrase in rockyou → a MySQL root password shared in a chat message, reused as the system root password. - browsed (Medium, Linux) — uploaded Chrome extensions are executed, so an MV3 service worker is an SSRF with a
console.logside channel;[[ "$1" -eq 0 ]]arithmetic-context command substitution; PEP 552 unchecked-hash.pycin a mode-0777__pycache__. - gavel (Medium, Linux) — exposed
.git→ a PHP "rule engine" that runs a per-auction string on every bid → a root daemon that "validates" a rule by running it, whose sandboxphp.inilives inside its ownopen_basedir. - overwatch (Medium, Windows / AD) — hard-coded password in the UTF-16 string heap of an in-house .NET tool → create the missing AD-DNS record for a linked server → rogue MSSQL captures the SQL-auth cleartext → the same tool's WCF
KillProcessfeedsPipeline.AddScriptas SYSTEM on the DC. - slonik (Medium, Linux) — NFS with only
root_squashtrusts a client-asserted UID → shell-less SSH account still forwards to a PostgreSQL UNIX socket →COPY … FROM PROGRAM→ a rootpg_basebackupcopies the data directory preserving modes. - store (Hard, Linux) — 9-byte repeating-key XOR; a
path.normalizeguard wired to the SFTP fetch while the local read runs unconditionally; chrootedsftpusernever denied TCP forwarding →node --inspect; root-owned ChromeDriver on loopback. - expressway (Easy, Linux) — IKEv1 Aggressive Mode publishes a username and a crackable PSK hash; the PSK is the SSH password; a hand-compiled
sudo1.9.17 shadows the held, patched package onPATH. - monitorsfour (Easy, Windows) — PHP
==type juggling (token=0matches every0e…), Cacti CVE-2025-24367 newline injection into therrdtoolcommand line, then the Docker Desktop engine API unauthenticated from inside the container. Not a single callback at any step. - forgotten (Easy, Linux) — LimeSurvey shipped uninstalled and its installer accepts an attacker-controlled database host; container escape through a writable bind mount with no user-namespace remapping.
Methodology write-back
- ad.md — AD-integrated DNS: the default zone ACL grants Authenticated Users
Create all child objects, so any domain account can add a new record while being unable to overwrite an existing one. A name that does not resolve is a name you can define, which turns any dangling reference in the estate — a linked server, a hardcoded update host, an unregisteredwpad— into a credential source or a content source.services.mdhad been pointing at this note for a release without it existing. - ad.md — trusts: DCSync gives you
krbtgtfor that domain; direction and SID filtering decide whether a golden ticket carrying an extra SID for a privileged group in the other domain is one command or a separate campaign. The useful group is often a delegated operations one rather thanDomain Admins. - privesc-linux.md — a Linux host joined to a Kerberos realm maps
principal@REALMto a local account by name, soCreateChildon a single OU is local root and a readable keytab is a passwordless credential. Generally: wherever a local authorisation decision is delegated to a remote naming authority, "who can create objects here" is a privilege-escalation question. - privesc-linux.md — a root job that copies files you can write, preserving modes. No archive, no filter bypass, no CVE: a mode-6777 file planted in the source directory comes out of a root-run
cp -a/rsync -a/base-backup root-owned and SUID. The archive step sitting next to it is usually the decoy. - privesc-linux.md — sudo CVE-2025-32462 (
--hosthonoured for command execution, not just-l) alongside 32463, ranked by precondition; and a privileged binary shadowed earlier onPATHthan the packaged one, sitting outside patch management entirely. - services.md — IPsec / IKE: an ISAKMP responder that speaks Aggressive Mode is a pre-authentication credential oracle, and the
IDpayload is often the more valuable half — an internal hostname and a valid username with no credential. Plus the adjacent TFTP device-config sweep. - containers.md — a writable bind mount plus no
userns-remapmaps container uid 0 to host uid 0; confirm with atouch, checkfindmntfornosuidbefore reaching for a setuid drop. - web.md — the unfinished installer (point it at a database you control, then pivot to the app's admin-only RCE); and test an SSTI probe where the value is reflected unescaped, not where it is escaped.
- SKILL.md — a primitive you cannot trigger is a cue to re-enumerate the version's CVE set rather than keep engineering the trigger; once egress is confirmed blocked, rank candidates by delivery direction at selection time; and the write-back must be recorded in the ledger as a
## Write-back into the loopsection, because it is the only step whose omission otherwise leaves no trace.
pwnloop 1.17.0
Two more machines rooted (34 → 38) and three write-ups published.
Write-ups
- dump (Hard) — zip filename argument-injection → www-data; plaintext SQLite passwords → fritz; sudo
tcpdumpmid-argument wildcard flag-injection (-Z root+ second-w+-r) → root-owned /etc/sudoers.d rule. - conversor (Easy) — user-supplied XSLT (libxslt
exsl:document) arbitrary write → web-writable cron → SSH;needrestart3.5 CVE-2024-48990. - bamboo
Methodology write-back
- web.md — zip/archiver argument injection over user-controlled filenames (
-T+--unzip-command), with the filename-constraint bypasses (dash sink, no-slash$(pwd|head -c1),;truncation); equal-length ciphertext ⇒ repeating-key XOR break; a canonicalisation check wired to the wrong operation is no guard. - privesc-linux.md — sudo mid-argument wildcard flag-injection: a
*anywhere matches spaces, so you can smuggle whole extra flags (fortcpdump:-Z/-r/-V/second-w); browser-automation servers (ChromeDriver/Selenium) running as root on a loopback port = local root. - services.md — an SFTP-only SSH account that still permits TCP forwarding is a pivot; Node
--inspect(V8 inspector) is unauthenticated RCE via CDPRuntime.evaluate. - SKILL.md — concise, path-focused terminal output rule.
pwnloop 1.16.0
One write-up published (Eighteen, Easy, Windows / AD) out of four machines rooted
this cycle — the other three are not retired, so they change the methodology without
becoming a chain; the rooted-machines count moves 30 → 34. The through-line is
the second consumer of your input is where the bug is: not the surface that
accepts it, but whatever re-parses it one layer down. references/source-review.md
states it directly — a subprocess.run([...]) with no shell=True is correct and
irrelevant if the shell script it calls then puts $1 in an arithmetic context,
where a[$(cmd)] executes; and a user-editable "rule"/"formula"/"policy" string is
code as soon as something evals it, so find the only writer of that field and
then look for the same engine a second time at a higher privilege.
references/privesc-linux.md carries the sharpest instance: a root daemon that runs
your code inside a real PHP sandbox (disable_functions + open_basedir) is not
closed just because exec is blocked — if the policy file that defines the
restrictions is writable by the runner and sits inside its own open_basedir, one
submission rewrites the rules and the next one calls system() as root (with the
obligation to restore the file afterwards). It also gains "loot the app's own install
tree first" — a support-bundles//backups/ directory under a product install is a
credential drop by design, and a password grep never matches a private-key body.
references/services.md grows a full MSSQL escalation and enumeration section:
walk the IMPERSONATE graph before concluding a low-privilege login is stuck,
TRUSTWORTHY + db_owner as the second class, the non-sysadmin xp_cmdshell decoy
(LogonUserW ... 1326 means a stale proxy password, not a way in), linked servers as
stored credentials you capture by hijacking the name they dial, and — when SMB and
LDAP are firewalled — MSSQL as the domain-enumeration oracle via --rid-brute and
SUSER_SID(). Two smaller traps join it: an sshd refusal does not rule out password
reuse (su goes through PAM against /etc/shadow, a different gate), and polling SSH
to detect a foothold trips fail2ban and bans your source IP for every account.
references/ad.md adds "a new OS/functional level ships new object classes — treat
them as new primitives": read the functional level first, ask who can create the new
object types rather than who holds the classic edges, and reason about what the new
object is trusted to assert — with Windows 2025 dMSA (Bad Successor, CVE-2025-53779)
as the worked example rather than the lesson. It also notes that enumerating
(objectClass=user) alone can miss the foothold account, because a user hidden from
your read ACL is still named by DN inside every group's member attribute.
references/privesc-windows.md gains the localhost-only custom service as a SYSTEM
lever — read its request handler before fuzzing, because the recurring .NET sink is
Pipeline.AddScript(base + input), and a WCF/SOAP front end makes it a synchronous
oracle needing no egress. references/web.md adds services that execute
attacker-supplied browser code (a headless browser or an accepted extension is a full
HTTP client inside the target's network namespace: build the return channel first,
read the automation's own log for unlinked internal vhosts, then reach loopback) and a
discipline note for blind sweeps — a timeout is not a negative, over-parallelising a
small worker pool poisons every probe, and when brute force fails, question the
shape of the candidates before adding words. references/cracking.md: check for a
native hashcat mode before the john-reformat dance (a framework pbkdf2:sha256:...
wrapper is mode 10900), and stop planning around the NetNTLMv1 DES shortcut — 2019+
and 2025 clients refuse the downgrade. references/foothold.md separates the four
causes of a callback that never came, and references/pivoting.md covers keeping an
agent alive when the foothold is a one-shot exec.
pwnloop 1.15.0
Three write-ups published (WingData Easy, AirTouch Medium, Facts Easy); rooted-machines count 27 → 30.
New methodology
references/wireless.md(new) — first 802.11 coverage: WPA2-PSK crack and traffic decryption, WPA-Enterprise evil-twin with a stolen RADIUS key, the PEAP inner-identity oracle, and hostapd/eaphammer operational traps. AirTouch is the first box here whose entire pivot medium is a simulated radio.references/web.md— two classes: stored-input-that-is-later-executed (+ a validation/sink null-byte desync, e.g. Wing FTP CVE-2025-47812), and "an arbitrary file read runs as the app user — read the app user's own secrets".references/privesc-linux.md— two classes: a root job extracting an attacker-influenced archive (tarfile.extractall(filter="data")is only as safe as the interpreter — CVE-2025-4517), andsudo-ing a tool that loads user-supplied code (Puppetfacterruns in-process Ruby as root even though it drops privileges for shelled-out commands).
Through-line
Pin the exact version and check the interpreter/library under a control, not just the control: Wing FTP 7.4.3, CamaleonCMS ≤ 2.9.1, a filter="data" extractor on an unpatched Python 3.12.3, and a hardened-looking captcha/registration that OCR walks straight through.
See CHANGELOG.md and writeups/.
pwnloop 1.14.1
Corrections release. Sets the confirmed difficulties on the write-ups from 1.14.0 — Data Easy, 2Million Easy, Pterodactyl Medium, CCTV Easy (Logging was already Medium) — in both the write-up headers and the writeups/README.md table.
pwnloop 1.14.0
Five write-ups published (Data, 2Million, Pterodactyl, CCTV, Logging) plus the methodology from the runs behind them. Rooted-machines count moves 21 → 27 (darkzeroreturns is rooted but not retired — it counts, but gets no write-up while active).
Through-line: read the environment's own hints, and enumerate as the principal you actually are.
- Pterodactyl —
/changelog.txt+ a MOTD security notice name the exact PEAR-LFI foothold (CVE-2025-49132) and the openSUSE udisks/PAM local-root chain (CVE-2025-6018/6019). - Logging — a full AD path whose privesc was only visible once AD CS was enumerated as the foothold user: ESC17 on
UpdateSrv+ a DNS write → a rogue-WSUS MITM that installs an update as SYSTEM.
References added/expanded: adcs.md (enumerate AD CS per-principal; ESC17 as a service-impersonation / WSUS-MITM primitive), web.md (pearcmd.php LFI→RCE), privesc-linux.md (unprivileged-userns/FUSE copy-up; udisks/polkit allow_active), services.md/api.md (Guacamole credential store; SAML sig-wrapping / IdP pivot).
Write-ups: flags redacted, addresses generalised, retired targets only.