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!

Web Cache Deception on OpenAI- 1113

Nagil    Reference →Posted 3 Years Ago
  • Caching is customized all over the place and many developers don't consider the ramifications of their actions. As a result, if sensitive data can be cached, then an attacker can steal this information.
  • Web Cache Deception relies on two quirks: caching by file extension and relaxed routing. The first one is obvious and common: cache based upon the extension, such as .js or other static files. Secondly, the routing needs to accept requests that are not exactly the expectation, including things with random file endings. For instance, /auth/test.css could still route to /auth.
  • With the combination of these two actions, a new vulnerability is born! If both of these are meant, then we can force specific routes to be cached that are unintended! This can lead to a major information disclosure.
  • In the case of OpenAI, they were using Cloudflare for caching. They will cache all static file types, such as .css and .js. OpenAI's website also had relaxed routing. The route /auth/sesssion/test.css and /auth/session/ would both be accepted by the web server.
  • The route /api/auth/session would return a users account information, including a session token. To exploit this, the Twitter user Nagil used the technique above and added victim.css to the end of it. The full link for the victim to click on is https://chat.openai.com/api/auth/session/victim.css.
  • Once the link was clicked on by the user, the item would be cached! Then, an attacker could go view the request to steal the session information. With the session JWT, they could likely takeover the account.
  • The fix was to not cache this specific endpoint. However, I find this extremely brittle. There are likely other endpoints with sensitive data that could be abused in this way. Overall, interesting finding on a new and hip website.

Euler Finance Incident Post-Mortem- 1112

Omniscia    Reference →Posted 3 Years Ago
  • When handling loans in the land of DeFi, extra considerations need to be taken compared to the real world.
  • There are two main parties with loans: lender and borrower. The lender is the entity allowing a user to borrow their funds. The borrower is the entity using the other entities funds.
  • In order to ensure the lender gets their money back, a user must provide collateral, otherwise known as an asset that can be kept if the value is not returned. To ensure that the borrower doesn't lose money, the borrower needs to provide more money than what they are borrowing.
  • What if the asset of the collateral drops in value? The borrower has no incentive to give the money back, since the asset they borrowed is worth more. To prevent this problem, loans can be liquidated by the liquidator. The liquidator can pay back the lender of the original asset and purchase the collateral at a discount.
  • Euler Finance protocol is a lending and borrowing DeFi protocol. The function for donation had a vulnerability in it that is very subtle. When making a donation, there is no check to see if the value being donated makes a loan underwater (bad). This can be abused to dramatically skew the loan.
  • Now, the interesting part: since the loan is in a bad state, somebody can come and liquidate it. However, the discount on the collateral is proportional to how bad the state of the loan is. As a result, if we donate a large amount of our loan, the state of the loan will be drastically skewed.
  • When the liquidator comes to purchase the assets back, the percentage discount can be pushed to 20%. From the attack, the author made 8.7M USD in funds by getting the very large discount on the liquidation.
  • Overall, a really interesting post on a huge hack. The loan system explained in this post is super helpful as well.

Demystifying Exploitable Bugs in Smart Contracts- 1111

Zhuo Zhang & Others    Reference →Posted 3 Years Ago
  • This article dives into finding exploitable smart contract vulnerabilities. In particular, it tries to find methodologies for vulnerability classes that are missed by static and dynamic analysis tools. Several classes, such as integer overflows and reeentrancy, can be detected with analysis, while others cannot.
  • The paper uses vulnerabilities from Code4rena and real world exploits. It compares the bugs that were discovered between the two locations, making a distinction on difficulty from these two. Unfortunately, there are only are handful of bugs on the real world exploit-side though.
  • They group the bugs that cannot be found by automated analysis into a several categories, with clear and defined ways to identify them. First, they talk about Price Oracle Manipulation. When operating an AMM or using money, a price must be generated for this. If this is done insecurely, then the price of an asset can be manipulated somehow. For finding this vulnerability, the author mentions looking at how prices are generated and see if any of the variables in the algorithm can be changed quickly.
  • The next one is privilege escalation, a sequence of events to bypass access controls. In the example contract, there is a proposal vote that forces action to be in separate blocks (vote and end). The passing of a proposal is calculated based upon the amount of votingToken given to the contract. Although the vote function is the only way to send tokens directly, a user can manipulate the funds in a single block by taking out a flash loan, transfer the funds to the contract, end the proposal then claim all of the locked funds.
  • For finding these bugs, the author talks about critical access controls checks being violated by internal or external calls to this contract. The difficultly in identifying this bug was that it requires domain specific knowledge in order to understand the implications.
  • The final bug class with a good example is atomic violations. In the world of Ethereum, this means demanding that state variables cannot be accessed by other flows while they are on-going. An example of this is a lottery system which tries to force lottery number picking to be different than submitting. However, a bug in the contract allows a user to look in the mempool (by frontrunning) to see if a number has been placed. If this is true, then they can place the correct number. Looking for proper use of booleans and lock variables is how to find these bugs.
  • The author also mentions a few other bug classes:
    • Erroneous Accounting: Complex math operations going wrong. This can be a bad yield percentage, inflation, infinite burn and weird numerical issues.
    • ID Uniqueness: Orders and NFT IDs should be unique in order to ensure the owner can act on it. If IDs can be duplicated in some way, a major problem can occur, similar to access control violations.
    • Inconsistent State Updates: Many variables are correlated with each other for a user, such as credit limit being proportional to a user collateral. If some state is updated but another is not, major problems occur.
  • To me, a lot of these violations can be solved with three questions: what are the threats for this contract, what are the security controls to prevent these threats and how can these controls be violated? The authors of this post used this knowledge to find 100K worth of bugs. They found two oracle manipulations, two erroneous accounting bugs, four privilege escalation bugs, two atomicity violations, one of both bad state updates and ID uniqueness and three contract implementation specific bugs. Pretty awesome paper with good thoughts on discovery!

The Uniswap Skim() token balance attack- 1110

Ancilia Inc.    Reference →Posted 3 Years Ago
  • In Uniswap, the skim function is used for a recovery mechanism in the case of the reserves storage (uint112) failing. If the difference between the current balance of the pair (balanceOf on the ERC20 token) and the reserves are different, the sender of the request gets the difference. Although this can save the contract, this is a very dangerous function to have in.
  • In the case of this particular coin, the balanceOf function is controlled the true balance of the user, _largeTotal, and _totalSupply. If any of these change, the balanceOf call will also change.
  • The function transfer will update the totalSupply whenever a transfer occurs. The problem is that there is no validation on whether the sender and recipient are the same! This means that the _totalSupply can be inflated indefinitely.
  • Since the price of the balanceOf function relies upon the totalSupply, it will increase the result! This means that the cost of the reserves and the amount that the tokens has is different. Using the skim function allows a user to withdraw funds now.
  • In particular, an attacker needs to call skim using the contract address over and over again. Since the totalSupply is updated over and over again and this changes the price, the funds of the Uniswap pool can be stolen using the skim. Overall, a very interesting attack exploiting how Uniswap works in conjunction with a bad vulnerability within the ERC20 token.

OpenBSD system-call-origin verification- 1109

Jake Edge - LWN    Reference →Posted 3 Years Ago
  • This post is from 2019, not was a defense-in-depth measure that I had not seen before. So, I thought it was worth making a note about!
  • In OpenBSD, the system will block all system calls (syscall) NOT made by LibC wrappers. This prevents the usage of shellcode with syscalls in it. One could make the argument that an attacker could simply use Return Oriented Programming, right?
  • The OpenBSD kernel relinks libc into a randomize location at boot. This means that the offsets into LibC are not obvious. So, ROPing into LibC isn't nearly as much of an option.
  • The overall goal is to remove all syscalls that are outside of libc. The main program text segment needs to be in the list of memory regions where system calls are allowed to come from. The kernel can mark regions are valid for syscalls with the msyscall, since it can only be called once per process.
  • For static binaries, the valid regions to make the calls would only be the text segment and the signal trampoline page. For dynamic binaries, ld.so/libc.so text, signal trampoline and the main programs text are the only regions.
  • Together, these features are a powerful protection against ROP. Being able to restrict where syscalls can be run in combination with libc function stub randomization, PIE and everything else would be major blockers in the binary exploitation front.

How was Dynamic Finance Exploited? - 1108

Shashank    Reference →Posted 3 Years Ago
  • Dynamic is a money market aggregator built that helps to enhance the DeFi lending experience.
  • DYNA is the token of the ecosystem. When staking this token, a user can earn interest on it. The amount of interest is directly correlated to the length of time the token has been staked.
  • If the more funds are added, then the portion should be changed. For instance, if I stake 5 DYNA then wait a month, I should get that interest. However, if I add funds to the contract after this point, the rewards of the new tokens should start from that point on.
  • The code was not updating the information related to time. As a result, an attacker could stake a little amount of funds to rake up some time. Then, stake a HUGE amount of funds to make the contract believe we had staked these funds for a while. With this, we could extract wayyyy more value from the contract than we should be able to.
  • In the real world, the attacker has a flash loan to make the amount of tokens much larger. Interesting hack when the time is not properly updated.

Readline crime: exploiting a SUID logic bug- 1107

Rory M - Trail of Bits    Reference →Posted 3 Years Ago
  • After Qualys posted a sudo vulnerability that shook the world a while ago, the author of the post was wondering what other setuid binaries installed have vulnerabilities. They specifically decided to look into how environmental variables for programs were being used. They tried this ENV being set with all of the setuid binaries on their system.
  • To test for environment variables, they added preloaded a library that simply logged all requests to getenv(). After going through a bunch of dead ends, the variable INPUTRC appeared. This is used for a configuration file.
  • They set the environment variable to /etc/shadow, hoping that something interesting could be leaked. While running chfn, the output of the program indicated that it was read! However, nothing useful was outputted.
  • The author decided to search through the source code to finding something interesting. Within the readline library was where this environment variable was doing its damage. While parsing through the configuration file, it will output the errors and badly parsed data. So, what gets outputted in the errors? Can we trick it to output something useful?
  • A line that begins with a quotation mark without a closing mark will get outputted. Additionally, a line that starts with a colon with no whitespace. Finally, and the most useful, a line without a space, tab or colon will output the entire line! SSH keys match this pattern, since its base64 encoded data.
  • What's the punch line? Don't use readline in setuid binaries. The binary could simply just clear this ENV variable as well. To me, the blame on the bug is hard to put. Is it reasonable for the maintainers of chfn to know this quirk? Additionally, is it reasonable for readline to output this errors? To me, the blame isn't on any of these devs.
  • Overall, great article that is concise, well-written and has many good jokes. setuid bugs aren't dead!

Yearn BribeV2 Voting Power Bug- 1106

Yearn    Reference →Posted 3 Years Ago
  • Yearn is a decentralized suite of products for managing yield of digital assets. The Yearn system was using Curve creation called veCRV (vote-escrowed CRV). Users will lock their tokens away for an extended period of time. As the balance decays of veCRV, the relative voting power for the system does as well.
  • Yearn comes into play here with BribeV2. The idea was to incentivize veCRV voters to make their votes to increase the emissions of CRV. A user could post their tokens into BribeV2 and the contract calculates how much relative voting power they should have. It should be noted that the gauge weights are determined by their veCRV balance instead of the users locked amount.
  • While viewing the SPELL bribes, the authors of Yearn noticed an irregularity. While calculating the amount of control of the system, Yearn was using the slope value instead of the bias value. The slope value is the veCRV is the decay rate per second of their locked amount without their lock duration. This is terrible, because a user would get paid out an equal rate compared to somebody who has locked their tokens away for a long time.
  • Because CRV can be withdrawn after a week, an attacker can cycle the same CRV through multiple wallets to perform the attacks mentioned above. Yikes! How did the devs miss something this bad? Interesting finding none-the-less.

KaoyaSwap Path Double Counting Error- 1105

BlockSec    Reference →Posted 3 Years Ago
  • KaoyaSwap is a BSC Chain that is an AMM via exchange pools.
  • A Fee-On-Transfer token is slightly different than the standard ERC20. Typically, they implement the ERC20 interface with the main difference being the receiver gets a smaller amount to pay for the costs of the transfer. Normally, the fees are on the cost of the sender.
  • The function swapExactTokensForETHSupportingFeeOnTransferTokens takes in a path. This path is used to determine the swaps that are taking place by the protocol. For instance, A->B->C would take in token A, swap this to B then swap that to C for the user. Finally, the receiver would pay the fees for the transfer.
  • When determining how much to send to a user, it does this by calculating the difference in the balance before the function call _swapSupportingFeeOnTransferTokens() and after the function call. Although this is benign most of the time, there is a subtle bug here.
  • The path variable will perform as many swaps as we want. Additionally, there is no validation that there are duplicate tokens in here. This means that the difference calculation from above may include a transfer used earlier in the path! For instance, Token A->wBNB->B->wBNB would double count the difference n wBNB because the transferring was performed twice.
  • By performing all of these operations, the attacker was able to profit 271wBNB and 37K BUSD for a total of $118K advantage. To make this attack worse, they used a flash loan for the larger numbers. This twitter thread has a root cause as well.

Binance Smart Chain Token Bridge Hack- 1104

Andrey Bachurin - PT Swarm Security    Reference →Posted 3 Years Ago
  • The Binance has a token hub bridge that allows interoperability between two chains. These two chains are the EVM compatible Binance Smart Chain (BSC) and the Binance Beacon Chain used for management purposes. The currency of these chains is BNB.
  • Bridges do not work as you'd expect; it's more of a lockbox than anything else. Chain A has ownership of some tokens on Chain B. So, we look the Chain A tokens then unlock them for the specific user on Chain B. In the case of Binance, the logic for going to Chain B was busted.
  • When Chain B does the verification from the Chain A blockchain in Solidity, it uses a type of binary tree that can be used for verification called a Merkle Tree. This tree allows for the verification of the existence of a transaction on the other chain. Any discrepancy will indicate that the data in the node has been tampered with.
  • The BSC bridge uses a balanced binary tree called an AVL tree. The function handlePackage is made in order to add this information to chain B. It should be noted that this function is only callable by a relayer.
  • In the land of Cosmos, the relayer is how IBC communicates that a transaction on Chain A has occurred that Chain B cares about. A relayer doesn't need to be 100% trusted. Because, at the end of the day, the merkle proof speaks for itself. The relayer for BNB will parse the events at the very end of a block and send this to chain B.
  • To send it to chain B, there are 4 parameters: a source chain ID, destination chain ID, a channel ID and a sequence number. After this initial handshake, the relayer calls Chain B with the transaction data, the proof and the block height. This is how the data goes from a Chain A request to a Chain B request.
  • The Merkle proof library in Solidity was a precompiled IAVL library written in Go that directly interacts with Cosmos via a special EVM hook specific to Binance. Within Go, the function computes the root hash and verifies that it matches the hash against the new data being provided. If this is true, then the transaction must be legitimate.
  • The tree verification algorithm assumes that only one leaf node will exist. If there's a left node (if statement), then it allows verifies that. If there's a right (else statement) node, then it only verifies that. This becomes a problem when there are two leafs, which is 100% possible. With the logic of the program, we can provide a right node that will never be verified!
  • To exploit this, the attacker took a legitimate transaction and modified the payload to add the right node. The node said that a transaction of 2 million BNB with each BNB worth $293 at the time, was sent to them. Once they had done this, they took the money out of the bridge and laundered it to some other places. This resulted in a $586 million dollar hack.
  • In the aftermath of this, Binance forked the BSC blockchain to remove this. Additionally, USDT blocked the attackers address, preventing them from accessing a percentage of their stolen funds. To fix this, the proof errors out if there is more than one child. Overall, an interesting dive into the Cosmos and Binance eco-system.