Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 50 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ Run CyberChef in a server and provide an API for clients to send [Cyberchef](htt

CyberChef has a useful Node.js API, but sometimes we want to be able to programmatically run CyberChef recipes in languages other than JavaScript. By running this server, you can use CyberChef operations in any language, as long as you can communicate via HTTP.

## Example use
Assuming you've downloaded the repository and are running it locally:
## Examples

### Decode some morse code
```bash
curl -X POST -H "Content-Type:application/json" -d '{"input":"... ---:.-.. --- -. --. --..--:.- -. -..:- .... .- -. -.- ...:..-. --- .-.:.- .-.. .-..:- .... .:..-. .. ... ....", "recipe":{"op":"from morse code", "args": {"wordDelimiter": "Colon"}}}' localhost:3000/bake
```
Expand All @@ -25,8 +26,11 @@ response:


## Features
- **Compatible with recipes saved from CyberChef**.
After using [CyberChef](https://gchq.github.io/CyberChef/) to experiment and find a suitable recipe, the exported recipe JSON can be used to post to the `/bake` endpoint. Just copy/paste it in as your `recipe` property as part of the POST body.
- Compatible with recipes saved from CyberChef.
- After using [CyberChef](https://gchq.github.io/CyberChef/) to experiment and find a suitable recipe, the exported recipe JSON can be used to post to the `/bake` endpoint. Just copy/paste it in as your `recipe` property as part of the POST body.
- Send files as input.
- See [Bake with multipart form data](#bake-files-with-multipart/form-data)



## Installing
Expand Down Expand Up @@ -166,6 +170,48 @@ Response:
}
```

### Bake files with multipart/form data

CyberChef-server will handle `multipart/form` data so you can send files as input data.

The parts are:

|Part|type|Description|
|---|---|--|
|input|file or field|The input to bake. This can be a path to a file, or a field.|
|recipe|file|The JSON file containing the recipe to bake the input with.|
|outputType|field|**Optional.** The [Data Type](https://github.com/gchq/CyberChef/wiki/Adding-a-new-operation#data-types) that you would like the result of the bake to be returned as.|


#### Example
recipe.json
```json
[
"from hexdump",
"gunzip"
]
```
hexdump.txt
```
00000000 1f 8b 08 00 12 bc f3 57 00 ff 0d c7 c1 09 00 20 |.....¼óW.ÿ.ÇÁ.. |
00000010 08 05 d0 55 fe 04 2d d3 04 1f ca 8c 44 21 5b ff |..ÐUþ.-Ó..Ê.D![ÿ|
00000020 60 c7 d7 03 16 be 40 1f 78 4a 3f 09 89 0b 9a 7d |`Ç×..¾@.xJ?....}|
00000030 4e c8 4e 6d 05 1e 01 8b 4c 24 00 00 00 |NÈNm....L$...|
```
Send the hexdump. Specify the output type as string:
```
curl -F "input=@hexdump.txt" -F "recipe=@recipe.json" -F "outputType=string" localhost:3000/bake
```
Response:
```
{
"value": "So long and thanks for all the fish.",
"type": "string"
}
```



### `/magic`

[Find more information about what the Magic operation does here](https://github.com/gchq/CyberChef/wiki/Automatic-detection-of-encoded-data-using-CyberChef-Magic)
Expand Down
11 changes: 6 additions & 5 deletions app.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,17 @@ const app = express();
app.disable("x-powered-by");


if (process.env.NODE_ENV === "production") {
if (process.env.DEBUG) {
app.use(pino({
level: "warn"
level: "debug",
prettyPrint: true
}));
app.use(helmet());
} else {
app.use(pino({
level: "debug",
prettyPrint: true
level: "warn",
prettyPrint: true,
}));
app.use(helmet());
}

app.use(express.json());
Expand Down
7 changes: 3 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
"esm": "^3.2.25",
"express": "~4.16.1",
"express-pino-logger": "^4.0.0",
"formidable": "^1.2.2",
"helmet": "^3.21.1",
"swagger-ui-express": "^4.1.2",
"yaml": "^1.7.2"
Expand Down
98 changes: 96 additions & 2 deletions routes/bake.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,105 @@
import fs from "fs";
import { promisify } from "util";

import { Router } from "express";
const router = Router();
import formidable from "formidable";

import { bake, Dish } from "cyberchef/src/node/index.mjs";

const router = Router();
const readFile = promisify(fs.readFile);

/**
* bakePost
*/
router.post("/", async function bakePost(req, res, next) {
if (req.is("multipart/form-data")) {
bakeMultipartForm(req, res, next);
} else {
bakeBody(req, res, next);
}
});

/**
* bakeMultipartForm
*
* Bake using data from multipart form data.
* recipe must be a JSON file
* input can be a file or a string in a field.
* outputType (optional) must be a string in a field.
*
* Any errors are passed onto `next`.
*/
async function bakeMultipartForm(req, res, next) {
const form = formidable();
form.parse(req, async (err, fields, files) => {
try {

if (err) {
throw err;
}

if (!("recipe" in files)) {
throw new TypeError("Could not find required 'recipe' attachment in multipart form data");
}

let recipe;
try {
const fileContents = await readFile(files.recipe.path);
recipe = JSON.parse(fileContents);
} catch (e) {
throw new TypeError(`Could not parse recipe file: ${e}`);
}

let dish;

if ("input" in files) {
const input = await readFile(files.input.path);
dish = await bake(input, recipe);

} else if ("input" in fields) {
dish = await bake(fields.input, recipe);

} else {
throw new TypeError("Could not find 'input' field in multipart form data.");
}

if (dish) {

if ("outputType" in fields) {
// dish.get takes a typeEnum
let typeEnum = parseInt(fields.outputType, 10);
if (isNaN(typeEnum)) {
typeEnum = Dish.typeEnum(fields.outputType);
}
dish.get(typeEnum);

// Browser should handle files as a download
if (typeEnum === Dish.FILE) {
res.set("Content-Disposition", "attachment");
}
}

res.send({
value: dish.value,
type: Dish.enumLookup(dish.type),
});
}

} catch (e) {
next(e);
}

});
}

/**
* bakeBody
*
* Bake using data from POST body
*/
async function bakeBody(req, res, next) {

try {
if (!req.body.input) {
throw new TypeError("'input' property is required in request body");
Expand All @@ -31,6 +125,6 @@ router.post("/", async function bakePost(req, res, next) {
} catch (e) {
next(e);
}
});
}

export default router;
1 change: 1 addition & 0 deletions sample
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"value":{"data":{"type":"Buffer","data":[84,104,101,32,99]},"name":"unknown","lastModified":1593791384717,"type":"application/unknown"},"type":"File"}
Loading