padding-oracle-attacker
Version:
CLI tool and library to execute padding oracle attacks easily
223 lines • 11.9 kB
JavaScript
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
;