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!

Gato X - GitHub Actions Security Scanner- 1714

Adnane Khan    Reference →Posted 7 Months Ago
  • Security scanner for GitHub Actions. Looks for Pwn Requests, TOCTOU issues, command injection and several other issues. It even has some post compromise exploitation it tries to do.

Fooling the Sandbox: A Chrome-atic Escape - 1713

Vincent Yeo - STAR Labs    Reference →Posted 7 Months Ago
  • The author of this post was exploiting a Windows vulnerability to escape the Chrome renderer Sandbox to become SYSTEM. The original vulnerability is a time of check, time of use (TOCTOU) issue on a user-provided pointer. By changing the userland pointer to be a kernel address, we obtain a partially controlled write.
  • In just Windows, this was a straightforward exploit. Once you use the bug to overwrite the Control bitfield in SepMediumDaclSd, it will skip the integrity check. Next, get the token of the renderer process via NtQueryInformationSystem. These steps are the same for the Chrome sandbox escape, but there are a few additional steps to take.
  • From the Chrome sandbox, this wasn't the case, though. The integrity level is untrusted, the job disallows the creation of child processes, and there was no privileges on the token. So, using this vulnerability requires some more work to exploit in this case.
  • When trying to call NtQuerySystemInformation to get a token, they got an access denied error because the renderprocess runs at a low privilege level. To fix this issue, they used the previous OOB write to overwrite the SE_SACL_PRESENT field to skip the integrity control check. Since this was a 32-byte write, they had to be clever at the byte offset to do this at though.
  • The second issue was another access issue around job integrity. The renderer process cannot call CreateProcess. The solution? Inject the code from the renderer process into another process (winlogon.exe) and have it call CreateProcess instead. This gives them code execution as a privileged user from Chrome!

Finding Mispriced Opcodes with Fuzzing - 1712

Max Ammann - Trail of Bits     Reference →Posted 7 Months Ago
  • The author of this post was working on a project for the custom blockchain runtime Fuel VM. They created a fuzzing setup with special invariants to try to catch some bugs. The Fuel network team initially used libFuzzer and cargo-fuzz, but this was slow and didn't make any internal calls.
  • To get better coverage, they used a shim to inject in LibAFL. This allowed for the execution of 1K per second on an eight-core machine versus the original 50 per second.
  • An additional redesign was around the input generation. Originally, the input was a simple byte vector with not much else. Since the Sway language compiler has data interleaved with the actual instructions, the byte vector was ineffective. The input is now a byte vector that has the script assembly, script data, and a magic value separating them. Using this, they used the Sway compiler to obtain a large number of test inputs.
  • They ran into some issues with fuzzing. The program secp256k1 0.27.0 version is not compatible with cargo-fuzz because it fails all the time. So, they had to use a different version for fuzzing. Secondly, they ended up modifying the FuelVM somewhat to add the offset into the execution data to have this within the context of the program.
  • It is assumed this fuzzer was used for a large number of reasons, but a major one they used was gas usage. In most blockchain runtimes, different opcodes cost different amounts of the native currency to run. So, they wanted to see if the gas usage correlates with the execution time. They ran the fuzzer and plotted the information. Within the document, there are several for weird outliers.
  • Through doing this and finding the outliers, they were able to find opcodes that should cost more in gas. In the "lessons learned" section, they state that fuzzers should be run until new coverage stops, pause the campaign when issues are found to let developers fix them, and save the corpus generated from each test. This is because it represents hours of refinement to find the edge cases in the codebase.
  • Overall, a great look into setting up a legitimate fuzzing campaign. It was a unique reason to use fuzzing that turned up good results for them.

How we Rooted Copilot - 1711

Vaisha Bernard - Eye Security    Reference →Posted 7 Months Ago
  • Copilot added a Python sandbox running Jupyter Notebook that can execute code. Within CoPilot, we can use %command to execute arbitrary commands in the environment. The author points out that it works sometimes and is like a child. After some time, though, it becomes more and more consistent in execution. I am guessing that it sees its past actions and is more likely to perform the actions as a result.
  • They read files from the system using this technique. When trying to read binaries, there are many mistakes. So, they had to base64 encode the binaries. After getting all of the files, they can steal the server code for this application that they are interacting with.
  • In the bash script that starts the server, it executes a set of commands every two seconds. Most of these are executed with lower permissions, but not all. When performing this loop, it calls pgrep with non-dropped privileges AND with an absolute path. By changing the PATH variables, it's possible to change the binary being used and execute it as root.
  • Funnily enough, there's zero impact to escalating this to root. Still some good alpha on using CoPilot and a good privilege escalation.

A Novel Technique for SQL Injection in PDO’s Prepared Statements- 1710

Adam Kues - Searchlight    Reference →Posted 7 Months Ago
  • PHP contains a library for prepared statements with PDO and contains prepared statements. In most languages, this is sufficient for preventing SQL injection. However, PDO in PHP isn't a true prepared statement. PDO attempts to do the escaping itself instead of binding the information, like many other languages do, which requires their own custom SQL parser.
  • There's a great quote that sums up the whole challenge: "if we can trick the PDO parser into parsing our input as a bound parameter where it shouldn’t, we can get an SQLi in a situation that would otherwise be impossible." They created this as a CTF challenge that contains a potential injection point within a column name; this is because column names cannot be bound. So, the parameter should be escaped adequately with backticks, but this isn't enough!
  • The parser contains a nice specification for how it parses the data. The type ANYNOEOF is defined as [\001-\377]. When adding a null byte and a question mark - ?\0 - it will see this as a SPECIAL and note a literal now. Naturally, this error occurs because it's trying to bind two things in the query. However, adding a comment - code>?#\0 - can easily remediate this problem. So, are we done? Nope! Still some more trickery for this to work.
  • Now, the first parameterized item in the query is used with OUR question mark and has to have a value. The substitution turns this into 'x'#\0, where the x is a controllable parameter and the parameterization adds the single quotes around the query because it thinks it's a string. There's another issue now: a null byte cannot be in a comment. The problem can be solved by adding a semicolon between the comment and nullbyte to make it a new line. With the stolen parameter x`;# and the same column name, this problem is solved.
  • The column 'x does not exist, though. What now? PDO still thinks that our injection point is in a string! Placing a \ as the first character in the string causes some MAJOR havoc. It will escape the single quote to allow for a context escape. The column name \?#\0 and the stolen parameter with x` FROM... allows us to create a legitimate query to perform SQL injection. Neat!
  • Older versions of PHP are much more susceptible to these types of attacks. This is because the lack of usage of backticks allowed for any injection of a question mark or semicolon, leading to SQL injection. There are numerous other ways to exploit this by creating a desync between the parameterization data. Overall, a pretty slick blog post on a fun SQL injection technique.

How We Gained Full Access to a $100M Zero-Trust Setup - 1709

How We Gained Full Access to a $100M Zero-Trust Setup     Reference →Posted 7 Months Ago
  • The cloud environment had an API for generating API keys. This had a CSRF vulnerability, mainly because it used a GET request to create the API keys. This ALSO worked with the admin user. By itself, this doesn't have much impact, though.
  • Upon looking deeper into the application, they realized that CORS was misconfigured. In this case, the origin was simply reflected, and credentials were sent. The combination of these two meant that the generated key from above could be stolen. Pretty good impact!
  • The application had a super simple proxy feature with no input validation. Using this method, it was possible to call the AWS metadata endpoint to steal AWS credentials. With the credentials, they decided to figure out what the API had access to.
  • While describing the other EC2 instances, they found passwords in plaintext but I'm unsure what fields these were in. One of the credentials allowed for admin access, allowing for privilege escalation to the AWS Administrator.
  • Overall, a group of super serious bugs leads to complete compromise. This is why defense-in-depth is so essential!

RCE in the Most Popular Survey Software You’ve Never Heard Of- 1708

Adam Kues - Searchlight    Reference →Posted 7 Months Ago
  • The blog post is about a pop-up asking for a survey that is made by Lighthouse Studio. This appears to be an open-source project, allowing for some access to the source code of Perl. Unfortunately, much of the Perl code is minified. After running Perl::Tidy to make it prettier to read, they still wanted some help reading it. Naturally, they used AI to deobfuscate the code. It had some missing issues, but much of it was correct.
  • After it was deobsfuscated, they found an interesting sink: eval. The subroutine _fop implemented a primitive templating engine. If something had [%...%], then it was evaluated as Perl code.
  • They found this sink but needed to find the correct source. The ciwweb.pl input hid_Random_ACARAT that plugged into this sink with [%257*7%25] to return 49. By adding backticks, this turns into pretty easy code injection.
  • On older versions, the backticks didn't work via some regex replaces. So, they needed to find another way to exploit it. In the case of an array being used for the value, the regex replace doesn't work. So, this worked on all versions.

Why Careful Validation Matters: A Vulnerability Originating in Inline Assembly- 1707

Sherlock    Reference →Posted 7 Months Ago
  • Solidity adds a lot of safety checks, such as integer overflow protection, at the compiler level. Because of this, there is a special lower-level language called Yul to help write more proformiant code. These blocks, commonly used to as "inline assembly" are complicated and hard to get correct.
  • The code in the blog post contains an inline assembly call() to a function. It has some Yul code that is worth discussing further:
    1. Copy the calldata of the program into EVM memory.
    2. Patch the callata that was placed into memory. User shouldn't be able to control this parameter directly unless it's zero.
    3. Call the function target with the pointer data.
  • To prevent craziness from happening, there is validation happening on the callee contract and function selector being specified. The patching code is used to overwrite the swapAmount in the calldata via a user controlled index. This is where the fun begins!
  • To calculate the address to write, the following calculation is used, where swapAmountInDataIndex is a 32 bit integer.
    ptr + 36 (0x24) + 
      swapAmountInDataIndex * 32 (0x20). 
    
  • The offset of 36 is used to prevent overwrites to the function selector of the call, which is wise. For some reason, the swapAmountInDataIndex variable is a uin256. Unfortantely, there's an integer overflow in this calculation. When performing the multiplication on the index, this can overflow. With a specially crafted value, it's possible to wrap back around to modify the function selector that had previously been verified. An arbitrary call in the context of a Solidity smart contract is effectively game over.
  • The solution is to limit the index by the size of the calldata. This prevents the overflow and any other out of bounds access. Overall, a solid and subtle bug in a Yul optimization.

Pwning Solana for Fun and Profit - Exploiting a Subtle Rust Bug for Validator RCE and Money-Printing- 1706

Anatomist    Reference →Posted 7 Months Ago
  • This post details the discovery and exploitation of the Anza Solana validator to achieve full remote code execution (RCE) on the validator written in Rust. Besides the vulnerability description, they discuss a lot of background (that I will skip) and the process of hunting for the bug, which is probably the most interesting part to me. Solana is extremely optimized, which comes at a cost in terms of security. For significant speed enhancements, such as memory address translation, it conceals a substantial security risk. For this reason, the team was closely monitoring new changes and features that looked dangerous.
  • In Solana, all account data had to be copied directly into the VM, which is very slow. So, the feature Direct Mapping was born to allow account buffers to be directly mapped into the VM memory rather than copying the entire data to the execution runtime. Dealing with raw pointers is very scary, which is why the authors of this post decided to keep looking at it. New code is the most likely to have bugs in it.
  • In Solana, these accounts have very important permission boundaries, such as only the owner of an account being able to modify it. Originally, these were validated post execution or when creating a call for a CPI to a local version of the data before being written to the global account cache. This is an important invariant to consider later.
  • Direct Mapping had the account.data point directly to the hosts buffer for the Solana memory region. Because everything is now using a shared pointer instead of a personal pointer, the validation must happen on each write called copy-on-write. This changes a key invariant of the system. Originally, all of the data was directly read from the underlying DB implementation. So, a copy was added to memory and updated once it was written to.
  • Direct Mapping also needs to consider situations where account sizes change. To make this infrequent, there is an overallocation that occurs. When CoW operations relocate data buffers during CPI execution, the original MemoryRegions structure for the previous call still points to the old buffer. To do this, it grabs the vm-data_addr to find the memory region of the original mapping to eventually update it.
  • The vulnerability lies in bad input validation from this process - the CallerAccount.vm_data_addr is stored completely in the VM's heap memory. By modifying the AccountInfo.data pointer in VM memory before triggering a CPI call, an attacker can forge an arbitrary vm_data_addr value. This causes the wrong memory region to have its host address updated, being mapped to an arbitrary location in virtual memory.
  • Their original exploitation method was to break memory write authorization checks - a core invariant of Solana. Their first PoC made an account writable that should not have been writable, leading to a major loss of funds bug on all Solana programs. Upon pulling the newest code, the attack no longer worked because of a fix to an unrelated bug. The exploit was killed because the CPI account being set to ReadOnly.
  • The core issue still isn't patched but we just need a new exploit strategy. They decided to approach this with a binary exploitation-like approach! Accounts with different sizes could now be mapped over the top of each other. By using this fact it's possible to read or write out of bounds on the directly mapped buffers in host memory. This is limited to the range of the other account that we're using though, limiting the size of the read/write.
  • They came up with a way to take this to an arbitrary write that required 3 accounts: small buffer (SWAP), large buffer (LEVERAGE) and the exploitation account (POINTER). Here are the steps:
    1. Trigger the vulnerability to map the SWAP address over the top of the LEVERAGE address. This will allow us to read/write OOB on SWAP.
    2. Hunt for the POINTER account within the MemoryRegion. By setting a simple value on the account data, we can locate it after some searching.
    3. Replace the POINTER host_addr and set its state to writable.
    4. Write to POINTER at any location in memory that is desired. With arbitrary read/write, this is a basic exercise to get RCE.
  • I really really really like this post. It contains methodology on why and when they were looking at this section of code. It describes a deep understanding of the core invariants of the software and how they found a bug that breaks the invariants. Finally, their multiple attempts at exploitation were nice to see as well. This is one of the more "real" write ups that I have read on bug hunting. Solid work!

LET ME COOK YOU A VULNERABILITY: EXPLOITING THE THERMOMIX TM5 - 1705

Baptiste Moine - SynAckTiv     Reference →Posted 7 Months Ago
  • The Thermomix TM5 is a multifunctional kitchen appliance. In previous research, Jean-Michel Besnard found a directory traversal flaw in BusyBox's tar implementation. This article describes a lot of reverse engineering of the device and a firmware downgrade vulnerability they found in the process.
  • The device contained a strangely formatted NAND flash; after considerable effort, they found a tool to decode it. There are cook sticks that hold recipes for use with the Thermomix. Upon looking at the UDP flash drives, they find out they are encrypted. Upon examining the NAND flash firmware, a file named /opt/cookey.txt is found to contain the encryption key. After some reviewing of a customized kernel driver, they are able to decrypt locally, but cannot modify it because the data is signed.
  • The Cook Key is a special device that enables users to connect the Thermonix to WiFi, download firmware updates, and access additional recipes from a cloud service. It contains a WLAN module, an LED controller, and a UDP flash drive for the file system. The first partition contains a cs.tar and the second partition contains recipes, cloud settings, and a recovery firmware image.
  • The author wanted to emulate the Cook Key completely. Therefore, they created a custom board with all the pre-signed information. This isn't an issue by itself but it allows for a larger attack surface.
  • The version section is what we're after. This contains three values: date, comment and force_flag, with the first two being arrays. The original usage of this contained a security issue: the firmware could be downgraded by swapping firmware update file sections between versions. A classic replay issue, but this was the past, and we needed a new vulnerability by swapping these individual sections around.
  • When verifying the firmware update file, each section is encrypted using the AES-EAX mode. This combines AES-CTR for encryption and OMAC-based tag for integrity. Each section is RSA-signed, but the nonce and tag are excluded from the signature so that they can be tampered with. We know the encryption algorithm in this case, but we're unable to modify anything because of the signature. Or can we?
  • In AES-EAX decryption, the keystream is generated from the nonce N and the AES key K. PT = C0 XOR K0 in most cases. This can be rearranged to K0' = C0 XOR P0'. If we XOR our plaintext with the ciphertext, we can know what key (nonce starting value) it can be generated with. Since we control the nonce and know the key, we can reverse the encryption process to find a nonce that will match this. Neat!
  • In practice, we control the first date string but everything else will be a garbled mess. The force_flag is something that we want to be set to 1 though. By brute forcing enough keys, it's possible to set this to 1. All of this works because A) the nonce is not verified and B) the header information with the date, comment, and force_flag is a singular encrypted piece of data with nothing else in it. I find it weird that the signature is unique per section, personally.
  • Cryptography, particularly AES-CTR mode, is hard to use properly. With both encryption and signatures, this scheme looked perfect but it gave the author just enough room to workaround it. Awesome post!