- Critical Thinking - Bug Bounty Podcast
- Posts
- [HackerNotes Ep. 174] Crypto Oracles, cPanel Auth Bypass, and the .git Trick That Pops Google Cloud
[HackerNotes Ep. 174] Crypto Oracles, cPanel Auth Bypass, and the .git Trick That Pops Google Cloud
A lot of great writeups today and some AI news
Hacker TL;DR
Salesforce Marketing Cloud got popped through Ampscript template injection plus an unauthenticated CBC bit-flipping attack. The 8 null-byte trick to leak the IV is the kind of crypto move you should keep in mind.
cPanel WHM auth bypass (CVE-2026-41940) chains a CRLF injection into a session file on disk with two different auth methods, then beats a cache to land a pre-auth session.
A single
.gitdirectory delete on Google Cloud Looker leads to RCE because git falls back to reading config from the working directory when.gitis missing. Variant goldmine for any code sandbox that lets you touch the filesystem.Skill optimizer from Tessl, prompt injection to deterministic XSS, and GPT-5.5 actually competing with Claude on black-box hacking. Lots of moving pieces this week.
We do subs at $25, $10, and $5, premium subscribers get access to:
– Hackalongs: live bug bounty hacking on real programs, VODs available
– Live data streams, exploits, tools, scripts & un-redacted bug reports
Need a Pentest? We just launched CTBB Pentests!
Hack full time? Check out the Full-Time Hunter’s Guild!
This Week in Bug Bounty
COST, AI frontier models and more: A measured take on the future of security testing - YesWeHack drops a piece on where AI-assisted testing actually changes the economics, and where the hype is doing the talking.
Common AI misconceptions debugged - Intigriti pushes back on the "AI slop is flooding programs" narrative with data showing validity ratios are holding steady.
BountySync + Social - Live event, worth a look if you're in the area.
Salesforce Marketing Cloud: Ampscript + Crypto Madness
The full chain starts with a server-side templating engine called Ampscript (Salesforce-specific, used in Marketing Cloud) and ends with reading every email in the platform.
Step 1: Double Evaluation Template Injection
Ampscript supports %%= and the lesser-known {{= syntax for evaluation. There's also a treat as content primitive that basically means "evaluate this string as Ampscript". Pair that with an HTTP GET, and you can chain a tiny injection into a much bigger one.
Joseph, Evan Connelly and Shubs used this a while back: 50-character payload, treat as content + HTTP GET pulls more Ampscript from your server, you get a second evaluation pass with no character limit. Classic double-evaluation move, but specific to this language.
When you find a server-side templating engine, keep adding the language-specific eval markers to your payload list. %%=, {{=, ${, all of it.
Step 2: Variant Hunting on Encrypted URLs
The emails were also viewable on a domain via a qs parameter. Three formats existed for that parameter:
A solid-looking JWT.
A hex value.
A raw, unencrypted version that the team only found later.
This is the classic "multi-format support" pattern: the dev fixes the broken one but doesn't backdate it, and the old formats stick around because the pipes that glue everything together are the same. Joseph's mental model: if it's encoded, decode it, else treat it as-is. So if you see an encrypted ID, try the unencrypted version. If you see base64, send the decoded version. Sometimes it still works and you can do something funky.
Use Wayback Machine / gau / waymore to pull the old URL formats. Audit those old implementations as hard as the new ones.
Step 3: Bit-Flipping on Unauthenticated CBC
The hex format used CBC, and the system wasn't checking integrity on the ciphertext. So bit-flipping was on the table. The tell: flip a few bits, and the decrypted output in the response just looks corrupted. Question marks, weird characters, nothing crashes. That's the smell of an unauthenticated CBC mode.
Justin's repeated advice on the pod: do not skip the crypto. Flip bits when you see ciphertext. Watch the response. A lot of padding oracles and CBC bugs are found just from this kind of poking.
They also used Padre for the padding oracle / CBC work. It's extensible, fast, and worth having in the toolkit.
Step 4: The 8 Null-Byte Trick to Leak the IV
Here is how CBC decryption works on the first block:
plaintext_block_1 = D(ciphertext_block_1) XOR IV
If you don't have the IV, you can't decrypt the first block. The trick: pad your ciphertext with 8 null bytes at the front. That makes the new first ciphertext block all nulls. When the decryption function processes it, the output is junk (unpredictable, but consistent), and that junk gets XORed with the IV.
The original first ciphertext block is now the second block. The way CBC chaining works, the decrypted output of that second block gets XORed with the first ciphertext block (your null bytes), so the XOR is a no-op and you get the raw D(ciphertext_block_1) straight out.
Now you have two values:
D(c1)(raw, from the second block of the modified ciphertext)D(c1) XOR IV(the original plaintext, from the first block of the original ciphertext)
XOR them together and you get the IV.
You don't even need to know the full plaintext. With partial plaintext knowledge from a Stack Exchange reference about the format (something like j=XXXXX), plus a bunch of sample IDs, you can brute force the IV against the expected plaintext format until the decrypted output matches.
Once they had the IV, they found an encryption oracle elsewhere in the system, used it to craft arbitrary encrypted values, and dumped every email and email content from every customer in Salesforce Marketing Cloud. They asked for the key not to be in the article, which means it's still active.
When working with CBC, the format of the plaintext matters. Always grab a few sample plaintexts (length, padding, structure) before you start brute forcing. That gives you the sanity check on each guess.
Crypto bugs are way more accessible now too. You don't have to love the math. Feed the article to Gemini or Claude, explain what you understand up to the point you don't, and let the model fill the gap.
cPanel WHM Auth Bypass: Force of Will Exploitation
Watchtowr's write-up on CVE-2026-41940. By the time you read this you've probably already digested it, but the technique stack is too good to skip.
Step 1: Read the Patch Comment
The patch they reversed had this comment:
Filter against \r\n from values before writing kills the CRLF injection primitive against the on-disk key value record format.
That's basically the article. CRLF injection into a session file on disk. cPanel really does write your auth material straight to /var/cpanel/sessions/raw/<session_id> in a \r\n delimited key-value format. Legacy Perl vibes, but it is what it is.
Step 2: Chain Two Auth Methods
The exploit needs to smuggle \r\n past the filter. They got there by combining two different authentication paths: one from a cookie, one from an auth header. Each path on its own was filtered. Together, they bypassed it.
This is a great variant hunting pattern. When you map an app, list every way to authenticate. Cookies, headers, query params, bearer tokens, basic auth, SSO callbacks. Each one is a different code path with different validators. Mix and match.
Step 3: Beat the Cache
The session file is \r\n delimited and loaded as JSON, but the system prefers a cached version in memory. So even after the injection worked on disk, the endpoint still saw the old session. They had to find a primitive that forced a reload from the raw file, which they did, only to hit a 403 on the next step because of a defense in depth password check.
So they found another value to inject that bypassed the password check entirely.
The whole thing reads like a horror movie of failed attempts and "we're done" moments followed by "shit, another 403". Watchtowr's writing is gold and they captured the grind perfectly: "Do we deserve this?"
The vibe to take away: keep going. If the patch is real, the primitive is real. The bug is somewhere in the chain. Don't stop at the first wall.
Searchlight Cyber's Companion Scanner
Searchlight Cyber dropped a high fidelity check.
Any time you see a vulnerability where auth material gets written to disk in a structured text format (key/value, JSON, INI), think CRLF injection, think attribute smuggling, think cache vs raw mismatches. Same playbook every time.
Skill Optimizer: Fix Your Skill Descriptions
Joseph spent a chunk of the ep on this and it is hands-down the cheapest win you can get this week. The skill is from Tessl, called Skill Optimizer. Point your agent at it and it does the following:
Writes evals for when your skill should be invoked.
Runs them, measures the invocation rate.
Rewrites the description, the name, and the content to maximize invocation.
Re-runs to verify.
On Joseph's and JD's hackbots, invocation rates went from ~10% to ~85% on a lot of skills. Three concrete takeaways:
1. The Description Frontmatter Is Single-Line by Default
In Markdown frontmatter, this:
description:
Some description on
multiple lines.
Does not parse as a multi-line description. Claude Code only sees the first line, which means everything else is invisible to the routing decision. The fix is the YAML folded-block syntax:
description: >-
Some description on
multiple lines.
The >- makes it pick up every line. Weird format, never seen before, but that is what works.
2. Snake Case Beats Camel Case for Skill Names
This is the one that hurts. Joseph claims my_skill_name invokes better than mySkillName. Justin is a tabs-using Python bro who still likes camelCase, but the optimizer kept rewriting names to snake_case across the board and invocation went up. So if you care about invocation more than aesthetics, switch.
3. The Word "Claude" Is Cursed
If your skill name has claude in it, change it. A skill called DM_other_claudes was renamed to DM_other_agents and invocation jumped. Either the word is parsed weird or treated as a reserved token, but it's a real effect.
This is the kind of low-effort, high-impact polish you can run on every skill you have in 30 minutes. Do it now.
Bug Bounty Triage Is Drowning: A Plate of Options
Joseph is on the HackerOne and Bugcrowd hacker advisory boards and the message from both is the same. Programs are buried. Triage SLAs are slipping. Outstanding-bug curves look exponential. There's not enough developer time to fix everything coming in.
If you are a program manager or CSM reading this, here is the plate Joseph offered:
Go private. Cuts volume hard. Not ideal, but fast.
Require video PoCs. Filters AI slop right at submission. Protects the hunter when the bug becomes "no longer reproducible" mid-triage.
Raise signal requirements. Top 30 hunters on H1 are mostly above 6 signal. A cutoff at 5 would cut a lot of low-quality reporters while keeping serious hunters in.
Verified / trusted hunters only. Same idea, different mechanic.
Reduce low and medium payouts. Portswigger left highs and crits the same and dropped lows / mediums. We're seeing this everywhere now.
Submission fees (HackenProof did this). $1 didn't help, $5 cut slop by ~80%, $10 stopped it cold. Refunded on valid bugs.
Bounty reducer based on signal. NA reports give you a 10% → 20% → 30% bounty cut. Doesn't stop the bleeding right now, but it scales fairly.
Voucher system for new hackers so the entry tier still exists.
Justin's preferred mix: video required for high / crit, and the HackenProof submission fee. Joseph also liked the signal floor.
Deterministic Prompt Injection Through Client-Side Feedback Loops
XSSDoctor and Monke at Starstrike cracked a clean problem: prompt injection that lands XSS only 20% to 50% of the time.
The Setup
A q parameter on a chatbot that allows prompt injection. The injection sometimes results in XSS. The goal: make it deterministic.
The Loop
The attacker page opens a small popup window and keeps it in the foreground next to the victim window. Both windows being "in view" relaxes the cross-origin postMessage rate limit. Then:
Send the prompt injection.
Wait 10 seconds for a callback from the XSS firing.
If no callback, re-inject.
Repeat.
After 30 seconds of dwell time on the page, the success rate is basically 100%.
The popup-in-front trick was originally discussed on the Critical Thinking Discord during an Adobe hack-along. Worth stealing for race conditions in general, not just AI bugs.
The Mutual Opener Trick (and a Cleaner Alternative)
XSSDoctor's challenge: the victim iframe has no window reference to the attacker page (only one-way). His fix: make both windows mutual openers, so each has a window.opener reference to the other.
Justin's cleaner alternative: use the event.source property on the postMessage event. When a postMessage hits an iframe, the handler's event object includes .source, which is a window reference to the sender. Save it the first time you get a message in, and you have your bidirectional channel without the mutual opener acrobatics.
window.addEventListener('message', (event) => {
// event.source is a Window reference to the sender
window.attackerWindowRef = event.source;
});
Either works. Knowing both gives you options.
GPT-5.5 Is Real, and Codex Has /goal
Two LinkedIn posts from Albert Ziegler and the Xbow team.
The Y-axis on their chart is "vulnerabilities found before first miss", which is a weird metric, but the takeaway is clear:
White-box: Claude (Opus 4.6 → 4.7) is ahead. Big jump at 4.6.
Black-box: GPT-5.5 is roughly double Opus 4.6 on the same eval. Almost 4 → 8 vulnerabilities before first miss.
Joseph bought a Codex sub on the back of this. Setup was symlinks:
ln -s ~/.claude/CLAUDE.md ~/.codex/AGENT.md
ln -s ~/.claude/skills/* ~/.codex/skills/
Then he just ran /goal overnight. Three P1s in the first 30 minutes, on a fresh Bugcrowd invite. 14 hours of /goal running used ~15% of his weekly token budget on the $200/month tier.
The /goal command is what makes Codex sticky. You give it a goal ("find five crits") and it just keeps going until it hits the success condition. No "ok continue".
Build your hacking system to be portable between Claude Code and Codex. Same skills, same rules, same agent files. You'll want to swap based on the target (white-box → Claude, black-box → Codex for now).
Justin also confirmed Opus 4.6 alone is no slouch. Same week, fresh Bugcrowd program, JWT forging bypass in 15 minutes.
RCE on Google Cloud Looker via a Single Directory Deletion (Ryotak)
Saved the best for last. Ryotak at Flatt Security on Google Cloud Looker (Business Intelligence platform).
The Primitive
A bug in validate_dir_name in Looker's Ruby code lets him delete an arbitrary directory inside a repository he owns. Including .git.
The Insanely Underrated Git Quirk
This is the part of the article that you read once and miss. Ryotak just slips it in:
Since it is possible to trick git into using forged git configurations if the .git directory is corrupt or deleted...
Wait. What?
Here is what's actually happening. When git runs a command in a repo and cannot find a .git directory, it falls back and assumes the current working directory is the .git directory. So it tries to read ./config as if it were .git/config, ./hooks/fsmonitor-watchman as if it were .git/hooks/fsmonitor-watchman, and so on.
Normally you can't push or upload a .git folder. Hosts strip it. That's where the security relies. But if you can delete the existing .git, and you control the rest of the working tree (your own repo contents), then git will happily load your malicious config and hooks from the root of your repo.
The number of code sandboxes and AI coding tools where this is now a variant hunt target is enormous. Anything that:
Clones a repo on your behalf, and
Lets you somehow remove
.gitor operate on a working tree without a real.git, andRuns a git command afterward.
Chaining the Deletion + Race for RCE
Ryotak's path delete doesn't only target .git. It deletes the whole repository directory recursively. So if the recursion just does rm -rf naively, after .git goes, the rest of his repo goes too, and his planted config would also get nuked.
His move: create a massive nested file tree in his repo (millions of files, very deep). The recursive Ruby FileUtils.rm_rf enters that subtree after deleting .git and gets stuck for a long time deleting files inside it. While that's happening, Ryotak hits another endpoint that runs git status on the same repo.
At the moment git status runs:
.gitis gone (already deleted).His malicious
configis still there at the root (the massive subtree hasn't been processed yet).Git falls back, reads his
config, triggers anfsmonitorhook.Hook executes arbitrary commands on the Google production server.
He even chains into a privilege escalation via the Kubernetes service account at /var/run/secrets/kubernetes.io/serviceaccount. Excessive permissions let him update secrets across other clusters. Full priv esc.
This is one of those exploits where every layer is a small idea, but stacked, they form a clean RCE chain. Race condition on a rm -rf against ext4. Git's .git fallback. Misconfigured Kubernetes service account. Chef's kiss.
Pro Tip: if you are auditing any AI coding sandbox or code-execution service, the new mental model is:
Can I make
.gitgo missing on a repo whose working tree I control?Can I trigger a git command afterward?
If yes, RCE.
Resources
Padre on GitHub. Padding oracle / CBC attack tool, very useful for crypto bugs.
Tessl Skill Optimizer. Rewrites your skill descriptions to massively boost invocation.
Ghosts of Encryption Past. Salesforce ExactTarget full chain.
Deterministic Prompt Injection via Client-Side Feedback Loops.
That's it for the week, keep hacking!

