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!
gw.conversionsapigateway.com is Meta's own deployment of the application. The fbq JavaScript module includes the collection/processing module in a JavaScript file named capig-events.js. Any vulnerability in this script could inherit the privileges of the site it's on, such as business.facebook.com.capig-events.js only triggers when the page has an opener window. This event includes various pieces of data, such as the message type. If the type is IWL_BOOTSTRAP then the script will check if the pixel_id exists in the list. The event origin is never explicitly verified, meaning that this can be called from any origin.event.origin is used in order to dynamically load JavaScript via <origin>/sdk/<pixel_id>/iwl.js. Since an attacker can control the origin of arbitrary loaded JavaScript, this creates the opportunity for some nasty XSS. This is a classic case of not validating the origin data from a post message call and using the data anyway.same-origin-allow-popups. On the surface, this appears to prevent the issue. However, the security is not evaluated on a single page or a single policy; it is evaluated across all contexts where the code runs.window.name. They found a vulnerability in a third-party application that allowed them to hijack the iframe to interact with the page with a CSP allowed site. Here's the full exploit chain:
capig-events.js script."]} could be used to add attacker-controlled JavaScript in the generated output. In practice, this creates stored XSS within the capig-events.js file. Notably, the payload is served to every user, including Meta-owned domains. This isn't just stored XSS; this is a supply chain attack.map (known as a hidden class) that represents the memory layout of a object. A map holds an array of property descriptors that contain information about each property, as well as the elements and their types. These maps are shared between objects that have the same layout. If a map doesn't exist, then a new one is created. When this happens, the old and new map are related by a transition that occurs to go from one map to another.o1 and o2 having a as an integer, if o1 gets a set to a double then the map in o2 is set to deprecated. This is because SMI (internal small integer) can be represented by a more generalized value. Eventually, the o2 object will be updated to the map of o1 once a property is accessed.PrepareForDataProperty are safe, there are two locations where the type can be updated to a dictionary map instead of the original object map. In CreateDataProperty, it may result in a dictionary map after an update. There are multiple routes to this but the usage of the spread syntax ended up being the most interesting....obj1) and the usage of a property accessor, the function CreateDataProperty will be called while it's being cloned. While this cloning is happening, it's possible to deprecate the map while it's being used for the clone. This allows for the updated map to be a fast map instead of a dictionary map! A type confusion in the JavaScript engine leads to memory corruption now.elements to a large value within the underlying data structure for NameDictionary. By doing this, we get an OOB read for property values that leads to improper object access. Creating a "fake object primitive" is one of the best primitives in JavaScript engine exploitation. So, just arrange the heap in a nice way to create a fake object. invoke or invoke_signed, you provide a set of accounts to be used. In raw Solana, you pass in AccountInfo directly, which is a handle to the in-memory runtime state. In Anchor, you pass in Account<'info, T>., which is a deserialized version of T and acts as a cached value.lamports, are read directly from the runtime state every time. If you reborrow the data, then the underlying bytes will also be updated.T on the AccountInfo is a deserialized snapshot of the account data bytes. At the start of the instruction, Anchor constructs the accounts by deserializing them in a generated handler from the info.data on the account. This means that the data is copied onto the stack/heap as a Rust value and is NOT a live reference to the runtime bytes. At the end of the instruction, Anchor will serialize the data structure and write it back to the runtime.balance on a token account, a token transfer would show the same balance before and after the CPI, regardless of whether the account balance changed.reload(). This will reload the data from storage via re-reading and deserializing the data within AccountInfo.data. The account data is now no longer stale.reload(). It's required when A) a CPI can be used to mutate account data, B) the account needs to be read/validated later and C) you are reading a cached struct. If lamports or native runtime fields are being read, then reloading isn't necessary.lamports didn't need to be reloaded but the data did; now I know!admin:admin. So, with access to a device, it's possible to login to it. Additionally, there is no key signing so it's trivial to reflash the firmware with arbitrary code.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.