diff --git a/.vitepress/sidebars/guides.ts b/.vitepress/sidebars/guides.ts index b97afb0..b327ab2 100644 --- a/.vitepress/sidebars/guides.ts +++ b/.vitepress/sidebars/guides.ts @@ -58,6 +58,10 @@ export const guidesSidebar: DefaultTheme.SidebarItem[] = [ text: "Storing Data in SQLite", link: "/guides/components/sqlite", }, + { + text: "Using Findings", + link: "/guides/components/findings", + }, { text: "Using Invalid UTF-8", link: "/guides/components/utf", diff --git a/src/_images/findings.png b/src/_images/findings.png new file mode 100644 index 0000000..b2ee1a5 Binary files /dev/null and b/src/_images/findings.png differ diff --git a/src/guides/components/findings.md b/src/guides/components/findings.md new file mode 100644 index 0000000..c782e2b --- /dev/null +++ b/src/guides/components/findings.md @@ -0,0 +1,75 @@ +# Using Findings + +Any requests or responses can be parsed for notable characteristics based on conditional statements using [Findings](https://docs.caido.io/guides/findings.html). As Caido proxies traffic, if it detects what you are looking for, an alert will be generated to draw your attention. + +## Creating Findings + +To create a Finding, use `sdk.findings.create()`. The `title`, `reporter` and `request` properties are required: + +``` ts +await sdk.findings.create({ + title: "Title", // Label your Finding. + description: "Description", // Add a description (optional). + reporter: "Reporter", // Specify which plugin discovered the Finding. + dedupeKey: `${request.getHost()}-${request.getPath()}`, // Prevents multiple alerts for request with matching characteristics (optional). + request, // The associated request. +}); +``` + +::: tip +The `dedupeKey` can be any value, including [request](https://developer.caido.io/reference/sdks/backend/#request) or [response](https://developer.caido.io/reference/sdks/backend/#response-3) object properties (_besides the body element_). If the value is detected a second time, the Finding will be considered a duplicate and an alert will not be generated. + +``` ts +// Dedupe based on a string. +dedupeKey: "Hello world!" +// Dedupe based on request path and method. +dedupeKey: `${request.getPath()}-${request.getMethod()}` +// Dedupe based on request path, response code and response header. +dedupeKey: `${request.getPath()}-${response.getCode()}-${response.getHeader("Content-Length")}` +``` + +::: + +## Conditional Findings + +You can then set conditions that must be met such as only creating a Finding if the request recieved a 200 response: + +``` ts +import type { DefineAPI, SDK } from "caido:plugin"; +import type { Request, Response } from "caido:utils"; + +export type API = DefineAPI<{ + // No API methods needed for this passive functionality. +}>; + +export function init(sdk: SDK) { + // Listen for intercepted responses. + sdk.events.onInterceptResponse( + async ( + sdk: SDK, + request: Request, + response: Response + ) => { + try { + // Only create Findings for 200 responses. + if (response.getCode() === 200) { + await sdk.findings.create({ + title: `Success Response ${response.getCode()}`, + description: `Request ID: ${request.getId()}\nResponse Code: ${response.getCode()}`, + reporter: "Response Logger Plugin", + request: request, + dedupeKey: `${request.getPath()}-${response.getCode()}` + }); + + sdk.console.log(`Created finding for successful request ${request.getId()}`); + } + } catch (err) { + sdk.console.error(`Error creating finding: ${err}`); + } + }); +} + ``` + +## The Result + +Finding alert. diff --git a/src/guides/components/sqlite.md b/src/guides/components/sqlite.md index 9ba52a4..fe275a2 100644 --- a/src/guides/components/sqlite.md +++ b/src/guides/components/sqlite.md @@ -4,7 +4,7 @@ For storing data generated by your plugin, Caido utilizes SQLite databases. SQLite is a lightweight database engine made available via a small library. It requires no setup, administration or separate server. Instead, all data is stored in a single file. -## Getting a Database Path +## Getting a Database The `sdk.meta.db()` utility provides a SQLite database specific to your plugin. You can view the location of the generated file using `sdk.meta.path()`: @@ -15,6 +15,21 @@ const dataPath = sdk.meta.path(); sdk.console.log(`Database will be stored in: ${dataPath}`); ``` +::: tip +To create a database at different location, use `open`: + +``` ts +import { open } from 'sqlite' + +async function newDatabase() { + const db = await open({ filename: "path/to/database.sqlite" }); + await db.exec("CREATE TABLE test (id INTEGER PRIMARY KEY, name TEXT);"); + await db.exec("INSERT INTO test (name) VALUES ('foo');"); +} +``` + +::: + ## Creating Tables You can run direct SQL statements by supplying them as an arguement to the `.exec()` method: @@ -43,14 +58,14 @@ const insertStatement = await db.prepare("INSERT INTO test (name) VALUES (?)"); const result = await insertStatement.run("Ninjeeter"); ``` -## Retrieving Data - Using `.lastInsertRowid` will return the ID of the last inserted row: ``` ts console.log(`Inserted row with ID: ${result.lastInsertRowid}`); ``` +## Retrieving Data + To select all the columns in a table and return every row, use the wildcard character `*` and the `.all()` method: ``` ts