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!

Stealing HttpOnly cookies with the cookie sandwich technique- 1587

Zakhar Fedotkin - Portswigger    Reference →Posted 1 Year Ago
  • From a previous post, the author dug into cookie parsing. They learned that long ago, the Version of a cookie was required. Many servers will downgrade their cookie parser to an older type if the version is found. This was used for WAF bypass techniques.
  • By abusing the parsers of a normal cookie and a legacy cookie, it's possible for both to be valid. In particular, weird things can be done with quotes and double quotes. Chrome, and many other browsers, don't care about the $Version in the cookie header because they don't support it. So, you're able to set this from JavaScript.
  • HTTPOnly is a browser protection to prevent the stealing of cookies with the flag. The author had found an XSS bug but wanted to steal the cookie. They found an endpoint that reflected the session name to be used as well. By using the $Version attribute to downgrade the parser, the ENTIRE quoted string would be sent back, including the PHPSESSID.
  • To do this attack, do the following:
    1. Inject a JavaScript cookie with $Version=1,session="deadbeef in it. Notice the double quote at the beginning of it that isn't closed.
    2. Append the cookie dummy=qz". This finishes the quoted cookie.
    3. Make the CORS request to get the session name. This will return the entire quoted string, including the real session information that was sandwiched in the middle of it.
  • The ordering of the cookies does matter immensely here. Luckily, this is deterministic and can be manipulated using the age of the cookie and the path. I personally love these parser differential bugs! Super fun issue.

Type confusion due to DefaultReferenceValue() `undefined` default value for kNoExtern - 1586

se...@gmail.com     Reference →Posted 1 Year Ago
  • The function DefaultReferenceValue() returns undefined as a default value for two different types. According to the wasm-gc specification, only null values can be used for reference types. So, why does this violation of the specification matter?
  • There are invariants in these complicated programs. For instance, only these X amount of types will be seen here or a value in the range of X..Y will be there. These constraints are then used throughout the codebase. So, once one of these invariants in violated, it causes major havoc.
  • The only allowed value for nullexternref is a JS null, but this has been broken. Optimizing compilers are now likely to optimize code incorrectly. Additionally, kNoExtern as undefined may be confused with other types. Using point 2, they were able to craft a string with an invalid length to cause a crash.
  • Neat bug! Reading the specification is helpful when multiple things rely on it.

If you know why x > y, you're a good C developer!- 1585

Dave W Plummer    Reference →Posted 1 Year Ago
  • The post is a simple C screenshot:
    int main(){
      int x = -10; 
      unsigned int y = 5; 
      if(x > y)
        printf("x is greater than y"\n);
      else
        printf("y is less than x\n";
      }
    
  • There are many different types of numbers in C. Integers differ in length (1, 2, 4, 8 bytes) and signed/unsigned. Additionally, there are floats that can store more precise numbers and larger numbers than floats but have gaps in them. I assumed that this code wouldn't compile without a cast. A friend of mine assumed that the right side would be casted to the right side, which was always wrong.
  • To move between these types with casting would be super annoying to do. So, C has some ranking system for automatic type casting. The "rank" of the unsigned integer takes precedence here. So, x is changed to an unsigned integer. Regarldess if this uses the - as a part of the number or not, it becomes bigger than 5.
  • "Otherwise, if the operand that has unsigned integer type has rank greater or equal to the rank of the type of the other operand, then the operand with signed integer type is converted to the type of the operand with unsigned integer type."
  • C is fun!

OP Stack Censorship Resistance- 1584

@gauthamzzz    Reference →Posted 1 Year Ago
  • Sony (not that Sony) has an L2 blockchain built on the Optimism Stack. They attempted to block unapproved tokens at the sequencer level. Their policy? If they don't like your contract, you get a "forbidden" response. No questions asked. This is a direct violation in the purpose of blockchain!
  • Early traders on the blockchain got rekt when Sony blocked transactions. If you can't execute a transaction when trading, then you're going to lose money. Damn, this is bad...
  • The Optimism Stack was built with this issue in mind: the sequencer can be bypassed. A transaction can be forced to the L2 by sending it through the L1, skipping the sequencer altogether. You can censor at the RPC level but you cannot sensor at this level.
  • Blockchains are meant to be open and let others execute things. In the real world, there are rules and regulations on what is allowed though. So, a company trying to write their own rules is going to happen. Good post!

PAT-tastrophe: How We Hacked Virtuals' $4.6B Agentic AI & Cryptocurrency Ecosystem - 1583

Shlomie    Reference →Posted 1 Year Ago
  • AI Agents are AI assistants that are capable of managing your digital life, such as posting on official. An AI agent in the cryptocurrency space is just managing a wallet. On Virtual, one of these is correct 83% of the time about price increases/decreases. Virtuals is an L2 network built on top of Ethereum that allows anyone to deploy and monetize AI agents.
  • Although it's AI meets blockchain, much of this is standard web2 architecture in the cloud. These agents can be updated through contributions - new data or model improvements - for data stored in Amazon S3 or IPFS. I honestly don't understand where the "blockchain" part is in this. Maybe the agents post their information to the blockchain?
  • While reviewing Virtuals, they were looking at the API responses and noticed a Github Personal Access Token (PAT) in a response. PATs are scoped access keys are Github. The reason this was being passed back was because the API needed to access a private repository. So, use a PAT to access or make it public?
  • With access to the repository, they used TruffleHog to review previous versions for secrets. While doing this, they found AWS keys, Pinecone creds and OpenAI tokens. These were "deleted" but remained preserved since git never forgets. Crazily enough, all of these keys were still active!
  • All of the AI Agents have a character card. With AWS keys, you can just modify these within the S3 bucket. Since the keys allow you to do this, it bypasses all access control. These "character cards" are the core programming of the platform. So, you'd be able to reprogram these AI agents. This is effectively an entire break of the platform.
  • Pinecone is used for Retrieval Augmented Generation (RAG) for Twitter posts, market information and other things. The LLM uses this information to understand what to do. An attacker with access to Pinecone could add, edit or delete the data used by the agents.
  • The scenario they post is terrifying. You could create a token then reprogram every AI bot to promote it. If the bots had a good analysis in the past, people would trust the analysis and buy the token.
  • The product has not undergone any real security reviews and doesn't have a bug bounty program. They got paid $10K, which seems low given the amount of money the protocol has and the impact. According to Virtual, the Agent's use a cached version of the S3 bucket so it wouldn't affect live agents. Still, with full access to the AWS account, there are infinite ways this could have been compromised.
  • The takeaways are good. In particular, I like the gap between Web2 and Web3 security is smaller than we think. Great write up!

WorstFit: Unveiling Hidden Transformers in Windows ANSI! - 1582

Orange Tsai    Reference →Posted 1 Year Ago
  • Windows supports Unicode for strings, now-a-days. This article discusses the evolution of string encodings on Windows and the requirement for backward compatibility. Originally, Windows used ANSI encoding. This relied on code pages for languages depending that did not fit within 8-bit ASCII. These code pages were specific to a given language so a Taiwanese message going to a Japanese computer would be rendered differently.
  • In Windows, there are actually two types of code pages. ANSI code pages (the focus of this article) and the OEM Code Page. Eventually, Windows moved over to UTF-16 which uses 16-bits for most characters and 32-bit for rarer ones. While making this change, Windows switched to wide characters UTF-16 on many of their APIs. To remain backward compatible, there are two sets of APIs: A for ANSI and W for UTF-16 wide.
  • The main focus of this post is when wide characters are passed into the ANSI APIs that doesn't exist in the existing code page. Instead of erroring out, the code attempts to do a best fit match back to the current ANSI code page. For instance, the infinity character gets mapped to 8 on code page 1252. Different languages have different quirks. To test this out, they created a tool to show off this functionality. The goal is to abuse this "best fit" feature in order to trick programs on Windows to do weird things.
  • The first instance they found of this was when using the PHP-CGI server. The original vulnerability (from 2012) demonstrated that adding a dash (-) to a query parameter could be used for argument injection, eventually leading to code execution. Using this same exploit method and our "best fit" trick, we can do the same. The URL query parameter ?%ADs will translate into a - on Chinese and Japanese computers. I remember reading the report yet had no idea why this mapping happened. I investigated why this happened but never came to a good conclusion on why. Now I do!
  • What else is affected by this? The Yen and Won symbols on Japanese/Korean Code pages will map to / and \ respectively. Since these are interesting characters for directory traversal, it could be a useful exploit. They found that the Cuckoo Sandbox could be escaped using this technique. The system saw the string as having same characters but the file access APIs in Windows did the "best fit" mapping under the hood.
  • The next target is command line arguments, similar to the PHP bug. In PHP, the function escapeshellarg() is the standard way to prevent command injection and argument injection. In Python, subprocess executes the command after doing some escaping. Under the hood, this will call into CreateProcess with the quoted parameters. If you can control ANY part of the data in the command, then U+FF02 (a full width quote) can be used to bypass this. This is because the functions don't escape it, but the system does the best-fit mapping BEFORE calling the executable.
  • This same attack can work by injecting a \ to remove the escape of another parameter. For instance, using the Won sign to add in a \ alongside a ", leads to the escape of \" on the double quote. Once the best-fit happens on the Won sign, this turns into \\" to void the escaping. They mention that argument splitting via spaces and tabs is fruitful using other characters as well. Neat!
  • ElFinder, which is a PHP application, could be used to pop a shell on by using the tar.exe command with the argument injection. The Open-With feature has a handler table in Windows. Since the filename is part of the argument, it becomes an attack surface. On Microsoft Excel, renaming this file to an argument-splitting payload leads to confusion in the interpretation. This leads to adding arbitrary arguments to excel.
  • You're not safe even if your program is just a simple C program! Using int main will default to the ANSI API usage to get the arguments and environment variables for the call. The compiler adds this in other the hood. A user could also specify wmain if they wanted to remediate this. Environment variables were a huge issue on this as well, leading to LFI and a WAF bypass in some PHP things.
  • Disclosure of these bugs was difficult. Developers thought the bug was in Windows while Windows said they needed to maintain backward compatibility. You can use the beta UTF-8 package on Windows as a user. Additionally, use safe APIs instead of shell commands when possible. Is the dawn of a new bug class on Windows? It appears so.

Earn $200K by fuzzing for a weekend: Part 2- 1579

Addison    Reference →Posted 1 Year Ago
  • This is a continuation of a previous blog post on Solana fuzzing. They found two bugs in the node software via fuzzing and this post is about triaging the bugs.
  • The first bug they found was an intricate memory leak. In Solana, the Instruction Meter is used to keep track of the amount of gas a program has left remaining. The meter is only checked at the end of a block, which should be fine. However, the ordering of operations matters here. During a previous security fix, the ordering was changed.
  • If an out of gas exception occurred at the same time that a unknown symbol error happened, then the latter error was simply overwritten. As a result, the string was left along on the heap without a reference to it. Although Rust provides memory safety, it doesn't guarantee that all memory is cleaned up. By default, this was only seven bytes, which was not a huge deal.
  • The string that gets allocated comes from the loaded ELF file - it's the name of the function. Since this is controllable, providing a very large name. The most we can do is 1M bytes being allocated. Using this in a loop on Solana over and over again will lead to the system being running out of RAM and crashing.
  • The second bug was easy to trigger but hard to diagnose. The JIT terminated with Ok(0) but the interpreter returned with an access violation error. Juicy! Looks like there's an out-of-bounds write in the JIT somewhere.
  • The violation was at 0x100000000 specifically. This memory address stores some read-only data present in the ELF provided. In order to find the bug, they used GDB to see when and why this was being written to. The program has access and bounds checks in it. Why is this failing?
  • The instruction was being executed was cmp DWORD PTR [rax+0x19], 0x0. This is surprising because it's not an 8-bit operand!? Why!? The x86 instruction uses the opcode 0x81 but only for 16, 32, and 64-bit register operands. If you want to compare the 8-bit version, you must use the 0x80 opcode instead. This leads to the CPU performing an incorrect comparison and using unintended values around it. Neat!
  • By breaking the comparison for the write using an 8-bit value, we can write a single byte to the read-only memory section. At first, the author thought this could be used to compromise the execution of a program. However, this only allows for modifying your own program and not others. Instead of the 1M they were hoping for, they got 100K for this bug (and the last one).
  • Overall, it is a fantastic series! I enjoyed the Oracle approach to finding bugs that weren't the typical "fuzzier crashed" situation. Both bugs were relatively complicated and deep, yet not too many lines of code. Good strategy!

Earn $200K by fuzzing for a weekend: Part 1- 1578

Addison    Reference →Posted 1 Year Ago
  • The journey begins with a Discord bot posting a Solana rBPF vulnerability. This CVE was particularly interesting because it was using a BPF and a JIT compiler written in Rust. Since they had developed some heavy-duty fuzzers for Rust software in the past, they decided to tackle this.
  • rBPF has both a Rust Virtual Machine and a JIT compiler. This stood out to them. Two different implementations of the same program which should have the same behavior. Luckily for us, this acts as a testing oracle. By doing differential fuzzing on this, any difference is certainly a bug.
  • Initially, they tried a dumb JIT fuzzer with random bytes. The code coverage was bad because jumps and other things are unlikely to be hit, resulting in no bugs being found. Let's make it smarter!
  • Their "smart fuzzer" grammar constrained the language by only allowing for the eBPF instruction set. Importantly, the constraints are only around the data being provided and not around the values. We want to explore the space as much as possible. It's a tough line between "valid", "invalid but potential for bugs" and "invalid no bugs".
  • Now that we can generate the inputs, we initialize and run both in parallel. Of course, this needs to pass the rBPF verification first. After running this for a few hours, they have two crashes that are in a separate post.

[CVE-2016-1824] Apple IOHIDFamily kernel race condition as root- 1577

Macro Grassi    Reference →Posted 1 Year Ago
  • XNU kernel and some IOKIT modules have been plagued by race condition issues. Many of these issues have been discussed, including one from Ian Beer. On the surface, these drivers either lack a locking mechanism altogether or use the wrong locks for the type. Because of this concurrent access of data types makes it easy to cause memory corruption.
  • The code in question callsrelease() on the kernel queue and then sets it to NULL. There is no lock provided on this code though. If another thread can access the kernel queue after it's been released but before it's set to NULL, then bad things can happen.
  • What exactly can happen? In the post by Ian Beer that was previously linked, he was able to overwrite a VTable pointer with this same type of bug.
  • The release() and NULL are very close to each other (literally a line apart). The PoC has a loop that tries to hit this. I'm curious how long this would take? Seems like a tough window to hit.
  • The author has a good takeaway: "Sometimes vendors just fix the immediate problem and bug, and don’t investigate carefully about the root cause and search for additional bugs that share the same pattern." If you see an interesting bug, there may be other variants of the same bug waiting to be discovered.

Remotely pwning iOS via WiFi and escaping the Sandbox (OLD UNPUBLISHED 2016 BLOGPOST) - 1576

Macro Grassi    Reference →Posted 1 Year Ago
  • When you log in to a WiFi network, you are automatically native to a captive portal. The browser that opens for the captive portal is not normal Safari—it's Websheet. Naturally, the author had some questions about the control that a user would have over the content and the sandboxing of the application.
  • We can return a malicious page using a malicious WiFi dongle and Linux machine. This page will have a Webkit exploit in it, which is out of the scope of this post. Since this is automatically rendered when connecting, it's a zero-click attack surface.
  • Unlike Safari, WebSheet doesn't use a split process model between the sandbox renderer process. So, if you find a web bug, you have code execution in the context of WebSheet. Once we're in this process, the sandbox is restrictive but the next goal is to see what we can do with it.
  • The entitlement com.apple.managedconfiguration.profiled-access can be used to install a configuration profile, which includes a RootCA and proxy settings. This can also be used to install an additional fake app in the background, silently backdooring the device forever.
  • The next interesting permission is com.apple.springboard.opensensitiveurl. This can be used to open sensitive URL entitlements. For instance, opening Safari. Using this issue, we can force a URL to be open once again and deliver the same WebKit exploit as before.
  • I personally love this post. Most of the time, we say, "How can we get in?" or require a full chain of bugs. This post assumes we have a WebKit bug the extrapolates what we can do with it. Defense-in-depth measures only matter if we properly scrutinize them. The entitlements on this application feel like too much and it's interesting to see how they can be abused.