diff --git a/.vitepress/sidebars/concepts.ts b/.vitepress/sidebars/concepts.ts index 32450de..b78a001 100644 --- a/.vitepress/sidebars/concepts.ts +++ b/.vitepress/sidebars/concepts.ts @@ -10,6 +10,15 @@ export const conceptsSidebar: DefaultTheme.SidebarItem[] = [ }, ], }, + { + text: "Backend", + items: [ + { + text: "Dealing with Binary", + link: "/concepts/backend/binary", + }, + ], + }, { text: "Essentials", items: [ diff --git a/.vitepress/sidebars/guides.ts b/.vitepress/sidebars/guides.ts index c799da1..aea38eb 100644 --- a/.vitepress/sidebars/guides.ts +++ b/.vitepress/sidebars/guides.ts @@ -46,6 +46,10 @@ export const guidesSidebar: DefaultTheme.SidebarItem[] = [ text: "Spawning a Process", link: "/guides/components/spawning_process", }, + { + text: "Using Invalid UTF-8", + link: "/guides/components/utf", + }, ], }, { diff --git a/src/_images/raw_byte_example.png b/src/_images/raw_byte_example.png new file mode 100644 index 0000000..a262dd3 Binary files /dev/null and b/src/_images/raw_byte_example.png differ diff --git a/src/_images/replaced_character.png b/src/_images/replaced_character.png new file mode 100644 index 0000000..b248f1e Binary files /dev/null and b/src/_images/replaced_character.png differ diff --git a/src/_images/rust_conversion.png b/src/_images/rust_conversion.png new file mode 100644 index 0000000..a87f50f Binary files /dev/null and b/src/_images/rust_conversion.png differ diff --git a/src/_images/utf_chart.png b/src/_images/utf_chart.png new file mode 100644 index 0000000..26d51dc Binary files /dev/null and b/src/_images/utf_chart.png differ diff --git a/src/concepts/backend/binary.md b/src/concepts/backend/binary.md new file mode 100644 index 0000000..ff7c5c7 --- /dev/null +++ b/src/concepts/backend/binary.md @@ -0,0 +1,63 @@ +# Dealing with Binary Data + +Caido plugins are written in JavaScript. When creating a plugin that must handle raw bytes, there are some caveats to be aware of as the backend language used by Caido is Rust. + +## UTF-8 Encoding + +For Unicode code points outside of the ASCII range (U+0000 to U+007F) multiple bytes are used. + +When encoding code points into multibyte UTF-8 characters, the bytes are prefixed with specific bit patterns. The first byte prefix indicates how many bytes are used based on the number of leading '1's, and any subsequent (continuation) bytes begin with '10'. + +UTF-8 encoding chart. + +This can be an issue if you want to create a Caido plugin that sends invalid UTF-8 to a target server to see how it is processed. + +For example, if your intention is to append the byte `\x85` (`[1000 0101]`) to a path: + +1. JavaScript strings are encoded using UTF-16 (`[0000 0000 1000 0101]`). +2. When passed to Rust, it is encoded as UTF-8. To produce valid UTF-8, the prefix is added to satisfy the multibyte pattern rules: + - First byte `C2`: `[1100 0010]` (prefix marks start of 2-byte sequence) + - Second byte `85`: `[1000 0101]` (continuation byte) +3. Now, `[1100 0010 1000 0101]` is sent instead of the intended `[1000 0101]`. + +::: tip +You can view this visually by listening for the request with Netcat: + +`ncat -lvnp 5555` + +Unprintable character. + +You can view the bytes by piping `xxd` to display the hex dump: + +`ncat -lvnp 5555 | xxd -g1` + +C2 85 +::: + +## Preserving the Raw Byte + +To avoid the conversion, the raw byte must be used instead. This can be accomplished by: + +1. Taking a request and returning a UInt8Array byte stream: + +`.getPath({raw: true})` + +2. Creating a new array that includes the existing bytes and the added byte: + +`let path = [...spec.getPath({raw: true}), 0x85]` + +3. Setting the path to use this new array: + +`spec.setPath(path)` + +::: tip +Now, `C2` will not be prefixed: + +`ncat -lvnp 5555 | xxd -g1` + +Just \x85. +::: + +## What's next? + +To view the full script and an additional technique that can be used to set raw bytes in Caido plugins, click [here](/guides/components/utf.md). diff --git a/src/concepts/essentials/tooling.md b/src/concepts/essentials/tooling.md index 52e27e2..16263fa 100644 --- a/src/concepts/essentials/tooling.md +++ b/src/concepts/essentials/tooling.md @@ -27,4 +27,4 @@ Once the package is developed, the code is processed by the [Vite](https://vitej The file related to the Vite build tool within the starterkit repository is: -- `vite.config.ts`: This file is a configuration file for customizing the build process. _View the [vite.config.js](https://v2.vitejs.dev/config/) documentation for more information. +- `vite.config.ts`: This file is a configuration file for customizing the build process. _View the [vite.config.js](https://v2.vitejs.dev/config/) documentation for more information._ diff --git a/src/concepts/index.md b/src/concepts/index.md index 30e62fa..088480b 100644 --- a/src/concepts/index.md +++ b/src/concepts/index.md @@ -2,11 +2,15 @@ Here you will find explanations of the core concepts that underpin Caido plugins. +## Backend + +- [Dealing with Binary Data](./backend/binary.md) - Using invalid UTF-8 in Caido. + ## Essentials - [Plugin Architecture](./essentials/package.md) - The structure of a plugin package. -- [Runtime](./essentials/runtime.md) - Javascript runtimes used by plugins. - [Tooling](./essentials/tooling.md) - Tools for the ease of plugin development. +- [Runtime](./essentials/runtime.md) - Javascript runtimes used by plugins. ## Modules diff --git a/src/guides/components/request.md b/src/guides/components/request.md index b4e7c57..9e6c914 100644 --- a/src/guides/components/request.md +++ b/src/guides/components/request.md @@ -138,7 +138,9 @@ By registering a command in the frontend, defining the command to make a backend ``` ts import type { Caido } from "@caido/sdk-frontend"; -import type { API } from "starterkit-plugin-backend"; +import type { API } from "../../backend/src/index"; + +import "./styles/index.css"; export type CaidoSDK = Caido; diff --git a/src/guides/components/utf.md b/src/guides/components/utf.md new file mode 100644 index 0000000..fb8e712 --- /dev/null +++ b/src/guides/components/utf.md @@ -0,0 +1,144 @@ +# Using Invalid UTF-8 + +::: tip +For conceptual information regarding this guide - click [here](/concepts/backend/binary.md). +::: + +## Using a Mutable Proxied Request + +To use invalid UTF-8 in the path of a request that passes through Caido, follow these steps: + +### /packages/backend/src/index.ts + +First import the `SDK`, the interface used to interact with Caido. + +``` ts +import { SDK } from "caido:plugin"; +``` + +Then create a function that takes proxied requests using `onInterceptRequest` and converts them into mutable, un-saved `RequestSpec` objects using the `.toSpec()` method. Next, store the path as a byte array using the spread operator `...`, and append the desired raw byte using `[...spec.getPath({raw: true}), 0x85];`. Update the request with the modified path using `.setPath()` and send the request. + +``` ts +export function init(sdk: SDK) { + sdk.events.onInterceptRequest(async (sdk, request) => { + const spec = request.toSpec(); + let path = [...spec.getPath({raw: true}), 0x85]; + spec.setPath(path); + await sdk.requests.send(spec); + }); +} +``` + +## Using a Newly Created Request + +To use invalid UTF-8 in the path of a `new RequestSpecRaw()` request, follow these steps: + +### /packages/backend/src/index.ts + +First, import the required dependencies. `SDK` is the interface used to interact with Caido. `DefineAPI` is used to structure the API: definining what methods or endpoints are available, the parameters those methods accept and what types of values they return. `RequestSpecRaw` is an object class that is used to create a request in raw byte format. + +``` ts +import { RequestSpecRaw } from "caido:utils"; +import { SDK, DefineAPI } from "caido:plugin"; +``` + +Next, define a function that will convert the path string into an array of bytes. + +``` ts +function stringToUint8Array(str: string): Uint8Array { + const arr = new Uint8Array(str.length); + for (let i = 0; i < str.length; i++) { + arr[i] = str.charCodeAt(i); + } + return arr; +} +``` + +Create an instance of the `RequestSpecRaw` class, by supplying the target URL as the constructor. + +``` ts +async function testSendRequest(sdk: SDK): Promise { + console.log("Testing send request"); + const req = new RequestSpecRaw("http://localhost:5555"); +``` + +Call the `stringToUint8Array` function on the request data to convert it into a byte array. Send the request and if a response is received, print it in plaintext. + +``` ts + const rawRequest = "GET /admin\x85 HTTP/1.1\r\nHost: localhost:5555\r\n\r\n"; + req.setRaw(stringToUint8Array(rawRequest)); + + const res = await sdk.requests.send(req); + console.log(res?.response.getRaw().toText()); +} +``` + +Since we are using a button on the frontend to issue this request, define the `testSendRequest` function as an API call and register it to the backend. + +``` ts +export type API = DefineAPI<{ + testSendRequest: typeof testSendRequest; +}>; + +export function init(sdk: SDK) { + sdk.api.register("testSendRequest", testSendRequest); +} +``` + +::: tip +
+To view the entire frontend script, expand the following: + +``` ts +import type { Caido } from "@caido/sdk-frontend"; +import type { API } from "../../backend/src/index"; + +import "./styles/index.css"; + +export type CaidoSDK = Caido; + +const Commands = { + sending: "my-plugin-page.req", +} as const; + +const sending = async (sdk: CaidoSDK) => { + await sdk.backend.testSendRequest(); +}; + +const createPage = (sdk: CaidoSDK) => { + const requestButton = sdk.ui.button({ + variant: "primary", + label: "Send Request", + }); + + requestButton.addEventListener("click", async () => { + await sending(sdk); + }); + + const bodyContainer = document.createElement("div"); + bodyContainer.appendChild(requestButton); + + const card = sdk.ui.card({ + body: bodyContainer, + + }); + + sdk.navigation.addPage("/my-plugin-page", { + body: card, + }); +}; + +export const init = (sdk: CaidoSDK) => { + createPage(sdk); + sdk.sidebar.registerItem("My Plugin", "/my-plugin-page", { + icon: "fas fa-rocket", + }); + + sdk.commands.register(Commands.sending, { + name: "Send Request", + run: () => sending(sdk), + }); +}; +``` + +
diff --git a/src/guides/distribution/repository.md b/src/guides/distribution/repository.md index 11956f6..d2cfeda 100644 --- a/src/guides/distribution/repository.md +++ b/src/guides/distribution/repository.md @@ -44,7 +44,7 @@ The steps above will create a repository under your own account. If you would like to host your repository under the [caido-community](https://github.com/caido-community) organization instead, you can request a repository on our [Discord server](https://links.caido.io/www-discord). ::: -## 3. Generate a key-pair +## 3. Generate a Key-Pair Plugin packages **must** be digitally signed to be installable in Caido. @@ -76,7 +76,7 @@ The file `private.pem` will contain the following format: -----END PRIVATE KEY----- ``` -### Generate the public key +### Generate the Public Key Run the following command to generate a public key: @@ -94,7 +94,7 @@ The file `public.pem` will contain the following format: -----END PUBLIC KEY----- ``` -## 4. Create a release +## 4. Create a Release Now that your repository and key-pair are ready, it’s time to create a release! diff --git a/src/guides/distribution/store.md b/src/guides/distribution/store.md index 613555c..20fc74b 100644 --- a/src/guides/distribution/store.md +++ b/src/guides/distribution/store.md @@ -55,7 +55,7 @@ Address any required changes and update the GitHub release with the new changes. We will publish the plugin as soon we have verified that all required changes have been addressed. -## Next steps +## What's next? Once your plugin is published, it is time to announce it to the community ✨