From 83d32d34d33d27f9dc447223d1bbe2cd9f4c43cb Mon Sep 17 00:00:00 2001 From: d98762625 Date: Fri, 26 Jun 2020 17:18:13 +0100 Subject: [PATCH 1/5] start accepting multipart form data in /bake --- app.js | 5 ++-- package-lock.json | 7 ++--- package.json | 3 +- routes/bake.js | 64 ++++++++++++++++++++++++++++++++++++++++- test-input.txt | 1 + test/bake.js | 34 ++++++++++++++++++++++ test/test-hex-input.txt | 1 + 7 files changed, 107 insertions(+), 8 deletions(-) create mode 100644 test-input.txt create mode 100644 test/test-hex-input.txt diff --git a/app.js b/app.js index 5d155d5..d570a29 100644 --- a/app.js +++ b/app.js @@ -16,9 +16,10 @@ const app = express(); app.disable("x-powered-by"); -if (process.env.NODE_ENV === "production") { +if (process.env.NODE_ENV === "production" || process.env.TEST_LOGGING) { app.use(pino({ - level: "warn" + level: "warn", + prettyPrint: true, })); app.use(helmet()); } else { diff --git a/package-lock.json b/package-lock.json index 10198c5..bb2a312 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3038,10 +3038,9 @@ } }, "formidable": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.2.1.tgz", - "integrity": "sha512-Fs9VRguL0gqGHkXS5GQiMCr1VhZBxz0JnJs4JmMp/2jL18Fmbzvv7vOFRU+U8TBkHEE/CX1qDXzJplVULgsLeg==", - "dev": true + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.2.2.tgz", + "integrity": "sha512-V8gLm+41I/8kguQ4/o1D3RIHRmhYFG4pnNyonvua+40rqcEmT4+V71yaZ3B457xbbgCsCfjSPi65u/W6vK1U5Q==" }, "forwarded": { "version": "0.1.2", diff --git a/package.json b/package.json index b782940..3cb006f 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,7 @@ "scripts": { "nodemon": "nodemon", "start": "DEBUG=cyberchef-server:server node -r esm ./bin/www", - "test": "mocha -r esm", + "test": "TEST_LOGGING=true mocha -r esm", "prod": "NODE_ENV=production node -r esm ./bin/www", "lint": "./node_modules/.bin/eslint --fix .", "lint-test": "./node_modules/.bin/eslint ." @@ -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" diff --git a/routes/bake.js b/routes/bake.js index 7097f7d..c55df4e 100644 --- a/routes/bake.js +++ b/routes/bake.js @@ -1,4 +1,5 @@ import { Router } from "express"; +import formidable from "formidable"; const router = Router(); import { bake, Dish } from "cyberchef/src/node/index.mjs"; @@ -6,6 +7,67 @@ import { bake, Dish } from "cyberchef/src/node/index.mjs"; * bakePost */ router.post("/", async function bakePost(req, res, next) { + if (req.is("multipart/form-data")) { + bakeMultipartForm(req, res, next); + } else { + bakeBody(req, res, next); + } +}); + +async function bakeMultipartForm(req, res, next) { + const form = formidable(); + + try { + + form.parse(req, async (err, fields, files) => { + + // req.log.warn(`Recipe: ${fields.recipe}`); + // req.log.warn(`Input: ${files.input}`); + // req.log.warn(`Other input: ${fields.input}`); + if (err) { + throw err; + } + + if (!('recipe' in fields)) { + throw new Error("Could not find required 'recipe' field in multipart form data"); + } + + let dish; + + // Case: data is in files.input + if('input' in files) { + // read the contents of the file and use it as an input + res.json({ fields, files }); + } else if ('input' in fields) { + + dish = await bake(fields.input, fields.recipe); + + } else { + throw new Error("Could not find 'input' field in multipart form data."); + } + + // Attempt to translate to another type. Any translation errors + // propagate through to the errorHandler. + if ('outputType' in fields) { + dish.get(req.body.outputType); + } + + if (dish) { + res.send({ + value: dish.value, + type: Dish.enumLookup(dish.type), + }); + } + + }); + + } catch (e) { + next(e); + } +} + +async function bakeBody(req, res, next) { + try { if (!req.body.input) { throw new TypeError("'input' property is required in request body"); @@ -31,6 +93,6 @@ router.post("/", async function bakePost(req, res, next) { } catch (e) { next(e); } -}); +} export default router; diff --git a/test-input.txt b/test-input.txt new file mode 100644 index 0000000..dfce2f7 --- /dev/null +++ b/test-input.txt @@ -0,0 +1 @@ +68 65 6c 6c 6f diff --git a/test/bake.js b/test/bake.js index 7a25a83..48364f0 100644 --- a/test/bake.js +++ b/test/bake.js @@ -208,3 +208,37 @@ describe("POST /bake", function() { }); }); + +describe("POST /bake files", function () { + it("should take input as a multipart file upload", (done) => { + request(app) + .post("/bake") + .field("recipe", "from hex") + .attach("input", "test/test-hex-input.txt") + .expect(200, done); + }); + + it("should take input as a multipart field", (done) => { + request(app) + .post("/bake") + .field("recipe", "to morse code") + .field("input", "The crowds stared around wildly") + .expect(200, done); + }); + + it("should perform a simple recipe with input as a form field", (done) => { + request(app) + .post("/bake") + .field("recipe", "to morse code") + .field("input", "The crowds stared around wildly") + .expect(200) + .expect({ + value: `- .... . +-.-. .-. --- .-- -.. ... +... - .- .-. . -.. +.- .-. --- ..- -. -.. +.-- .. .-.. -.. .-.. -.--`, + type: "string", + }, done); + }) +}); diff --git a/test/test-hex-input.txt b/test/test-hex-input.txt new file mode 100644 index 0000000..3346d74 --- /dev/null +++ b/test/test-hex-input.txt @@ -0,0 +1 @@ +54 68 65 20 63 72 6f 77 64 73 20 73 74 61 72 65 64 20 61 72 6f 75 6e 64 20 77 69 6c 64 6c 79 From f9ee7fe9ba04d36c9efdb867f93b579d9bbe5c3e Mon Sep 17 00:00:00 2001 From: d98762625 Date: Fri, 3 Jul 2020 15:10:39 +0100 Subject: [PATCH 2/5] bake file: recipe must be file, add more test cases --- app.js | 12 +- package.json | 4 +- routes/bake.js | 83 ++++++--- test/bake.js | 166 +++++++++++++++++- test/test_data/chef.png | Bin 0 -> 1154 bytes .../hex_input.txt} | 0 test/test_data/recipe_detect_file_type.json | 3 + test/test_data/recipe_invalid_json.json | 1 + test/test_data/recipe_multi_op_invalid.json | 4 + test/test_data/recipe_multi_op_with_args.json | 22 +++ test/test_data/recipe_single_op.json | 3 + test/test_data/recipe_single_op_invalid.json | 1 + test/test_data/recipe_take_bytes.json | 3 + test/test_data/recipe_to_morse.json | 3 + test/test_data/text_input.txt | 1 + 15 files changed, 263 insertions(+), 43 deletions(-) create mode 100644 test/test_data/chef.png rename test/{test-hex-input.txt => test_data/hex_input.txt} (100%) create mode 100644 test/test_data/recipe_detect_file_type.json create mode 100644 test/test_data/recipe_invalid_json.json create mode 100644 test/test_data/recipe_multi_op_invalid.json create mode 100644 test/test_data/recipe_multi_op_with_args.json create mode 100644 test/test_data/recipe_single_op.json create mode 100644 test/test_data/recipe_single_op_invalid.json create mode 100644 test/test_data/recipe_take_bytes.json create mode 100644 test/test_data/recipe_to_morse.json create mode 100644 test/test_data/text_input.txt diff --git a/app.js b/app.js index d570a29..3209561 100644 --- a/app.js +++ b/app.js @@ -16,17 +16,17 @@ const app = express(); app.disable("x-powered-by"); -if (process.env.NODE_ENV === "production" || process.env.TEST_LOGGING) { +if (process.env.DEBUG) { app.use(pino({ - level: "warn", - prettyPrint: true, + 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()); diff --git a/package.json b/package.json index 3cb006f..b47ac5d 100644 --- a/package.json +++ b/package.json @@ -26,8 +26,8 @@ "scripts": { "nodemon": "nodemon", "start": "DEBUG=cyberchef-server:server node -r esm ./bin/www", - "test": "TEST_LOGGING=true mocha -r esm", - "prod": "NODE_ENV=production node -r esm ./bin/www", + "test": "mocha -r esm", + "prod": "node -r esm ./bin/www", "lint": "./node_modules/.bin/eslint --fix .", "lint-test": "./node_modules/.bin/eslint ." }, diff --git a/routes/bake.js b/routes/bake.js index c55df4e..1d56f18 100644 --- a/routes/bake.js +++ b/routes/bake.js @@ -1,8 +1,14 @@ +import fs from "fs"; +import { promisify } from "util"; + import { Router } from "express"; import formidable from "formidable"; -const router = Router(); + import { bake, Dish } from "cyberchef/src/node/index.mjs"; +const router = Router(); +const readFile = promisify(fs.readFile); + /** * bakePost */ @@ -14,58 +20,81 @@ router.post("/", async function bakePost(req, res, next) { } }); +/** + * bakeMultipartForm + * @param {*} req + * @param {*} res + * @param {*} next + */ async function bakeMultipartForm(req, res, next) { const form = formidable(); + form.parse(req, async (err, fields, files) => { + try { - try { - - form.parse(req, async (err, fields, files) => { - - // req.log.warn(`Recipe: ${fields.recipe}`); - // req.log.warn(`Input: ${files.input}`); - // req.log.warn(`Other input: ${fields.input}`); if (err) { throw err; } - if (!('recipe' in fields)) { - throw new Error("Could not find required 'recipe' field in multipart form data"); + 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; - // Case: data is in files.input - if('input' in files) { - // read the contents of the file and use it as an input - res.json({ fields, files }); - } else if ('input' in fields) { + if ("input" in files) { + const input = await readFile(files.input.path); + dish = await bake(input, recipe); - dish = await bake(fields.input, fields.recipe); + } else if ("input" in fields) { + dish = await bake(fields.input, recipe); } else { - throw new Error("Could not find 'input' field in multipart form data."); - } - - // Attempt to translate to another type. Any translation errors - // propagate through to the errorHandler. - if ('outputType' in fields) { - dish.get(req.body.outputType); + 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); + } - } catch (e) { - next(e); - } + }); } +/** + * bakeBody + * @param {} req + * @param {*} res + * @param {*} next + */ async function bakeBody(req, res, next) { try { diff --git a/test/bake.js b/test/bake.js index 48364f0..7086c81 100644 --- a/test/bake.js +++ b/test/bake.js @@ -1,6 +1,11 @@ +import assert from "assert"; + import request from "supertest"; + import app from "../app"; +const testDataPath = "test/test_data"; + describe("GET /bake", function() { it("doesnt exist", function(done) { @@ -210,26 +215,64 @@ describe("POST /bake", function() { }); describe("POST /bake files", function () { - it("should take input as a multipart file upload", (done) => { + it("should complain if you do not send a recipe with your input", (done) => { request(app) .post("/bake") - .field("recipe", "from hex") - .attach("input", "test/test-hex-input.txt") - .expect(200, done); + .attach("input", `${testDataPath}/hex_input.txt`) + .expect(400, done); + }); + + it("should complain if it cannot find input field in form", (done) => { + request(app) + .post("/bake") + .attach("recipe", `${testDataPath}/recipe_single_op.json`) + .expect(400, done); + }); + + it("should complain if it cannot find a matching operation - single op recipe - input field", (done) => { + request(app) + .post("/bake") + .attach("recipe", `${testDataPath}/recipe_single_op_invalid.json`) + .field("input", "testing - one, two, three") + .expect(400, done); + }); + + it("should complain if it cannot find a matching operation - multi-op recipe - input field", (done) => { + request(app) + .post("/bake") + .attach("recipe", `${testDataPath}/recipe_multi_op_invalid.json`) + .field("input", "testing - one, two, three") + .expect(400, done); + }); + + it("should complain if the recipe file is not valid JSON", (done) => { + request(app) + .post("/bake") + .attach("recipe", `${testDataPath}/recipe_invalid_json.json`) + .field("input", "testing - one, two, three") + .expect(400, done); }); it("should take input as a multipart field", (done) => { request(app) .post("/bake") - .field("recipe", "to morse code") + .attach("recipe", `${testDataPath}/recipe_single_op.json`) .field("input", "The crowds stared around wildly") .expect(200, done); }); - it("should perform a simple recipe with input as a form field", (done) => { + it("should take input as a multipart file upload", (done) => { + request(app) + .post("/bake") + .attach("recipe", `${testDataPath}/recipe_single_op.json`) + .attach("input", `${testDataPath}/hex_input.txt`) + .expect(200, done); + }); + + it("should perform a simple recipe with input as a field", (done) => { request(app) .post("/bake") - .field("recipe", "to morse code") + .attach("recipe", `${testDataPath}/recipe_to_morse.json`) .field("input", "The crowds stared around wildly") .expect(200) .expect({ @@ -240,5 +283,112 @@ describe("POST /bake files", function () { .-- .. .-.. -.. .-.. -.--`, type: "string", }, done); - }) + }); + + it("should perform a simple recipe with input as a file", (done) => { + request(app) + .post("/bake") + .attach("recipe", `${testDataPath}/recipe_to_morse.json`) + .attach("input", `${testDataPath}/text_input.txt`) + .expect(200) + .expect({ + value: `- .... . +-.-. .-. --- .-- -.. ... +... - .- .-. . -.. +.- .-. --- ..- -. -.. +.-- .. .-.. -.. .-.. -.--`, + type: "string", + }, done); + }); + + it("should bake a multi-op recipe with arguments", (done) => { + request(app) + .post("/bake") + .attach("recipe", `${testDataPath}/recipe_multi_op_with_args.json`) + .field("input", "some input") + .expect(200) + .expect({ + value: "begin_something_anananaaaaak_da_aaak_da_aaaaananaaaaaaan_da_aaaaaaanan_da_aaak_end_something", + type: "string", + }, done); + }); + + it("should handle image files as input", (done) => { + request(app) + .post("/bake") + .attach("recipe", `${testDataPath}/recipe_detect_file_type.json`) + .attach("input", `${testDataPath}/chef.png`) + .expect(200) + .expect({ + type: "string", + value: `File type: Portable Network Graphics image +Extension: png +MIME type: image/png +` + }, done); + }); + + it("should optionally transform the output to outputType", (done) => { + request(app) + .post("/bake") + .attach("recipe", `${testDataPath}/recipe_take_bytes.json`) + .attach("input", `${testDataPath}/text_input.txt`) + .field("outputType", "string") + .expect(200) + .expect({ + type: "string", + value: "The c" + }, done); + }); + + it("should optionally transform the output to outputType, when outputType is an enum", (done) => { + request(app) + .post("/bake") + .attach("recipe", `${testDataPath}/recipe_take_bytes.json`) + .attach("input", `${testDataPath}/text_input.txt`) + .field("outputType", 1) + .expect(200) + .expect({ + type: "string", + value: "The c" + }, done); + }); + + it("should set content-disposition header if File outputType is requested", (done) => { + request(app) + .post("/bake") + .attach("recipe", `${testDataPath}/recipe_take_bytes.json`) + .attach("input", `${testDataPath}/text_input.txt`) + .field("outputType", "file") + .expect(200) + .expect("Content-Disposition", "attachment") + .end((err, res) => { + if (err) return done(err); + assert.deepEqual(res.body.type, "File"); + assert.deepEqual(res.body.value.name, "unknown"); + assert.deepEqual(res.body.value.data.data, [84, 104, 101, 32, 99]); + return done(); + }); + // /** + // * Not sure this is how we want to present this. Need to validate with + // * web page response. + // */ + // type: "File", + // value: { + // data: { + // data: [ + // 84, + // 104, + // 101, + // 32, + // 99, + // ], + // type: "Buffer" + // }, + // lastModified: 1593784118511, + // name: "unknown", + // type: "application/unknown", + // } + }); + }); diff --git a/test/test_data/chef.png b/test/test_data/chef.png new file mode 100644 index 0000000000000000000000000000000000000000..5880f33568883f1ff79f80781006cce3b8effe52 GIT binary patch literal 1154 zcmV-|1bzF7P)7_C+d zD7K)WAUi!heNIV9iT>ALf89m?UJ&DqjEoN|D=T+5G&E@I>gtM7YOB=>L>vGojYh+g z&nlJb^mW%=cV|*kQczY_R$pf`B9_TyLom|I#EdUK0AFQgW$g&W5M^d&##6gv)(DI#&RQa2E^|IeVUb^2vM1IMM=?mXR77tSpRaES&0H{m(4 zSbXRH{rmke`abmOcM(9LP#nYmyOG}NDh0Ti7C@;~9>xE=UMK)ba&qzyc!001TemWE z=gtMbrc9ZVF65oydYw+!U^E(mFT9X2X=!P|S4v8X5&!R6mz`g_|K=O!1iwATn7P?z z%H9gyRbToOtC7iUyLRnj=g*(tL$hbkKKI2JUwFEl`o;b)Y}PJ_f{JDFJSD(cT?tex zmCWwlyCEPT0DOIYd&tMf=d!!I`}0n_$Gr34qdTHrxsurT12C-U-)SzJ7XwF&a{$`f z>;QNtgn66ifm@s#XKn`y3JNNC_uY5zb#rrjMv8|TV7BKoQ9GkvfsJ7=!4b6#&R1xm zUzk74oG~3rGBe>qeLeh~n8@bjBMMaIF0$3gR!fIm92jFCDX@OG;g&kn^>ea-whj91TdwYBP z5rF9E=qY{1*8wU1K>joi?s&=Kol1jx00#^Gu@wd+Y$MSjStXWY-=l2%H?c! zb~Z8P13-Lyydf|!Fc{5uef{;86^Ruz)W6Vo{b#y0EB2f Ucj4)+$N&HU07*qoM6N<$f}sj4F8}}l literal 0 HcmV?d00001 diff --git a/test/test-hex-input.txt b/test/test_data/hex_input.txt similarity index 100% rename from test/test-hex-input.txt rename to test/test_data/hex_input.txt diff --git a/test/test_data/recipe_detect_file_type.json b/test/test_data/recipe_detect_file_type.json new file mode 100644 index 0000000..8617823 --- /dev/null +++ b/test/test_data/recipe_detect_file_type.json @@ -0,0 +1,3 @@ +[ + "detect file type" +] diff --git a/test/test_data/recipe_invalid_json.json b/test/test_data/recipe_invalid_json.json new file mode 100644 index 0000000..9585b22 --- /dev/null +++ b/test/test_data/recipe_invalid_json.json @@ -0,0 +1 @@ +{ 1234567 diff --git a/test/test_data/recipe_multi_op_invalid.json b/test/test_data/recipe_multi_op_invalid.json new file mode 100644 index 0000000..2e5caaf --- /dev/null +++ b/test/test_data/recipe_multi_op_invalid.json @@ -0,0 +1,4 @@ +[ + "to hex", + "not a valid operation" +] diff --git a/test/test_data/recipe_multi_op_with_args.json b/test/test_data/recipe_multi_op_with_args.json new file mode 100644 index 0000000..baca24b --- /dev/null +++ b/test/test_data/recipe_multi_op_with_args.json @@ -0,0 +1,22 @@ +[ + { + "op": "To Morse Code", + "args": [ + "Dash/Dot", + "Backslash", + "Comma" + ] + }, + { + "op": "Hex to PEM", + "args": [ + "SOMETHING" + ] + }, + { + "op": "To Snake case", + "args": [ + false + ] + } +] diff --git a/test/test_data/recipe_single_op.json b/test/test_data/recipe_single_op.json new file mode 100644 index 0000000..34db753 --- /dev/null +++ b/test/test_data/recipe_single_op.json @@ -0,0 +1,3 @@ +[ + "from hex" +] diff --git a/test/test_data/recipe_single_op_invalid.json b/test/test_data/recipe_single_op_invalid.json new file mode 100644 index 0000000..a21dd9f --- /dev/null +++ b/test/test_data/recipe_single_op_invalid.json @@ -0,0 +1 @@ +["not an operation"] diff --git a/test/test_data/recipe_take_bytes.json b/test/test_data/recipe_take_bytes.json new file mode 100644 index 0000000..5549303 --- /dev/null +++ b/test/test_data/recipe_take_bytes.json @@ -0,0 +1,3 @@ +[ + "take bytes" +] diff --git a/test/test_data/recipe_to_morse.json b/test/test_data/recipe_to_morse.json new file mode 100644 index 0000000..97ea3af --- /dev/null +++ b/test/test_data/recipe_to_morse.json @@ -0,0 +1,3 @@ +[ + "to morse code" +] diff --git a/test/test_data/text_input.txt b/test/test_data/text_input.txt new file mode 100644 index 0000000..7cd2fac --- /dev/null +++ b/test/test_data/text_input.txt @@ -0,0 +1 @@ +The crowds stared around wildly \ No newline at end of file From 81c7906575d3d2552c61ee337f18f690aa04395a Mon Sep 17 00:00:00 2001 From: d98762625 Date: Fri, 3 Jul 2020 15:16:07 +0100 Subject: [PATCH 3/5] remove old test file --- test-input.txt | 1 - 1 file changed, 1 deletion(-) delete mode 100644 test-input.txt diff --git a/test-input.txt b/test-input.txt deleted file mode 100644 index dfce2f7..0000000 --- a/test-input.txt +++ /dev/null @@ -1 +0,0 @@ -68 65 6c 6c 6f From e2265b3dd44611dc75d55745cac4b080cd88a341 Mon Sep 17 00:00:00 2001 From: d98762625 Date: Fri, 3 Jul 2020 16:39:58 +0100 Subject: [PATCH 4/5] update readme for accepting files --- README.md | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 50 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index bf85898..768082f 100644 --- a/README.md +++ b/README.md @@ -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 ``` @@ -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 @@ -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) From a948536fe2e326d5f5c9e6d42ea702c32fe0db54 Mon Sep 17 00:00:00 2001 From: d98762625 Date: Fri, 3 Jul 2020 17:01:20 +0100 Subject: [PATCH 5/5] tidy comments --- package.json | 2 +- routes/bake.js | 15 +++++++++------ sample | 1 + test/bake.js | 20 -------------------- 4 files changed, 11 insertions(+), 27 deletions(-) create mode 100644 sample diff --git a/package.json b/package.json index b47ac5d..fc79e0c 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,7 @@ "nodemon": "nodemon", "start": "DEBUG=cyberchef-server:server node -r esm ./bin/www", "test": "mocha -r esm", - "prod": "node -r esm ./bin/www", + "prod": "NODE_ENV=production node -r esm ./bin/www", "lint": "./node_modules/.bin/eslint --fix .", "lint-test": "./node_modules/.bin/eslint ." }, diff --git a/routes/bake.js b/routes/bake.js index 1d56f18..a62254c 100644 --- a/routes/bake.js +++ b/routes/bake.js @@ -22,9 +22,13 @@ router.post("/", async function bakePost(req, res, next) { /** * bakeMultipartForm - * @param {*} req - * @param {*} res - * @param {*} next + * + * 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(); @@ -91,9 +95,8 @@ async function bakeMultipartForm(req, res, next) { /** * bakeBody - * @param {} req - * @param {*} res - * @param {*} next + * + * Bake using data from POST body */ async function bakeBody(req, res, next) { diff --git a/sample b/sample new file mode 100644 index 0000000..7ebcf7b --- /dev/null +++ b/sample @@ -0,0 +1 @@ +{"value":{"data":{"type":"Buffer","data":[84,104,101,32,99]},"name":"unknown","lastModified":1593791384717,"type":"application/unknown"},"type":"File"} \ No newline at end of file diff --git a/test/bake.js b/test/bake.js index 7086c81..58838b1 100644 --- a/test/bake.js +++ b/test/bake.js @@ -369,26 +369,6 @@ MIME type: image/png assert.deepEqual(res.body.value.data.data, [84, 104, 101, 32, 99]); return done(); }); - // /** - // * Not sure this is how we want to present this. Need to validate with - // * web page response. - // */ - // type: "File", - // value: { - // data: { - // data: [ - // 84, - // 104, - // 101, - // 32, - // 99, - // ], - // type: "Buffer" - // }, - // lastModified: 1593784118511, - // name: "unknown", - // type: "application/unknown", - // } }); });