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!

When NULL isn't null: mapping memory at 0x0 on Linux- 1609

disconnect3d    Reference →Posted 1 Year Ago
  • When referencing a null pointer in C, we assume this is invalid. What if this could point to valid memory in some conditions? In this blog post, they discuss how to do this on Linux.
  • This article stems from an interview question: "What happens when the following C or C++ code executes: *(int*)(rand_int()) = 0x41424344; In reality this is undefined behavior. This will change depending on the CPU architecture, privilege level, or compiler version.
  • This will succeed if the address is within the process-mapped virtual address space with write permissions. If the address is unmapped or lacks write permissions, the process will crash. Practically, this is more nauced, though. The invalid memory access will be intercepted by the CPU, which triggers an exception. The Linux kernel then sends a SIGSEGV signal to the process. To me, this is what makes it a great interview question: it allows you to dig into the interviewees' knowledge of the system without making them feel bad.
  • Another edge case is that if the memory is barely in front of the stack address location, then it will attempt to expand the stack. Those CTF players definitely know their esoteric edge cases! What happens if the address is at 0x0?
  • The Linux kernel configurable vm.mmap_min_addr is the minimum virtual address that a call to mmap can include. By default, it's set to 0x10000, but the root user can modify this and bypass this restriction altogether. The reason for this value and not 0x1 is that we want to protect against pointer dereferences with slight index offsets.
  • This configuration was added in order to prevent null pointer dereferences in the Linux kernel leading to vulnerabilities with horrible impact. The idea would be to allocate data at address 0x0 then use a null pointer dereference to access this memory from the kernel to hijack the kernel. According to the author, this is a realic of the past though: SMAP and SMEP prevent this type of exploitation now-a-days.
  • Overall, an interesting blog post! Good read with some fun edge cases.

how to gain code execution on millions of people and hundreds of popular apps- 1608

Kirby.town    Reference →Posted 1 Year Ago
  • The author was trying to use Cursor, an AI coding assistant. When downloading this tool, they got a hit on a firewall software that it was making an outgoing connection to download.todesktop.com. todesktop is an Electron app bundling service that provides an SDK for Electron apps.
  • Since this was a deployment service, the author was interested in its security. A vulnerability would allow for the compromise of hundreds of apps. After seeing that this used Firestore, firebase's no-sql database that is often used in frontend, they quickly got to work hunting for bugs. This led them to an NPM CLI package.
  • The cloud function getSignedURL() had an arbitrary S3 upload vulnerability. However, they didn't have a useful place to upload files to so they just moved on.
  • Since this entire platform was about building and deploying, they were curious about how this was done. They added a postinstall script to the package.json. They found an encrypted configuration file when navigating this container with a reverse shell used for Firebase. After finding the decryption code, they got a hardcoded Firebase admin key.
  • This service allowed them to auto-update any app of their liking, including Cursor. They tested this by deploying an update to their custom app and immediately saw the results. There is a huge impact on this!
  • To fix this issue, the build container has a privileged sidecar that does the signing, uploading and everything else, while the main container only has the user code. User isolation on arbitrary code is extremely difficult, in my opinion. They got a 5K bounty from todesktop, which the author said was fair because of the company's size. Cursor gave them 50K too, which is amazing. Overall, great post!

BLS12-381 Pairing Consensus Issue - 1607

MariusVanDerWijden    Reference →Posted 1 Year Ago
  • Ethereum has implemented several EVMs and consensus clients, which is important for security. If there's a horrible double-spend issue, it's unlikely that it will be on two different clients. Unfortunately, this has created an entirely new bug class: consensus disagreements. If two implementations differ at all, then these two stacks will come to different conclusions on values, resulting in a chain split.
  • Geth and Nethermind are two such EVM implementations. A competitor in the Pectra Audit contest noticed that there is a difference in the results of the BLS12-381 pre-compile. The specification for this smart contract had the following: "If any input is the infinity point, pairing result will be 1. Protocols may want to check and reject infinity points prior to calling the precompile."
  • This has two interpretations:
    1. The pair may be ignored, where P or Q is infinity.
    2. The precompile should return 1 and the multi-pairing shouldn't be computed.
  • The author intended 1. Nethermind had implemented 2, and Geth had implemented 1. So, it was decided to use the Geth-compliant implementation in this case. Consensus issues are super nasty; they quickly lead to the division of chains. Unfortunately, it's nearly impossible to be perfectly compliant with every little feature of a piece of software.

ASA-2025-004: Non-deterministic JSON Unmarshalling of IBC Acknowledgement can result in a chain halt- 1606

Zygimantass    Reference →Posted 1 Year Ago
  • Blockchains are effectively code that runs on a bunch of different computers. Naturally, it's important that all of these computers have the same result. If the output is different between computers, then consensus can fail. If consensus fails, then the entire blockchain will likely stop working.
  • This vulnerability affects the Cosmos SDK IBC Go library. When deserailizing a cross-chain acknowledgment, the JSON can be unmarshalled differently in some cases. Why is JSON non-deterministic here? Probably because it didn't NEED to be deterministic in the past. Even an extra space can cause issues here.
  • Here's the PR for the fix. It simply unmarshals and remarshals the ACK packet data then compares the values. If some weird non-deterministic behavior was happening here then this would fail.

Hacking High-Profile Bug Bounty Targets: Deep Dive into a Client-Side Chain - 1605

Vitor Falcao    Reference →Posted 1 Year Ago
  • Client-side path traversal (CSPT) is a classic path traversal but on the client-side. In particular, it's about tricking how an API works to make requests to the incorrect API. This can be used to get XSS with the wrong content type and several other issues. This paired with open redirects can be useful as well.
  • On this private in-person competition, the author noticed an API vulnerable to CSPT. This API was used for signing an S3 bucket path on the backend, where the filename was the controlled parameter that was part of the URL. By URL encoding the path, we get /categories/..%2Fredirect%3Furl%3Dmalicious.com that will result in /categories/../redirect. Neat!
  • By itself, this isn't very helpful, though. A friend of the authors noticed that using ?redirect=true on the API would result in a 301 redirect. This returns the file URL instead of the raw contents of the file. This means that we may be able to get XSS from it!
  • Initially, they ran into some CORS issues. Since CloudFront was used for caching, it was caching the initial download of the file without the CORS header. When trying to access the file from the page, it would then fail with a CORS violation. By ONLY using the redirect route instead of the regular file download, this issue can be avoided. Sometimes, debugging web things is complicated and annoying.
  • This XSS is a self-XSS because the store is bound to another account or only during the buying process. The application was vulnerable to a login/logout CSRF as well, giving them another primitive to work with.
  • The final piece of the puzzle surrounds cookies. Cookies are scoped to a particular path, and there is a limit to the number of cookies that are allowed to be there. Both of these are important for exploitation.
  • There's the full exploit path:
    1. Add the malicious cart item with the self-XSS to a dummy account.
    2. Convince a user to click on your web page. Using this site, a logout/login CSRF will happen to log them into the attackers dummy account.
    3. Self-XSS is triggered on the dummy account. This will cookie bomb (hit cookie limit) the current session, then add our own cookie at the proper path for the CSPT.
    4. The server will force logout the user because of the cookie bomb.
    5. Original XSS opens the login page to prompt the user to login again. This is the trick - there are two logged in users now! The actual user and the dummy user at the particular path.
    6. Logged in user is redirected to the CSPT vulnerable path for the second XSS payload.
    7. The second XSS payload will call any of the state changing APIs they want with the main users creds.
  • The usage of scoped cookies to have two session cookies be valid is super clever! It's crazy how much effort was required to exploit this. Client-side security is baffling to me.

Do you know this common Go vulnerability? - 1604

LiveOverflow    Reference →Posted 1 Year Ago
  • Go is built to run concurrent code. In this CTF challenge, a subtle issue is abused around concurrency.
  • The challenge has key-value store HTTP service. The service also has an arbitrary file read vulnerability by specifying the name. However, the flag file cannot be simply read because there is a flag string check.
  • This protection can be bypassed using /proc file system. However, this requires the intended solution to have a file descriptor open for the flag. This works but wasn't the intended solution. Still, a super clever abuse and solve!
  • The intended solution is a subtle issue with Go. The /get and /set HTTP handlers allow for concurrent access. The err variable for the arbitrary file read flag check is global! This means that other threads, such as set can use this variable as well.
  • So, here's how to exploit it:
    1. Use the arbitrary file read to read the flag on the /get API. This will return an error because of the string check.
    2. Use /set to change the error variable to be false.
    3. The check on /get call on error will now fail because it was set in the other thread.
    4. Flag is read!
  • Concurrency is Golang is a core component of the design. As a result, insecure concurrent uses should be checked for. I loved both the intended and unintended solutions for this!

No More Bets - How Ctrl+F led to breaking Polymarket's polling markets- 1603

Trust Security    Reference →Posted 1 Year Ago
  • Gnosis wrote the Conditional Token Framework (CTF). It is a complex tree of tokens, each representing some subset of choices. When a bet is made, users deposit collateral for a "full" (all options) token. Then, they trade sub-tokens in an external market to eventually arrive at a final position. Polymarket is a prediction market web3 company that uses CTF. While looking for variants of a bug found during an audit of Buter Conditional Funding Markets, they found a vulnerability in Polymarket.
  • In CTF, the function prepareCondition() creates the new condition for a position. This takes in an oracle, question and answer count as parameters. After this has been done, SplitPosition is used to split into the various outcomes. It has a very crucial condition: this function can only be called once.
  • Before Trust audited Butter, a patch with pretty clear security implications around CTF was submitted. Most protocols using this are permissionless, if an attacker can submit arbitrary parameters to prepareCondition(), then it prevents others from doing so in the future. This is a clear denial of service issue with the integration of the CTF library.
  • When Trust comes across a bug, they see if others have made the same mistake. So, they went to Github and searched for prepareCondition() not being wrapped correctly. In Polymarket, they noticed that an admin can call initialize() to create a new poll. By frontrunning this submission, it's possible to ensure that no questions can ever be answered.
  • Trust is/was on the Immunefi suspension list. So, they tried to reach out to Polymarket directly. When doing so, Trust initially was didn't want to disclose the exact issue until a payment range was decided. Even when they refused to give a range, the report was given to them.
  • According to Polymarket, the bug had been reported to them through Immunefi already, making this a dup. Since this had been known prior and things like Polygon fastlane can prevent it, it wasn't an issue to them. Additionally, Polymarket pointed out that this impact wasn't directly in scope, which is a terrible reason since there is real user impact. The bug being paid out in the first place and, therefore, nothing going to Trust is a legit reason not to pay out, though.
  • Unfortunately, Polymarket is not going to fix the issue. This puts users at risk and it all takes is a bad actor to prevent any/all usage of the platform. According to Trust, this was to dodge paying a big bug bounty under Immunefi terms but it's hard to say. I do wish that the security issue was documented as a known issue though - that's probably something that more programs should do imo.
  • Trust seems to be very good at variant analysis, which is awesome way to find bugs. I've been doing this lately and had pretty good success at it. Interesting bug and bug discovery!

LayerZero’s Cross-Chain Messaging Vulnerability- 1602

Heuss    Reference →Posted 1 Year Ago
  • Layer Zero is a cross-chain messaging protocol. It allows for the customization of various entities involved in the protocol. In particular, the relayer who triggers the message on the destination chain can be set arbitrarily.
  • Blockian found a race condition in this functionality. By having the relayer set to LZ, changing it to your own with zero fees and then switching it back, LZ would relay the message for free. The remediation to this issue was modify the protocol contracts, which led to another even worse security issue.
  • The function setConfig is used to change the oracle/relayer of a UA. If this is set in the same transaction that a message is sent, then the relayer should NOT relay the message. Only the owner of a UA is able to change the configuration. So, this seems like a sane remediation.
  • The consequence was that the relayer was checking if the AppConfigUpdated happened at all. Consequently, it wasn't checking that it was the same UA that triggered the update as the one that was being executed. This meant that it was possible to get the relayer to drop messages from legitimate calls, such as Stargate.
  • The consequences are somewhat LZ specific. LZ has an increasing nonce that requires that everyone is done in perfect order. By dropping a message, it's possible to prevent all messages after this point to become stuck. In the case of a well-used app like Stargate, this is pretty neat.
  • LZ could manually force the stuck message through though. Although things would be stuck for a bit, it wouldn't be permanent. So, this was paid out as a medium instead of a critical as a result. To fix this issue, the relayer just needs to see if the SetConfig event UA matches the TX being submitted.
  • An interesting part is that the test would have been necessary to perform on a testnet, since none of the off-chain infrastructure is open source. If somebody would have discovered this beforehand, then major damage could have been caused to LZ.

Blackboxing LayerZero Labs’ off-chain Relayer for 25,000$- 1601

Blockian    Reference →Posted 1 Year Ago
  • Layer Zero is a cross-chain messaging protocol. The architecture is as follows:
    1. User Application (UA) calls endpoint.
    2. Endpoint emits an event on chain A.
    3. Off-chain infrastructure attests the message.
    4. Relayer sends the message through on chain B.
    5. UA receives the message on chain B.
  • The application allows for configuraion of the relayer and the oracle per application. This seems that anyone can implement an off-chain relayer and use it themselves. The author had a question: "when does the LayerZero Labs Relayer stop listening to messages?"
  • In the contract UltraLightNodeV2, the function send() handles the event emission process for a cross-chain message. Interestingly, the event does NOT emit the relayer address itself. This peaked the authors interest! If it's not in the event, then the LZ relayer must keep track of each User Application (UA) that it supports. This feels racy.
  • Remember, there's no source for the off-chain infra! So, they started asking questions... what happens if a user changes their config? They submitted a PoC on chain where the Relayer and Oracle price submissions were 0 then changed the Oracle/Relayer back to the original LZ default.
  • By diong this, the LZ relayer relayed the transaction without getting paid during the submission process. This means that you can use LZ for free and drain the funds from the LZ relayer wallet. Naturally, if these funds are drained then the other apps would no longer work.
  • The smart contracts are open source but none of the off-chain code is. The author decided to black-box test some code to see how it would react. To me, this is interesting but crosses a important trust threshold. What if a malicious actor was looking at these transactions and then mimiced the exploit? Unlike web2, where your traffic is your own, doing live testing on-chain could lead to further issues.

Microsoft Edge Developer VM Remote Code Execution- 1600

Roman Mueller    Reference →Posted 1 Year Ago
  • The Microsoft Edge Developer VM were images that Microsoft published to make testing on different versions of Edge or IE easier. One day, while looking at processes on Windows, they noticed a Ruby script associated with Puppet running. Puppet is a configuration management system that they had seen in the past.
  • Confusingly, the Puppet configuration was never setup. By initializing it yourself, you're able to take control of the instance. This requires the ability to edit the hostname puppet to point to a particular IP though.
  • Software NOT being configured or being able to reconfigure is a real bug class that needs to be considered. Low impact but still interesting none-the-less.