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!

iframe and window.open magic - 1417

Huli    Reference →Posted 1 Year Ago
  • An iFrame tag is used to bring in other web pages into your own. Some pages restrict this (to prevent clickjackin and phishing, among other things) but can be awesome for developers. The src attribute is used for the location of the page. Placing a javascript: URI can lead to XSS here and in forms and anchor tags.
  • Interestingly, the data used in the URI can be HTML encoded and will still render as we want. When using data: URIs here the code can be executed but it's executed on the null origin. To prevent attacks on the URLs, a developer can restrict the domain to only contain http/s: in it. Still, redirects can be made from the page.
  • Cross origin pages normally cannot access the page. However, there is some data still accessible. The only callable functions are focus, blur and postMessage. There are also readable properties like frames, top, opener, parent and others. Additionally, the location.href property is writable.
  • iFrame has a srcdoc attribute as well. It's similar to the src but the iFrame will take in data for the doc and share the same origin as the original page. These can be HTML encoded as much as you want as well.
  • Iframes have a sandbox property which is off by default. If just "sandbox" is used then all protections are on but can also have explicit protections turned on. There are several flags that change how the upper layer can be redirected. There are some function related ones, like allow-downloads.
  • allow-popups allows window.open() to be called in order to open new pages, but this page is still sandboxed. Adding allow-modals allows for the escaping of the sandbox on calls with a null origin. allow-popups-to-escape-sandbox will remove the sandbox on popped windows.
  • allow-same-origin will set the origin to non-null it will keep the original origin of the call instead of a unique origin with separate cookies. allow-scripts allows JavaScript to be executed within the frame. There is also a CSP sandbox attribute for the iFrame as well.
  • window.open has three parameters. URL is the obvious one. The second one is the name of the window. If there already is a window with the same name as provided then it will provide a reference to the other one named this way.
  • There are six ways to generate named windows. Anchors, forms, iframes, objects and embed tags, as well as the window.open.
  • There are some other details on detecting page loading and other things. Overall, just a great references for iframe protections and windows in the browser.

Parallel Code Security: The Challenge of Concurrency- 1416

Stefan Schiller - Sonar Source    Reference →Posted 1 Year Ago
  • Apache Guacamole is a remote Desktop gateway used for accessing hosts and isolated applications from the webs browser. The application itself contains a client facing server written in Java and a local server that the client server interacts with written in C.
  • The server makes heavy use of parallelism to make it fast. There are 8+ threads that do various things, from handling a connection to communications back to the client and much more. Since there were so many different threads running, they were curious about the security of this. In general, mutexes were used and threads were loosely coupled, making it mostly secure. From a developers perspective, reasoning about multithreaded code is very hard to do, meaning that there are likely bugs in this area.
  • Staring at the audio processing seems to be a fruitful area because there were three threads involved with it. One thread (the user thread) creates two buffers: one for the user and one for the socket connection, with the user structure having a pointer to the socket. After this, a third thread, called the rdp_client_thread, is created for a RDP connection. This structure holds a pointer to the user structure, creating a pointer chain.
  • These different objects can be closed or destroyed at any point. By disconnecting from a session (after using the microphone), the user_thread is torn down - this frees the socket and user structures. However, the rdp_client_thread still has a pointer to this object! So, if a message is sent to close the audio channel, this is accessing a freed pointer! This creates a classic use after free vulnerability.
  • To exploit this, the author looked into overlaying this object over the top of something else. However, the thread was closed quickly afterwards, making this approach not very helpful. So, we'll have to dive into threads and glibc malloc clean up to make this work! There is a function pointer at slot 8 in the user object to another poitner. So, if we could edit this and call it from the other thread, it would be game over.
  • By abusing some functionality within glibc's freeing process, it was possible to stick the freed user chunk into the unsorted bin, which references some data controlled by the attacker. Although we don't have a memory leak, the pointer to a function pointer now goes from our freed data, to free data that we control (since it's never cleaned up in malloc). To get control over the execution, an attacker can call strdup() with a size of 0x90. But, this is limited to zero nullbytes, making it infeasible. So, now what?
  • malloc is limited to a small amount of arenas or heaps. By sharing connections on Apache Guacamole, we can create lots of arenas, until we hit the limit. This means that we can use the UAF on a different thread that is much more useful! The author found that getting the input_thread allocated with arena4 was the best thing for controlling the data within for our UAF.
  • With control over RIP on the function pointer, we need to setup a ROP chain. The lock_handler() function pointer is called on an argument provided by itself. Since the data after the function pointer gets corrupted, they use a pivot gadget before proceeding. Once done, they called system with their provided parameter to pop a shell. Pretty neat!
  • Being a big fan boy of malloc heap exploitation, I really enjoyed this article! The knowledge required to exploit this was super high, which was cool to see. I found the simplicity of the UAF interesting as well - just a simple dangling pointer with no race conditions. Amazing post! :)

High Risk Bug Disclosure: Across Bridge Double-Spend- 1415

iosiro - Jason Matthyser    Reference →Posted 1 Year Ago
  • Across protocol allows users to bridge funds between various EVM chains very fast - faster than finality. There are a couple of main users. First, the relayer who has funds on all chains. Second, the data worker for slow relays who always has enough liquidity.
  • The relayers search for a given transaction to have occurred within the EVM smart contract. If it's profitable and they have the liquidity on the other chain, they will do a transfer for them. If it's going too slow (aka it's not profitable) then the user can increase the fee. If it's profitable but the relayer doesn't have the funds then they call fillRelay() and the dataworker will handle it.
  • There are two types of events being used: deposit and fill. The deposit is what the relayer does and the fill is what the dataworker does, after something has happened to the initial fill. Being able to tie a fill to a deposit is important to ensuring that double spends don't occur - both for the on chain and off chain infrastructure.
  • There needs to be some fairly complex logic for ensuring that two deposits are not made to the user. Onchain, to prevent this, a hash of the deposit is made in order to track it. Offchain, the function validateFillForDeposit Fill() filters all recent fills to find the proper deposit for it.
  • The goal is to trick either the dataworker or the relayer to process the event when it should not. Within the relayer code, the function getValidUnfilledAmountForDDeposit() obtains the previous fills for the deposit against depositsWithBlockNumbers(). Additionally, there is a function that handles updates() that were being made to the transfer.
  • The relayerFeePct field would be updated for a sped up deposit within the local object. Since the hash of the original object and the new object were different, it saw that as a valid fill! The tying together portion of the code has been broken.
  • To exploit this, the following steps need to be done:
    1. Perform a transfer from chain A to chain B.
    2. Trigger a slow relay manually. This is to A) get the transfer in a different state and B) get the relayers to stop looking at it.
    3. Update the relayerFeePct on the source chain. This will get the relayer to see the deposit to NOT see it as a slow relay anymore.
    4. Transfer from relayer and dataworker is made. The hash is different than the original on the chain for both TXs. So, we steal funds!
  • To fix the issue, the client side properties now checked to ensure that clients could see the deposits that had been filled or not, regardless of the state of relayerFeePct. Personally, I don't like the client side fix very much; I feel like doing something with the hash would make more sense. Unfortunately, there are times where hashing too many things is just as bad as hashing too few.

The Graph Rounding Error Bugfix Review- 1414

@GregadETH - Immunefi    Reference →Posted 1 Year Ago
  • The Graph is a decentralized indexing protocol. Developers can access and query data across different blockchain using web2 APIs. Many projects, use this for UIs but also for backend services. It falls into the blockchain infrastructure category. To pay for using the service, there is a token. This is where the TWO bugs are at.
  • A subgraph API is the curator of creator of the content. To have this be created, a user needs to stake tokens. Alongside this, they pay a 1% curation tax in GRT tokens to the platform. When calling mint() the amount of tokens paid is rounded down. If the tax was 1%, then sending in 99 tokens would result in 0 tokens, instead of 1, being sent in.
  • To me, this feels fairly insignificant because the cost of gas would be much higher. However, the article claims that since this was deployed on Avalanche (low gas cost EVM chain). To be honest, this felt sorta hand-wavy but I'll live with it. By batching 99 tokens per call in a contract, the cost is cheaper than the cost of gas. This steals revenue from the protocol, which is bad.
  • When a node operator for the Graph wants to provide services to earn rewards, they stake their GRT tokens for some period of time. When unstaking, there is a thawing period in order to ensure that bad indexers are penalized for their actions.
  • When calling unstake() there's a calculation error that allows a user to bypass the lock duration. There's a weighted average function for unstaking. It takes into consideration the total unstaked tokens and the amount of tokens newly being unstaked. Given this information, it will return a time where the tokens can be unstaked.
  • This function is vulnerable to a rounding issue when the currentUnstakedTokens is 201600 larger than newUnstakedTokens. When this happens, the newLockedUntil function will return the previous time! Using the same strategy as before, an attacker can unstake small batches of tokens at a time to avoid the locking period.
  • Rounding bugs are very unintuitive issues for me. The first one makes sense but required a specific blockchain to make viable. The second one I stared at the function for a while and played with some numbers until I understood it. It seems that with small numbers, the rounding is bad!
  • I had two big takeaways. First, if the supported chain for the protocol is on a cheap fee blockchain, then the small rounding errors become a bigger deal. Second, is a heuristic for checking these in my head. Initially, checking if the rounding direction if good or bad for the protocol. If it's bad, then review the impact then play with the numbers some to understand the impact. Good review!

Sonne Hack- 1413

Daniel Van Fange    Reference →Posted 1 Year Ago
  • Compound and AAVE both have a bug that allows the entire protocol to be drained IF there's empty market open. Apparently, this has destroyed a large amount of forks.
  • Sonne was aware of this issue and had a mitigation strategy. First, add a timelock to add a market. Second, adds the funds. Finally, have the timelock open up the market for use. If followed in this order, it would be totally fine.
  • Sonne queued all of the multisig operations as seperate operations in the timelock. Since there was no order that had to be followed, this was a problem. Anybody could come execute these in any order they wanted.
  • The attacker executed the TWO timelock operations without adding funds in the middle of it. With this, the Compound/AAVE bug could be exploited once again, as before.
  • What should have been done better? Governance actions that must happen in a certain order must have restrictions on the ordering. For Open Zeppelin's timelock, scheduleBatch() can be used. Overall, interesting hack for 20M!

CVE-2024-21115: An Oracle VirtualBox LPE Used to Win Pwn2Own - 1412

Cody Gallagher - ZDI    Reference →Posted 1 Year Ago
  • Within the VGAState struct of VirtualBox there is a bitmap used for tracking dirty pages of a VRAM buffer. This bitmap is large enough to use the maximum vram allowed by vbox at 256MB. When clearing the dirty bits, the start_addr is incorrectly multiplied by 4! If the address is larger than 64MB, then leads to an out of bounds access.
  • What primitive does this give us? A heap based bit clear. Doesn't seem like a lot and seems inconsistent on the location. To trigger the bug, they set a bunch of setting within the ioport communication. How is this exploitable?
  • Within VGAState, there is a section called CritSect. This is a critical section that can only be used by one thread at a time for in and out instructions for each devices MMIO region. The cLockers variable is effectively a locking variable to ensure that other threads don't access it at the same time. By abusing a bit clear, it could be possible to create an artificial race condition here.
  • Using this race condition, there is a problem though: there is a secondary check on the ownership that will crash changing the owner . The flag RTCRITSECT_FLAGS_NOP determines whether locking operations are checked at all, which controls the check above. The idea is to use the original exploitation path to change the flag BEFORE the crash happens. Then, after that, we can continue using the race condition for other things.
  • With the protections removed on the race condition detection, corruption is much easier to cause. The primitive used for corrupting the flag with the function vbe_ioport_write_data can be used once again to corrupt the size of the buffer cScratchRegion. With the size corrupted, it creates an easy out of bounds read and write.
  • After the VGAState variable is PDMPCIDEV. Since this is part of the initial allocation, it's always in the same spot! It contains several function pointers, leading to easy code execution. Even with CFG turned on it doesn't matter because we control the pointer and two of the parameters being passed in.
  • It's crazy how such a little bug turned into such a large impact. Awesome post on exploit development!

Hotwire CSP bypass on Github.com- 1411

joaxcar    Reference →Posted 1 Year Ago
  • Using the drag and drop functionality with invalid data, innerHTML was being set. Johan Carlsson was approached about needing a CSP bypass on Github.com for this XSS in order to make it exploitable. Using things like autofilling credentials with a form didn't even work because the form-action was being set or css due to a strict allowlist.
  • Github has three different XSS protections on the UI: CSP, form-specific CSRF nonces and the session sudo mode.
  • Hotwire is an HTML over the wire framework to get HTML from the server side by observing what the page needs. The saw this was being used with the HTML element turbo-frame. By adding in a form with turbo-frame in it, Rails will listen for the inserted element and grab it from the backend dynamically for us. Since it was grabbed in a legit fashion, it also grabs the CSRF token.
  • However, this only loads the form - we still need a way to add information to it. Using turbo-streams an attacker can modify the input forms with a click anywhere on the page and submit the form. The impact of passing CSRF protections is that an attacker can call any form-based request, such as add SSH keys.
  • While messing around with this, they found a mechanism to remove the original two clicks. They found a piece of JavaScript that automates the clicking of an element! By passing in the function focusOrLoadElement, it's possible to force the page to click the various buttons for us.
  • The world of CSP bypasses is much deeper than I realized! Using the hotwire framework to turn this into something more useful and the reuse of niche JavaScript functions was also interesting. Overall, a great post with a better discussion on the bug bounty podcast.

onhashchange can be triggered cross-origin- 1410

Critical Thinking Podcast    Reference →Posted 1 Year Ago
  • The web browser attempts to isolate all pages by default but allows some cross-domain communication. An interesting, yet new to me, method is by using the hash. This has been documented for a long time but was not something that I knew about.
  • The hash of a given page can be changed by something with a completely different domain. The twitter post uses window.open on the target window in order to do this. The post I linked above from WellCaffeinated does this by simply setting the frame source.
  • Why is this useful? Some pages do routing based upon the hash or use it in some other way. Being able to trigger this cross-domain can be used to have crazy effects. This is a short note but something that I wanted to remember for later.

Code Interoperability: The Hazards of Technological Variety- 1409

Stefan Schiller    Reference →Posted 1 Year Ago
  • Apache Guacamole is a remote desktop gateway server. The architecture consists of a Java component with a C backend server. So, they go through a classic difference between two parsers to create serious security impact.
  • All communication is done via the custom Guacamole protocol, which is a generic wrapper that abstracts SSH, VNC and SSH. This contains an opcode with a length and value, then arguments after the opcode. When initially connecting to a server, the select instruction is used. Most values are taken from a database but the image type is directly controlled by a connecting attacker.
  • The documentation states that the LENGTH field is not the bytelength but the codepoint length for UTF8. Since UTF8 implementations differ and we have two locations parsing the characters (Java and C), there is likely to be a bug here. The article has a good descriptor on what they mean by this - Technological Variety.
  • To test this out, they wrote a small fuzzing harness. The fuzzer would generate random unicode symbols then have both Java and C process it. If there is a difference, then we have a problem. After some fuzzing, they ran into a difference in the length() of the object in Java compared to C. Sending in a 4 byte UTF8 character sequence was interpreted as a 2 byte sequence in Java. Why?
  • In Java 9, they use compact strings. So, this means that strings are either dynamically encoded as LATIN-1 or UTF-16 depending on the situation, dynamically. For instance, an 'A' is encoded as a LATIN-1 string internally but the greek beta would be encoded as UTF-16. What's weird about this is incoming data in UTF8 must be converted to UTF-16.
  • The byte length is determined by shifting the byte array of the coder value. If it's LATIN-1, then just one byte. If it's UTF16 then it's encoded by dividing the length by 2. For 1,2 and 3 byte sequences the logic works fine. However, there is a subtle issue when dealing with 4 byte UTF8 sequences.
  • In particular, the conversion turns this into a surrogate pair instead of a single codepoint! As a result, only the first part of the surrogate pair is recognized in the length, resulting in less bytes being processed than expected. The Java length() function returns the number of Unicode code units instead of code points. Weird!
  • To exploit this, we have to think about the parsing of it. The instruction creation step is done by the Java side then the instruction parsing is done by the C side, in this order. The blog post has some amazing graphics for understanding this, so please refer to do that. The idea is to send two GUAC_IMAGE parameters: one with four 4 byte unicode characters and the other with our payload we want to smuggle in.
  • The one with the four 4 byte unicode characters will be set to contain a length of 8 by the Java service. However, the C service will see each of these as a single codepoint! As a result, it will read more than the expected 4 bytes and read 8 instead. So, the second set of bytes is where we smuggle in our input. By putting a semi-colon then extra data, the command will be interpreted as a new instruction!
  • What do we want to smuggle in though? If we smuggle in the connect instruction, we can control the host that an attacker connects to. This can be used to leverage data, such as credentials. Or, RDP drive redirection can be enabled to leak world-readable files on the server.
  • Integrating between difference languages appears to be absolute hell for encoding. The post is amazing at talking about the differences between parsers and is super enjoyable for that reason. I personally don't like the text-based wire format for Guacamole, as it is prone to these types of issues. Great read!

Digging for SSRF in NextJS apps- 1408

AssetNote    Reference →Posted 1 Year Ago
  • NextJS is an extremely popular 'static' site generator, which this website actually uses. So, finding configuration issues or straight up vulnerabilities in NextJS is awesome for bug hunting, since many things would be affected by it.
  • The _next/image component is a built in image optimiziation library that is enabled by default. This works by making a request to the endpoint _next/image under the hood, which implements caching. Now, the actual server makes a request to //localhost/duck.jpg with the provided URL. There is a remotePatterns configuration that restricts the protocols and hostnames set, which is commonly set to '*'.
  • When configured as such, this can lead to SSRF bugs with https://example.com/_next/image?url=https://localhost:2345/api/v1/x&w=256&q=75. Most of the time, this is a blind GET request but there was situations where the impact can be escalated. First, with an old version of NextJS or dangerouslyAllowSVG is set then this SSRF leads to an XSS via the image reflection on the domain. If the response doesn't have a Content-Type then the full response is leaked.
  • Even though NextJS is a client side library, there are many crazy server side features like Server Actions. This feature allows writing JS code that is executed on the server instead of the client side to interact with. When handling redirects within the server side code, it takes in the Host header to make the request. So, by providing the Host header, it's possible to force a localhost redirect leading to SSRF.
  • To exploit this, an action must be defined where the action redirects to some URL. To turn this into a full SSRF read, we can spoof the HEAD request to do some janky things.
    1. Set up a server that takes requests to any path.
    2. On a HEAD request return a 200 with a specific content-type to satisfy the constraints of the system.
    3. On the actual GET request, return a 302 to our victim IP.
    4. Data is returned on the request.
  • Overall, an interesting post on a bug within NextJS and a universal exploit method as well. This is the second issue found with this functionality - the first SSRF on the image stuff was found via Sam Curry.