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!
GET / returns the notes, with a search parameter in query, and a note can be created via POST /new, which is vulnerable to CSRF. One of the notes on the bot contains the flag, and it's your job to steal it from another JavaScript tab. The timeout is 60s but there's no HTML injection, no sorting, no CSS and no other loaded resources.ETag header is an HTTP response header that acts as a unique identifier for a specific version of a web resource. It's useful for caching data more effectively. Mozilla docs. The application sets the tag via jshttp/etag, which formats the content size in hex as a prefix. The ETag length can differ by 1 depending on the response size and is controlled because of the CSRF bug.ETag header, subsequent requests will use the same URL with the If-None-Match header containing the ETag. Many web servers have a maximum size for request headers and will output a 431 Request Header Fields Too Large error if exceeded.If-None-Match byte can be the difference between a 200 Ok and a 431. Using the search, this can be abused to check whether the searched bytes match or not, cross-origin. But, can you see this? Cross-origin status codes are opaque!history. If the same URL is accessed twice in a row but the second navigation fails, only one history event is added. If they both succeed, then two events are added. By looking at the number of entries in the page history, we can determine whether the navigation succeeded or failed.history.length of the frame to see whether the second navigation occurred or not.AdditionalJavaArguments inside of it, a type and a Name that looked like Java functions.origin, it's allowed. The message contained no origin check, no authorization mechanism, or anything else. So, they tried connecting from a Websocket with a bogus origin header, and it worked. This means the application can be accessed from any website the user visits. Neat!minecraftTaskLaunchInstance. It contains a parameter for arbitrary additional Java arguments that is used to start the game. Another interesting one is createModpack. This is creating a modpack on the user's system. This is required because we need a valid modpack to call minecraftTaskLaunchInstance with.-XX:MaxMetaspaceSize=16m; this limits the JVM's memory space. Since the JVM crashes, it will call an out-of-memory handler, which can be anything. The second flag is -XX:OnOutOfMemoryError="cmd.exe /c calc", that gets triggered on crash.CurseAgent doesn't bind its WebSocket server to a fixed port. It listens to a randomly assigned local port whenever the launcher starts. So they wrote a JavaScript scanner that scans 16K ports to find this. PublicKey used in the exploit, can be created with user code. So, the attacker declared a value as a PublicKey and then encoded a resource-containing structure as a value in its place. This allowed for smuggling a resource inside a struct context.add(), the static types being used on the call were not validated to match the contract itself. This allowed turning a static public key into a resource. An absolutely crazy chain of three bugs!constructor directly, the runtime denies access to this during React rendering. This broke the exploit path. Even with a WAF bypass, this runtime check would remove all exploitation. :constructor with a colon. By finding another gadget for property access that used property access and string interpolation, it was possible to use constructor instead. This shows the power of slight deviations in the original exploit.$@0 allows for data to be streamed later on an as-needed basis, which is a promise. The twitter post linked is great but the wiz article has a little bit of an easier payload to follow.status to be resolved_model, an attacker can express the internal state of the application for React..then() is considered a Promise. Adding then to the internal object treats it as a promise and executes the provided then function. This gets executed as a promise because of the previously used $@0. So, chunk 1 triggers the resolution process for the promise in chunk 0, which causes the vulnerability. The then contains $1:then. response._formData.get(response._prefix + obj). By overwriting the get function of the response objects with another function and controlling the prefix, we can make an arbitrary function call within the context of React. By using the constructor() as the get and JavaScript code as the parameter, we get arbitrary execution.eval() and os.system().init and init_if_needed. init requires for account creation to occur otherwise it exists. init_if_needed will always run but will create the account if it doesn't already exist.init is required? Associated Token Accounts (ATAs) are 100% permissionless to create. So, using init with ATAs is a bad idea. callback() parameter in the encrypted payload that triggered an eval(). So, if you could somehow get valid data to be decrypted, you'd get an RCE.AES-256-CBC under the hood, where the key is a value known as the salt. Despite a change several years ago to fix this, legacy salt generation was kept around via the uniqid() command, which simply returns the machine's microseconds since installation. This timestamp can be leaked by inspecting the default categories in the setup.hashId that is computed via md5(salt). Since all the information is public except the salt, we can compute hashes with different salts until we find the matching one. This allows us to leak salt.encrypt_decrypt() function uses the system root path as the IV. Either via educated guesses or using the the leaked posterPortraitPath from another API, this can be figured out. With the salt known and the RCE path identified, we can execute RCE on the machine. Pretty neat!eval() callback remained. CVE-2023-46746 by directly calling a PATCH request to store the endpoint used for a webhook. There's also a TOCTOU bug in the verification. Now, we can set the domain to be localhost.'posthog_table' into 'posthog_table\'', but the backslash is a literal here. So, we now have SQL injection on the incoming query that can be used to write on a GET request as well.;END, which makes it read-only. Next, execute a command using cmd_exec and put the results into a payload. An important trick is to use $$ instead of single quotes within the payload. Otherwise, the single quotes would have been escaped once again.