UNPKG

padding-oracle-attacker

Version:

CLI tool and library to execute padding oracle attacks easily

223 lines 11.9 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const chalk_1 = __importDefault(require("chalk")); const wrap_ansi_1 = __importDefault(require("wrap-ansi")); const log_update_1 = __importDefault(require("log-update")); const ansi_styles_1 = __importDefault(require("ansi-styles")); const pretty_bytes_1 = __importDefault(require("pretty-bytes")); const table_1 = require("table"); const util_1 = require("./util"); const { isTTY } = process.stdout; function getBar(percent, barSize) { const barComplete = '█'.repeat(percent * barSize); const barIncomplete = '░'.repeat(barSize - barComplete.length); return { barComplete, barIncomplete }; } // eslint-disable-next-line @typescript-eslint/no-explicit-any const aStyles = ansi_styles_1.default; function colorizeHex({ cipherHex, totalSize, foundOffsets, currentByteColor, currentByteHex, currentByteOffset }) { let result = ''; let lastColor = ''; for (let i = 0; i < totalSize; i++) { const isCurrentByte = currentByteOffset === i; let color = 'gray'; if (isCurrentByte) color = currentByteColor; else if (foundOffsets.has(i) || i >= (totalSize - 16)) color = 'green'; const byteHex = cipherHex.slice(i * 2, i * 2 + 2); if (lastColor !== color) { result += (lastColor ? aStyles[lastColor].close : '') + aStyles[color].open; lastColor = color; } result += isCurrentByte ? currentByteHex : byteHex; } result += aStyles[lastColor].close; return result; } const log = isTTY ? log_update_1.default : console.log; const wrapAndSplit = (text, size) => wrap_ansi_1.default(text, size, { hard: true }).split('\n'); function logProgress({ plaintext, ciphertext, foundOffsets, blockSize, blockI, byteI, byte, decryptionSuccess, networkStats, startFromFirstBlock, isCacheEnabled }) { const cipherHex = ciphertext.toString('hex'); const currentByteHex = byte.toString(16).padStart(2, '0'); const start = blockSize * blockI; const grayEnd = 2 * (start + byteI); const greenStart = 2 * (start + byteI + 1); const currentByteColor = decryptionSuccess ? 'green' : 'yellow'; const colorized = startFromFirstBlock ? colorizeHex({ cipherHex, totalSize: ciphertext.length, foundOffsets, currentByteColor, currentByteHex, currentByteOffset: start + byteI }) : [ chalk_1.default.gray(cipherHex.slice(0, grayEnd)), chalk_1.default[currentByteColor](currentByteHex), chalk_1.default.green(cipherHex.slice(greenStart)) ].join(''); const printable = util_1.getPrintable(plaintext.toString('utf8')); const plainHex = plaintext.toString('hex'); const plainHexColorized = chalk_1.default.gray(plainHex.slice(0, grayEnd)) + plainHex.slice(grayEnd); const plainHexSplit = wrapAndSplit(plainHexColorized, blockSize * 2); const percent = (foundOffsets.size + blockSize) / ciphertext.length; const mapFunc = (ciphertextBlockHex, i) => { const xStart = (i - 1) * blockSize; const plain = printable.slice(xStart, xStart + blockSize); const hex = plainHexSplit[i - 1] || ''; return `${String(i + 1).padStart(2)}. ${ciphertextBlockHex} ${hex} ${plain}`; }; const cipherplain = wrapAndSplit(colorized, blockSize * 2) .map(mapFunc) .join('\n'); const { barComplete, barIncomplete } = getBar(percent, blockSize * 4 + 5); log(cipherplain, '\n' + barComplete + barIncomplete, (percent * 100).toFixed(1).padStart(5) + '%', `${blockI + 1}x${byteI + 1}`.padStart(5), `${byte}/256`.padStart(7), chalk_1.default `\n\n{yellow ${String(networkStats.count).padStart(4)}} total network requests`, chalk_1.default `| last request took {yellow ${String(networkStats.lastDownloadTime).padStart(4)}ms}`, chalk_1.default `| {yellow ${pretty_bytes_1.default(networkStats.bytesDown).padStart(7)}} downloaded`, chalk_1.default `| {yellow ${pretty_bytes_1.default(networkStats.bytesUp).padStart(7)}} uploaded`, isCacheEnabled ? '' : chalk_1.default `| cache: {gray disabled}`); } exports.logProgress = logProgress; function logWarning(txt) { log_update_1.default.done(); console.error(chalk_1.default ` {yellow.underline Warning}: ${txt} `); } exports.logWarning = logWarning; const stringifyHeaders = (headers) => Object.entries(headers).map(([k, v]) => `${chalk_1.default.gray(k.padEnd(20))}: ${v}`).join('\n'); function logRequest(request) { console.log(request.statusCode, request.url); console.log(stringifyHeaders(request.headers)); console.log(); const size = request.body.length; if (size > 1024) { console.log(request.body.slice(0, 1024), chalk_1.default.gray(`[...and ${(size - 1024).toLocaleString()} more bytes]`)); } else { console.log(request.body); } } const logHeader = (h) => console.log(chalk_1.default.blue(`---${h}---`)); exports.decryption = { async logStart({ blockCount, totalSize, initialRequest: initialRequestPromise, decryptionSuccess }) { console.log(chalk_1.default.bold.white('~~~DECRYPTING~~~')); console.log('total bytes:', chalk_1.default.yellow(String(totalSize)), '|', 'blocks:', chalk_1.default.yellow(String(blockCount - 1))); console.log(); logHeader('making request with original ciphertext'); const initialRequest = await initialRequestPromise; if (initialRequest) { if (!await decryptionSuccess) { logWarning(`Decryption failed for initial request with original ciphertext. The parameter you provided for determining decryption success seems to be incorrect.`); } logRequest(initialRequest); } console.log(); }, logCompletion({ foundBytes, interBytes }) { log_update_1.default.done(); console.log(); logHeader('plaintext printable bytes in utf8'); console.log(util_1.getPrintable(foundBytes.toString('utf8'))); console.log(); logHeader('plaintext bytes in hex'); console.log(foundBytes.toString('hex')); console.log(); logHeader('intermediate bytes in hex'); console.log(interBytes.toString('hex')); console.log(); } }; exports.encryption = { logStart({ blockCount, totalSize }) { console.log(chalk_1.default.bold.white('~~~ENCRYPTING~~~')); console.log('total bytes:', chalk_1.default.yellow(String(totalSize)), '|', 'blocks:', chalk_1.default.yellow(String(blockCount - 1))); console.log(); }, logCompletion({ foundBytes, interBytes, finalRequest }) { log_update_1.default.done(); console.log(); logHeader('ciphertext bytes in hex'); console.log(foundBytes.toString('hex')); console.log(); logHeader('intermediate bytes in hex'); console.log(interBytes.toString('hex')); console.log(); if (!finalRequest) return; logHeader('final http request'); logRequest(finalRequest); console.log(); } }; exports.analysis = { logStart({ url, blockSize, tmpDirPath }) { console.log(chalk_1.default.bold.white('~~~RESPONSE ANALYSIS~~~')); console.log('url:', chalk_1.default.yellow(url), '|', 'block size:', chalk_1.default.yellow(String(blockSize))); console.log('will make 256 network requests and analyze responses'); if (tmpDirPath) console.log('responses will be saved to', chalk_1.default.underline(tmpDirPath)); console.log(); }, logCompletion({ responsesTable, statusCodeFreq, bodyLengthFreq, tmpDirPath, networkStats, isCacheEnabled }) { const tableConfig = { border: table_1.getBorderCharacters('void'), columnDefault: { paddingLeft: 0, paddingRight: 2 }, singleLine: true }; const secondTableConfig = { border: table_1.getBorderCharacters('honeywell'), columnDefault: { alignment: 'right', paddingLeft: 2, paddingRight: 2 }, singleLine: true }; const headerRows = ['Byte', 'Status Code', 'Content Length'].map(x => chalk_1.default.gray(x)); const scFreqEntries = Object.entries(statusCodeFreq); const clFreqEntries = Object.entries(bodyLengthFreq); const tabled = table_1.table([headerRows, ...responsesTable], tableConfig); logHeader('responses'); console.log(tabled); logHeader('status code frequencies'); console.log(table_1.table(scFreqEntries.map(([k, v]) => [k, v + ' time(s)']), secondTableConfig)); logHeader('content length frequencies'); console.log(table_1.table(clFreqEntries.map(([k, v]) => [k, v + ' time(s)']), secondTableConfig)); logHeader('network stats'); console.log(chalk_1.default `{yellow ${String(networkStats.count)}} total network requests`, chalk_1.default `| last request took {yellow ${String(networkStats.lastDownloadTime)}ms}`, chalk_1.default `| {yellow ${pretty_bytes_1.default(networkStats.bytesDown)}} downloaded`, chalk_1.default `| {yellow ${pretty_bytes_1.default(networkStats.bytesUp)}} uploaded`, isCacheEnabled ? '' : chalk_1.default `| cache: {gray disabled}`, '\n'); if (tmpDirPath) { logHeader('all responses saved to'); console.log(tmpDirPath + '\n'); } logHeader('automated analysis'); const commonTips = [ tmpDirPath && chalk_1.default `{gray *} Inspect the saved responses in {underline ${tmpDirPath}}`, chalk_1.default `{gray *} Change the <block_size> argument. Common block sizes are 8, 16, 32.`, chalk_1.default `{gray *} Make sure the injection point {underline \{POPAYLOAD\}} is correctly set.` ].filter(Boolean).join('\n'); if (scFreqEntries.length === 1 && clFreqEntries.length === 1) { console.log("Responses don't seem to differ by status code or content length.\n" + commonTips); } else if (scFreqEntries.length !== 2 && clFreqEntries.length !== 2) { console.log('Responses seem to widely differ.\n' + commonTips); } else { if (scFreqEntries.length === 2) { const errorStatusCode = scFreqEntries.find(([, v]) => v === 255); const successStatusCode = scFreqEntries.find(([, v]) => v === 1); if (successStatusCode && errorStatusCode) { const sc = chalk_1.default[util_1.getStatusCodeColor(+errorStatusCode[0])](errorStatusCode[0]); console.log(chalk_1.default `Responses are likely to have a ${sc} status code when a decryption error occurs.\nYou can try specifying ${sc} for the {bold <error>} argument.\n`); } } if (clFreqEntries.length === 2) { const errorContentLength = clFreqEntries.find(([, v]) => v === 255); const successContentLength = clFreqEntries.find(([, v]) => v === 1); if (successContentLength && errorContentLength) { console.log('Responses are likely to be sized', chalk_1.default.yellow(errorContentLength[0]), 'bytes when a decryption error occurs.', tmpDirPath ? chalk_1.default `\nYou can find out how the response differs by inspecting the saved responses in\n{underline ${tmpDirPath}}\n` : '\n'); } } } console.log(); } }; function logError(err) { log_update_1.default.done(); console.error(chalk_1.default.red(err.stack || err.message)); } exports.logError = logError; //# sourceMappingURL=logging.js.map