Resources

People often ask me "How did you learn how to hack?" The answer: by reading. This page is a collection of the blog posts and other articles that I have accumulated over the years of my journey. Enjoy!

AI will make you a faster security researcher. It will also make you worse.- 1915

Martin Marchev    Reference →Posted 4 Days Ago
  • AI is starting to become REALLY good at finding security vulnerabilities. Is it going to replace us? The claim of this author is that it hollows you out and makes you dumb. This post is about the journey of this Certora researcher as they use AI in their security workflow.
  • Early on, AI was a huge help. Understanding codebases quickly, bouncing reasoning off something... lots of hard things being done very quickly. This was great, until they realized something: they were reaching for AI earlier and earlier in the process. They started using it NOT for context, but for judgment calls. Is this code really exploitable? They would just ask the AI instead of tracing through everything themselves and accept the answer.
  • There is a blurry line between good and bad prompts. One is asking the AI to do all of the work; the other is asking a comprehension question and doing the work yourself. The latter is a force multiplier while the former is hardly convincing. Worse than that, because you didn't reason through this process, you can't tell if the logic is off. You never had a mental model to begin with. The LLM may be wrong, and you would never know.
  • Threat modeling is a muscle. Sitting with uncertainty for hours with a hypothesis is a skill that either breaks or holds. This uncertainty is uncomfortable. So, the AI is able to remove this uncertainty and make them more confident. According to the author, the feeling of I think there's something here but I can't prove it yet IS a major part of the process. It's important to sit in this.
  • The author says that many folks are delegating this at great cost. They are faster and can cover more code. But their hit rate hasn't gone up. AI gave them breadth but took their depth. This is a bad, bad trade in the world of security research.
  • Here's the process that the author now uses:
    1. Write the attack scenario in plain language.
    2. Use AI to verify the mechanics. Execution paths, state transitions, etc. AI excels at understanding the logic of a complex chain of calls. If the AI is wrong somewhere, you can quickly disprove it with your context.
    3. Try to disprove the finding. The AI is useful for gathering evidence here.
  • They leave with a good quote: "The security researchers who will thrive with AI are the ones who treat it like a debugger. A tool that extends your reach without replacing your judgment. The ones who will quietly decline are those who let it think for them, one prompt at a time. They will never notice the moment they stopped being the researcher and became a triage layer for an LLM."

Solv Protocol Hack Analysis- 1914

Taichi Audits    Reference →Posted 10 Days Ago
  • Solv Protocol is a wrapped Bitcoin implementation. The standard token implementation is ERC-20; their token is ERC-3525, which is a semi-fungible token standard. It has ERC-721 token IDs but balances that are fungible. Instead of thinking about this as balances, positions are more accurate. They are supposed to behave like bond-like claims, tranche positions, and other financial positions.
  • With this standard, there are two types of transfers: whole token transfers (similar to ERC-721) and value transfers (similar to a part of a value). Depending on the function being used, the transferred value can be merged into another token ID or transfer the whole value to a recipient. So, the contract has to implement both the IERC721Receiver and IERC3525Receiver.
  • The BitcoinReserveOffering contract takes an ERC-3525 position, or some value from that position, and wraps it into an ERC20-like token. So, the underlying asset is a semi-fungible position, and the wrapped turns the deposited position value into fungible shares. When burning the shares later, the position value is given back. The wrapper keeps track of an internally held token ID that acts the contract's main pooled position. If the contract receives value, the deposits are merged into this token ID.
  • The vulnerability appears to stem from a set of complexity in supporting both ERC-3525 and ERC-721 style transfers. Both of these must have callbacks. If a user deposits their entire SFT balance, mint() will call the ERC3525TransferHelper.doSafeTransferIn(). Eventually, this will trigger a onERC721Received() callback that calls _mint(). Once control returns, mint() is called again. So, this leads to a double mint by design.
  • The contract also supports depositing on part of the SFT value. Upon doing this transfer, onERC3525Received() gets called on the contract after triggering a transfer via transferFrom(). The callback contains a _mint() then the control flow executes another mint(). This leads to another double-mint path.
  • To exploit the vulnerability, just go back via calling burn(). The attack is calling mint on a position, receive double the ERC20 shares, call burn to redeem the inflated shares back into SFT value and then redo the process again to profit more. It's crazy how fundamental this vulnerability is to the protocol... I imagine that the math for tokens to share was hard to reason about and it was just assumed to be working as intended, when it really wasn't. Overall, a fantastic write up on the vulnerability!

How to scan for vulnerabilities with GitHub Security Lab’s open source AI-powered framework- 1913

Github Labs    Reference →Posted 10 Days Ago
  • GitHub Security Labs has an agent specialized in finding security vulnerabilities, as described here. Given a set of taskflows defined in YAML, the LLM will perform the actions described. This is meant to be a generalized framework for adding the appropriate context for a codebase, such as defining entry points and user roles. By using smaller tasks instead of a single large prompt, it forces each step to be done more accurately.
  • The taskflow starts with a threat modeling stage. This drastically helps reduce false positives. This identifies individual applications or sub-components, entrypoints for untrusted input and user actions. By understanding this, the context provided will help with the issue suggestion stage. In the issue suggestion stage, an emphasis is put on the intended usage and risk from untrusted input above everything else. The LLM has the freedom to explore and suggest different vulnerabilities here.
  • In the issue auditing stage, it takes the suggestions of vulnerability classes and attempts to find issues. This is a lot of complicated prompting, mostly. To avoid LLM hallucinations, it asks for a concrete attack scenario and provide concrete evidence in the source code. Another trick is that is says there may NOT be vulnerabilities in the component. To avoid unnecessary reports, it emphasis's only high severity problems should be reported and to consult the threat model for whether something has impact or not.
  • After creating this, they decided to run this on several open source web applications. Within Outline they found an authorization logic vulnerability that allowed for privilege escalation. In particular, a lower privilege user with READ/WRITE on a document was able to manage groups on a document. This would have allowed a non-admin document collaborator to grant permissions via groups, including admin permissions.
  • The next set of vulnerabilities was found was in PHP Commerce websites. In WooCommerce, they found a single way to review all guest orders. In Spree Commerce, they found a way to emuerate addresses and phone numbers on all guest orders because of an incrementing value. I suppose this makes sense; guest checkouts are permissionless so it becomes difficult to scope who has access to them.
  • The final vulnerability was a funny subtly of TypeScript. When doing password validations using bcrypt in a separate function, it returns a promise. When using this value, it performed an && operation for a boolean on the promise! Promises are always truey. So, the boolean valid was always true when a user had a bcrypt password set. It's interesting that the LLM picked out this subtle bug. This realistically should have been found via testing... a single happy and sad test case would have solved this.
  • When running the tool on 40 repos, they found 1K potential issues. After the audit stage, 139 were marked as legitimate. After de-duplication, 91 were valid; this was required because they ran the tool multiple times to find more bugs. Of those 91, 20 were straight false positives, 52 were extremely low severity bugs and 19 had meaningful impact to report.
  • In a table, they show the classes of vulnerabilities that they have found. The most common are access control issues; this feels like a class of issues that LLMs are better at finding than classic static analysis because of the context required to understand it. The tool found several XSS and CSRF issues as well; given the amount of PHP code they scanned, this makes a little more sense. From there, authentication, path traversal and SSSRF had some hits as well. Authentication issues tend to be critical so it's surprising to me that these many issues were found.
  • From the takeaways, they point out that LLMs are good at finding logical bugs. TMore complicated logic bugs are probably not going to be found for a while, imo. They claim that LLMs are good at rejecting low-severity issues, which reduces the noise drastically. They note that when protections are across different sections of the codebase or are mitigated by third-party things, such as the browser, it struggles. Finally, LLMs are very good at threat modeling. It's able to account for expected usage very well. Overall, a fantastic post on LLM bug hunting!

Unfaithful Claims: Breaking 6 zkVMs- 1912

OtterSec    Reference →Posted 12 Days Ago
  • Zero Knowledge Proofs (ZKP) are a crazy but black-magic mechanism for knowing that something happened without revealing what happened. For instance, proving that a person voted without giving up that person. Usually, this requires interactions or entities sending data back and forth. By hashing everything, it's possible to make proofs non-interactive via the Fiat-Shamir heuristic.
  • The system doesn't reexecute each instruction one by one. Instead, it uses algebraic constraints over the committed polynomials to calculate correctness. If an attacker can send executable code that was never executed or is an invalid state transition, this would violate the property of soundness. The verification flow is done as follows:
    1. Fix public statement data.
    2. Parse proof payload, such as commitments, reduction messages, and openings.
    3. Rebuild the Fiat-Shamir challenges from the transcript data.
    4. Check constraint equations and check PCS/opening consistency.
    5. Accept the state change if all checks are consistent.
  • The Fiat-Shamir transformation is required because blockchains can't have real-time communication. Instead of generating a random number via a challenge protocol, the verifier hashes the existing state. This creates a really important invariant: the hash must include everything that affects verification BEFORE the challenges derived from it are used. If a value affects the equation but isn't used in the hash, it's possible to control that value in the rest of the protocol. The term absorbed is used a lot; it's similar to a hash function.
  • The SumCheck protocol proves that a polynomial sums to a claimed value over a Boolean hypercube. By doing some clever math, it's possible to turn this from 2^n to a single evaluation for a global check called the claimed_sum. This value is provided and MUST be part of the committed value. This issue was found 6 times in various codebases.
  • Finding values that meet the attack's needs requires advanced math that is either trivial to solve or requires some brute-forcing. Regardless, this missing binding happened in six separate implementations and was catastrophic.
  • So, why did this happen so many times? They give a few reasons in the post. First, academic papers usually describe interactive protocols rather than the Fiat-Shamir portion, making it ambiguous how to do it correctly. Second, zkVMs are very modular in structure. So, each layer assumes that the other performs transcript binding of a value. There is also heavy pressure for performance in the prover that makes checks nice to remove.
  • The fix is simple for each implementation: just bind the value to the challenge. At a higher-level it's important to make these less error-prone. One idea is to make the proof and the transcript (previous state) equal, so the values are automatically absorbed. A very similar bug can also be found here. Even though I'm not a cryptography person, I still learned a lot from this post and really enjoyed it!

Pac4j-jwt Critical Authentication Bypass- 1911

CodeAnt AI    Reference →Posted 12 Days Ago
  • CodeAnt AI is a AI assisted code review platform. They were scanning open-source repositories for CVE patches and checking whether the patches actually fixed the claimed vulnerability. Since patches are sometimes wrong, this is a good way to find bugs.
  • While reviewing the code, the AI tool flagged a null check within signedJWT directly before the signature verification block. If the signedJWT is not null, then verify the signature. Otherwise, do nothing. In the case of an exception in Java, the code exited. So, what does toSignedJWT() normally return?
  • toSignedJWT() will try to parse the decrypted payload as a signed JWT. If it's a PlainJWT, which is an unsigned token, it returns null. Using this type completely bypasses JWT verification.
  • They found another issue in simple git using a similar type of analysis. This bug was a case-sensitivity issue in a regex that allowed a bypass of two previous patches.
  • The bug is interesting, but the blog post was somewhat misleading and poorly written from a technical standpoint. Although it's a good bug, it felt more like marketing and introductory-level analysis. "Look at us" and "look at the impact" were the vibes of the post.

Vulnerability Disclosure Report: XRPL Batch Amendment – Unauthorized Inner Transaction Execution- 1910

XRPL Labs    Reference →Posted 18 Days Ago
  • XRPL added a Batch execution feature. The idea was to have multiple instruction types, such as multiple payments, in a single transaction by batching them altogether. Right before launching the feature, the Cantina AI tool found a horrific vulnerability in the codebase. This is the bug report.
  • The inner transactions in a Batch are intentionally unsigned. This is because the authorization is delegated to the outer batch list of signers. Because of this, there's a function that checks that the owner of the transaction matches the signers. Otherwise, a batch could impersonate other users, so this check is crucial.
  • This validation could be bypassed in a single case: a signer whose account did not yet exist on the ledger and whose signing key matched their account would immediately return success without further validating the inner instructions. As a result, the validation of the remaining signers was completely skipped, allowing for impersonation of other users.
  • Here are the exploit steps:
    1. Attacker constructs a batch transaction with three inner instructions. One that creates a new account. One simple transaction from the new account with a valid signer. The final one is the exploit: a transaction where the attacker impersonates the victim.
    2. The attacker provides two batch signer entries: a legitimate one for account B and a forged one with the victim account.
    3. Because account B doesn't exist at validation time, the signer check exits successfully without validating the second.
    4. The victim's payment executes without the victim's key signing.
  • To remediate the issue, the batch proposal was rejected. The team is currently working on a fix for the vulnerability on the early exit. The bug was a funny edge case in the system. To me, this really shows that the more complex the system is, the more bugs like this will appear. Good job to the engineer and the Cantina AI for the discovery! We have entered a new era.

uXSS on Samsung Browser- 1909

Omid Rezaei    Reference →Posted 18 Days Ago
  • In an Android Manifest, the most interesting part is anything with android:exported="true". If it has a deeplink, then it makes it possible to trigger from just a single link on the web. There were several of these scopes within the context of the Bixby Launcher Activity, used for voice-activated commands.
  • One of these handled a full URL string, such as samsunginternet://com.sec.android.app.sbrowser/Task/Path?params. For the AccessWebsite task, it performs input validation on the URL before launching it.
  • Upon launching this activity, a check was made to see if it was allowed. The goal of these checks was to ensure that it wasn't an emulator, it was ONLY a VIEW action, and that the referrer comes from BIXBY itself. The only one with meaningful protection is the com.samsung.android.bixby.agent check. This ensures that the intent can only be triggered from a particular package. Upon finishing validation, it would call com.sec.android.app.sbrowser.SBrowserMainActivity.
  • The SBrowserMainActivity activity was also exported. The accessWebsite activity checks whether a tab exists. If not, it would open the URL in a new tab. Otherwise, it will immediately call loadUrl(str).
  • The bug is that the SBrowserMainActivity is exported! So, all of the previous input validation can be ignored and this called directly. By setting the URL to be javascript:alert(origin) after loading a page, you get XSS on the loaded page! Because of how this works, this leads to XSS on ANY website. Pretty neat!
  • The bug was pretty awesome but I found the ordering of the article somewhat hard to follow. It was hard to determine what was important and what wasn't. They also only got $2.7K for this vulnerability, even though it could have been catastrophic.

Buy A Help Desk, Bundle A Remote Access Solution? (SolarWinds Web Help Desk Pre-Auth RCE Chain(s))- 1908

Watchtowr Labs    Reference →Posted 23 Days Ago
  • In 2024, SolarWinds Web Help Desk made headlines after being exploited in the wild with an RCE via Java deserialization. The issue was pre-auth, and led to several others being discovered in that year. The post goes through several new bugs they discovered in SolarWinds in recent months.
  • CVE-2024-28986 is a good case study. The application uses the Java WebObjects framework. It's a stateful web framework where the final path contains many numbers that represent the user's state. The first number is the current state (operation counter), which increments with each action. The other values have a hierarchy of pages and components currently in use. WebObjects requires traversing the full component hierarchy to reach a given page or component.
  • The API request /helpdesk/WebObjects/Helpdesk.woa/ajax/9.7.43.0.0.0.4.3.7.0.7.1.1.1 contains a method and params parameters in JSON. Upon seeing this, they found the method takeValueForKey(), which performs deserialization via a custom JSON-to-Java bridge. Further down the path, it's a classic setter-based deserialization attack where the attacker controls the target type. WrapperConnectionPoolDataSource contains one and this leads to RCE
  • CVE-2025-26399 was a bypass for the original SolarWinds issue. The patch added a regex-based check for which classes were NOT allowed via a blacklist. The request JSON body is decoded with Apache Commons. The same JSON is extracted via a different parser in org.json.JSONObject. They found an encoding difference between the libraries... Apache Commons didn't support short hex escape sequences (2 bytes instead of 4), but the JSONObject did. So, java\\x43lass bypassed the check.
  • Crazily enough, this wasn't how CVE-2025-26399 was exploited. The new sanitizeRequest method parses the JSON into Jackson and checks whether the Params key is there. If so, it overrides params with an empty array. Why? If the request includes parameters for AjexProxy, it strips them away. Using the same trick as before, this can be bypassed with p\\x61rams. Jackson doesn't see the \\x escape, and the sanitizer logic is bypassed. So, this creates a new zero-day on SolarWinds!
  • They found an authentication bypass through the odd routing in Java WebObjects. Java WebObjects is supposed to enforce hierarchical page traversal, but the wopage parameter allows selecting an arbitrary page. Using this, it's possible to access any page without authentication. Putting this together with the previous RCE bug and a new gadget, they got a pre-auth RCE. They had an additional authentication bypass that was less powerful.
  • Overall, a great post on real vulnerability discovery. It's funny they found a bypass for a fix without even realizing it initially... patch bypasses are fairly common, especially with something like SolarWinds that feels like a game of wack-a-mole with its design.

Almost Impossible: Java Deserialization Through Broken Crypto in OpenText Directory Services- 1907

Searchlight Cyber    Reference →Posted 23 Days Ago
  • OpenText Directory Services (OTDS) is a Java web application that provides authentication and user management for OpenText applications. They had seen this in various client projects and decided to take a closer look. Upon reviewing the code, nothing obvious came up besides a potential deserialization sink.
  • The function cookieToMap had the exact traits of a deserialization-to-RCE bug: using readObject() on user-controlled data. Except, there was a catch: the cookie must be signed. So, this seems like a dead end... the entire post is about their method for exploiting this.
  • The signed data is generated using an HMAC-SHA1 hash with a controlled message and IV. The message and IV were user-controlled and concatenated together to create the HMAC. The cookie had the following format:
    1. 2 bytes for the length of the signature
    2. Signature data
    3. Length of IV (2 bytes)
    4. IV.
    5. Length of message (2 bytes)
    6. Message. A serialized Java object.
  • The concatenation had no separation between the IV and the message. So, AA,BB is the same as A,ABB. If a part of the message could contain a deserialization payload, then this could be turned into RCE! So, what's the catch? There's a lot of controllable data in the cookie, but it must be valid UTF-8 strings. Additionally, the HMAC was using the compressed message rather than the actual message. This sounds like a CTF!
  • The compression library was zlib. For each implementation of the deflate algorithm, there's a table that must be provided. There is nothing that mandates the exact bytes and table to be used. So, you can just use a table from only 0x01 to 0x7F; there's even a tool called ascii-zip that already does this! So, they generated a payload with ysoserial and compressed it with ascii-zip. Now, this just had to land in the middle of the message.
  • They ended up needing to create their own encoder due to constraints with zlib, thinking the data was uncompressible. They ended up encoding everything by hand due to the complexity of the requirements. After encoding everything by hand, they got a DNS request to work!
  • They were able to use some of the data as a signing oracle, which was super interesting to me. Overall, a great, technical post on hacking software under real constraints.

Google API Keys Weren't Secrets. But then Gemini Changed the Rules.- 1906

truffle security    Reference →Posted 23 Days Ago
  • Google Cloud uses a single API key format for public identification and sensitive authentication. Google Maps, Firebase, and similar services were okay to embed directly on the page. This was acceptable because it was part of the design. The keys were designed for billing and restricted on the frontend via HTTP referer allowlisting so this was alright.
  • Gemini uses the same API keys (public API) as the existing project. So, by using a public API key that's been on a website for years, it's now possible to access information about that project's Gemini usage. this retroactive privilege expansion is not good in practice. The defaults are that an API key is valid for ALL services on the project and not scoped down.
  • An attacker can access private data, such as the files and cached contents. They can also increase your bill. The authors found 2.8K keys, including several of Google's own keys that were vulnerable to this attack.
  • So, what was Google's response? Initially, they said this was a customer issue, until they showed Google doing this wrong. To fix this, they added scoped defaults within the AI studio, API keys that are leaked are automatically blocked. Additionally, there's proactive notification to identify which teams would be vulnerable. Response to this is hard... You don't want to brick anybody's website, but you also don't want them to lose data. Hmmmm.
  • A great find! This is a classic example of threat models changing in a large ecosystem without understanding the results. Great work!