From 327e554f8eb5be06f52d6303b09b428b49b0315b Mon Sep 17 00:00:00 2001 From: Igor Matlin Date: Tue, 16 Dec 2025 20:04:08 -0600 Subject: [PATCH 1/5] Added team assignment functionality for new applications --- action.yml | 4 + dist/index.js | 1446 ++++++++++++++++++++++++++++++++++--------------- src/action.js | 100 +++- 3 files changed, 1095 insertions(+), 455 deletions(-) diff --git a/action.yml b/action.yml index 8a150f2..a97b424 100644 --- a/action.yml +++ b/action.yml @@ -23,6 +23,10 @@ inputs: required: true description: "APK or IPA File" #default: "${{ steps.apk-path.outputs.path }}" + team_name: + required: false + description: "Team name to assign the app to (default: Default)" + default: "Default" runs: using: 'node20' diff --git a/dist/index.js b/dist/index.js index 34c2f5d..f2a5d7f 100644 --- a/dist/index.js +++ b/dist/index.js @@ -3827,7 +3827,7 @@ function expand(str, isTop) { var isOptions = m.body.indexOf(',') >= 0; if (!isSequence && !isOptions) { // {a},b} - if (m.post.match(/,.*\}/)) { + if (m.post.match(/,(?!,).*\}/)) { str = m.pre + '{' + m.body + escClose + m.post; return expand(str); } @@ -5239,6 +5239,9 @@ module.exports.wrap = wrap; /***/ 6454: /***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { +"use strict"; + + var CombinedStream = __nccwpck_require__(5630); var util = __nccwpck_require__(9023); var path = __nccwpck_require__(6928); @@ -5247,24 +5250,20 @@ var https = __nccwpck_require__(5692); var parseUrl = (__nccwpck_require__(7016).parse); var fs = __nccwpck_require__(9896); var Stream = (__nccwpck_require__(2203).Stream); +var crypto = __nccwpck_require__(6982); var mime = __nccwpck_require__(4096); var asynckit = __nccwpck_require__(1324); var setToStringTag = __nccwpck_require__(8700); +var hasOwn = __nccwpck_require__(4076); var populate = __nccwpck_require__(1835); -// Public API -module.exports = FormData; - -// make it a Stream -util.inherits(FormData, CombinedStream); - /** * Create readable "multipart/form-data" streams. * Can be used to submit forms * and file uploads to other web applications. * * @constructor - * @param {Object} options - Properties to be added/overriden for FormData and CombinedStream + * @param {object} options - Properties to be added/overriden for FormData and CombinedStream */ function FormData(options) { if (!(this instanceof FormData)) { @@ -5277,35 +5276,39 @@ function FormData(options) { CombinedStream.call(this); - options = options || {}; - for (var option in options) { + options = options || {}; // eslint-disable-line no-param-reassign + for (var option in options) { // eslint-disable-line no-restricted-syntax this[option] = options[option]; } } +// make it a Stream +util.inherits(FormData, CombinedStream); + FormData.LINE_BREAK = '\r\n'; FormData.DEFAULT_CONTENT_TYPE = 'application/octet-stream'; -FormData.prototype.append = function(field, value, options) { - - options = options || {}; +FormData.prototype.append = function (field, value, options) { + options = options || {}; // eslint-disable-line no-param-reassign // allow filename as single option - if (typeof options == 'string') { - options = {filename: options}; + if (typeof options === 'string') { + options = { filename: options }; // eslint-disable-line no-param-reassign } var append = CombinedStream.prototype.append.bind(this); // all that streamy business can't handle numbers - if (typeof value == 'number') { - value = '' + value; + if (typeof value === 'number' || value == null) { + value = String(value); // eslint-disable-line no-param-reassign } // https://github.com/felixge/node-form-data/issues/38 if (Array.isArray(value)) { - // Please convert your array into string - // the way web server expects it + /* + * Please convert your array into string + * the way web server expects it + */ this._error(new Error('Arrays are not supported.')); return; } @@ -5321,15 +5324,17 @@ FormData.prototype.append = function(field, value, options) { this._trackLength(header, value, options); }; -FormData.prototype._trackLength = function(header, value, options) { +FormData.prototype._trackLength = function (header, value, options) { var valueLength = 0; - // used w/ getLengthSync(), when length is known. - // e.g. for streaming directly from a remote server, - // w/ a known file a size, and not wanting to wait for - // incoming file to finish to get its size. + /* + * used w/ getLengthSync(), when length is known. + * e.g. for streaming directly from a remote server, + * w/ a known file a size, and not wanting to wait for + * incoming file to finish to get its size. + */ if (options.knownLength != null) { - valueLength += +options.knownLength; + valueLength += Number(options.knownLength); } else if (Buffer.isBuffer(value)) { valueLength = value.length; } else if (typeof value === 'string') { @@ -5339,12 +5344,10 @@ FormData.prototype._trackLength = function(header, value, options) { this._valueLength += valueLength; // @check why add CRLF? does this account for custom/multiple CRLFs? - this._overheadLength += - Buffer.byteLength(header) + - FormData.LINE_BREAK.length; + this._overheadLength += Buffer.byteLength(header) + FormData.LINE_BREAK.length; // empty or either doesn't have path or not an http response or not a stream - if (!value || ( !value.path && !(value.readable && Object.prototype.hasOwnProperty.call(value, 'httpVersion')) && !(value instanceof Stream))) { + if (!value || (!value.path && !(value.readable && hasOwn(value, 'httpVersion')) && !(value instanceof Stream))) { return; } @@ -5354,9 +5357,8 @@ FormData.prototype._trackLength = function(header, value, options) { } }; -FormData.prototype._lengthRetriever = function(value, callback) { - if (Object.prototype.hasOwnProperty.call(value, 'fd')) { - +FormData.prototype._lengthRetriever = function (value, callback) { + if (hasOwn(value, 'fd')) { // take read range into a account // `end` = Infinity –> read file till the end // @@ -5365,54 +5367,52 @@ FormData.prototype._lengthRetriever = function(value, callback) { // Fix it when node fixes it. // https://github.com/joyent/node/issues/7819 if (value.end != undefined && value.end != Infinity && value.start != undefined) { - // when end specified // no need to calculate range // inclusive, starts with 0 - callback(null, value.end + 1 - (value.start ? value.start : 0)); + callback(null, value.end + 1 - (value.start ? value.start : 0)); // eslint-disable-line callback-return - // not that fast snoopy + // not that fast snoopy } else { // still need to fetch file size from fs - fs.stat(value.path, function(err, stat) { - - var fileSize; - + fs.stat(value.path, function (err, stat) { if (err) { callback(err); return; } // update final size based on the range options - fileSize = stat.size - (value.start ? value.start : 0); + var fileSize = stat.size - (value.start ? value.start : 0); callback(null, fileSize); }); } - // or http response - } else if (Object.prototype.hasOwnProperty.call(value, 'httpVersion')) { - callback(null, +value.headers['content-length']); + // or http response + } else if (hasOwn(value, 'httpVersion')) { + callback(null, Number(value.headers['content-length'])); // eslint-disable-line callback-return - // or request stream http://github.com/mikeal/request - } else if (Object.prototype.hasOwnProperty.call(value, 'httpModule')) { + // or request stream http://github.com/mikeal/request + } else if (hasOwn(value, 'httpModule')) { // wait till response come back - value.on('response', function(response) { + value.on('response', function (response) { value.pause(); - callback(null, +response.headers['content-length']); + callback(null, Number(response.headers['content-length'])); }); value.resume(); - // something else + // something else } else { - callback('Unknown stream'); + callback('Unknown stream'); // eslint-disable-line callback-return } }; -FormData.prototype._multiPartHeader = function(field, value, options) { - // custom header specified (as string)? - // it becomes responsible for boundary - // (e.g. to handle extra CRLFs on .NET servers) - if (typeof options.header == 'string') { +FormData.prototype._multiPartHeader = function (field, value, options) { + /* + * custom header specified (as string)? + * it becomes responsible for boundary + * (e.g. to handle extra CRLFs on .NET servers) + */ + if (typeof options.header === 'string') { return options.header; } @@ -5420,7 +5420,7 @@ FormData.prototype._multiPartHeader = function(field, value, options) { var contentType = this._getContentType(value, options); var contents = ''; - var headers = { + var headers = { // add custom disposition as third element or keep it two elements if not 'Content-Disposition': ['form-data', 'name="' + field + '"'].concat(contentDisposition || []), // if no content type. allow it to be empty array @@ -5428,18 +5428,18 @@ FormData.prototype._multiPartHeader = function(field, value, options) { }; // allow custom headers. - if (typeof options.header == 'object') { + if (typeof options.header === 'object') { populate(headers, options.header); } var header; - for (var prop in headers) { - if (Object.prototype.hasOwnProperty.call(headers, prop)) { + for (var prop in headers) { // eslint-disable-line no-restricted-syntax + if (hasOwn(headers, prop)) { header = headers[prop]; // skip nullish headers. if (header == null) { - continue; + continue; // eslint-disable-line no-restricted-syntax, no-continue } // convert all headers to arrays. @@ -5457,49 +5457,45 @@ FormData.prototype._multiPartHeader = function(field, value, options) { return '--' + this.getBoundary() + FormData.LINE_BREAK + contents + FormData.LINE_BREAK; }; -FormData.prototype._getContentDisposition = function(value, options) { - - var filename - , contentDisposition - ; +FormData.prototype._getContentDisposition = function (value, options) { // eslint-disable-line consistent-return + var filename; if (typeof options.filepath === 'string') { // custom filepath for relative paths filename = path.normalize(options.filepath).replace(/\\/g, '/'); - } else if (options.filename || value.name || value.path) { - // custom filename take precedence - // formidable and the browser add a name property - // fs- and request- streams have path property - filename = path.basename(options.filename || value.name || value.path); - } else if (value.readable && Object.prototype.hasOwnProperty.call(value, 'httpVersion')) { + } else if (options.filename || (value && (value.name || value.path))) { + /* + * custom filename take precedence + * formidable and the browser add a name property + * fs- and request- streams have path property + */ + filename = path.basename(options.filename || (value && (value.name || value.path))); + } else if (value && value.readable && hasOwn(value, 'httpVersion')) { // or try http response filename = path.basename(value.client._httpMessage.path || ''); } if (filename) { - contentDisposition = 'filename="' + filename + '"'; + return 'filename="' + filename + '"'; } - - return contentDisposition; }; -FormData.prototype._getContentType = function(value, options) { - +FormData.prototype._getContentType = function (value, options) { // use custom content-type above all var contentType = options.contentType; // or try `name` from formidable, browser - if (!contentType && value.name) { + if (!contentType && value && value.name) { contentType = mime.lookup(value.name); } // or try `path` from fs-, request- streams - if (!contentType && value.path) { + if (!contentType && value && value.path) { contentType = mime.lookup(value.path); } // or if it's http-reponse - if (!contentType && value.readable && Object.prototype.hasOwnProperty.call(value, 'httpVersion')) { + if (!contentType && value && value.readable && hasOwn(value, 'httpVersion')) { contentType = value.headers['content-type']; } @@ -5509,18 +5505,18 @@ FormData.prototype._getContentType = function(value, options) { } // fallback to the default content type if `value` is not simple value - if (!contentType && typeof value == 'object') { + if (!contentType && value && typeof value === 'object') { contentType = FormData.DEFAULT_CONTENT_TYPE; } return contentType; }; -FormData.prototype._multiPartFooter = function() { - return function(next) { +FormData.prototype._multiPartFooter = function () { + return function (next) { var footer = FormData.LINE_BREAK; - var lastPart = (this._streams.length === 0); + var lastPart = this._streams.length === 0; if (lastPart) { footer += this._lastBoundary(); } @@ -5529,18 +5525,18 @@ FormData.prototype._multiPartFooter = function() { }.bind(this); }; -FormData.prototype._lastBoundary = function() { +FormData.prototype._lastBoundary = function () { return '--' + this.getBoundary() + '--' + FormData.LINE_BREAK; }; -FormData.prototype.getHeaders = function(userHeaders) { +FormData.prototype.getHeaders = function (userHeaders) { var header; var formHeaders = { 'content-type': 'multipart/form-data; boundary=' + this.getBoundary() }; - for (header in userHeaders) { - if (Object.prototype.hasOwnProperty.call(userHeaders, header)) { + for (header in userHeaders) { // eslint-disable-line no-restricted-syntax + if (hasOwn(userHeaders, header)) { formHeaders[header.toLowerCase()] = userHeaders[header]; } } @@ -5548,11 +5544,14 @@ FormData.prototype.getHeaders = function(userHeaders) { return formHeaders; }; -FormData.prototype.setBoundary = function(boundary) { +FormData.prototype.setBoundary = function (boundary) { + if (typeof boundary !== 'string') { + throw new TypeError('FormData boundary must be a string'); + } this._boundary = boundary; }; -FormData.prototype.getBoundary = function() { +FormData.prototype.getBoundary = function () { if (!this._boundary) { this._generateBoundary(); } @@ -5560,60 +5559,55 @@ FormData.prototype.getBoundary = function() { return this._boundary; }; -FormData.prototype.getBuffer = function() { - var dataBuffer = new Buffer.alloc(0); +FormData.prototype.getBuffer = function () { + var dataBuffer = new Buffer.alloc(0); // eslint-disable-line new-cap var boundary = this.getBoundary(); // Create the form content. Add Line breaks to the end of data. for (var i = 0, len = this._streams.length; i < len; i++) { if (typeof this._streams[i] !== 'function') { - // Add content to the buffer. - if(Buffer.isBuffer(this._streams[i])) { - dataBuffer = Buffer.concat( [dataBuffer, this._streams[i]]); - }else { - dataBuffer = Buffer.concat( [dataBuffer, Buffer.from(this._streams[i])]); + if (Buffer.isBuffer(this._streams[i])) { + dataBuffer = Buffer.concat([dataBuffer, this._streams[i]]); + } else { + dataBuffer = Buffer.concat([dataBuffer, Buffer.from(this._streams[i])]); } // Add break after content. - if (typeof this._streams[i] !== 'string' || this._streams[i].substring( 2, boundary.length + 2 ) !== boundary) { - dataBuffer = Buffer.concat( [dataBuffer, Buffer.from(FormData.LINE_BREAK)] ); + if (typeof this._streams[i] !== 'string' || this._streams[i].substring(2, boundary.length + 2) !== boundary) { + dataBuffer = Buffer.concat([dataBuffer, Buffer.from(FormData.LINE_BREAK)]); } } } // Add the footer and return the Buffer object. - return Buffer.concat( [dataBuffer, Buffer.from(this._lastBoundary())] ); + return Buffer.concat([dataBuffer, Buffer.from(this._lastBoundary())]); }; -FormData.prototype._generateBoundary = function() { +FormData.prototype._generateBoundary = function () { // This generates a 50 character boundary similar to those used by Firefox. - // They are optimized for boyer-moore parsing. - var boundary = '--------------------------'; - for (var i = 0; i < 24; i++) { - boundary += Math.floor(Math.random() * 10).toString(16); - } - this._boundary = boundary; + // They are optimized for boyer-moore parsing. + this._boundary = '--------------------------' + crypto.randomBytes(12).toString('hex'); }; // Note: getLengthSync DOESN'T calculate streams length -// As workaround one can calculate file size manually -// and add it as knownLength option -FormData.prototype.getLengthSync = function() { +// As workaround one can calculate file size manually and add it as knownLength option +FormData.prototype.getLengthSync = function () { var knownLength = this._overheadLength + this._valueLength; - // Don't get confused, there are 3 "internal" streams for each keyval pair - // so it basically checks if there is any value added to the form + // Don't get confused, there are 3 "internal" streams for each keyval pair so it basically checks if there is any value added to the form if (this._streams.length) { knownLength += this._lastBoundary().length; } // https://github.com/form-data/form-data/issues/40 if (!this.hasKnownLength()) { - // Some async length retrievers are present - // therefore synchronous length calculation is false. - // Please use getLength(callback) to get proper length + /* + * Some async length retrievers are present + * therefore synchronous length calculation is false. + * Please use getLength(callback) to get proper length + */ this._error(new Error('Cannot calculate proper length in synchronous way.')); } @@ -5623,7 +5617,7 @@ FormData.prototype.getLengthSync = function() { // Public API to check if length of added values is known // https://github.com/form-data/form-data/issues/196 // https://github.com/form-data/form-data/issues/262 -FormData.prototype.hasKnownLength = function() { +FormData.prototype.hasKnownLength = function () { var hasKnownLength = true; if (this._valuesToMeasure.length) { @@ -5633,7 +5627,7 @@ FormData.prototype.hasKnownLength = function() { return hasKnownLength; }; -FormData.prototype.getLength = function(cb) { +FormData.prototype.getLength = function (cb) { var knownLength = this._overheadLength + this._valueLength; if (this._streams.length) { @@ -5645,13 +5639,13 @@ FormData.prototype.getLength = function(cb) { return; } - asynckit.parallel(this._valuesToMeasure, this._lengthRetriever, function(err, values) { + asynckit.parallel(this._valuesToMeasure, this._lengthRetriever, function (err, values) { if (err) { cb(err); return; } - values.forEach(function(length) { + values.forEach(function (length) { knownLength += length; }); @@ -5659,31 +5653,26 @@ FormData.prototype.getLength = function(cb) { }); }; -FormData.prototype.submit = function(params, cb) { - var request - , options - , defaults = {method: 'post'} - ; - - // parse provided url if it's string - // or treat it as options object - if (typeof params == 'string') { +FormData.prototype.submit = function (params, cb) { + var request; + var options; + var defaults = { method: 'post' }; - params = parseUrl(params); + // parse provided url if it's string or treat it as options object + if (typeof params === 'string') { + params = parseUrl(params); // eslint-disable-line no-param-reassign + /* eslint sort-keys: 0 */ options = populate({ port: params.port, path: params.pathname, host: params.hostname, protocol: params.protocol }, defaults); - - // use custom params - } else { - + } else { // use custom params options = populate(params, defaults); // if no port provided use default one if (!options.port) { - options.port = options.protocol == 'https:' ? 443 : 80; + options.port = options.protocol === 'https:' ? 443 : 80; } } @@ -5691,14 +5680,14 @@ FormData.prototype.submit = function(params, cb) { options.headers = this.getHeaders(params.headers); // https if specified, fallback to http in any other case - if (options.protocol == 'https:') { + if (options.protocol === 'https:') { request = https.request(options); } else { request = http.request(options); } // get content length and fire away - this.getLength(function(err, length) { + this.getLength(function (err, length) { if (err && err !== 'Unknown stream') { this._error(err); return; @@ -5730,7 +5719,7 @@ FormData.prototype.submit = function(params, cb) { return request; }; -FormData.prototype._error = function(err) { +FormData.prototype._error = function (err) { if (!this.error) { this.error = err; this.pause(); @@ -5741,7 +5730,10 @@ FormData.prototype._error = function(err) { FormData.prototype.toString = function () { return '[object FormData]'; }; -setToStringTag(FormData, 'FormData'); +setToStringTag(FormData.prototype, 'FormData'); + +// Public API +module.exports = FormData; /***/ }), @@ -5749,12 +5741,13 @@ setToStringTag(FormData, 'FormData'); /***/ 1835: /***/ ((module) => { -// populates missing values -module.exports = function(dst, src) { +"use strict"; - Object.keys(src).forEach(function(prop) - { - dst[prop] = dst[prop] || src[prop]; + +// populates missing values +module.exports = function (dst, src) { + Object.keys(src).forEach(function (prop) { + dst[prop] = dst[prop] || src[prop]; // eslint-disable-line no-param-reassign }); return dst; @@ -39155,7 +39148,7 @@ exports.PathScurry = process.platform === 'win32' ? PathScurryWin32 /***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { "use strict"; -/*! Axios v1.9.0 Copyright (c) 2025 Matt Zabriskie and contributors */ +/*! Axios v1.13.2 Copyright (c) 2025 Matt Zabriskie and contributors */ const FormData$1 = __nccwpck_require__(6454); @@ -39164,6 +39157,7 @@ const url = __nccwpck_require__(7016); const proxyFromEnv = __nccwpck_require__(7777); const http = __nccwpck_require__(8611); const https = __nccwpck_require__(5692); +const http2 = __nccwpck_require__(5675); const util = __nccwpck_require__(9023); const followRedirects = __nccwpck_require__(1573); const zlib = __nccwpck_require__(3106); @@ -39178,11 +39172,19 @@ const url__default = /*#__PURE__*/_interopDefaultLegacy(url); const proxyFromEnv__default = /*#__PURE__*/_interopDefaultLegacy(proxyFromEnv); const http__default = /*#__PURE__*/_interopDefaultLegacy(http); const https__default = /*#__PURE__*/_interopDefaultLegacy(https); +const http2__default = /*#__PURE__*/_interopDefaultLegacy(http2); const util__default = /*#__PURE__*/_interopDefaultLegacy(util); const followRedirects__default = /*#__PURE__*/_interopDefaultLegacy(followRedirects); const zlib__default = /*#__PURE__*/_interopDefaultLegacy(zlib); const stream__default = /*#__PURE__*/_interopDefaultLegacy(stream); +/** + * Create a bound version of a function with a specified `this` context + * + * @param {Function} fn - The function to bind + * @param {*} thisArg - The value to be passed as the `this` parameter + * @returns {Function} A new function that will call the original function with the specified `this` context + */ function bind(fn, thisArg) { return function wrap() { return fn.apply(thisArg, arguments); @@ -39234,7 +39236,7 @@ const isUndefined = typeOfTest('undefined'); */ function isBuffer(val) { return val !== null && !isUndefined(val) && val.constructor !== null && !isUndefined(val.constructor) - && isFunction(val.constructor.isBuffer) && val.constructor.isBuffer(val); + && isFunction$1(val.constructor.isBuffer) && val.constructor.isBuffer(val); } /** @@ -39279,7 +39281,7 @@ const isString = typeOfTest('string'); * @param {*} val The value to test * @returns {boolean} True if value is a Function, otherwise false */ -const isFunction = typeOfTest('function'); +const isFunction$1 = typeOfTest('function'); /** * Determine if a value is a Number @@ -39323,6 +39325,27 @@ const isPlainObject = (val) => { return (prototype === null || prototype === Object.prototype || Object.getPrototypeOf(prototype) === null) && !(toStringTag in val) && !(iterator in val); }; +/** + * Determine if a value is an empty object (safely handles Buffers) + * + * @param {*} val The value to test + * + * @returns {boolean} True if value is an empty object, otherwise false + */ +const isEmptyObject = (val) => { + // Early return for non-objects or Buffers to prevent RangeError + if (!isObject(val) || isBuffer(val)) { + return false; + } + + try { + return Object.keys(val).length === 0 && Object.getPrototypeOf(val) === Object.prototype; + } catch (e) { + // Fallback for any other objects that might cause RangeError with Object.keys() + return false; + } +}; + /** * Determine if a value is a Date * @@ -39366,7 +39389,7 @@ const isFileList = kindOfTest('FileList'); * * @returns {boolean} True if value is a Stream, otherwise false */ -const isStream = (val) => isObject(val) && isFunction(val.pipe); +const isStream = (val) => isObject(val) && isFunction$1(val.pipe); /** * Determine if a value is a FormData @@ -39379,10 +39402,10 @@ const isFormData = (thing) => { let kind; return thing && ( (typeof FormData === 'function' && thing instanceof FormData) || ( - isFunction(thing.append) && ( + isFunction$1(thing.append) && ( (kind = kindOf(thing)) === 'formdata' || // detect form-data instance - (kind === 'object' && isFunction(thing.toString) && thing.toString() === '[object FormData]') + (kind === 'object' && isFunction$1(thing.toString) && thing.toString() === '[object FormData]') ) ) ) @@ -39445,6 +39468,11 @@ function forEach(obj, fn, {allOwnKeys = false} = {}) { fn.call(null, obj[i], i, obj); } } else { + // Buffer check + if (isBuffer(obj)) { + return; + } + // Iterate over object keys const keys = allOwnKeys ? Object.getOwnPropertyNames(obj) : Object.keys(obj); const len = keys.length; @@ -39458,6 +39486,10 @@ function forEach(obj, fn, {allOwnKeys = false} = {}) { } function findKey(obj, key) { + if (isBuffer(obj)){ + return null; + } + key = key.toLowerCase(); const keys = Object.keys(obj); let i = keys.length; @@ -39498,7 +39530,7 @@ const isContextDefined = (context) => !isUndefined(context) && context !== _glob * @returns {Object} Result of all merge properties */ function merge(/* obj1, obj2, obj3, ... */) { - const {caseless} = isContextDefined(this) && this || {}; + const {caseless, skipUndefined} = isContextDefined(this) && this || {}; const result = {}; const assignValue = (val, key) => { const targetKey = caseless && findKey(result, key) || key; @@ -39508,7 +39540,7 @@ function merge(/* obj1, obj2, obj3, ... */) { result[targetKey] = merge({}, val); } else if (isArray(val)) { result[targetKey] = val.slice(); - } else { + } else if (!skipUndefined || !isUndefined(val)) { result[targetKey] = val; } }; @@ -39531,7 +39563,7 @@ function merge(/* obj1, obj2, obj3, ... */) { */ const extend = (a, b, thisArg, {allOwnKeys}= {}) => { forEach(b, (val, key) => { - if (thisArg && isFunction(val)) { + if (thisArg && isFunction$1(val)) { a[key] = bind(val, thisArg); } else { a[key] = val; @@ -39747,13 +39779,13 @@ const reduceDescriptors = (obj, reducer) => { const freezeMethods = (obj) => { reduceDescriptors(obj, (descriptor, name) => { // skip restricted props in strict mode - if (isFunction(obj) && ['arguments', 'caller', 'callee'].indexOf(name) !== -1) { + if (isFunction$1(obj) && ['arguments', 'caller', 'callee'].indexOf(name) !== -1) { return false; } const value = obj[name]; - if (!isFunction(value)) return; + if (!isFunction$1(value)) return; descriptor.enumerable = false; @@ -39790,6 +39822,8 @@ const toFiniteNumber = (value, defaultValue) => { return value != null && Number.isFinite(value = +value) ? value : defaultValue; }; + + /** * If the thing is a FormData object, return true, otherwise return false. * @@ -39798,7 +39832,7 @@ const toFiniteNumber = (value, defaultValue) => { * @returns {boolean} */ function isSpecCompliantForm(thing) { - return !!(thing && isFunction(thing.append) && thing[toStringTag] === 'FormData' && thing[iterator]); + return !!(thing && isFunction$1(thing.append) && thing[toStringTag] === 'FormData' && thing[iterator]); } const toJSONObject = (obj) => { @@ -39811,6 +39845,11 @@ const toJSONObject = (obj) => { return; } + //Buffer check + if (isBuffer(source)) { + return source; + } + if(!('toJSON' in source)) { stack[i] = source; const target = isArray(source) ? [] : {}; @@ -39835,7 +39874,7 @@ const toJSONObject = (obj) => { const isAsyncFn = kindOfTest('AsyncFunction'); const isThenable = (thing) => - thing && (isObject(thing) || isFunction(thing)) && isFunction(thing.then) && isFunction(thing.catch); + thing && (isObject(thing) || isFunction$1(thing)) && isFunction$1(thing.then) && isFunction$1(thing.catch); // original code // https://github.com/DigitalBrainJS/AxiosPromise/blob/16deab13710ec09779922131f3fa5954320f83ab/lib/utils.js#L11-L34 @@ -39859,7 +39898,7 @@ const _setImmediate = ((setImmediateSupported, postMessageSupported) => { })(`axios@${Math.random()}`, []) : (cb) => setTimeout(cb); })( typeof setImmediate === 'function', - isFunction(_global.postMessage) + isFunction$1(_global.postMessage) ); const asap = typeof queueMicrotask !== 'undefined' ? @@ -39868,7 +39907,7 @@ const asap = typeof queueMicrotask !== 'undefined' ? // ********************* -const isIterable = (thing) => thing != null && isFunction(thing[iterator]); +const isIterable = (thing) => thing != null && isFunction$1(thing[iterator]); const utils$1 = { @@ -39882,6 +39921,7 @@ const utils$1 = { isBoolean, isObject, isPlainObject, + isEmptyObject, isReadableStream, isRequest, isResponse, @@ -39891,7 +39931,7 @@ const utils$1 = { isFile, isBlob, isRegExp, - isFunction, + isFunction: isFunction$1, isStream, isURLSearchParams, isTypedArray, @@ -40017,11 +40057,18 @@ AxiosError.from = (error, code, config, request, response, customProps) => { return prop !== 'isAxiosError'; }); - AxiosError.call(axiosError, error.message, code, config, request, response); + const msg = error && error.message ? error.message : 'Error'; + + // Prefer explicit code; otherwise copy the low-level error's code (e.g. ECONNREFUSED) + const errCode = code == null && error ? error.code : code; + AxiosError.call(axiosError, msg, errCode, config, request, response); - axiosError.cause = error; + // Chain the original error on the standard field; non-enumerable to avoid JSON noise + if (error && axiosError.cause == null) { + Object.defineProperty(axiosError, 'cause', { value: error, configurable: true }); + } - axiosError.name = error.name; + axiosError.name = (error && error.name) || 'Error'; customProps && Object.assign(axiosError, customProps); @@ -40143,6 +40190,10 @@ function toFormData(obj, formData, options) { return value.toISOString(); } + if (utils$1.isBoolean(value)) { + return value.toString(); + } + if (!useBlob && utils$1.isBlob(value)) { throw new AxiosError('Blob is not supported. Use a Buffer instead.'); } @@ -40305,9 +40356,7 @@ function encode(val) { replace(/%3A/gi, ':'). replace(/%24/g, '$'). replace(/%2C/gi, ','). - replace(/%20/g, '+'). - replace(/%5B/gi, '['). - replace(/%5D/gi, ']'); + replace(/%20/g, '+'); } /** @@ -40385,7 +40434,7 @@ class InterceptorManager { * * @param {Number} id The ID that was returned by `use` * - * @returns {Boolean} `true` if the interceptor was removed, `false` otherwise + * @returns {void} */ eject(id) { if (this.handlers[id]) { @@ -40527,7 +40576,7 @@ const platform = { }; function toURLEncodedForm(data, options) { - return toFormData(data, new platform.classes.URLSearchParams(), Object.assign({ + return toFormData(data, new platform.classes.URLSearchParams(), { visitor: function(value, key, path, helpers) { if (platform.isNode && utils$1.isBuffer(value)) { this.append(key, value.toString('base64')); @@ -40535,8 +40584,9 @@ function toURLEncodedForm(data, options) { } return helpers.defaultVisitor.apply(this, arguments); - } - }, options)); + }, + ...options + }); } /** @@ -40732,7 +40782,7 @@ const defaults = { const strictJSONParsing = !silentJSONParsing && JSONRequested; try { - return JSON.parse(data); + return JSON.parse(data, this.parseReviver); } catch (e) { if (strictJSONParsing) { if (e.name === 'SyntaxError') { @@ -41259,7 +41309,7 @@ function buildFullPath(baseURL, requestedURL, allowAbsoluteUrls) { return requestedURL; } -const VERSION = "1.9.0"; +const VERSION = "1.13.2"; function parseProtocol(url) { const match = /^([-+\w]{1,25})(:?\/\/|:)/.exec(url); @@ -41687,7 +41737,7 @@ function throttle(fn, freq) { clearTimeout(timer); timer = null; } - fn.apply(null, args); + fn(...args); }; const throttled = (...args) => { @@ -41752,6 +41802,80 @@ const progressEventDecorator = (total, throttled) => { const asyncDecorator = (fn) => (...args) => utils$1.asap(() => fn(...args)); +/** + * Estimate decoded byte length of a data:// URL *without* allocating large buffers. + * - For base64: compute exact decoded size using length and padding; + * handle %XX at the character-count level (no string allocation). + * - For non-base64: use UTF-8 byteLength of the encoded body as a safe upper bound. + * + * @param {string} url + * @returns {number} + */ +function estimateDataURLDecodedBytes(url) { + if (!url || typeof url !== 'string') return 0; + if (!url.startsWith('data:')) return 0; + + const comma = url.indexOf(','); + if (comma < 0) return 0; + + const meta = url.slice(5, comma); + const body = url.slice(comma + 1); + const isBase64 = /;base64/i.test(meta); + + if (isBase64) { + let effectiveLen = body.length; + const len = body.length; // cache length + + for (let i = 0; i < len; i++) { + if (body.charCodeAt(i) === 37 /* '%' */ && i + 2 < len) { + const a = body.charCodeAt(i + 1); + const b = body.charCodeAt(i + 2); + const isHex = + ((a >= 48 && a <= 57) || (a >= 65 && a <= 70) || (a >= 97 && a <= 102)) && + ((b >= 48 && b <= 57) || (b >= 65 && b <= 70) || (b >= 97 && b <= 102)); + + if (isHex) { + effectiveLen -= 2; + i += 2; + } + } + } + + let pad = 0; + let idx = len - 1; + + const tailIsPct3D = (j) => + j >= 2 && + body.charCodeAt(j - 2) === 37 && // '%' + body.charCodeAt(j - 1) === 51 && // '3' + (body.charCodeAt(j) === 68 || body.charCodeAt(j) === 100); // 'D' or 'd' + + if (idx >= 0) { + if (body.charCodeAt(idx) === 61 /* '=' */) { + pad++; + idx--; + } else if (tailIsPct3D(idx)) { + pad++; + idx -= 3; + } + } + + if (pad === 1 && idx >= 0) { + if (body.charCodeAt(idx) === 61 /* '=' */) { + pad++; + } else if (tailIsPct3D(idx)) { + pad++; + } + } + + const groups = Math.floor(effectiveLen / 4); + const bytes = groups * 3 - (pad || 0); + return bytes > 0 ? bytes : 0; + } + + return Buffer.byteLength(body, 'utf8'); +} + const zlibOptions = { flush: zlib__default["default"].constants.Z_SYNC_FLUSH, finishFlush: zlib__default["default"].constants.Z_SYNC_FLUSH @@ -41772,6 +41896,7 @@ const supportedProtocols = platform.protocols.map(protocol => { return protocol + ':'; }); + const flushOnFinish = (stream, [throttled, flush]) => { stream .on('end', flush) @@ -41780,6 +41905,102 @@ const flushOnFinish = (stream, [throttled, flush]) => { return throttled; }; +class Http2Sessions { + constructor() { + this.sessions = Object.create(null); + } + + getSession(authority, options) { + options = Object.assign({ + sessionTimeout: 1000 + }, options); + + let authoritySessions = this.sessions[authority]; + + if (authoritySessions) { + let len = authoritySessions.length; + + for (let i = 0; i < len; i++) { + const [sessionHandle, sessionOptions] = authoritySessions[i]; + if (!sessionHandle.destroyed && !sessionHandle.closed && util__default["default"].isDeepStrictEqual(sessionOptions, options)) { + return sessionHandle; + } + } + } + + const session = http2__default["default"].connect(authority, options); + + let removed; + + const removeSession = () => { + if (removed) { + return; + } + + removed = true; + + let entries = authoritySessions, len = entries.length, i = len; + + while (i--) { + if (entries[i][0] === session) { + if (len === 1) { + delete this.sessions[authority]; + } else { + entries.splice(i, 1); + } + return; + } + } + }; + + const originalRequestFn = session.request; + + const {sessionTimeout} = options; + + if(sessionTimeout != null) { + + let timer; + let streamsCount = 0; + + session.request = function () { + const stream = originalRequestFn.apply(this, arguments); + + streamsCount++; + + if (timer) { + clearTimeout(timer); + timer = null; + } + + stream.once('close', () => { + if (!--streamsCount) { + timer = setTimeout(() => { + timer = null; + removeSession(); + }, sessionTimeout); + } + }); + + return stream; + }; + } + + session.once('close', removeSession); + + let entry = [ + session, + options + ]; + + authoritySessions ? authoritySessions.push(entry) : authoritySessions = this.sessions[authority] = [entry]; + + return session; + } +} + +const http2Sessions = new Http2Sessions(); + + /** * If the proxy or config beforeRedirects functions are defined, call them with the options * object. @@ -41891,16 +42112,75 @@ const resolveFamily = ({address, family}) => { const buildAddressEntry = (address, family) => resolveFamily(utils$1.isObject(address) ? address : {address, family}); +const http2Transport = { + request(options, cb) { + const authority = options.protocol + '//' + options.hostname + ':' + (options.port || 80); + + const {http2Options, headers} = options; + + const session = http2Sessions.getSession(authority, http2Options); + + const { + HTTP2_HEADER_SCHEME, + HTTP2_HEADER_METHOD, + HTTP2_HEADER_PATH, + HTTP2_HEADER_STATUS + } = http2__default["default"].constants; + + const http2Headers = { + [HTTP2_HEADER_SCHEME]: options.protocol.replace(':', ''), + [HTTP2_HEADER_METHOD]: options.method, + [HTTP2_HEADER_PATH]: options.path, + }; + + utils$1.forEach(headers, (header, name) => { + name.charAt(0) !== ':' && (http2Headers[name] = header); + }); + + const req = session.request(http2Headers); + + req.once('response', (responseHeaders) => { + const response = req; //duplex + + responseHeaders = Object.assign({}, responseHeaders); + + const status = responseHeaders[HTTP2_HEADER_STATUS]; + + delete responseHeaders[HTTP2_HEADER_STATUS]; + + response.headers = responseHeaders; + + response.statusCode = +status; + + cb(response); + }); + + return req; + } +}; + /*eslint consistent-return:0*/ const httpAdapter = isHttpAdapterSupported && function httpAdapter(config) { return wrapAsync(async function dispatchHttpRequest(resolve, reject, onDone) { - let {data, lookup, family} = config; + let {data, lookup, family, httpVersion = 1, http2Options} = config; const {responseType, responseEncoding} = config; const method = config.method.toUpperCase(); let isDone; let rejected = false; let req; + httpVersion = +httpVersion; + + if (Number.isNaN(httpVersion)) { + throw TypeError(`Invalid protocol version: '${config.httpVersion}' is not a number`); + } + + if (httpVersion !== 1 && httpVersion !== 2) { + throw TypeError(`Unsupported protocol version '${httpVersion}'`); + } + + const isHttp2 = httpVersion === 2; + if (lookup) { const _lookup = callbackify$1(lookup, (value) => utils$1.isArray(value) ? value : [value]); // hotfix to support opt.all option which is required for node 20.x @@ -41917,8 +42197,17 @@ const httpAdapter = isHttpAdapterSupported && function httpAdapter(config) { }; } - // temporary internal emitter until the AxiosRequest class will be implemented - const emitter = new events.EventEmitter(); + const abortEmitter = new events.EventEmitter(); + + function abort(reason) { + try { + abortEmitter.emit('abort', !reason || reason.type ? new CanceledError(null, config, req) : reason); + } catch(err) { + console.warn('emit error', err); + } + } + + abortEmitter.once('abort', reject); const onFinished = () => { if (config.cancelToken) { @@ -41929,29 +42218,40 @@ const httpAdapter = isHttpAdapterSupported && function httpAdapter(config) { config.signal.removeEventListener('abort', abort); } - emitter.removeAllListeners(); + abortEmitter.removeAllListeners(); }; - onDone((value, isRejected) => { + if (config.cancelToken || config.signal) { + config.cancelToken && config.cancelToken.subscribe(abort); + if (config.signal) { + config.signal.aborted ? abort() : config.signal.addEventListener('abort', abort); + } + } + + onDone((response, isRejected) => { isDone = true; + if (isRejected) { rejected = true; onFinished(); + return; + } + + const {data} = response; + + if (data instanceof stream__default["default"].Readable || data instanceof stream__default["default"].Duplex) { + const offListeners = stream__default["default"].finished(data, () => { + offListeners(); + onFinished(); + }); + } else { + onFinished(); } }); - function abort(reason) { - emitter.emit('abort', !reason || reason.type ? new CanceledError(null, config, req) : reason); - } - emitter.once('abort', reject); - if (config.cancelToken || config.signal) { - config.cancelToken && config.cancelToken.subscribe(abort); - if (config.signal) { - config.signal.aborted ? abort() : config.signal.addEventListener('abort', abort); - } - } + // Parse url const fullPath = buildFullPath(config.baseURL, config.url, config.allowAbsoluteUrls); @@ -41959,6 +42259,21 @@ const httpAdapter = isHttpAdapterSupported && function httpAdapter(config) { const protocol = parsed.protocol || supportedProtocols[0]; if (protocol === 'data:') { + // Apply the same semantics as HTTP: only enforce if a finite, non-negative cap is set. + if (config.maxContentLength > -1) { + // Use the exact string passed to fromDataURI (config.url); fall back to fullPath if needed. + const dataUrl = String(config.url || fullPath || ''); + const estimated = estimateDataURLDecodedBytes(dataUrl); + + if (estimated > config.maxContentLength) { + return reject(new AxiosError( + 'maxContentLength size of ' + config.maxContentLength + ' exceeded', + AxiosError.ERR_BAD_RESPONSE, + config + )); + } + } + let convertedData; if (method !== 'GET') { @@ -42142,7 +42457,8 @@ const httpAdapter = isHttpAdapterSupported && function httpAdapter(config) { protocol, family, beforeRedirect: dispatchBeforeRedirect, - beforeRedirects: {} + beforeRedirects: {}, + http2Options }; // cacheable-lookup integration hotfix @@ -42159,18 +42475,23 @@ const httpAdapter = isHttpAdapterSupported && function httpAdapter(config) { let transport; const isHttpsRequest = isHttps.test(options.protocol); options.agent = isHttpsRequest ? config.httpsAgent : config.httpAgent; - if (config.transport) { - transport = config.transport; - } else if (config.maxRedirects === 0) { - transport = isHttpsRequest ? https__default["default"] : http__default["default"]; + + if (isHttp2) { + transport = http2Transport; } else { - if (config.maxRedirects) { - options.maxRedirects = config.maxRedirects; - } - if (config.beforeRedirect) { - options.beforeRedirects.config = config.beforeRedirect; + if (config.transport) { + transport = config.transport; + } else if (config.maxRedirects === 0) { + transport = isHttpsRequest ? https__default["default"] : http__default["default"]; + } else { + if (config.maxRedirects) { + options.maxRedirects = config.maxRedirects; + } + if (config.beforeRedirect) { + options.beforeRedirects.config = config.beforeRedirect; + } + transport = isHttpsRequest ? httpsFollow : httpFollow; } - transport = isHttpsRequest ? httpsFollow : httpFollow; } if (config.maxBodyLength > -1) { @@ -42190,7 +42511,7 @@ const httpAdapter = isHttpAdapterSupported && function httpAdapter(config) { const streams = [res]; - const responseLength = +res.headers['content-length']; + const responseLength = utils$1.toFiniteNumber(res.headers['content-length']); if (onDownloadProgress || maxDownloadRate) { const transformStream = new AxiosTransformStream$1({ @@ -42253,10 +42574,7 @@ const httpAdapter = isHttpAdapterSupported && function httpAdapter(config) { responseStream = streams.length > 1 ? stream__default["default"].pipeline(streams, utils$1.noop) : streams[0]; - const offListeners = stream__default["default"].finished(responseStream, () => { - offListeners(); - onFinished(); - }); + const response = { status: res.statusCode, @@ -42282,7 +42600,7 @@ const httpAdapter = isHttpAdapterSupported && function httpAdapter(config) { // stream.destroy() emit aborted event before calling reject() on Node.js v16 rejected = true; responseStream.destroy(); - reject(new AxiosError('maxContentLength size of ' + config.maxContentLength + ' exceeded', + abort(new AxiosError('maxContentLength size of ' + config.maxContentLength + ' exceeded', AxiosError.ERR_BAD_RESPONSE, config, lastRequest)); } }); @@ -42324,7 +42642,7 @@ const httpAdapter = isHttpAdapterSupported && function httpAdapter(config) { }); } - emitter.once('abort', err => { + abortEmitter.once('abort', err => { if (!responseStream.destroyed) { responseStream.emit('error', err); responseStream.destroy(); @@ -42332,9 +42650,12 @@ const httpAdapter = isHttpAdapterSupported && function httpAdapter(config) { }); }); - emitter.once('abort', err => { - reject(err); - req.destroy(err); + abortEmitter.once('abort', err => { + if (req.close) { + req.close(); + } else { + req.destroy(err); + } }); // Handle errors @@ -42356,7 +42677,7 @@ const httpAdapter = isHttpAdapterSupported && function httpAdapter(config) { const timeout = parseInt(config.timeout, 10); if (Number.isNaN(timeout)) { - reject(new AxiosError( + abort(new AxiosError( 'error trying to parse `config.timeout` to int', AxiosError.ERR_BAD_OPTION_VALUE, config, @@ -42378,14 +42699,16 @@ const httpAdapter = isHttpAdapterSupported && function httpAdapter(config) { if (config.timeoutErrorMessage) { timeoutErrorMessage = config.timeoutErrorMessage; } - reject(new AxiosError( + abort(new AxiosError( timeoutErrorMessage, transitional.clarifyTimeoutError ? AxiosError.ETIMEDOUT : AxiosError.ECONNABORTED, config, req )); - abort(); }); + } else { + // explicitly reset the socket timeout value for a possible `keep-alive` request + req.setTimeout(0); } @@ -42411,7 +42734,8 @@ const httpAdapter = isHttpAdapterSupported && function httpAdapter(config) { data.pipe(req); } else { - req.end(data); + data && req.write(data); + req.end(); } }); }; @@ -42433,27 +42757,38 @@ const cookies = platform.hasStandardBrowserEnv ? // Standard browser envs support document.cookie { - write(name, value, expires, path, domain, secure) { - const cookie = [name + '=' + encodeURIComponent(value)]; - - utils$1.isNumber(expires) && cookie.push('expires=' + new Date(expires).toGMTString()); - - utils$1.isString(path) && cookie.push('path=' + path); + write(name, value, expires, path, domain, secure, sameSite) { + if (typeof document === 'undefined') return; - utils$1.isString(domain) && cookie.push('domain=' + domain); + const cookie = [`${name}=${encodeURIComponent(value)}`]; - secure === true && cookie.push('secure'); + if (utils$1.isNumber(expires)) { + cookie.push(`expires=${new Date(expires).toUTCString()}`); + } + if (utils$1.isString(path)) { + cookie.push(`path=${path}`); + } + if (utils$1.isString(domain)) { + cookie.push(`domain=${domain}`); + } + if (secure === true) { + cookie.push('secure'); + } + if (utils$1.isString(sameSite)) { + cookie.push(`SameSite=${sameSite}`); + } document.cookie = cookie.join('; '); }, read(name) { - const match = document.cookie.match(new RegExp('(^|;\\s*)(' + name + ')=([^;]*)')); - return (match ? decodeURIComponent(match[3]) : null); + if (typeof document === 'undefined') return null; + const match = document.cookie.match(new RegExp('(?:^|; )' + name + '=([^;]*)')); + return match ? decodeURIComponent(match[1]) : null; }, remove(name) { - this.write(name, '', Date.now() - 86400000); + this.write(name, '', Date.now() - 86400000, '/'); } } @@ -42496,11 +42831,11 @@ function mergeConfig(config1, config2) { } // eslint-disable-next-line consistent-return - function mergeDeepProperties(a, b, prop , caseless) { + function mergeDeepProperties(a, b, prop, caseless) { if (!utils$1.isUndefined(b)) { - return getMergedValue(a, b, prop , caseless); + return getMergedValue(a, b, prop, caseless); } else if (!utils$1.isUndefined(a)) { - return getMergedValue(undefined, a, prop , caseless); + return getMergedValue(undefined, a, prop, caseless); } } @@ -42558,10 +42893,10 @@ function mergeConfig(config1, config2) { socketPath: defaultToConfig2, responseEncoding: defaultToConfig2, validateStatus: mergeDirectKeys, - headers: (a, b , prop) => mergeDeepProperties(headersToObject(a), headersToObject(b),prop, true) + headers: (a, b, prop) => mergeDeepProperties(headersToObject(a), headersToObject(b), prop, true) }; - utils$1.forEach(Object.keys(Object.assign({}, config1, config2)), function computeConfigValue(prop) { + utils$1.forEach(Object.keys({...config1, ...config2}), function computeConfigValue(prop) { const merge = mergeMap[prop] || mergeDeepProperties; const configValue = merge(config1[prop], config2[prop], prop); (utils$1.isUndefined(configValue) && merge !== mergeDirectKeys) || (config[prop] = configValue); @@ -42573,7 +42908,7 @@ function mergeConfig(config1, config2) { const resolveConfig = (config) => { const newConfig = mergeConfig({}, config); - let {data, withXSRFToken, xsrfHeaderName, xsrfCookieName, headers, auth} = newConfig; + let { data, withXSRFToken, xsrfHeaderName, xsrfCookieName, headers, auth } = newConfig; newConfig.headers = headers = AxiosHeaders$1.from(headers); @@ -42586,17 +42921,21 @@ const resolveConfig = (config) => { ); } - let contentType; - if (utils$1.isFormData(data)) { if (platform.hasStandardBrowserEnv || platform.hasStandardBrowserWebWorkerEnv) { - headers.setContentType(undefined); // Let the browser set it - } else if ((contentType = headers.getContentType()) !== false) { - // fix semicolon duplication issue for ReactNative FormData implementation - const [type, ...tokens] = contentType ? contentType.split(';').map(token => token.trim()).filter(Boolean) : []; - headers.setContentType([type || 'multipart/form-data', ...tokens].join('; ')); + headers.setContentType(undefined); // browser handles it + } else if (utils$1.isFunction(data.getHeaders)) { + // Node.js FormData (like form-data package) + const formHeaders = data.getHeaders(); + // Only set safe headers to avoid overwriting security headers + const allowedHeaders = ['content-type', 'content-length']; + Object.entries(formHeaders).forEach(([key, val]) => { + if (allowedHeaders.includes(key.toLowerCase())) { + headers.set(key, val); + } + }); } - } + } // Add xsrf header // This is only done if running in a standard browser environment. @@ -42713,15 +43052,18 @@ const xhrAdapter = isXHRAdapterSupported && function (config) { }; // Handle low level network errors - request.onerror = function handleError() { - // Real errors are hidden from us by the browser - // onerror should only fire if it's a network error - reject(new AxiosError('Network Error', AxiosError.ERR_NETWORK, config, request)); - - // Clean up request - request = null; + request.onerror = function handleError(event) { + // Browsers deliver a ProgressEvent in XHR onerror + // (message may be empty; when present, surface it) + // See https://developer.mozilla.org/docs/Web/API/XMLHttpRequest/error_event + const msg = event && event.message ? event.message : 'Network Error'; + const err = new AxiosError(msg, AxiosError.ERR_NETWORK, config, request); + // attach the underlying event for consumers who want details + err.event = event || null; + reject(err); + request = null; }; - + // Handle timeout request.ontimeout = function handleTimeout() { let timeoutErrorMessage = _config.timeout ? 'timeout of ' + _config.timeout + 'ms exceeded' : 'timeout exceeded'; @@ -42937,14 +43279,18 @@ const trackStream = (stream, chunkSize, onProgress, onFinish) => { }) }; -const isFetchSupported = typeof fetch === 'function' && typeof Request === 'function' && typeof Response === 'function'; -const isReadableStreamSupported = isFetchSupported && typeof ReadableStream === 'function'; +const DEFAULT_CHUNK_SIZE = 64 * 1024; + +const {isFunction} = utils$1; + +const globalFetchAPI = (({Request, Response}) => ({ + Request, Response +}))(utils$1.global); + +const { + ReadableStream: ReadableStream$1, TextEncoder: TextEncoder$1 +} = utils$1.global; -// used only inside the fetch adapter -const encodeText = isFetchSupported && (typeof TextEncoder === 'function' ? - ((encoder) => (str) => encoder.encode(str))(new TextEncoder()) : - async (str) => new Uint8Array(await new Response(str).arrayBuffer()) -); const test = (fn, ...args) => { try { @@ -42954,278 +43300,380 @@ const test = (fn, ...args) => { } }; -const supportsRequestStream = isReadableStreamSupported && test(() => { - let duplexAccessed = false; +const factory = (env) => { + env = utils$1.merge.call({ + skipUndefined: true + }, globalFetchAPI, env); - const hasContentType = new Request(platform.origin, { - body: new ReadableStream(), - method: 'POST', - get duplex() { - duplexAccessed = true; - return 'half'; - }, - }).headers.has('Content-Type'); + const {fetch: envFetch, Request, Response} = env; + const isFetchSupported = envFetch ? isFunction(envFetch) : typeof fetch === 'function'; + const isRequestSupported = isFunction(Request); + const isResponseSupported = isFunction(Response); - return duplexAccessed && !hasContentType; -}); + if (!isFetchSupported) { + return false; + } -const DEFAULT_CHUNK_SIZE = 64 * 1024; + const isReadableStreamSupported = isFetchSupported && isFunction(ReadableStream$1); -const supportsResponseStream = isReadableStreamSupported && - test(() => utils$1.isReadableStream(new Response('').body)); + const encodeText = isFetchSupported && (typeof TextEncoder$1 === 'function' ? + ((encoder) => (str) => encoder.encode(str))(new TextEncoder$1()) : + async (str) => new Uint8Array(await new Request(str).arrayBuffer()) + ); + const supportsRequestStream = isRequestSupported && isReadableStreamSupported && test(() => { + let duplexAccessed = false; -const resolvers = { - stream: supportsResponseStream && ((res) => res.body) -}; + const hasContentType = new Request(platform.origin, { + body: new ReadableStream$1(), + method: 'POST', + get duplex() { + duplexAccessed = true; + return 'half'; + }, + }).headers.has('Content-Type'); -isFetchSupported && (((res) => { - ['text', 'arrayBuffer', 'blob', 'formData', 'stream'].forEach(type => { - !resolvers[type] && (resolvers[type] = utils$1.isFunction(res[type]) ? (res) => res[type]() : - (_, config) => { - throw new AxiosError(`Response type '${type}' is not supported`, AxiosError.ERR_NOT_SUPPORT, config); - }); + return duplexAccessed && !hasContentType; }); -})(new Response)); -const getBodyLength = async (body) => { - if (body == null) { - return 0; - } + const supportsResponseStream = isResponseSupported && isReadableStreamSupported && + test(() => utils$1.isReadableStream(new Response('').body)); - if(utils$1.isBlob(body)) { - return body.size; - } + const resolvers = { + stream: supportsResponseStream && ((res) => res.body) + }; - if(utils$1.isSpecCompliantForm(body)) { - const _request = new Request(platform.origin, { - method: 'POST', - body, + isFetchSupported && ((() => { + ['text', 'arrayBuffer', 'blob', 'formData', 'stream'].forEach(type => { + !resolvers[type] && (resolvers[type] = (res, config) => { + let method = res && res[type]; + + if (method) { + return method.call(res); + } + + throw new AxiosError(`Response type '${type}' is not supported`, AxiosError.ERR_NOT_SUPPORT, config); + }); }); - return (await _request.arrayBuffer()).byteLength; - } + })()); - if(utils$1.isArrayBufferView(body) || utils$1.isArrayBuffer(body)) { - return body.byteLength; - } + const getBodyLength = async (body) => { + if (body == null) { + return 0; + } - if(utils$1.isURLSearchParams(body)) { - body = body + ''; - } + if (utils$1.isBlob(body)) { + return body.size; + } - if(utils$1.isString(body)) { - return (await encodeText(body)).byteLength; - } -}; + if (utils$1.isSpecCompliantForm(body)) { + const _request = new Request(platform.origin, { + method: 'POST', + body, + }); + return (await _request.arrayBuffer()).byteLength; + } -const resolveBodyLength = async (headers, body) => { - const length = utils$1.toFiniteNumber(headers.getContentLength()); + if (utils$1.isArrayBufferView(body) || utils$1.isArrayBuffer(body)) { + return body.byteLength; + } - return length == null ? getBodyLength(body) : length; -}; + if (utils$1.isURLSearchParams(body)) { + body = body + ''; + } -const fetchAdapter = isFetchSupported && (async (config) => { - let { - url, - method, - data, - signal, - cancelToken, - timeout, - onDownloadProgress, - onUploadProgress, - responseType, - headers, - withCredentials = 'same-origin', - fetchOptions - } = resolveConfig(config); + if (utils$1.isString(body)) { + return (await encodeText(body)).byteLength; + } + }; - responseType = responseType ? (responseType + '').toLowerCase() : 'text'; + const resolveBodyLength = async (headers, body) => { + const length = utils$1.toFiniteNumber(headers.getContentLength()); + + return length == null ? getBodyLength(body) : length; + }; + + return async (config) => { + let { + url, + method, + data, + signal, + cancelToken, + timeout, + onDownloadProgress, + onUploadProgress, + responseType, + headers, + withCredentials = 'same-origin', + fetchOptions + } = resolveConfig(config); - let composedSignal = composeSignals$1([signal, cancelToken && cancelToken.toAbortSignal()], timeout); + let _fetch = envFetch || fetch; - let request; + responseType = responseType ? (responseType + '').toLowerCase() : 'text'; - const unsubscribe = composedSignal && composedSignal.unsubscribe && (() => { + let composedSignal = composeSignals$1([signal, cancelToken && cancelToken.toAbortSignal()], timeout); + + let request = null; + + const unsubscribe = composedSignal && composedSignal.unsubscribe && (() => { composedSignal.unsubscribe(); - }); + }); - let requestContentLength; + let requestContentLength; - try { - if ( - onUploadProgress && supportsRequestStream && method !== 'get' && method !== 'head' && - (requestContentLength = await resolveBodyLength(headers, data)) !== 0 - ) { - let _request = new Request(url, { - method: 'POST', - body: data, - duplex: "half" - }); + try { + if ( + onUploadProgress && supportsRequestStream && method !== 'get' && method !== 'head' && + (requestContentLength = await resolveBodyLength(headers, data)) !== 0 + ) { + let _request = new Request(url, { + method: 'POST', + body: data, + duplex: "half" + }); - let contentTypeHeader; + let contentTypeHeader; - if (utils$1.isFormData(data) && (contentTypeHeader = _request.headers.get('content-type'))) { - headers.setContentType(contentTypeHeader); - } + if (utils$1.isFormData(data) && (contentTypeHeader = _request.headers.get('content-type'))) { + headers.setContentType(contentTypeHeader); + } - if (_request.body) { - const [onProgress, flush] = progressEventDecorator( - requestContentLength, - progressEventReducer(asyncDecorator(onUploadProgress)) - ); + if (_request.body) { + const [onProgress, flush] = progressEventDecorator( + requestContentLength, + progressEventReducer(asyncDecorator(onUploadProgress)) + ); - data = trackStream(_request.body, DEFAULT_CHUNK_SIZE, onProgress, flush); + data = trackStream(_request.body, DEFAULT_CHUNK_SIZE, onProgress, flush); + } } - } - if (!utils$1.isString(withCredentials)) { - withCredentials = withCredentials ? 'include' : 'omit'; - } + if (!utils$1.isString(withCredentials)) { + withCredentials = withCredentials ? 'include' : 'omit'; + } - // Cloudflare Workers throws when credentials are defined - // see https://github.com/cloudflare/workerd/issues/902 - const isCredentialsSupported = "credentials" in Request.prototype; - request = new Request(url, { - ...fetchOptions, - signal: composedSignal, - method: method.toUpperCase(), - headers: headers.normalize().toJSON(), - body: data, - duplex: "half", - credentials: isCredentialsSupported ? withCredentials : undefined - }); + // Cloudflare Workers throws when credentials are defined + // see https://github.com/cloudflare/workerd/issues/902 + const isCredentialsSupported = isRequestSupported && "credentials" in Request.prototype; + + const resolvedOptions = { + ...fetchOptions, + signal: composedSignal, + method: method.toUpperCase(), + headers: headers.normalize().toJSON(), + body: data, + duplex: "half", + credentials: isCredentialsSupported ? withCredentials : undefined + }; - let response = await fetch(request); + request = isRequestSupported && new Request(url, resolvedOptions); - const isStreamResponse = supportsResponseStream && (responseType === 'stream' || responseType === 'response'); + let response = await (isRequestSupported ? _fetch(request, fetchOptions) : _fetch(url, resolvedOptions)); - if (supportsResponseStream && (onDownloadProgress || (isStreamResponse && unsubscribe))) { - const options = {}; + const isStreamResponse = supportsResponseStream && (responseType === 'stream' || responseType === 'response'); - ['status', 'statusText', 'headers'].forEach(prop => { - options[prop] = response[prop]; - }); + if (supportsResponseStream && (onDownloadProgress || (isStreamResponse && unsubscribe))) { + const options = {}; - const responseContentLength = utils$1.toFiniteNumber(response.headers.get('content-length')); + ['status', 'statusText', 'headers'].forEach(prop => { + options[prop] = response[prop]; + }); - const [onProgress, flush] = onDownloadProgress && progressEventDecorator( - responseContentLength, - progressEventReducer(asyncDecorator(onDownloadProgress), true) - ) || []; + const responseContentLength = utils$1.toFiniteNumber(response.headers.get('content-length')); - response = new Response( - trackStream(response.body, DEFAULT_CHUNK_SIZE, onProgress, () => { - flush && flush(); - unsubscribe && unsubscribe(); - }), - options - ); - } + const [onProgress, flush] = onDownloadProgress && progressEventDecorator( + responseContentLength, + progressEventReducer(asyncDecorator(onDownloadProgress), true) + ) || []; - responseType = responseType || 'text'; + response = new Response( + trackStream(response.body, DEFAULT_CHUNK_SIZE, onProgress, () => { + flush && flush(); + unsubscribe && unsubscribe(); + }), + options + ); + } - let responseData = await resolvers[utils$1.findKey(resolvers, responseType) || 'text'](response, config); + responseType = responseType || 'text'; - !isStreamResponse && unsubscribe && unsubscribe(); + let responseData = await resolvers[utils$1.findKey(resolvers, responseType) || 'text'](response, config); - return await new Promise((resolve, reject) => { - settle(resolve, reject, { - data: responseData, - headers: AxiosHeaders$1.from(response.headers), - status: response.status, - statusText: response.statusText, - config, - request - }); - }) - } catch (err) { - unsubscribe && unsubscribe(); + !isStreamResponse && unsubscribe && unsubscribe(); - if (err && err.name === 'TypeError' && /Load failed|fetch/i.test(err.message)) { - throw Object.assign( - new AxiosError('Network Error', AxiosError.ERR_NETWORK, config, request), - { - cause: err.cause || err - } - ) + return await new Promise((resolve, reject) => { + settle(resolve, reject, { + data: responseData, + headers: AxiosHeaders$1.from(response.headers), + status: response.status, + statusText: response.statusText, + config, + request + }); + }) + } catch (err) { + unsubscribe && unsubscribe(); + + if (err && err.name === 'TypeError' && /Load failed|fetch/i.test(err.message)) { + throw Object.assign( + new AxiosError('Network Error', AxiosError.ERR_NETWORK, config, request), + { + cause: err.cause || err + } + ) + } + + throw AxiosError.from(err, err && err.code, config, request); } + } +}; + +const seedCache = new Map(); + +const getFetch = (config) => { + let env = (config && config.env) || {}; + const {fetch, Request, Response} = env; + const seeds = [ + Request, Response, fetch + ]; + + let len = seeds.length, i = len, + seed, target, map = seedCache; - throw AxiosError.from(err, err && err.code, config, request); + while (i--) { + seed = seeds[i]; + target = map.get(seed); + + target === undefined && map.set(seed, target = (i ? new Map() : factory(env))); + + map = target; } -}); + return target; +}; + +getFetch(); + +/** + * Known adapters mapping. + * Provides environment-specific adapters for Axios: + * - `http` for Node.js + * - `xhr` for browsers + * - `fetch` for fetch API-based requests + * + * @type {Object} + */ const knownAdapters = { http: httpAdapter, xhr: xhrAdapter, - fetch: fetchAdapter + fetch: { + get: getFetch, + } }; +// Assign adapter names for easier debugging and identification utils$1.forEach(knownAdapters, (fn, value) => { if (fn) { try { - Object.defineProperty(fn, 'name', {value}); + Object.defineProperty(fn, 'name', { value }); } catch (e) { // eslint-disable-next-line no-empty } - Object.defineProperty(fn, 'adapterName', {value}); + Object.defineProperty(fn, 'adapterName', { value }); } }); +/** + * Render a rejection reason string for unknown or unsupported adapters + * + * @param {string} reason + * @returns {string} + */ const renderReason = (reason) => `- ${reason}`; +/** + * Check if the adapter is resolved (function, null, or false) + * + * @param {Function|null|false} adapter + * @returns {boolean} + */ const isResolvedHandle = (adapter) => utils$1.isFunction(adapter) || adapter === null || adapter === false; -const adapters = { - getAdapter: (adapters) => { - adapters = utils$1.isArray(adapters) ? adapters : [adapters]; +/** + * Get the first suitable adapter from the provided list. + * Tries each adapter in order until a supported one is found. + * Throws an AxiosError if no adapter is suitable. + * + * @param {Array|string|Function} adapters - Adapter(s) by name or function. + * @param {Object} config - Axios request configuration + * @throws {AxiosError} If no suitable adapter is available + * @returns {Function} The resolved adapter function + */ +function getAdapter(adapters, config) { + adapters = utils$1.isArray(adapters) ? adapters : [adapters]; - const {length} = adapters; - let nameOrAdapter; - let adapter; + const { length } = adapters; + let nameOrAdapter; + let adapter; - const rejectedReasons = {}; + const rejectedReasons = {}; - for (let i = 0; i < length; i++) { - nameOrAdapter = adapters[i]; - let id; + for (let i = 0; i < length; i++) { + nameOrAdapter = adapters[i]; + let id; - adapter = nameOrAdapter; + adapter = nameOrAdapter; - if (!isResolvedHandle(nameOrAdapter)) { - adapter = knownAdapters[(id = String(nameOrAdapter)).toLowerCase()]; + if (!isResolvedHandle(nameOrAdapter)) { + adapter = knownAdapters[(id = String(nameOrAdapter)).toLowerCase()]; - if (adapter === undefined) { - throw new AxiosError(`Unknown adapter '${id}'`); - } - } - - if (adapter) { - break; + if (adapter === undefined) { + throw new AxiosError(`Unknown adapter '${id}'`); } + } - rejectedReasons[id || '#' + i] = adapter; + if (adapter && (utils$1.isFunction(adapter) || (adapter = adapter.get(config)))) { + break; } - if (!adapter) { + rejectedReasons[id || '#' + i] = adapter; + } - const reasons = Object.entries(rejectedReasons) - .map(([id, state]) => `adapter ${id} ` + - (state === false ? 'is not supported by the environment' : 'is not available in the build') - ); + if (!adapter) { + const reasons = Object.entries(rejectedReasons) + .map(([id, state]) => `adapter ${id} ` + + (state === false ? 'is not supported by the environment' : 'is not available in the build') + ); - let s = length ? - (reasons.length > 1 ? 'since :\n' + reasons.map(renderReason).join('\n') : ' ' + renderReason(reasons[0])) : - 'as no adapter specified'; + let s = length ? + (reasons.length > 1 ? 'since :\n' + reasons.map(renderReason).join('\n') : ' ' + renderReason(reasons[0])) : + 'as no adapter specified'; - throw new AxiosError( - `There is no suitable adapter to dispatch the request ` + s, - 'ERR_NOT_SUPPORT' - ); - } + throw new AxiosError( + `There is no suitable adapter to dispatch the request ` + s, + 'ERR_NOT_SUPPORT' + ); + } - return adapter; - }, + return adapter; +} + +/** + * Exports Axios adapters and utility to resolve an adapter + */ +const adapters = { + /** + * Resolve an adapter from a list of adapter names or functions. + * @type {Function} + */ + getAdapter, + + /** + * Exposes all known adapters + * @type {Object} + */ adapters: knownAdapters }; @@ -43268,7 +43716,7 @@ function dispatchRequest(config) { config.headers.setContentType('application/x-www-form-urlencoded', false); } - const adapter = adapters.getAdapter(config.adapter || defaults$1.adapter); + const adapter = adapters.getAdapter(config.adapter || defaults$1.adapter, config); return adapter(config).then(function onAdapterResolution(response) { throwIfCancellationRequested(config); @@ -43539,8 +43987,8 @@ class Axios { if (!synchronousRequestInterceptors) { const chain = [dispatchRequest.bind(this), undefined]; - chain.unshift.apply(chain, requestInterceptorChain); - chain.push.apply(chain, responseInterceptorChain); + chain.unshift(...requestInterceptorChain); + chain.push(...responseInterceptorChain); len = chain.length; promise = Promise.resolve(config); @@ -43556,8 +44004,6 @@ class Axios { let newConfig = config; - i = 0; - while (i < len) { const onFulfilled = requestInterceptorChain[i++]; const onRejected = requestInterceptorChain[i++]; @@ -43861,6 +44307,12 @@ const HttpStatusCode = { LoopDetected: 508, NotExtended: 510, NetworkAuthenticationRequired: 511, + WebServerIsDown: 521, + ConnectionTimedOut: 522, + OriginIsUnreachable: 523, + TimeoutOccurred: 524, + SslHandshakeFailed: 525, + InvalidSslCertificate: 526, }; Object.entries(HttpStatusCode).forEach(([key, value]) => { @@ -44001,6 +44453,7 @@ const consoleUrl = core.getInput('console_url', { required: false }); const clientId = core.getInput('client_id', { required: true }); const clientSecret = core.getInput('client_secret', { required: true }); const clientApp = core.getInput('app_file', { required: true }); +const teamName = core.getInput('team_name', { required: false }) || 'Default'; const DOWNLOAD_POLL_TIME = 6/*seconds*/ * 1000/*ms*/; const STATUS_POLL_TIME = 30/*seconds*/ * 1000/*ms*/; @@ -44092,6 +44545,15 @@ async function uploadApp() { } }); core.info(`Upload successful for ${file}`); + + core.debug(`buildId: ${response.data.buildId}`); + core.debug(`zdevAppId: ${response.data.zdevAppId}`); + core.debug(`teamId: ${response.data.teamId}`); + core.debug(`buildUploadedAt: ${response.data.buildUploadedAt}`); + core.debug(`buildNumber: ${response.data.buildNumber}`); + core.debug(`bundleIdentifier: ${response.data.bundleIdentifier}`); + core.debug(`appVersion: ${response.data.appVersion}`); + const result = response.data; result.originalFileName = file; results.push(result); @@ -44184,6 +44646,41 @@ async function pollDownload(appId, originalFileName) { } } +async function getTeams() { + const loginResponse = await loginHttpRequest(); + try { + const response = await axios.get(`${baseUrl}/api/auth/public/v1/teams`, { + headers: { + 'Authorization': 'Bearer ' + loginResponse.accessToken + } + }); + return response.data.content; + } catch (error) { + core.error(`Failed to fetch teams list: ${error.message}`); + throw error; + } +} + +async function assignAppToTeam(appId, teamId) { + const loginResponse = await loginHttpRequest(); + try { + const response = await axios.put(`${baseUrl}/api/zdev-app/public/v1/apps/${appId}/upload`, + { "teamId": teamId }, + { + headers: { + 'Authorization': 'Bearer ' + loginResponse.accessToken, + 'Content-Type': 'application/json' + } + } + ); + core.info(`App assigned to team successfully`); + return response.data; + } catch (error) { + core.error(`Failed to assign app to team: ${error.message}`); + throw error; + } +} + function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } @@ -44196,11 +44693,58 @@ core.debug(`app: ${clientApp}`); uploadApp().then(uploadResults => { const promises = uploadResults.map(result => - pollStatus(result.buildId).then(statusResult => { - if (statusResult.zdevMetadata.analysis !== 'Failed') { - return pollDownload(statusResult.id, result.originalFileName); + (async () => { + try { + // Check if app needs to be assigned to a team + if (result.teamId === null || result.teamId === undefined) { + core.info(`App ${result.zdevAppId} not assigned to a team, attempting to assign to team: ${teamName}`); + + try { + const teams = await getTeams(); + let targetTeamId = null; + + // Find the team ID matching the requested team name + for (const team of teams) { + if (team.name === teamName) { + targetTeamId = team.id; + core.info(`Found team "${teamName}" with ID: ${targetTeamId}`); + break; + } + } + + // If team not found, use Default team + if (targetTeamId === null) { + core.info(`Team "${teamName}" not found, attempting to use Default team`); + for (const team of teams) { + if (team.name === 'Default') { + targetTeamId = team.id; + core.info(`Found 'Default' team with ID: ${targetTeamId}`); + break; + } + } + } + + if (targetTeamId === null) { + core.error('Could not find team to assign the app to. Continuing with scan...'); + } else { + await assignAppToTeam(result.zdevAppId, targetTeamId); + core.info(`App ${result.zdevAppId} successfully assigned to team ${teamName}`); + } + } catch (teamAssignmentError) { + core.warning(`Team assignment failed: ${teamAssignmentError.message}. Continuing with scan...`); + } + } else { + core.info(`App ${result.zdevAppId} already belongs to team (ID: ${result.teamId})`); + } + + const statusResult = await pollStatus(result.buildId); + if (statusResult.zdevMetadata.analysis !== 'Failed') { + return pollDownload(statusResult.id, result.originalFileName); + } + } catch (error) { + throw error; } - }) + })() ); Promise.all(promises).then((downloadResults) => { diff --git a/src/action.js b/src/action.js index b57fcac..4ae1d99 100644 --- a/src/action.js +++ b/src/action.js @@ -10,6 +10,7 @@ const consoleUrl = core.getInput('console_url', { required: false }); const clientId = core.getInput('client_id', { required: true }); const clientSecret = core.getInput('client_secret', { required: true }); const clientApp = core.getInput('app_file', { required: true }); +const teamName = core.getInput('team_name', { required: false }) || 'Default'; const DOWNLOAD_POLL_TIME = 6/*seconds*/ * 1000/*ms*/; const STATUS_POLL_TIME = 30/*seconds*/ * 1000/*ms*/; @@ -101,6 +102,15 @@ async function uploadApp() { } }); core.info(`Upload successful for ${file}`); + + core.debug(`buildId: ${response.data.buildId}`); + core.debug(`zdevAppId: ${response.data.zdevAppId}`); + core.debug(`teamId: ${response.data.teamId}`); + core.debug(`buildUploadedAt: ${response.data.buildUploadedAt}`); + core.debug(`buildNumber: ${response.data.buildNumber}`); + core.debug(`bundleIdentifier: ${response.data.bundleIdentifier}`); + core.debug(`appVersion: ${response.data.appVersion}`); + const result = response.data; result.originalFileName = file; results.push(result); @@ -193,6 +203,41 @@ async function pollDownload(appId, originalFileName) { } } +async function getTeams() { + const loginResponse = await loginHttpRequest(); + try { + const response = await axios.get(`${baseUrl}/api/auth/public/v1/teams`, { + headers: { + 'Authorization': 'Bearer ' + loginResponse.accessToken + } + }); + return response.data.content; + } catch (error) { + core.error(`Failed to fetch teams list: ${error.message}`); + throw error; + } +} + +async function assignAppToTeam(appId, teamId) { + const loginResponse = await loginHttpRequest(); + try { + const response = await axios.put(`${baseUrl}/api/zdev-app/public/v1/apps/${appId}/upload`, + { "teamId": teamId }, + { + headers: { + 'Authorization': 'Bearer ' + loginResponse.accessToken, + 'Content-Type': 'application/json' + } + } + ); + core.info(`App assigned to team successfully`); + return response.data; + } catch (error) { + core.error(`Failed to assign app to team: ${error.message}`); + throw error; + } +} + function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } @@ -205,11 +250,58 @@ core.debug(`app: ${clientApp}`); uploadApp().then(uploadResults => { const promises = uploadResults.map(result => - pollStatus(result.buildId).then(statusResult => { - if (statusResult.zdevMetadata.analysis !== 'Failed') { - return pollDownload(statusResult.id, result.originalFileName); + (async () => { + try { + // Check if app needs to be assigned to a team + if (result.teamId === null || result.teamId === undefined) { + core.info(`App ${result.zdevAppId} not assigned to a team, attempting to assign to team: ${teamName}`); + + try { + const teams = await getTeams(); + let targetTeamId = null; + + // Find the team ID matching the requested team name + for (const team of teams) { + if (team.name === teamName) { + targetTeamId = team.id; + core.info(`Found team "${teamName}" with ID: ${targetTeamId}`); + break; + } + } + + // If team not found, use Default team + if (targetTeamId === null) { + core.info(`Team "${teamName}" not found, attempting to use Default team`); + for (const team of teams) { + if (team.name === 'Default') { + targetTeamId = team.id; + core.info(`Found 'Default' team with ID: ${targetTeamId}`); + break; + } + } + } + + if (targetTeamId === null) { + core.error('Could not find team to assign the app to. Continuing with scan...'); + } else { + await assignAppToTeam(result.zdevAppId, targetTeamId); + core.info(`App ${result.zdevAppId} successfully assigned to team ${teamName}`); + } + } catch (teamAssignmentError) { + core.warning(`Team assignment failed: ${teamAssignmentError.message}. Continuing with scan...`); + } + } else { + core.info(`App ${result.zdevAppId} already belongs to team (ID: ${result.teamId})`); + } + + const statusResult = await pollStatus(result.buildId); + if (statusResult.zdevMetadata.analysis !== 'Failed') { + return pollDownload(statusResult.id, result.originalFileName); + } + } catch (error) { + throw error; } - }) + })() ); Promise.all(promises).then((downloadResults) => { From 742b2b01308b966c43955e8dc0e92c7fe59bbbf9 Mon Sep 17 00:00:00 2001 From: Igor Matlin Date: Thu, 18 Dec 2025 11:49:13 -0600 Subject: [PATCH 2/5] Added test action back. Renamed variables to reflect their content properly. --- .../workflows}/zScanAction.yml | 27 +++++++++---------- README.md | 1 + Sample_Insecure_Bank_App_zscan.sarif | 1 + dist/index.js | 18 +++++++------ src/action.js | 17 ++++++------ 5 files changed, 34 insertions(+), 30 deletions(-) rename {workflows => .github/workflows}/zScanAction.yml (74%) create mode 100644 Sample_Insecure_Bank_App_zscan.sarif diff --git a/workflows/zScanAction.yml b/.github/workflows/zScanAction.yml similarity index 74% rename from workflows/zScanAction.yml rename to .github/workflows/zScanAction.yml index 5a20221..d13259d 100644 --- a/workflows/zScanAction.yml +++ b/.github/workflows/zScanAction.yml @@ -19,9 +19,9 @@ name: "Zimperium zScan" on: push: - branches: [ "main" ] + branches: [ "master" ] pull_request: - branches: [ "main" ] + branches: [ "master" ] permissions: contents: read @@ -36,26 +36,25 @@ jobs: actions: read # only required for a private repository by github/codeql-action/upload-sarif to get the Action run status steps: - name: Checkout repository - uses: actions/checkout@v3 - - # - name: Execute gradle build - # run: echo "./gradlew build" + uses: actions/checkout@v4 - name: Run Zimperium zScan - uses: zimperium/zscanmarketplace@v1.3 + uses: zimperium/zscanmarketplace@v1 timeout-minutes: 60 with: - # REPLACE: Zimperium Console URL - console_url: "https://mapsfreemium.zimperium.com" - # REPLACE: Zimperium Client ID - client_id: ${{ secrets.ZSCAN_CLIENT_ID }} - # REPLACE: Zimperium Client Secret + # REPLACE: Zimperium Console URL + console_url: "https://zc202.zimperium.com" + # REPLACE: Zimperium Client ID + client_id: ${{ vars.ZSCAN_CLIENT_ID }} + # REPLACE: Zimperium Client Secret client_secret: ${{ secrets.ZSCAN_CLIENT_SECRET }} - # REPLACE: The path to an .ipa or .apk + # REPLACE: The path to an .ipa or .apk app_file: ./Sample_Insecure_Bank_App.apk + # REPLACE: Team name to assign the app to (default: Default) + team_name: "Americas" - name: Upload SARIF file - uses: github/codeql-action/upload-sarif@v3 + uses: github/codeql-action/upload-sarif@v4 with: sarif_file: Sample_Insecure_Bank_App_zscan.sarif \ No newline at end of file diff --git a/README.md b/README.md index f62ab7d..1921f39 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,7 @@ The zimperium-zscan action scans your mobile app binary (ios or android) and ide client_id: client_secret: ${{ secrets.ZSCAN_CLIENT_SECRET }} app_file: ./Sample_Insecure_Bank_App.apk + team_name: Default - name: Upload SARIF file uses: github/codeql-action/upload-sarif@v3 diff --git a/Sample_Insecure_Bank_App_zscan.sarif b/Sample_Insecure_Bank_App_zscan.sarif new file mode 100644 index 0000000..69418ae --- /dev/null +++ b/Sample_Insecure_Bank_App_zscan.sarif @@ -0,0 +1 @@ +{"$schema":"https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json","version":"2.1.0","runs":[{"tool":{"driver":{"name":"Zimperium zScan","semanticVersion":"0.0","informationUri":"https://www.zimperium.com/zscan","rules":[{"id":"63ee197a-f141-dd00-ed71-43f400000000","name":"PossibleHardcodedInformation","shortDescription":{"text":"Files may contain hardcoded sensitive information such as user names, passwords, and keys."},"defaultConfiguration":{},"helpUri":"https://www.zimperium.com/zscan","help":{"text":"Files may contain hardcoded sensitive information such as user names, passwords, and keys.\n\nUsing clear text storage for sensitive information in an Android app can have potentially risky results, including exposure of confidential information, violation of compliance requirements, damage to reputation, and the potential for man-in-the-middle (MITM) attacks. To avoid this, app developers should follow best practices for secure data storage and encrypt sensitive information to prevent unauthorized access and potential attacks.","markdown":"

\uD83D\uDFE6  Description

Files may contain hardcoded sensitive information such as user names, passwords, and keys.

\uD83D\uDFE6  Business Impact

Using clear text storage for sensitive information in an Android app can have potentially risky results, including exposure of confidential information, violation of compliance requirements, damage to reputation, and the potential for man-in-the-middle (MITM) attacks. To avoid this, app developers should follow best practices for secure data storage and encrypt sensitive information to prevent unauthorized access and potential attacks.

"},"properties":{"tags":["MASVS MSTG-STORAGE-14","OWASP M9"],"severity":"Low","type":"privacy","category":"Vulnerability","subcategory":"Data Leakage"}},{"id":"63a5da01-834f-de00-1102-f91500000000","name":"SensitiveDataProtection","shortDescription":{"text":"This app is applying properly sensitive data management through the user interface (UI)."},"defaultConfiguration":{"level":"note"},"helpUri":"https://www.zimperium.com/zscan","help":{"text":"This app is applying properly sensitive data management through the user interface (UI).\n\nThe app follows the best practice to prevent sensitive data leakage in the UI.","markdown":"

\uD83D\uDFE6  Description

This app is applying properly sensitive data management through the user interface (UI).

\uD83D\uDFE6  Business Impact

The app follows the best practice to prevent sensitive data leakage in the UI.

"},"properties":{"tags":["MASVS MSTG-STORAGE-7"],"severity":"Best Practices","type":"privacy","category":"Data Leakage","subcategory":"UI"}},{"id":"63a02646-7625-7900-1469-a8d500000000","name":"KeyboardCacheDisabled","shortDescription":{"text":"The keyboard cache is disabled on text inputs that process sensitive data."},"defaultConfiguration":{"level":"note"},"helpUri":"https://www.zimperium.com/zscan","help":{"text":"The keyboard cache is disabled on text inputs that process sensitive data.\n\nThis application follows a best practice to remove sensitive data, which could be read by an attacker, from the keyboard cache.","markdown":"

\uD83D\uDFE6  Description

The keyboard cache is disabled on text inputs that process sensitive data.

\uD83D\uDFE6  Business Impact

This application follows a best practice to remove sensitive data, which could be read by an attacker, from the keyboard cache.

"},"properties":{"tags":["MASVS MSTG-STORAGE-5"],"severity":"Best Practices","type":"privacy","category":"Data Leakage","subcategory":"UI"}},{"id":"61cb48eb-3ea7-004a-a9a1-cd8400000000","name":"UnprotectedComponent","shortDescription":{"text":"The content provider is not protected by signature permission and exported in the AndroidManifest.xml file."},"defaultConfiguration":{},"helpUri":"https://www.zimperium.com/zscan","help":{"text":"The content provider is not protected by signature permission and exported in the AndroidManifest.xml file.\n\nAn attacker can read or write the exported content provider, leading to leakage of sensitive information or unpredictable application behavior. If the content providers are used as interfaces for a database, the attacker can access and potentially extract, update, insert and delete information. In addition, there might be options for SQL injection and path traversal attacks. ","markdown":"

\uD83D\uDFE6  Description

The content provider is not protected by signature permission and exported in the AndroidManifest.xml file.

\uD83D\uDFE6  Business Impact

An attacker can read or write the exported content provider, leading to leakage of sensitive information or unpredictable application behavior. If the content providers are used as interfaces for a database, the attacker can access and potentially extract, update, insert and delete information. In addition, there might be options for SQL injection and path traversal attacks.

"},"properties":{"tags":["CVSS 2.0 score 4.6","CVSS 2.0 vector AV:L/AC:L/Au:N/C:P/I:P/A:P","CVSS 3.1 score 9.8","CVSS 3.1 vector AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H","CWE-359","CWE-926","MASVS MSTG-PLATFORM-4"],"severity":"High","type":"privacy","category":"Vulnerability","subcategory":"Components"}},{"id":"63fe316a-b4ef-1e00-171b-d42800000000","name":"StringxmlFileMayContainHardcodedCredentialsOrSensitiveInformation","shortDescription":{"text":"The string.xml file may contain hardcoded sensitive information such as usernames, passwords, and keys."},"defaultConfiguration":{},"helpUri":"https://www.zimperium.com/zscan","help":{"text":"The string.xml file may contain hardcoded sensitive information such as usernames, passwords, and keys.\n\nUsing clear text storage for sensitive information in an Android app can cause exposure of confidential information, violation of compliance requirements, damage to reputation, and the potential for Man-in-the-Middle (MITM) attacks. To avoid this, app developers should follow best practices for secure data storage and encrypt sensitive information to prevent unauthorized access and potential attacks.","markdown":"

\uD83D\uDFE6  Description

The string.xml file may contain hardcoded sensitive information such as usernames, passwords, and keys.

\uD83D\uDFE6  Business Impact

Using clear text storage for sensitive information in an Android app can cause exposure of confidential information, violation of compliance requirements, damage to reputation, and the potential for Man-in-the-Middle (MITM) attacks. To avoid this, app developers should follow best practices for secure data storage and encrypt sensitive information to prevent unauthorized access and potential attacks.

"},"properties":{"tags":["OWASP M3"],"severity":"Medium","type":"security","category":"Vulnerability","subcategory":"Data Leakage"}},{"id":"5225b063-3a08-f68e-2bad-572100000000","name":"DebuggableApp","shortDescription":{"text":"This app has the \"android:debuggable\" attribute in the Android manifest file set to true."},"defaultConfiguration":{},"helpUri":"https://www.zimperium.com/zscan","help":{"text":"This app has the \"android:debuggable\" attribute in the Android manifest file set to true.\n\nReleasing an app with the “android:debuggable” attribute enabled exposes information that can possibly be used to make reverse engineering of the app much easier (information such as debug log statements, debug symbols etc).","markdown":"

\uD83D\uDFE6  Description

This app has the \"android:debuggable\" attribute in the Android manifest file set to true.

\uD83D\uDFE6  Business Impact

Releasing an app with the “android:debuggable” attribute enabled exposes information that can possibly be used to make reverse engineering of the app much easier (information such as debug log statements, debug symbols etc).

"},"properties":{"tags":["CVSS 2.0 score 3.5","CVSS 2.0 vector AV:N/AC:M/Au:S/C:P/I:N/A:N","CVSS 3.1 score 6.5","CVSS 3.1 vector AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N","MASVS MSTG-CODE-4","OWASP M7"],"severity":"High","type":"security","category":"Code Analysis","subcategory":"Binary Protections Testing"}},{"id":"5b86446e-1cfc-cd18-3a11-f25c00000000","name":"ZimperiumZ9MalwareScan","shortDescription":{"text":"The Zimperium z9 detection engine discovered that this application contains malware and is an active threat."},"defaultConfiguration":{},"helpUri":"https://www.zimperium.com/zscan","help":{"text":"The Zimperium z9 detection engine discovered that this application contains malware and is an active threat.\n\nThe Zimperium z9 engine analyzed the app and determined that it contains malware. This app should be considered as an active threat.","markdown":"

\uD83D\uDFE6  Description

The Zimperium z9 detection engine discovered that this application contains malware and is an active threat.

\uD83D\uDFE6  Business Impact

The Zimperium z9 engine analyzed the app and determined that it contains malware. This app should be considered as an active threat.

"},"properties":{"tags":[],"severity":"Critical","type":"security","category":"Vulnerability","subcategory":"Malware Detection"}},{"id":"60d0b70e-f1a7-9107-8fbb-669900000000","name":"ImplicitActivityStart","shortDescription":{"text":"The application has an implicit activity start vulnerability."},"defaultConfiguration":{},"helpUri":"https://www.zimperium.com/zscan","help":{"text":"The application has an implicit activity start vulnerability.\n\nUsing an implicit activity start is not recommended since the component is not set and the Android OS may ask the user what to start. An attacker could register their own activity with an action from the intent in the AndroidManifest.xml file and specify a 999 priority in the intent-filter attribute.\r\n\r\nAdditionally, using an implicit intent without a signature permission protection level while sending broadcasts enables any third-party application to intercept or hijack information between components. This can lead to the disclosure of application usage statistics and application states.","markdown":"

\uD83D\uDFE6  Description

The application has an implicit activity start vulnerability.

\uD83D\uDFE6  Business Impact

Using an implicit activity start is not recommended since the component is not set and the Android OS may ask the user what to start. An attacker could register their own activity with an action from the intent in the AndroidManifest.xml file and specify a 999 priority in the intent-filter attribute.\r
\r
Additionally, using an implicit intent without a signature permission protection level while sending broadcasts enables any third-party application to intercept or hijack information between components. This can lead to the disclosure of application usage statistics and application states.

"},"properties":{"tags":["CWE-927"],"severity":"Medium","type":"security","category":"Vulnerability","subcategory":"Components"}},{"id":"60e3f8fc-7552-e2f0-e9ca-648e00000000","name":"ImplicitIntent","shortDescription":{"text":"An implicit intent is used without signature permission for broadcasting and sending it to another component of the application. "},"defaultConfiguration":{},"helpUri":"https://www.zimperium.com/zscan","help":{"text":"An implicit intent is used without signature permission for broadcasting and sending it to another component of the application. \n\nUsing an implicit intent without signature protection during the broadcast allows any third-party application installed on the same mobile device to intercept or hijack information between components, leading to leakage of sensitive information or falsification of data broadcasts between components of the application.","markdown":"

\uD83D\uDFE6  Description

An implicit intent is used without signature permission for broadcasting and sending it to another component of the application.

\uD83D\uDFE6  Business Impact

Using an implicit intent without signature protection during the broadcast allows any third-party application installed on the same mobile device to intercept or hijack information between components, leading to leakage of sensitive information or falsification of data broadcasts between components of the application.

"},"properties":{"tags":["CWE-927"],"severity":"Medium","type":"security","category":"Vulnerability","subcategory":"Components"}},{"id":"63c7aacb-f7ca-34f8-62f5-126000000000","name":"HardcodedKeys","shortDescription":{"text":"A hardcoded cryptographic key was found in the app."},"defaultConfiguration":{},"helpUri":"https://www.zimperium.com/zscan","help":{"text":"A hardcoded cryptographic key was found in the app.\n\nHardcoded keys might be leaked or used for seeding. It is important to limit their scope.","markdown":"

\uD83D\uDFE6  Description

A hardcoded cryptographic key was found in the app.

\uD83D\uDFE6  Business Impact

Hardcoded keys might be leaked or used for seeding. It is important to limit their scope.

"},"properties":{"tags":["OWASP M10"],"severity":"Medium","type":"security","category":"Compliance","subcategory":"Cryptography"}},{"id":"63770e36-eff2-45a3-8feb-909900000000","name":"ManifestdeclaredBroadcastReceiverForNonsystemActions","shortDescription":{"text":"This app uses a manifest-declared broadcast receiver for non-system actions. The detected app's broadcast receiver is dynamically registered in the code, is not protected by signature permission in the AndroidManifest.xml file, and is exported.\r\n\r\n"},"defaultConfiguration":{},"helpUri":"https://www.zimperium.com/zscan","help":{"text":"This app uses a manifest-declared broadcast receiver for non-system actions. The detected app's broadcast receiver is dynamically registered in the code, is not protected by signature permission in the AndroidManifest.xml file, and is exported.\r\n\r\n\n\nUsing a malware app, an attacker can broadcast arbitrary data to the exported receiver, which can lead to invocation of different components of the app or to code execution.","markdown":"

\uD83D\uDFE6  Description

This app uses a manifest-declared broadcast receiver for non-system actions. The detected app's broadcast receiver is dynamically registered in the code, is not protected by signature permission in the AndroidManifest.xml file, and is exported.\r
\r

\uD83D\uDFE6  Business Impact

Using a malware app, an attacker can broadcast arbitrary data to the exported receiver, which can lead to invocation of different components of the app or to code execution.

"},"properties":{"tags":[],"severity":"Low","type":"security","category":"Vulnerability","subcategory":"Components"}},{"id":"6268f908-f255-393a-3274-df8500000000","name":"ExposedActivity","shortDescription":{"text":"The Android app exposes an activity component for use by other apps, but might not properly restrict which apps can launch the component or access the data it contains.\r\nAll activities are non-exported by default, unless the android:exported attribute is set to \"true\" or the intent-filter element is defined.\r\nOnce we target Android 12, the system will require us to be explicit about the value for android:exported.\r\nIf the intent-filter is registered to an implicit action intent that can be broadcast by all apps, the activity exposes itself to a third-party app.\r\n\r\nThis activity is exported or registered to standard broadcast actions.\r\nExamples of common implicit actions include:\r\nACTION_EDIT\r\nACTION_VIEW\r\nACTION_ATTACH_DATA\r\nACTION_EDIT\r\nACTION_PICK\r\nACTION_CHOOSER\r\nACTION_GET_CONTENT\r\nACTION_DIAL\r\nACTION_CALL\r\nACTION_SEND\r\n\r\nThe exceptional case of an improper activity exposure is when the activity action starts the main entry point and does not expect to receive data:\r\nACTION_MAIN"},"defaultConfiguration":{},"helpUri":"https://www.zimperium.com/zscan","help":{"text":"The Android app exposes an activity component for use by other apps, but might not properly restrict which apps can launch the component or access the data it contains.\r\nAll activities are non-exported by default, unless the android:exported attribute is set to \"true\" or the intent-filter element is defined.\r\nOnce we target Android 12, the system will require us to be explicit about the value for android:exported.\r\nIf the intent-filter is registered to an implicit action intent that can be broadcast by all apps, the activity exposes itself to a third-party app.\r\n\r\nThis activity is exported or registered to standard broadcast actions.\r\nExamples of common implicit actions include:\r\nACTION_EDIT\r\nACTION_VIEW\r\nACTION_ATTACH_DATA\r\nACTION_EDIT\r\nACTION_PICK\r\nACTION_CHOOSER\r\nACTION_GET_CONTENT\r\nACTION_DIAL\r\nACTION_CALL\r\nACTION_SEND\r\n\r\nThe exceptional case of an improper activity exposure is when the activity action starts the main entry point and does not expect to receive data:\r\nACTION_MAIN\n\nIf access to an exported activity is not restricted, any app (including one that may not be trusted) will be able to launch the activity. This may allow a malicious app to gain access to sensitive information, modify the internal state of the app, or trick a user into interacting with the victim app while believing they are still interacting with the malicious app.","markdown":"

\uD83D\uDFE6  Description

The Android app exposes an activity component for use by other apps, but might not properly restrict which apps can launch the component or access the data it contains.\r
All activities are non-exported by default, unless the android:exported attribute is set to \"true\" or the intent-filter element is defined.\r
Once we target Android 12, the system will require us to be explicit about the value for android:exported.\r
If the intent-filter is registered to an implicit action intent that can be broadcast by all apps, the activity exposes itself to a third-party app.\r
\r
This activity is exported or registered to standard broadcast actions.\r
Examples of common implicit actions include:\r
ACTION_EDIT\r
ACTION_VIEW\r
ACTION_ATTACH_DATA\r
ACTION_EDIT\r
ACTION_PICK\r
ACTION_CHOOSER\r
ACTION_GET_CONTENT\r
ACTION_DIAL\r
ACTION_CALL\r
ACTION_SEND\r
\r
The exceptional case of an improper activity exposure is when the activity action starts the main entry point and does not expect to receive data:\r
ACTION_MAIN

\uD83D\uDFE6  Business Impact

If access to an exported activity is not restricted, any app (including one that may not be trusted) will be able to launch the activity. This may allow a malicious app to gain access to sensitive information, modify the internal state of the app, or trick a user into interacting with the victim app while believing they are still interacting with the malicious app.

"},"properties":{"tags":["CWE-926","OWASP M8"],"severity":"Medium","type":"security","category":"Vulnerability","subcategory":"Components"}},{"id":"63d8db78-1e43-ad8f-1243-c49400000000","name":"UnsafeHttphostScheme","shortDescription":{"text":"The Apache HttpClient uses an HttpHost data structure to describe HTTP and HTTPS connections. HttpHost does not have any internal consistency checks. For example, it allows connections on port 443 to adopt HTTP as their scheme."},"defaultConfiguration":{},"helpUri":"https://www.zimperium.com/zscan","help":{"text":"The Apache HttpClient uses an HttpHost data structure to describe HTTP and HTTPS connections. HttpHost does not have any internal consistency checks. For example, it allows connections on port 443 to adopt HTTP as their scheme.\n\nThe TCP port 443 indicates the traffic is passed over the default port for HTTPS, but the session is not encrypted if the scheme is HTTP. This critical vulnerability allows attackers to monitor a legitimate user's network traffic, exposing any sensitive information the user may supply.","markdown":"

\uD83D\uDFE6  Description

The Apache HttpClient uses an HttpHost data structure to describe HTTP and HTTPS connections. HttpHost does not have any internal consistency checks. For example, it allows connections on port 443 to adopt HTTP as their scheme.

\uD83D\uDFE6  Business Impact

The TCP port 443 indicates the traffic is passed over the default port for HTTPS, but the session is not encrypted if the scheme is HTTP. This critical vulnerability allows attackers to monitor a legitimate user's network traffic, exposing any sensitive information the user may supply.

"},"properties":{"tags":["MASVS MSTG-NETWORK-1"],"severity":"High","type":"security","category":"Communications","subcategory":"Weakness"}},{"id":"642b9a5f-f96b-cd69-3591-2a7400000000","name":"DeveloperBackdoor","shortDescription":{"text":"A backdoor attack is a way to access a computer system or encrypted data while bypassing the system's customary security mechanisms. A developer can create a backdoor so that an application, operating system, or data can be accessed for troubleshooting or other purposes."},"defaultConfiguration":{},"helpUri":"https://www.zimperium.com/zscan","help":{"text":"A backdoor attack is a way to access a computer system or encrypted data while bypassing the system's customary security mechanisms. A developer can create a backdoor so that an application, operating system, or data can be accessed for troubleshooting or other purposes.\n\nA backdoor attack occurs when threat actors create or use a backdoor to gain remote access to a system. \r\n\r\nThis type of attack leads to authentication bypass, which can result in malicious actions by threat actors such as stealing sensitive information, performing fraudulent transactions, launching denial of service (DoS) attacks, hijacking servers, and defacing websites.","markdown":"

\uD83D\uDFE6  Description

A backdoor attack is a way to access a computer system or encrypted data while bypassing the system's customary security mechanisms. A developer can create a backdoor so that an application, operating system, or data can be accessed for troubleshooting or other purposes.

\uD83D\uDFE6  Business Impact

A backdoor attack occurs when threat actors create or use a backdoor to gain remote access to a system. \r
\r
This type of attack leads to authentication bypass, which can result in malicious actions by threat actors such as stealing sensitive information, performing fraudulent transactions, launching denial of service (DoS) attacks, hijacking servers, and defacing websites.

"},"properties":{"tags":[],"severity":"High","type":"security","category":"Code Analysis","subcategory":"Data Protection"}},{"id":"642b9a56-f96b-cd69-3591-2a7300000000","name":"WeakAuthorizationMechanism","shortDescription":{"text":"A value in the app's /res/values directory is used as a predicate for important privileges like being an admin or gaining access to the premium app features."},"defaultConfiguration":{},"helpUri":"https://www.zimperium.com/zscan","help":{"text":"A value in the app's /res/values directory is used as a predicate for important privileges like being an admin or gaining access to the premium app features.\n\nThis value can changed in order to bypass the authorization control, thus gaining admin privileges.","markdown":"

\uD83D\uDFE6  Description

A value in the app's /res/values directory is used as a predicate for important privileges like being an admin or gaining access to the premium app features.

\uD83D\uDFE6  Business Impact

This value can changed in order to bypass the authorization control, thus gaining admin privileges.

"},"properties":{"tags":["OWASP M3"],"severity":"High","type":"security","category":"Code Analysis","subcategory":"App Modification"}},{"id":"54631b68-d8c9-7547-8e76-8efd00000000","name":"StaticDataExposure","shortDescription":{"text":"This application is susceptible to reverse engineering."},"defaultConfiguration":{},"helpUri":"https://www.zimperium.com/zscan","help":{"text":"This application is susceptible to reverse engineering.\n\nDuring analysis, the classes, method names, strings, and resources were available to inspect. It is recommended that you obfuscate all data in the application so it is not easily readable. Good obfuscation practices prevent de-obfuscation by tools such as IDA Pro and Hopper.","markdown":"

\uD83D\uDFE6  Description

This application is susceptible to reverse engineering.

\uD83D\uDFE6  Business Impact

During analysis, the classes, method names, strings, and resources were available to inspect. It is recommended that you obfuscate all data in the application so it is not easily readable. Good obfuscation practices prevent de-obfuscation by tools such as IDA Pro and Hopper.

"},"properties":{"tags":["CWE-200","HIPAA §164.312(a)(1)","PCI 6.5","OWASP M7"],"severity":"Low","type":"security","category":"Code Analysis","subcategory":"Binary Protections Testing"}},{"id":"67e11d2b-85c8-6900-ca1e-527b00000000","name":"MinsdkversionNoncompliance","shortDescription":{"text":"This app does not meet ADA requirement 1.6.1, as its minSdkVersion is set to a platform version older than the N-2 range, where N represents the latest Android release. This lack of compliance means the app does not take full advantage of recent Android updates, potentially exposing users to security vulnerabilities and limiting compatibility with modern features."},"defaultConfiguration":{"level":"note"},"helpUri":"https://www.zimperium.com/zscan","help":{"text":"This app does not meet ADA requirement 1.6.1, as its minSdkVersion is set to a platform version older than the N-2 range, where N represents the latest Android release. This lack of compliance means the app does not take full advantage of recent Android updates, potentially exposing users to security vulnerabilities and limiting compatibility with modern features.\n\nFailing to target an up-to-date platform version leaves the app vulnerable to known security threats that have been addressed in more recent Android versions. Users may experience degraded functionality or issues on newer devices due to deprecated APIs.","markdown":"

\uD83D\uDFE6  Description

This app does not meet ADA requirement 1.6.1, as its minSdkVersion is set to a platform version older than the N-2 range, where N represents the latest Android release. This lack of compliance means the app does not take full advantage of recent Android updates, potentially exposing users to security vulnerabilities and limiting compatibility with modern features.

\uD83D\uDFE6  Business Impact

Failing to target an up-to-date platform version leaves the app vulnerable to known security threats that have been addressed in more recent Android versions. Users may experience degraded functionality or issues on newer devices due to deprecated APIs.

"},"properties":{"tags":[],"severity":"Informational","type":"security","category":"Code Analysis","subcategory":"Content API"}},{"id":"667eb2a2-8452-657c-816c-289a00000000","name":"LocationPermissions","shortDescription":{"text":"The app was found to be using location permissions which, according to Google Play Store policies, require justifying the use of background location, describing the benefits to the user and providing information on how location information is handled securely."},"defaultConfiguration":{"level":"note"},"helpUri":"https://www.zimperium.com/zscan","help":{"text":"The app was found to be using location permissions which, according to Google Play Store policies, require justifying the use of background location, describing the benefits to the user and providing information on how location information is handled securely.\n\nDevice location is regarded as personal and sensitive user data subject to the Personal and Sensitive Information policy and the Background Location policy. Not complying with Google's Play Store specifications may cause the app to be removed. For this policy, e.g:\r\n- Misusing location data without explicit user consent.\r\n- Failing to provide clear disclosures on how location information is used.","markdown":"

\uD83D\uDFE6  Description

The app was found to be using location permissions which, according to Google Play Store policies, require justifying the use of background location, describing the benefits to the user and providing information on how location information is handled securely.

\uD83D\uDFE6  Business Impact

Device location is regarded as personal and sensitive user data subject to the Personal and Sensitive Information policy and the Background Location policy. Not complying with Google's Play Store specifications may cause the app to be removed. For this policy, e.g:\r
- Misusing location data without explicit user consent.\r
- Failing to provide clear disclosures on how location information is used.

"},"properties":{"tags":["GDPR Article 5, Section 1","GDPR Article 13, Section 1","GDPR Article 25, Section 1"],"severity":"Informational","type":"security","category":"Compliance","subcategory":"App Store"}},{"id":"667eb276-8452-657c-816c-289900000000","name":"SmsAndCallLogPermissions","shortDescription":{"text":"The app was found to be using SMS and Call Log permissions that require specific actions according to Google Play Store policies. For the Call Log permission group, the application must be actively registered as the default phone or assistant handler on the device. For the SMS permission group, it must be actively registered as the default SMS or assistant handler on the device. These permissions including READ_CALL_LOG, WRITE_CALL_LOG, READ_SMS, and SEND_SMS, are necessary for specific functionalities such as managing call logs and sending SMS messages."},"defaultConfiguration":{"level":"note"},"helpUri":"https://www.zimperium.com/zscan","help":{"text":"The app was found to be using SMS and Call Log permissions that require specific actions according to Google Play Store policies. For the Call Log permission group, the application must be actively registered as the default phone or assistant handler on the device. For the SMS permission group, it must be actively registered as the default SMS or assistant handler on the device. These permissions including READ_CALL_LOG, WRITE_CALL_LOG, READ_SMS, and SEND_SMS, are necessary for specific functionalities such as managing call logs and sending SMS messages.\n\nFailure to properly register the application as the default handler for calls or SMS may result with the app being non-compliant with Google Play Store requirements and potential expulsion from the store. Moreover, misuse of these permissions to collect user data without consent can lead to serious legal and reputation consequences. Strict adherence to Google's privacy and security policies is essential to avoid any issues.","markdown":"

\uD83D\uDFE6  Description

The app was found to be using SMS and Call Log permissions that require specific actions according to Google Play Store policies. For the Call Log permission group, the application must be actively registered as the default phone or assistant handler on the device. For the SMS permission group, it must be actively registered as the default SMS or assistant handler on the device. These permissions including READ_CALL_LOG, WRITE_CALL_LOG, READ_SMS, and SEND_SMS, are necessary for specific functionalities such as managing call logs and sending SMS messages.

\uD83D\uDFE6  Business Impact

Failure to properly register the application as the default handler for calls or SMS may result with the app being non-compliant with Google Play Store requirements and potential expulsion from the store. Moreover, misuse of these permissions to collect user data without consent can lead to serious legal and reputation consequences. Strict adherence to Google's privacy and security policies is essential to avoid any issues.

"},"properties":{"tags":["GDPR Article 5, Section 1","GDPR Article 13, Section 1","GDPR Article 25, Section 1","GDPR Article 32, Section 1"],"severity":"Informational","type":"security","category":"Compliance","subcategory":"App Store"}},{"id":"63d8fb8f-b544-7900-1078-5c8400000000","name":"ClipboardVulnerability","shortDescription":{"text":"The app can be vulnerable to clipboard attacks."},"defaultConfiguration":{},"helpUri":"https://www.zimperium.com/zscan","help":{"text":"The app can be vulnerable to clipboard attacks.\n\nThe Android clipboard vulnerability is a security issue that allows malicious apps to access sensitive data stored in the clipboard, such as passwords, personal information, and other confidential data. This vulnerability exists because the Android clipboard is not secured and can be accessed by any app, leading to potential data theft.","markdown":"

\uD83D\uDFE6  Description

The app can be vulnerable to clipboard attacks.

\uD83D\uDFE6  Business Impact

The Android clipboard vulnerability is a security issue that allows malicious apps to access sensitive data stored in the clipboard, such as passwords, personal information, and other confidential data. This vulnerability exists because the Android clipboard is not secured and can be accessed by any app, leading to potential data theft.

"},"properties":{"tags":["OWASP M9"],"severity":"Low","type":"security","category":"Vulnerability","subcategory":"Data Leakage"}},{"id":"63ca495c-b0a8-7a00-262f-409800000000","name":"HardcodedSymmetricKey","shortDescription":{"text":"The app exposes symmetric secret key on the code and it is the only encryption method used."},"defaultConfiguration":{},"helpUri":"https://www.zimperium.com/zscan","help":{"text":"The app exposes symmetric secret key on the code and it is the only encryption method used.\n\nHardcoding secret keys, such as encryption keys, in software can pose a significant security risk. If an attacker is able to access the code, they can easily extract the key and use it for unauthorized access or decryption. Additionally, if the code is made public, the key can be discovered easily by anyone. This can lead to data breaches, unauthorized access to systems, and other security incidents. To mitigate this risk, secret keys should be stored in secure, external locations and accessed by the software at runtime, rather than being hardcoded into the codebase.\r\n\r\nThe Cryptographic Key Generation does not satisfy the requeriment for NIAP compliance.","markdown":"

\uD83D\uDFE6  Description

The app exposes symmetric secret key on the code and it is the only encryption method used.

\uD83D\uDFE6  Business Impact

Hardcoding secret keys, such as encryption keys, in software can pose a significant security risk. If an attacker is able to access the code, they can easily extract the key and use it for unauthorized access or decryption. Additionally, if the code is made public, the key can be discovered easily by anyone. This can lead to data breaches, unauthorized access to systems, and other security incidents. To mitigate this risk, secret keys should be stored in secure, external locations and accessed by the software at runtime, rather than being hardcoded into the codebase.\r
\r
The Cryptographic Key Generation does not satisfy the requeriment for NIAP compliance.

"},"properties":{"tags":["MASVS MSTG-CRYPTO-1","NIAP FCS_CKM_EXT.1.1","OWASP M10"],"severity":"High","type":"security","category":"Code Analysis","subcategory":"Cryptography"}},{"id":"63cff208-1499-a500-0f37-337700000000","name":"ApkCanBeEasilyTampered","shortDescription":{"text":"The app can be tampered with easily. It contains readable method names, has been signed with a weak APK Signature Scheme, and appears not to be using any known code obfuscation tools."},"defaultConfiguration":{},"helpUri":"https://www.zimperium.com/zscan","help":{"text":"The app can be tampered with easily. It contains readable method names, has been signed with a weak APK Signature Scheme, and appears not to be using any known code obfuscation tools.\n\nAn adversary can easily use accessible tools to extract this metadata and reveal significant information about sensitive parts of the program. The adversary can find this information useful on its own or use it as a stepping stone to perform unauthorized code modifications. \r\n\r\nAlso, using a weak APK signature makes it difficult to ensure that no one has tampered with the contents of the APK. \r\n\r\nIn addition, this app appears not to be using any known code obfuscation tools. Code obfuscation is the process of modifying an executable so that it is no longer useful to a hacker but remains fully functional. Making an app much more difficult to reverse-engineer helps protect against trade secret (intellectual property) theft, unauthorized access, bypassing licensing or other controls, and vulnerability discovery.\r\n\r\nThese issues combined make it easy for an attacker to modify the APK and add unexpected behaviors, such as bypassing authorization control and gaining privileges.","markdown":"

\uD83D\uDFE6  Description

The app can be tampered with easily. It contains readable method names, has been signed with a weak APK Signature Scheme, and appears not to be using any known code obfuscation tools.

\uD83D\uDFE6  Business Impact

An adversary can easily use accessible tools to extract this metadata and reveal significant information about sensitive parts of the program. The adversary can find this information useful on its own or use it as a stepping stone to perform unauthorized code modifications. \r
\r
Also, using a weak APK signature makes it difficult to ensure that no one has tampered with the contents of the APK. \r
\r
In addition, this app appears not to be using any known code obfuscation tools. Code obfuscation is the process of modifying an executable so that it is no longer useful to a hacker but remains fully functional. Making an app much more difficult to reverse-engineer helps protect against trade secret (intellectual property) theft, unauthorized access, bypassing licensing or other controls, and vulnerability discovery.\r
\r
These issues combined make it easy for an attacker to modify the APK and add unexpected behaviors, such as bypassing authorization control and gaining privileges.

"},"properties":{"tags":["OWASP M7"],"severity":"Medium","type":"security","category":"Code Analysis","subcategory":"Binary Protections Testing"}},{"id":"63bd9c4e-d6d6-9f00-0f5b-8df700000000","name":"BareminimumPermissions","shortDescription":{"text":"The app asks for the minimum set of permissions required for it to fully operate."},"defaultConfiguration":{"level":"note"},"helpUri":"https://www.zimperium.com/zscan","help":{"text":"The app asks for the minimum set of permissions required for it to fully operate.\n\nThis is a best practice.","markdown":"

\uD83D\uDFE6  Description

The app asks for the minimum set of permissions required for it to fully operate.

\uD83D\uDFE6  Business Impact

This is a best practice.

"},"properties":{"tags":["MASVS MSTG-PLATFORM-1"],"severity":"Best Practices","type":"security","category":"Code Analysis","subcategory":"Entitlements"}},{"id":"5b62edc8-bb6e-303a-8140-a37300000000","name":"ExternalStorageAccess","shortDescription":{"text":"Files created on external storage are world readable and writable, meaning any app can read or write to them."},"defaultConfiguration":{},"helpUri":"https://www.zimperium.com/zscan","help":{"text":"Files created on external storage are world readable and writable, meaning any app can read or write to them.\n\nSince external storage can be removed from a device and connected to any other computer, it is not possible to enforce access control for data stored on external storage. \r\n\r\nUsing external storage could open up the app to a Man-in-the-Disk attack which harms apps and data stored in external storage. When an app is downloaded and saved in external storage, updated or received data from an app's server provider is passed through external storage and it gives the adversary an opportunity to manipulate the data held in the external storage.","markdown":"

\uD83D\uDFE6  Description

Files created on external storage are world readable and writable, meaning any app can read or write to them.

\uD83D\uDFE6  Business Impact

Since external storage can be removed from a device and connected to any other computer, it is not possible to enforce access control for data stored on external storage. \r
\r
Using external storage could open up the app to a Man-in-the-Disk attack which harms apps and data stored in external storage. When an app is downloaded and saved in external storage, updated or received data from an app's server provider is passed through external storage and it gives the adversary an opportunity to manipulate the data held in the external storage.

"},"properties":{"tags":["CWE-276","CWE-284","GDPR Article 25, Section 1","HIPAA §164.312(a)(1)","OWASP M9"],"severity":"Low","type":"security","category":"Code Analysis","subcategory":"File Storage"}},{"id":"5d4203bc-7d33-27a1-308b-456700000000","name":"Sharedpreferences","shortDescription":{"text":"This app uses a SharedPreferences instance."},"defaultConfiguration":{},"helpUri":"https://www.zimperium.com/zscan","help":{"text":"This app uses a SharedPreferences instance.\n\nSharedPreferences on Android stores all of your values unencrypted in the /data/data/ location as an XML file, simply protected by the user-restricted file system on Android. If an adversary gains root access to an Android device, they have full read and write access to the application preferences, even if it was created with MODE_PRIV. ","markdown":"

\uD83D\uDFE6  Description

This app uses a SharedPreferences instance.

\uD83D\uDFE6  Business Impact

SharedPreferences on Android stores all of your values unencrypted in the /data/data/ location as an XML file, simply protected by the user-restricted file system on Android. If an adversary gains root access to an Android device, they have full read and write access to the application preferences, even if it was created with MODE_PRIV.

"},"properties":{"tags":["GDPR Article 5, Section 1","GDPR Article 13, Section 1","GDPR Article 25, Section 1"],"severity":"Low","type":"security","category":"Code Analysis","subcategory":"Content API"}},{"id":"63921084-031a-7c00-0f01-0dc500000000","name":"ChainOfTrustValidation","shortDescription":{"text":"All secure endpoints passed the chain of trust validation testing."},"defaultConfiguration":{"level":"note"},"helpUri":"https://www.zimperium.com/zscan","help":{"text":"All secure endpoints passed the chain of trust validation testing.\n\nIf the app does not follow a chain of trust of a certificate to a root server, the certificate loses all value as a metric of trust. This makes the app susceptible to an attack that poisons the DNS cache or uses an Adversary-in-the-Middle (AITM) attack to modify the traffic from server to client.","markdown":"

\uD83D\uDFE6  Description

All secure endpoints passed the chain of trust validation testing.

\uD83D\uDFE6  Business Impact

If the app does not follow a chain of trust of a certificate to a root server, the certificate loses all value as a metric of trust. This makes the app susceptible to an attack that poisons the DNS cache or uses an Adversary-in-the-Middle (AITM) attack to modify the traffic from server to client.

"},"properties":{"tags":["MASVS MSTG-NETWORK-3","NIAP FCS_TLSC_EXT.1.3"],"severity":"Best Practices","type":"security","category":"Communications","subcategory":"SSL Checks"}},{"id":"5da893bf-7d33-27f0-1f8b-456a00000000","name":"NetworkCommunications","shortDescription":{"text":"This application has access to the network communication, which is used to send and receive data to local or remote services and systems."},"defaultConfiguration":{"level":"note"},"helpUri":"https://www.zimperium.com/zscan","help":{"text":"This application has access to the network communication, which is used to send and receive data to local or remote services and systems.\n\nThis finding is a requirement for NIAP compliance.","markdown":"

\uD83D\uDFE6  Description

This application has access to the network communication, which is used to send and receive data to local or remote services and systems.

\uD83D\uDFE6  Business Impact

This finding is a requirement for NIAP compliance.

"},"properties":{"tags":["NIAP FDP_DEC_EXT.1.1, FDP_DEC_EXT.1.2"],"severity":"Informational","type":"security","category":"Compliance","subcategory":"Hardware Access"}},{"id":"5da89d29-7d33-27f1-1f8b-456800000000","name":"TelephonyHardware","shortDescription":{"text":"This application has access to the telephony services that is used to send and receive data to local or remote services and systems."},"defaultConfiguration":{"level":"note"},"helpUri":"https://www.zimperium.com/zscan","help":{"text":"This application has access to the telephony services that is used to send and receive data to local or remote services and systems.\n\nThis finding is a requirement for NIAP compliance.","markdown":"

\uD83D\uDFE6  Description

This application has access to the telephony services that is used to send and receive data to local or remote services and systems.

\uD83D\uDFE6  Business Impact

This finding is a requirement for NIAP compliance.

"},"properties":{"tags":["NIAP FDP_DEC_EXT.1.1, FDP_DEC_EXT.1.2"],"severity":"Informational","type":"security","category":"Compliance","subcategory":"Hardware Access"}},{"id":"5db9a286-7d33-277c-4b8b-456700000000","name":"DbrgCipherSuggestion","shortDescription":{"text":"This application is not using any deterministic random bit generation (DRBG) functionality."},"defaultConfiguration":{"level":"note"},"helpUri":"https://www.zimperium.com/zscan","help":{"text":"This application is not using any deterministic random bit generation (DRBG) functionality.\n\nThis finding is a requirement for NIAP compliance.","markdown":"

\uD83D\uDFE6  Description

This application is not using any deterministic random bit generation (DRBG) functionality.

\uD83D\uDFE6  Business Impact

This finding is a requirement for NIAP compliance.

"},"properties":{"tags":["MASVS MSTG-CRYPTO-2, MSTG-CRYPTO-3","NIAP FCS_RBG_EXT.2.1, FCS_RBG_EXT.1.1"],"severity":"Informational","type":"security","category":"Compliance","subcategory":"Random Bit Generation"}},{"id":"5e0a1763-a430-cf64-d379-2d6400000000","name":"NoCertificatePinningDetected","shortDescription":{"text":"This app has not implemented SSL certificate pinning."},"defaultConfiguration":{},"helpUri":"https://www.zimperium.com/zscan","help":{"text":"This app has not implemented SSL certificate pinning.\n\nPinning leverages knowledge of the pre-existing relationship between the user and an organization or service to help make better security-related decisions. Because you already have information on the server or service, you don't need to rely on generalized mechanisms meant to solve the key distribution problem. ","markdown":"

\uD83D\uDFE6  Description

This app has not implemented SSL certificate pinning.

\uD83D\uDFE6  Business Impact

Pinning leverages knowledge of the pre-existing relationship between the user and an organization or service to help make better security-related decisions. Because you already have information on the server or service, you don't need to rely on generalized mechanisms meant to solve the key distribution problem.

"},"properties":{"tags":["OWASP M5"],"severity":"Medium","type":"security","category":"Communications","subcategory":"SSL Checks"}},{"id":"5fb402d1-d9a8-c20a-5e58-e34300000000","name":"WeakApkSigningScheme","shortDescription":{"text":"This application is signed with version 1 or 2 of the APK Signature Scheme. "},"defaultConfiguration":{},"helpUri":"https://www.zimperium.com/zscan","help":{"text":"This application is signed with version 1 or 2 of the APK Signature Scheme. \n\nThe recommended APK Signature Scheme version 3 was introduced in Android 9. Using the latest APK Signature Scheme helps ensure that no one has tampered with the contents of the APK.","markdown":"

\uD83D\uDFE6  Description

This application is signed with version 1 or 2 of the APK Signature Scheme.

\uD83D\uDFE6  Business Impact

The recommended APK Signature Scheme version 3 was introduced in Android 9. Using the latest APK Signature Scheme helps ensure that no one has tampered with the contents of the APK.

"},"properties":{"tags":["CVSS 2.0 score 3.8","CVSS 2.0 vector AV:L/AC:H/Au:S/C:N/I:C/A:N","CVSS 3.1 score 5.9","CVSS 3.1 vector AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:H/A:N","MASVS MSTG-CODE-1","NIAP FPT_TUD_EXT.1.6"],"severity":"Medium","type":"security","category":"Code Analysis","subcategory":"Cryptography"}},{"id":"5f7eef85-c098-1160-dd2a-481f00000000","name":"NoCodeObfuscation","shortDescription":{"text":"This application appears not to be using any known code obfuscation tools."},"defaultConfiguration":{},"helpUri":"https://www.zimperium.com/zscan","help":{"text":"This application appears not to be using any known code obfuscation tools.\n\nCode obfuscation is the process of modifying an executable so that it is no longer useful to a hacker but remains fully functional. Making an application much more difficult to reverse-engineer helps protect against trade secret (intellectual property) theft, unauthorized access, bypassing licensing or other controls, and vulnerability discovery.","markdown":"

\uD83D\uDFE6  Description

This application appears not to be using any known code obfuscation tools.

\uD83D\uDFE6  Business Impact

Code obfuscation is the process of modifying an executable so that it is no longer useful to a hacker but remains fully functional. Making an application much more difficult to reverse-engineer helps protect against trade secret (intellectual property) theft, unauthorized access, bypassing licensing or other controls, and vulnerability discovery.

"},"properties":{"tags":["MASVS MSTG-RESILIENCE-9","OWASP M7"],"severity":"Medium","type":"security","category":"Code Analysis","subcategory":"Binary Protections Testing"}},{"id":"60faf47f-178b-b800-121f-af7600000000","name":"JailbreakAndRootDetection","shortDescription":{"text":"This application has code to detect if the device the application is executing on is jailbroken or rooted."},"defaultConfiguration":{"level":"note"},"helpUri":"https://www.zimperium.com/zscan","help":{"text":"This application has code to detect if the device the application is executing on is jailbroken or rooted.\n\nThe goal of this detection is to increase the difficulty of running the app on a compromised device. This forces the adversary to defeat the jailbreak and rooted device checks to fully execute the app. ","markdown":"

\uD83D\uDFE6  Description

This application has code to detect if the device the application is executing on is jailbroken or rooted.

\uD83D\uDFE6  Business Impact

The goal of this detection is to increase the difficulty of running the app on a compromised device. This forces the adversary to defeat the jailbreak and rooted device checks to fully execute the app.

"},"properties":{"tags":[],"severity":"Best Practices","type":"security","category":"Code Analysis","subcategory":"Binary Protections Testing"}},{"id":"636277c5-cab2-bc00-0f71-f3f500000000","name":"ProtectedProgramDataSymbols","shortDescription":{"text":"This application has obfuscated or encrypted program data symbols."},"defaultConfiguration":{"level":"note"},"helpUri":"https://www.zimperium.com/zscan","help":{"text":"This application has obfuscated or encrypted program data symbols.\n\nSymbol names and locations reveal the internal assets of the application. Protecting these symbols with encryption or obfuscation make it difficult for an attacker to understand the internal assets of the application.","markdown":"

\uD83D\uDFE6  Description

This application has obfuscated or encrypted program data symbols.

\uD83D\uDFE6  Business Impact

Symbol names and locations reveal the internal assets of the application. Protecting these symbols with encryption or obfuscation make it difficult for an attacker to understand the internal assets of the application.

"},"properties":{"tags":["MASVS MSTG-CODE-3"],"severity":"Best Practices","type":"security","category":"Code Analysis","subcategory":"Binary Protections Testing"}},{"id":"64ad4f15-2e24-ce00-5648-756a00000000","name":"NoObfuscationDetected","shortDescription":{"text":"There is no obfuscation detected in the app."},"defaultConfiguration":{"level":"note"},"helpUri":"https://www.zimperium.com/zscan","help":{"text":"There is no obfuscation detected in the app.\n\nThe app has no code obfuscation. This allows for reverse engineering and full app analysis.","markdown":"

\uD83D\uDFE6  Description

There is no obfuscation detected in the app.

\uD83D\uDFE6  Business Impact

The app has no code obfuscation. This allows for reverse engineering and full app analysis.

"},"properties":{"tags":["NIAP AVA_VAN.1.1C"],"severity":"Informational","type":"security","category":"Code Analysis","subcategory":"Binary Protections Testing"}},{"id":"591d4dc1-12df-5b5d-87e6-1e2d00000000","name":"UnsecuredStorageDataMode","shortDescription":{"text":"This app uses the unsecured storage data mode (WORLD_READABLE, WORLD_WRITABLE), which could allow any app or adversary to access the data."},"defaultConfiguration":{},"helpUri":"https://www.zimperium.com/zscan","help":{"text":"This app uses the unsecured storage data mode (WORLD_READABLE, WORLD_WRITABLE), which could allow any app or adversary to access the data.\n\nUsing unsecured storage data modes in an app can lead to data breaches.","markdown":"

\uD83D\uDFE6  Description

This app uses the unsecured storage data mode (WORLD_READABLE, WORLD_WRITABLE), which could allow any app or adversary to access the data.

\uD83D\uDFE6  Business Impact

Using unsecured storage data modes in an app can lead to data breaches.

"},"properties":{"tags":["CVSS 2.0 score 4.3","CVSS 2.0 vector AV:N/AC:M/Au:N/C:P/I:N/A:N","CVSS 3.1 score 5.9","CVSS 3.1 vector AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N","CWE-276","NIAP FMT_CFG_EXT.1.2"],"severity":"Low","type":"security","category":"Code Analysis","subcategory":"File Storage"}},{"id":"633d121c-0e3b-5700-1234-49c900000000","name":"CryptographicPrimitives","shortDescription":{"text":"This application is using cryptographic primitives."},"defaultConfiguration":{"level":"note"},"helpUri":"https://www.zimperium.com/zscan","help":{"text":"This application is using cryptographic primitives.\n\nCryptographic primitives are well-established, low-level cryptographic algorithms that are frequently used to build cryptographic protocols for computer security systems. These routines include, but are not limited to, one-way hash functions and encryption functions.","markdown":"

\uD83D\uDFE6  Description

This application is using cryptographic primitives.

\uD83D\uDFE6  Business Impact

Cryptographic primitives are well-established, low-level cryptographic algorithms that are frequently used to build cryptographic protocols for computer security systems. These routines include, but are not limited to, one-way hash functions and encryption functions.

"},"properties":{"tags":["MASVS MSTG-CRYPTO-2, MSTG-CRYPTO-3"],"severity":"Informational","type":"security","category":"Compliance","subcategory":"Cryptography"}},{"id":"63eb7110-f141-dd00-6a42-035400000000","name":"WebviewCleanup","shortDescription":{"text":"No WebView cleaning measures are implemented."},"defaultConfiguration":{},"helpUri":"https://www.zimperium.com/zscan","help":{"text":"No WebView cleaning measures are implemented.\n\nData, including sensitive data such as user credentials, financial information, or personal data can be stored insecurely, making them vulnerable to theft or manipulation by adversaries.","markdown":"

\uD83D\uDFE6  Description

No WebView cleaning measures are implemented.

\uD83D\uDFE6  Business Impact

Data, including sensitive data such as user credentials, financial information, or personal data can be stored insecurely, making them vulnerable to theft or manipulation by adversaries.

"},"properties":{"tags":["MASVS MSTG-PLATFORM-10"],"severity":"Medium","type":"security","category":"Vulnerability","subcategory":"WebView"}},{"id":"558af6f7-3601-8303-e4d5-958000000000","name":"JavascriptEnabled","shortDescription":{"text":"The application has been configured to allow JavaScript execution in the WebView control. "},"defaultConfiguration":{},"helpUri":"https://www.zimperium.com/zscan","help":{"text":"The application has been configured to allow JavaScript execution in the WebView control. \n\nA common attack vector for mobile apps is ads. Advertisements from external sources are often loaded in WebViews, and blocking JavaScript execution is a good way to prevent malicious code from being injected and protect the app users.","markdown":"

\uD83D\uDFE6  Description

The application has been configured to allow JavaScript execution in the WebView control.

\uD83D\uDFE6  Business Impact

A common attack vector for mobile apps is ads. Advertisements from external sources are often loaded in WebViews, and blocking JavaScript execution is a good way to prevent malicious code from being injected and protect the app users.

"},"properties":{"tags":["CWE-830","MASVS MSTG-PLATFORM-5"],"severity":"Medium","type":"security","category":"WebView","subcategory":"JavaScript"}},{"id":"575ecc52-d100-c5af-f726-2ac400000000","name":"JavaReflectionApiInvoked","shortDescription":{"text":"Reflection is an API that is used to examine or modify the behavior of methods, classes, and interfaces at runtime. An application can use the Java reflection APIs to access and update fields, and execute methods that are forbidden by normal Java access and visibility rules."},"defaultConfiguration":{},"helpUri":"https://www.zimperium.com/zscan","help":{"text":"Reflection is an API that is used to examine or modify the behavior of methods, classes, and interfaces at runtime. An application can use the Java reflection APIs to access and update fields, and execute methods that are forbidden by normal Java access and visibility rules.\n\nReflection can assist the developer in inspecting a class, interface, class structure, methods, and fields without knowing the names of the classes at compile time. What makes it even more interesting is that developers can manipulate fields, invoke methods, and also instantiate new objects. However, with access to private fields and other items, inspection and modification of internal data is possible and could lead to various malicious exploits and data leakage.","markdown":"

\uD83D\uDFE6  Description

Reflection is an API that is used to examine or modify the behavior of methods, classes, and interfaces at runtime. An application can use the Java reflection APIs to access and update fields, and execute methods that are forbidden by normal Java access and visibility rules.

\uD83D\uDFE6  Business Impact

Reflection can assist the developer in inspecting a class, interface, class structure, methods, and fields without knowing the names of the classes at compile time. What makes it even more interesting is that developers can manipulate fields, invoke methods, and also instantiate new objects. However, with access to private fields and other items, inspection and modification of internal data is possible and could lead to various malicious exploits and data leakage.

"},"properties":{"tags":[],"severity":"Medium","type":"security","category":"Code Analysis","subcategory":"Method"}}],"properties":{"policyName":null,"appRulesVersion":"9943727e80b24e105fd4cb8646a7f084"}}},"artifacts":[{"location":{"uri":"Sample_Insecure_Bank_App.apk","uriBaseId":"%binroot%"},"properties":{"appVersion":"1.0","appPlatform":"android","appMD5Hash":"5ee4829065640f9c936ac861d1650ffc","appName":"InsecureBankv2","appBundle":"com.android.insecurebankv2","appBuild":"1"}}],"results":[{"ruleId":"63ee197a-f141-dd00-ed71-43f400000000","ruleIndex":0,"message":{"text":"\uD83D\uDFE9 Recommendation

Sensitive, hardcoded data (such as private IPs/emails and user/DB details) should not be stored unless secured specifically. An attacker can use that data for further malicious intentions.

\uD83D\uDFE6 Code Snippet

\uD83D\uDFE7 com.android.insecurebankv2.CryptoClass

  public class CryptoClass {
              String base64Text;
              byte[] cipherData;
              String cipherText;
              String plainText;
‣‣            String key = \"This is the super secret key 123\";
              byte[] ivBytes = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
  
      public static byte[] aes256encrypt(byte[] ivBytes, byte[] keyBytes, byte[] textBytes) throws BadPaddingExceptio...
          AlgorithmParameterSpec ivSpec = new IvParameterSpec(ivBytes);
          SecretKeySpec newKey = new SecretKeySpec(keyBytes, \"AES\");

"},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.android.insecurebankv2.CryptoClass"},"region":{"startLine":22,"snippet":{"text":" public class CryptoClass {\n String base64Text;\n byte[] cipherData;\n String cipherText;\n String plainText;\n String key = \"This is the super secret key 123\";\n byte[] ivBytes = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};\n\n public static byte[] aes256encrypt(byte[] ivBytes, byte[] keyBytes, byte[] textBytes) throws BadPaddingException, NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, InvalidKeyException, UnsupportedEncodingException, InvalidAlgorithmParameterException {\n AlgorithmParameterSpec ivSpec = new IvParameterSpec(ivBytes);\n SecretKeySpec newKey = new SecretKeySpec(keyBytes, \"AES\");"}}}}]},{"ruleId":"63a5da01-834f-de00-1102-f91500000000","ruleIndex":1,"message":{"text":"\uD83D\uDFE9 Recommendation

Do not expose sensitive data. Use appropriate EditText attributes to mask potentially sensitive user input (for example, use dots instead of the input characters for password or pins).

"},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+classes.dex"},"region":{"startLine":1}}}]},{"ruleId":"63a02646-7625-7900-1469-a8d500000000","ruleIndex":2,"message":{"text":"\uD83D\uDFE9 Recommendation

Always use appropriate EditText attributes for sensitive data inputs.

\uD83D\uDFE7 Locations

com.android.insecurebankv2.BuildConfig
com.android.insecurebankv2.MyBroadCastReceiver
com.android.insecurebankv2.CryptoClass
com.android.insecurebankv2.FilePrefActivity
com.android.insecurebankv2.DoLogin
com.android.insecurebankv2.MyWebViewClient
com.android.insecurebankv2.LoginActivity
com.android.insecurebankv2.ChangePassword
com.android.insecurebankv2.WrongLogin
com.android.insecurebankv2.PostLogin
com.google.ads.mediation.admob.AdMobAdapter
com.android.insecurebankv2.ViewStatement
com.android.insecurebankv2.TrackUserContentProvider
com.google.ads.mediation.customevent.CustomEventBanner
com.google.ads.mediation.customevent.CustomEventBannerListener
com.google.ads.mediation.customevent.CustomEventAdapter
com.google.ads.mediation.customevent.CustomEventInterstitialListener
com.google.ads.mediation.customevent.CustomEventInterstitial
com.google.ads.mediation.customevent.CustomEvent
com.android.insecurebankv2.R
com.google.ads.mediation.customevent.CustomEventListener
com.google.ads.mediation.EmptyNetworkExtras
com.google.ads.mediation.customevent.CustomEventServerParameters
com.google.ads.mediation.MediationAdapter
com.google.ads.mediation.AdUrlAdapter
com.google.ads.mediation.MediationAdRequest
com.android.insecurebankv2.DoTransfer
com.google.ads.mediation.AbstractAdViewAdapter
com.google.ads.mediation.MediationBannerAdapter
com.google.ads.mediation.MediationBannerListener
com.google.ads.mediation.MediationInterstitialAdapter
com.google.ads.mediation.NetworkExtras
com.google.ads.mediation.MediationInterstitialListener
com.google.ads.mediation.MediationServerParameters
com.google.ads.AdRequest
com.google.ads.AdSize

\uD83D\uDFE6 Code Snippets

\uD83D\uDFE7 com.android.insecurebankv2.LoginActivity

  
  
  public class LoginActivity extends Activity {
              public static final String MYPREFS = \"mySharedPreferences\";
              EditText Password_Text;
‣‣            EditText Username_Text;
              Button createuser_buttons;
              Button fillData_button;
              Button login_buttons;
              String usernameBase64ByteString;
  

\uD83D\uDFE7 com.android.insecurebankv2.DoTransfer

  EditText from;
  Button getAccounts;
  InputStream in;
  JSONObject jsonObject;
  String passNormalized;
‣‣EditText phoneNumber;
  BufferedReader reader;
  HttpResponse responseBody;
  String result;
  SharedPreferences serverDetails;
  EditText to;

"},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.android.insecurebankv2.BuildConfig"},"region":{"startLine":1}}},{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.android.insecurebankv2.MyBroadCastReceiver"},"region":{"startLine":1}}},{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.android.insecurebankv2.CryptoClass"},"region":{"startLine":1}}},{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.android.insecurebankv2.FilePrefActivity"},"region":{"startLine":1}}},{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.android.insecurebankv2.DoLogin"},"region":{"startLine":1}}},{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.android.insecurebankv2.MyWebViewClient"},"region":{"startLine":1}}},{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.android.insecurebankv2.LoginActivity"},"region":{"startLine":27,"snippet":{"text":"\n\n public class LoginActivity extends Activity {\n public static final String MYPREFS = \"mySharedPreferences\";\n EditText Password_Text;\n EditText Username_Text;\n Button createuser_buttons;\n Button fillData_button;\n Button login_buttons;\n String usernameBase64ByteString;\n"}}}},{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.android.insecurebankv2.ChangePassword"},"region":{"startLine":1}}},{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.android.insecurebankv2.WrongLogin"},"region":{"startLine":1}}},{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.android.insecurebankv2.PostLogin"},"region":{"startLine":1}}},{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.google.ads.mediation.admob.AdMobAdapter"},"region":{"startLine":1}}},{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.android.insecurebankv2.ViewStatement"},"region":{"startLine":1}}},{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.android.insecurebankv2.TrackUserContentProvider"},"region":{"startLine":1}}},{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.google.ads.mediation.customevent.CustomEventBanner"},"region":{"startLine":1}}},{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.google.ads.mediation.customevent.CustomEventBannerListener"},"region":{"startLine":1}}},{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.google.ads.mediation.customevent.CustomEventAdapter"},"region":{"startLine":1}}},{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.google.ads.mediation.customevent.CustomEventInterstitialListener"},"region":{"startLine":1}}},{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.google.ads.mediation.customevent.CustomEventInterstitial"},"region":{"startLine":1}}},{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.google.ads.mediation.customevent.CustomEvent"},"region":{"startLine":1}}},{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.android.insecurebankv2.R"},"region":{"startLine":1}}},{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.google.ads.mediation.customevent.CustomEventListener"},"region":{"startLine":1}}},{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.google.ads.mediation.EmptyNetworkExtras"},"region":{"startLine":1}}},{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.google.ads.mediation.customevent.CustomEventServerParameters"},"region":{"startLine":1}}},{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.google.ads.mediation.MediationAdapter"},"region":{"startLine":1}}},{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.google.ads.mediation.AdUrlAdapter"},"region":{"startLine":1}}},{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.google.ads.mediation.MediationAdRequest"},"region":{"startLine":1}}},{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.android.insecurebankv2.DoTransfer"},"region":{"startLine":54,"snippet":{"text":" EditText from;\n Button getAccounts;\n InputStream in;\n JSONObject jsonObject;\n String passNormalized;\n EditText phoneNumber;\n BufferedReader reader;\n HttpResponse responseBody;\n String result;\n SharedPreferences serverDetails;\n EditText to;"}}}},{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.google.ads.mediation.AbstractAdViewAdapter"},"region":{"startLine":1}}},{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.google.ads.mediation.MediationBannerAdapter"},"region":{"startLine":1}}},{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.google.ads.mediation.MediationBannerListener"},"region":{"startLine":1}}},{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.google.ads.mediation.MediationInterstitialAdapter"},"region":{"startLine":1}}},{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.google.ads.mediation.NetworkExtras"},"region":{"startLine":1}}},{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.google.ads.mediation.MediationInterstitialListener"},"region":{"startLine":1}}},{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.google.ads.mediation.MediationServerParameters"},"region":{"startLine":1}}},{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.google.ads.AdRequest"},"region":{"startLine":1}}},{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.google.ads.AdSize"},"region":{"startLine":1}}}]},{"ruleId":"61cb48eb-3ea7-004a-a9a1-cd8400000000","ruleIndex":3,"message":{"text":"\uD83D\uDFE9 Recommendation

If other applications should not have access to this content provider, mark them as \"android:exported=false\" in the application manifest. Otherwise, set the \"android:exported\" attribute to true to allow other apps to access the stored data.

If this app is intentionally exporting the content provider, specify one or more permissions for reading and writing. If the content provider is for sharing data between the same developer across different apps, it is preferable to use the \"android:protectionLevel\" attribute and set it to \"signature\" protection. Signature permissions do not require user confirmation. And they provide a better user experience and more controlled access to the content provider data when the apps accessing the data are signed with the same key.

For applications that set either \"android:minSdkVersion\" or \"android:targetSdkVersion\" to 17 and higher, all of the providers are non-exported by default, unless the \"android:exported\" attribute is set to true or an intent-filter element is defined. For applications that set either \"android:minSdkVersion\" or \"android:targetSdkVersion\" to 16 or lower, a default exported status is true.

When accessing a content provider, use parameterized query methods such as query(), update(), and delete() to avoid potential SQL injection from untrusted sources. Using parameterized methods is insufficient if the selection argument is built by concatenating user data, before submitting it to the method. Check if access to sensitive information is possible or change it to bypass authorization mechanisms.

When creating a content provider that is exported for use by other applications, specify a single permission for reading and writing, or specify distinct permissions for reading and writing. Limit the permissions to those required to accomplish the task. Remember that it's usually easier to add permissions later to expose new functionality, than it is to take them away and impact existing users.

Content providers can also provide more granular access by declaring the \"android:grantUriPermissions\" attribute and using the FLAG_GRANT_READ_URI_PERMISSION and FLAG_GRANT_WRITE_URI_PERMISSION flags in the Intent object that activates the component. The scope of these permissions can be further limited by the element.

\uD83D\uDFE6 Code Snippet

\uD83D\uDFE7 com.android.insecurebankv2.TrackUserContentProvider(Showing 11 lines of 106)

  package com.android.insecurebankv2;
  public class TrackUserContentProvider extends android.content.ContentProvider {
      static final android.net.Uri CONTENT_URI = None;
      static final String CREATE_DB_TABLE = \" CREATE TABLE names (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NO...
      static final String DATABASE_NAME = \"mydb\";
      static final int DATABASE_VERSION = 1;
      static final String PROVIDER_NAME = \"com.android.insecurebankv2.TrackUserContentProvider\";
      static final String TABLE_NAME = \"names\";
      static final String URL = \"content://com.android.insecurebankv2.TrackUserContentProvider/trackerusers\";
      static final String name = \"name\";
      static final int uriCode = 1;

"},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.android.insecurebankv2.TrackUserContentProvider"},"region":{"startLine":1,"snippet":{"text":"package com.android.insecurebankv2;\npublic class TrackUserContentProvider extends android.content.ContentProvider {\n static final android.net.Uri CONTENT_URI = None;\n static final String CREATE_DB_TABLE = \" CREATE TABLE names (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL);\";\n static final String DATABASE_NAME = \"mydb\";\n static final int DATABASE_VERSION = 1;\n static final String PROVIDER_NAME = \"com.android.insecurebankv2.TrackUserContentProvider\";\n static final String TABLE_NAME = \"names\";\n static final String URL = \"content://com.android.insecurebankv2.TrackUserContentProvider/trackerusers\";\n static final String name = \"name\";\n static final int uriCode = 1;\n static final android.content.UriMatcher uriMatcher;\n private static java.util.HashMap values;\n private android.database.sqlite.SQLiteDatabase db;\n\n static TrackUserContentProvider()\n {\n com.android.insecurebankv2.TrackUserContentProvider.CONTENT_URI = android.net.Uri.parse(content://com.android.insecurebankv2.TrackUserContentProvider/trackerusers);\n com.android.insecurebankv2.TrackUserContentProvider.uriMatcher = new android.content.UriMatcher(-1);\n com.android.insecurebankv2.TrackUserContentProvider.uriMatcher.addURI(com.android.insecurebankv2.TrackUserContentProvider, trackerusers, 1);\n com.android.insecurebankv2.TrackUserContentProvider.uriMatcher.addURI(com.android.insecurebankv2.TrackUserContentProvider, trackerusers/*, 1);\n return;\n }\n\n public TrackUserContentProvider()\n {\n return;\n }\n\n public int delete(android.net.Uri p5, String p6, String[] p7)\n {\n switch (com.android.insecurebankv2.TrackUserContentProvider.uriMatcher.match(p5)) {\n case 1:\n int v0 = this.db.delete(names, p6, p7);\n this.getContext().getContentResolver().notifyChange(p5, 0);\n return v0;\n default:\n throw new IllegalArgumentException(new StringBuilder().append(Unknown URI ).append(p5).toString());\n }\n }\n\n public String getType(android.net.Uri p4)\n {\n switch (com.android.insecurebankv2.TrackUserContentProvider.uriMatcher.match(p4)) {\n case 1:\n return vnd.android.cursor.dir/u;\n default:\n throw new IllegalArgumentException(new StringBuilder().append(Unsupported URI: ).append(p4).toString());\n }\n }\n\n public android.net.Uri insert(android.net.Uri p7, android.content.ContentValues p8)\n {\n long v2 = this.db.insert(names, , p8);\n if (v2 <= 0) {\n throw new android.database.SQLException(new StringBuilder().append(Failed to add a record into ).append(p7).toString());\n } else {\n android.net.Uri v0 = android.content.ContentUris.withAppendedId(com.android.insecurebankv2.TrackUserContentProvider.CONTENT_URI, v2);\n this.getContext().getContentResolver().notifyChange(v0, 0);\n return v0;\n }\n }\n\n public boolean onCreate()\n {\n int v2_0;\n this.db = new com.android.insecurebankv2.TrackUserContentProvider$DatabaseHelper(this.getContext()).getWritableDatabase();\n if (this.db == null) {\n v2_0 = 0;\n } else {\n v2_0 = 1;\n }\n return v2_0;\n }\n\n public android.database.Cursor query(android.net.Uri p10, String[] p11, String p12, String[] p13, String p14)\n {\n android.database.sqlite.SQLiteQueryBuilder v0_1 = new android.database.sqlite.SQLiteQueryBuilder();\n v0_1.setTables(names);\n switch (com.android.insecurebankv2.TrackUserContentProvider.uriMatcher.match(p10)) {\n case 1:\n v0_1.setProjectionMap(com.android.insecurebankv2.TrackUserContentProvider.values);\n if ((p14 == null) || (p14 == )) {\n p14 = name;\n }\n android.database.Cursor v8 = v0_1.query(this.db, p11, p12, p13, 0, 0, p14);\n v8.setNotificationUri(this.getContext().getContentResolver(), p10);\n return v8;\n default:\n throw new IllegalArgumentException(new StringBuilder().append(Unknown URI ).append(p10).toString());\n }\n }\n\n public int update(android.net.Uri p5, android.content.ContentValues p6, String p7, String[] p8)\n {\n switch (com.android.insecurebankv2.TrackUserContentProvider.uriMatcher.match(p5)) {\n case 1:\n int v0 = this.db.update(names, p6, p7, p8);\n this.getContext().getContentResolver().notifyChange(p5, 0);\n return v0;\n default:\n throw new IllegalArgumentException(new StringBuilder().append(Unknown URI ).append(p5).toString());\n }\n }\n}\n"}}}}]},{"ruleId":"63fe316a-b4ef-1e00-171b-d42800000000","ruleIndex":4,"message":{"text":"\uD83D\uDFE9 Recommendation

Sensitive, hardcoded data (such as Private IPs/Emails or User/DB details) should not be stored unless secured specifically. An attacker can use that data for further malicious actions.

"},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+common_google_play_services_api_unavailable_text%3A+%251%24s+requires+one+or+more+Google+Play+services+that+are+not+currently+available.+Please+contact+the+developer+for+assistance."},"region":{"startLine":1}}}]},{"ruleId":"5225b063-3a08-f68e-2bad-572100000000","ruleIndex":5,"message":{"text":"\uD83D\uDFE9 Recommendation

Remove the \"android:debuggable=true\" setting from the Android manifest file or set it to “false” to mitigate this threat.

Zimperium's zShield provides debugger detection and tamper resistance to defend against the malicious use of these tools.

"},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+classes.dex"},"region":{"startLine":1}}}]},{"ruleId":"5b86446e-1cfc-cd18-3a11-f25c00000000","ruleIndex":6,"message":{"text":"\uD83D\uDFE9 Recommendation

Review the added code from third-parties to include any advertising libraries for potential malware activity.

"},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+Malware.FakeApp%2F22629"},"region":{"startLine":1}}}]},{"ruleId":"60d0b70e-f1a7-9107-8fbb-669900000000","ruleIndex":7,"message":{"text":"\uD83D\uDFE9 Recommendation

It is recommended to use explicit intents to start activities using the setComponent, setPackage, setClass or setClassName methods of the Intent class. It is also recommended to always use explicit intents to broadcast data within the same application or the LocalBroadcastManager to use a signature-permission protection level.

\uD83D\uDFE7 Locations

com.android.insecurebankv2.ChangePassword : onOptionsItemSelected
com.android.insecurebankv2.DoLogin : postData
com.android.insecurebankv2.DoLogin : onOptionsItemSelected
com.android.insecurebankv2.DoTransfer : onOptionsItemSelected
com.android.insecurebankv2.FilePrefActivity : onOptionsItemSelected
com.android.insecurebankv2.LoginActivity : onOptionsItemSelected
com.android.insecurebankv2.PostLogin : changePasswd
com.android.insecurebankv2.PostLogin : onOptionsItemSelected
com.android.insecurebankv2.PostLogin : viewStatment
com.android.insecurebankv2.ViewStatement : onOptionsItemSelected
com.android.insecurebankv2.WrongLogin : onOptionsItemSelected

\uD83D\uDFE6 Code Snippets (Showing 3 of 11)

\uD83D\uDFE7 com.android.insecurebankv2.ChangePassword : onOptionsItemSelected(Showing 11 lines of 19)

  
  public boolean onOptionsItemSelected(android.view.MenuItem p6)
  {
      boolean v2 = 1;
      int v1 = p6.getItemId();
      if (v1 != 2131558557) {
          if (v1 != 2131558558) {
              v2 = super.onOptionsItemSelected(p6);
          } else {
              android.content.Intent v0_0 = new android.content.Intent(this.getBaseContext(), com.android.insecureban...
              v0_0.addFlags(67108864);

\uD83D\uDFE7 com.android.insecurebankv2.DoLogin : postData(Showing 11 lines of 35)

  
      public void postData(String p13)
      {
          org.apache.http.HttpResponse v6;
          org.apache.http.impl.client.DefaultHttpClient v0_1 = new org.apache.http.impl.client.DefaultHttpClient();
‣‣        org.apache.http.client.methods.HttpPost v1_1 = new org.apache.http.client.methods.HttpPost(new StringBuilde...
          org.apache.http.client.methods.HttpPost v2_1 = new org.apache.http.client.methods.HttpPost(new StringBuilde...
          java.util.ArrayList v4_1 = new java.util.ArrayList(2);
          v4_1.add(new org.apache.http.message.BasicNameValuePair(username, this.this$0.username));
          v4_1.add(new org.apache.http.message.BasicNameValuePair(password, this.this$0.password));
          if (!this.this$0.username.equals(devadmin)) {

\uD83D\uDFE7 com.android.insecurebankv2.PostLogin : changePasswd

  
  protected void changePasswd()
  {
‣‣    android.content.Intent v0_1 = new android.content.Intent(this.getApplicationContext(), com.android.insecurebank...
      v0_1.putExtra(uname, this.uname);
      this.startActivity(v0_1);
      return;
  }
  

"},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.android.insecurebankv2.ChangePassword+%3A+onOptionsItemSelected"},"region":{"startLine":10,"snippet":{"text":"\n public boolean onOptionsItemSelected(android.view.MenuItem p6)\n {\n boolean v2 = 1;\n int v1 = p6.getItemId();\n if (v1 != 2131558557) {\n if (v1 != 2131558558) {\n v2 = super.onOptionsItemSelected(p6);\n } else {\n android.content.Intent v0_0 = new android.content.Intent(this.getBaseContext(), com.android.insecurebankv2.LoginActivity);\n v0_0.addFlags(67108864);\n this.startActivity(v0_0);\n }\n } else {\n this.callPreferences();\n }\n return v2;\n }\n"}}}},{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.android.insecurebankv2.DoLogin+%3A+postData"},"region":{"startLine":28,"snippet":{"text":"\n public void postData(String p13)\n {\n org.apache.http.HttpResponse v6;\n org.apache.http.impl.client.DefaultHttpClient v0_1 = new org.apache.http.impl.client.DefaultHttpClient();\n org.apache.http.client.methods.HttpPost v1_1 = new org.apache.http.client.methods.HttpPost(new StringBuilder().append(this.this$0.protocol).append(this.this$0.serverip).append(:).append(this.this$0.serverport).append(/login).toString());\n org.apache.http.client.methods.HttpPost v2_1 = new org.apache.http.client.methods.HttpPost(new StringBuilder().append(this.this$0.protocol).append(this.this$0.serverip).append(:).append(this.this$0.serverport).append(/devlogin).toString());\n java.util.ArrayList v4_1 = new java.util.ArrayList(2);\n v4_1.add(new org.apache.http.message.BasicNameValuePair(username, this.this$0.username));\n v4_1.add(new org.apache.http.message.BasicNameValuePair(password, this.this$0.password));\n if (!this.this$0.username.equals(devadmin)) {\n v1_1.setEntity(new org.apache.http.client.entity.UrlEncodedFormEntity(v4_1));\n v6 = v0_1.execute(v1_1);\n } else {\n v2_1.setEntity(new org.apache.http.client.entity.UrlEncodedFormEntity(v4_1));\n v6 = v0_1.execute(v2_1);\n }\n this.this$0.result = this.convertStreamToString(v6.getEntity().getContent());\n this.this$0.result = this.this$0.result.replace(\n, );\n if (this.this$0.result != null) {\n if (this.this$0.result.indexOf(Correct Credentials) == -1) {\n this.this$0.startActivity(new android.content.Intent(this.this$0.getApplicationContext(), com.android.insecurebankv2.WrongLogin));\n } else {\n android.util.Log.d(Successful Login:, new StringBuilder().append(, account=).append(this.this$0.username).append(:).append(this.this$0.password).toString());\n this.saveCreds(this.this$0.username, this.this$0.password);\n this.trackUserLogins();\n android.content.Intent v5_1 = new android.content.Intent(this.this$0.getApplicationContext(), com.android.insecurebankv2.PostLogin);\n v5_1.putExtra(uname, this.this$0.username);\n this.this$0.startActivity(v5_1);\n }\n }\n return;\n }\n"}}}},{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.android.insecurebankv2.DoLogin+%3A+onOptionsItemSelected"},"region":{"startLine":10,"snippet":{"text":"\n public boolean onOptionsItemSelected(android.view.MenuItem p6)\n {\n boolean v2 = 1;\n int v1 = p6.getItemId();\n if (v1 != 2131558557) {\n if (v1 != 2131558558) {\n v2 = super.onOptionsItemSelected(p6);\n } else {\n android.content.Intent v0_0 = new android.content.Intent(this.getBaseContext(), com.android.insecurebankv2.LoginActivity);\n v0_0.addFlags(67108864);\n this.startActivity(v0_0);\n }\n } else {\n this.callPreferences();\n }\n return v2;\n }\n"}}}},{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.android.insecurebankv2.DoTransfer+%3A+onOptionsItemSelected"},"region":{"startLine":10,"snippet":{"text":"\n public boolean onOptionsItemSelected(android.view.MenuItem p6)\n {\n boolean v2 = 1;\n int v1 = p6.getItemId();\n if (v1 != 2131558557) {\n if (v1 != 2131558558) {\n v2 = super.onOptionsItemSelected(p6);\n } else {\n android.content.Intent v0_0 = new android.content.Intent(this.getBaseContext(), com.android.insecurebankv2.LoginActivity);\n v0_0.addFlags(67108864);\n this.startActivity(v0_0);\n }\n } else {\n this.callPreferences();\n }\n return v2;\n }\n"}}}},{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.android.insecurebankv2.FilePrefActivity+%3A+onOptionsItemSelected"},"region":{"startLine":10,"snippet":{"text":"\n public boolean onOptionsItemSelected(android.view.MenuItem p6)\n {\n boolean v2 = 1;\n int v1 = p6.getItemId();\n if (v1 != 2131558557) {\n if (v1 != 2131558558) {\n v2 = super.onOptionsItemSelected(p6);\n } else {\n android.content.Intent v0_0 = new android.content.Intent(this.getBaseContext(), com.android.insecurebankv2.LoginActivity);\n v0_0.addFlags(67108864);\n this.startActivity(v0_0);\n }\n } else {\n this.callPreferences();\n }\n return v2;\n }\n"}}}},{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.android.insecurebankv2.LoginActivity+%3A+onOptionsItemSelected"},"region":{"startLine":10,"snippet":{"text":"\n public boolean onOptionsItemSelected(android.view.MenuItem p6)\n {\n boolean v2 = 1;\n int v1 = p6.getItemId();\n if (v1 != 2131558557) {\n if (v1 != 2131558558) {\n v2 = super.onOptionsItemSelected(p6);\n } else {\n android.content.Intent v0_0 = new android.content.Intent(this.getBaseContext(), com.android.insecurebankv2.LoginActivity);\n v0_0.addFlags(67108864);\n this.startActivity(v0_0);\n }\n } else {\n this.callPreferences();\n }\n return v2;\n }\n"}}}},{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.android.insecurebankv2.PostLogin+%3A+changePasswd"},"region":{"startLine":4,"snippet":{"text":"\n protected void changePasswd()\n {\n android.content.Intent v0_1 = new android.content.Intent(this.getApplicationContext(), com.android.insecurebankv2.ChangePassword);\n v0_1.putExtra(uname, this.uname);\n this.startActivity(v0_1);\n return;\n }\n"}}}},{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.android.insecurebankv2.PostLogin+%3A+onOptionsItemSelected"},"region":{"startLine":10,"snippet":{"text":"\n public boolean onOptionsItemSelected(android.view.MenuItem p6)\n {\n boolean v2 = 1;\n int v1 = p6.getItemId();\n if (v1 != 2131558557) {\n if (v1 != 2131558558) {\n v2 = super.onOptionsItemSelected(p6);\n } else {\n android.content.Intent v0_0 = new android.content.Intent(this.getBaseContext(), com.android.insecurebankv2.LoginActivity);\n v0_0.addFlags(67108864);\n this.startActivity(v0_0);\n }\n } else {\n this.callPreferences();\n }\n return v2;\n }\n"}}}},{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.android.insecurebankv2.PostLogin+%3A+viewStatment"},"region":{"startLine":4,"snippet":{"text":"\n protected void viewStatment()\n {\n android.content.Intent v0_1 = new android.content.Intent(this.getApplicationContext(), com.android.insecurebankv2.ViewStatement);\n v0_1.putExtra(uname, this.uname);\n this.startActivity(v0_1);\n return;\n }\n"}}}},{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.android.insecurebankv2.ViewStatement+%3A+onOptionsItemSelected"},"region":{"startLine":10,"snippet":{"text":"\n public boolean onOptionsItemSelected(android.view.MenuItem p6)\n {\n boolean v2 = 1;\n int v1 = p6.getItemId();\n if (v1 != 2131558557) {\n if (v1 != 2131558558) {\n v2 = super.onOptionsItemSelected(p6);\n } else {\n android.content.Intent v0_0 = new android.content.Intent(this.getBaseContext(), com.android.insecurebankv2.LoginActivity);\n v0_0.addFlags(67108864);\n this.startActivity(v0_0);\n }\n } else {\n this.callPreferences();\n }\n return v2;\n }\n"}}}},{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.android.insecurebankv2.WrongLogin+%3A+onOptionsItemSelected"},"region":{"startLine":10,"snippet":{"text":"\n public boolean onOptionsItemSelected(android.view.MenuItem p6)\n {\n boolean v2 = 1;\n int v1 = p6.getItemId();\n if (v1 != 2131558557) {\n if (v1 != 2131558558) {\n v2 = super.onOptionsItemSelected(p6);\n } else {\n android.content.Intent v0_0 = new android.content.Intent(this.getBaseContext(), com.android.insecurebankv2.LoginActivity);\n v0_0.addFlags(67108864);\n this.startActivity(v0_0);\n }\n } else {\n this.callPreferences();\n }\n return v2;\n }\n"}}}}]},{"ruleId":"60e3f8fc-7552-e2f0-e9ca-648e00000000","ruleIndex":8,"message":{"text":"\uD83D\uDFE9 Recommendation

It is recommended to always use explicit intents for the broadcast of data within the same application.

If it is not required to send broadcasts to components outside of the app, then send and receive local broadcasts using the LocalBroadcastManager available in the Support Library. The LocalBroadcastManager is much more efficient because no interprocess communication is needed. Also, this minimizes security issues related to other apps being able to receive or send broadcasts. Local broadcasts can be used as a general-purpose pub/sub event bus in the app without any overheads of system-wide broadcasts.\n\nDo not broadcast sensitive information using an implicit intent. The information can be read by any app that registers to receive the broadcast. There are several ways to control who can receive the broadcasts:\n\nIn Android 4.0 and higher, you can specify a package with setPackage(String) when sending a broadcast. The system restricts the broadcast to the set of apps that match the package.\nAdditionally, send local broadcasts with LocalBroadcastManager.

\uD83D\uDFE6 Code Snippet

\uD83D\uDFE7 com.android.insecurebankv2.ChangePassword : broadcastChangepasswordSMS(Showing 11 lines of 15)

  
  private void broadcastChangepasswordSMS(String p4, String p5)
  {
      if (!android.text.TextUtils.isEmpty(p4.toString().trim())) {
          android.content.Intent v0_1 = new android.content.Intent();
          v0_1.setAction(theBroadcast);
          v0_1.putExtra(phonenumber, p4);
          v0_1.putExtra(newpass, p5);
          this.sendBroadcast(v0_1);
‣‣    } else {
          System.out.println(Phone number Invalid.);

"},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.android.insecurebankv2.ChangePassword+%3A+broadcastChangepasswordSMS"},"region":{"startLine":5,"snippet":{"text":"\n private void broadcastChangepasswordSMS(String p4, String p5)\n {\n if (!android.text.TextUtils.isEmpty(p4.toString().trim())) {\n android.content.Intent v0_1 = new android.content.Intent();\n v0_1.setAction(theBroadcast);\n v0_1.putExtra(phonenumber, p4);\n v0_1.putExtra(newpass, p5);\n this.sendBroadcast(v0_1);\n } else {\n System.out.println(Phone number Invalid.);\n }\n return;\n }\n"}}}}]},{"ruleId":"63c7aacb-f7ca-34f8-62f5-126000000000","ruleIndex":9,"message":{"text":"\uD83D\uDFE9 Recommendation

Hardcoded keys must be avoided. When possible, replace them with ephemeral keys.

\uD83D\uDFE7 Locations

Lcom/android/insecurebankv2/CryptoClass; : aes256decrypt
Lcom/android/insecurebankv2/CryptoClass; : aes256encrypt

\uD83D\uDFE6 Code Snippets

\uD83D\uDFE7 Lcom/android/insecurebankv2/CryptoClass; : aes256decrypt

  
  public static byte[] aes256decrypt(byte[] p4, byte[] p5, byte[] p6)
  {
      javax.crypto.spec.IvParameterSpec v1_1 = new javax.crypto.spec.IvParameterSpec(p4);
‣‣    javax.crypto.spec.SecretKeySpec v2_1 = new javax.crypto.spec.SecretKeySpec(p5, AES);
      javax.crypto.Cipher v0 = javax.crypto.Cipher.getInstance(AES/CBC/PKCS5Padding);
      v0.init(2, v2_1, v1_1);
      return v0.doFinal(p6);
  }
  

\uD83D\uDFE7 Lcom/android/insecurebankv2/CryptoClass; : aes256encrypt

  
  public static byte[] aes256encrypt(byte[] p4, byte[] p5, byte[] p6)
  {
      javax.crypto.spec.IvParameterSpec v1_1 = new javax.crypto.spec.IvParameterSpec(p4);
‣‣    javax.crypto.spec.SecretKeySpec v2_1 = new javax.crypto.spec.SecretKeySpec(p5, AES);
      javax.crypto.Cipher v0 = javax.crypto.Cipher.getInstance(AES/CBC/PKCS5Padding);
      v0.init(1, v2_1, v1_1);
      return v0.doFinal(p6);
  }
  

"},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+Lcom%2Fandroid%2Finsecurebankv2%2FCryptoClass%3B+%3A+aes256decrypt"},"region":{"startLine":2,"snippet":{"text":"\n public static byte[] aes256decrypt(byte[] p4, byte[] p5, byte[] p6)\n {\n javax.crypto.spec.IvParameterSpec v1_1 = new javax.crypto.spec.IvParameterSpec(p4);\n javax.crypto.spec.SecretKeySpec v2_1 = new javax.crypto.spec.SecretKeySpec(p5, AES);\n javax.crypto.Cipher v0 = javax.crypto.Cipher.getInstance(AES/CBC/PKCS5Padding);\n v0.init(2, v2_1, v1_1);\n return v0.doFinal(p6);\n }\n"}}}},{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+Lcom%2Fandroid%2Finsecurebankv2%2FCryptoClass%3B+%3A+aes256encrypt"},"region":{"startLine":2,"snippet":{"text":"\n public static byte[] aes256encrypt(byte[] p4, byte[] p5, byte[] p6)\n {\n javax.crypto.spec.IvParameterSpec v1_1 = new javax.crypto.spec.IvParameterSpec(p4);\n javax.crypto.spec.SecretKeySpec v2_1 = new javax.crypto.spec.SecretKeySpec(p5, AES);\n javax.crypto.Cipher v0 = javax.crypto.Cipher.getInstance(AES/CBC/PKCS5Padding);\n v0.init(1, v2_1, v1_1);\n return v0.doFinal(p6);\n }\n"}}}}]},{"ruleId":"63770e36-eff2-45a3-8feb-909900000000","ruleIndex":10,"message":{"text":"\uD83D\uDFE9 Recommendation

To secure manifest-declared broadcast receivers, you should always declare appropriate permissions during the call to the registerReceiver method.

Broadcast receivers represent a likely exploitable component which is often used to start services, so it is highly recommended to verify that all of the external data is passed to them. To enable the most restrictive (and therefore secure) policy, use the signature permissions to minimize the number of exported intents.

If you do not need to send broadcasts to components outside of your app, then send and receive local broadcasts with the LocalBroadcastManager, which is available in the Support Library. The LocalBroadcastManager is much more efficient (no interprocess communication needed) and allows you to avoid any security issues related to other apps being able to receive or send your broadcasts. Local broadcasts can be used as a general purpose pub/sub event bus in your app without any overheads of system-wide broadcasts.

When you register a receiver, any app can send potentially malicious broadcasts to your app's receiver. Here are some ways to limit the broadcasts that your app receives:

- Specify a permission when registering a broadcast receiver.\r\n- For manifest-declared receivers, set the android:exported attribute to \"false\" in the manifest. The receiver does not receive broadcasts from sources outside of the app.\r\n- Limit yourself to only local broadcasts with LocalBroadcastManager.\r\n- Specify a permission when registering a broadcast receiver.

\uD83D\uDFE6 Code Snippet

\uD83D\uDFE7 com.android.insecurebankv2.MyBroadCastReceiver(Showing 11 lines of 34)

  package com.android.insecurebankv2;
  public class MyBroadCastReceiver extends android.content.BroadcastReceiver {
      public static final String MYPREFS = \"mySharedPreferences\";
      String usernameBase64ByteString;
  
      public MyBroadCastReceiver()
      {
          return;
      }
  
      public void onReceive(android.content.Context p17, android.content.Intent p18)

"},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.android.insecurebankv2.MyBroadCastReceiver"},"region":{"startLine":1,"snippet":{"text":"package com.android.insecurebankv2;\npublic class MyBroadCastReceiver extends android.content.BroadcastReceiver {\n public static final String MYPREFS = \"mySharedPreferences\";\n String usernameBase64ByteString;\n\n public MyBroadCastReceiver()\n {\n return;\n }\n\n public void onReceive(android.content.Context p17, android.content.Intent p18)\n {\n String v12 = p18.getStringExtra(phonenumber);\n String v10 = p18.getStringExtra(newpass);\n if (v12 == null) {\n System.out.println(Phone number is null);\n } else {\n try {\n android.content.SharedPreferences v13 = p17.getSharedPreferences(mySharedPreferences, 1);\n this.usernameBase64ByteString = new String(android.util.Base64.decode(v13.getString(EncryptedUsername, 0), 0), UTF-8);\n String v8 = new com.android.insecurebankv2.CryptoClass().aesDeccryptedString(v13.getString(superSecurePassword, 0));\n String v2 = v12.toString();\n String v4 = new StringBuilder().append(Updated Password from: ).append(v8).append( to: ).append(v10).toString();\n android.telephony.SmsManager v1 = android.telephony.SmsManager.getDefault();\n System.out.println(new StringBuilder().append(For the changepassword - phonenumber: ).append(v2).append( password is: ).append(v4).toString());\n v1.sendTextMessage(v2, 0, v4, 0, 0);\n } catch (Exception v9) {\n v9.printStackTrace();\n }\n }\n return;\n }\n}\n"}}}}]},{"ruleId":"6268f908-f255-393a-3274-df8500000000","ruleIndex":11,"message":{"text":"\uD83D\uDFE9 Recommendation

If the activity does not need to be shared by other applications, explicitly mark components with android:exported=\"false\" in the app manifest.\r\nIf the exported component will only be shared between related apps under your control, use android:protectionLevel=\"signature\" in the XML manifest to restrict access to applications signed by you.

\uD83D\uDFE7 Locations

com.android.insecurebankv2.PostLogin
com.android.insecurebankv2.DoTransfer
com.android.insecurebankv2.ViewStatement
com.android.insecurebankv2.ChangePassword

\uD83D\uDFE6 Code Snippets

\uD83D\uDFE7 com.android.insecurebankv2.PostLogin(Showing 11 lines of 137)

  package com.android.insecurebankv2;
  public class PostLogin extends android.app.Activity {
      android.widget.Button changepasswd_button;
      android.widget.TextView root_status;
      android.widget.Button statement_button;
      android.widget.Button transfer_button;
      String uname;
  
      public PostLogin()
      {
          return;

\uD83D\uDFE7 com.android.insecurebankv2.DoTransfer(Showing 11 lines of 109)

  package com.android.insecurebankv2;
  public class DoTransfer extends android.app.Activity {
      public static final String MYPREFS2 = \"mySharedPreferences\";
      String acc1;
      String acc2;
      android.widget.EditText amount;
      android.widget.Button button1;
      android.widget.EditText from;
      android.widget.Button getAccounts;
      java.io.InputStream in;
      org.json.JSONObject jsonObject;

\uD83D\uDFE7 com.android.insecurebankv2.ViewStatement(Showing 11 lines of 62)

  package com.android.insecurebankv2;
  public class ViewStatement extends android.app.Activity {
      String uname;
  
      public ViewStatement()
      {
          return;
      }
  
      public void callPreferences()
      {

\uD83D\uDFE7 com.android.insecurebankv2.ChangePassword(Showing 11 lines of 114)

  package com.android.insecurebankv2;
  public class ChangePassword extends android.app.Activity {
      private static final String PASSWORD_PATTERN = \"((?=.*\\\\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[@#$%]).{6,20})\";
      android.widget.Button changePassword_button;
      android.widget.EditText changePassword_text;
      private java.util.regex.Matcher matcher;
      private java.util.regex.Pattern pattern;
      String protocol;
      java.io.BufferedReader reader;
      String result;
      android.content.SharedPreferences serverDetails;

"},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.android.insecurebankv2.PostLogin"},"region":{"startLine":1,"snippet":{"text":"package com.android.insecurebankv2;\npublic class PostLogin extends android.app.Activity {\n android.widget.Button changepasswd_button;\n android.widget.TextView root_status;\n android.widget.Button statement_button;\n android.widget.Button transfer_button;\n String uname;\n\n public PostLogin()\n {\n return;\n }\n\n private boolean doesSUexist()\n {\n int v3_0 = 1;\n try {\n String v5_3 = Runtime.getRuntime();\n java.io.InputStream v6_2 = new String[2];\n v6_2[0] = /system/xbin/which;\n v6_2[1] = su;\n Process v1 = v5_3.exec(v6_2);\n } catch (Throwable v2) {\n if (v1 != null) {\n v1.destroy();\n }\n v3_0 = 0;\n return v3_0;\n } catch (int v3_1) {\n if (v1 != null) {\n v1.destroy();\n }\n throw v3_1;\n }\n if (new java.io.BufferedReader(new java.io.InputStreamReader(v1.getInputStream())).readLine() == null) {\n if (v1 != null) {\n v1.destroy();\n }\n v3_0 = 0;\n return v3_0;\n } else {\n if (v1 == null) {\n return v3_0;\n } else {\n v1.destroy();\n return v3_0;\n }\n }\n }\n\n private boolean doesSuperuserApkExist(String p5)\n {\n int v2 = 1;\n if (Boolean.valueOf(new java.io.File(/system/app/Superuser.apk).exists()).booleanValue() != 1) {\n v2 = 0;\n }\n return v2;\n }\n\n public void callPreferences()\n {\n this.startActivity(new android.content.Intent(this, com.android.insecurebankv2.FilePrefActivity));\n return;\n }\n\n protected void changePasswd()\n {\n android.content.Intent v0_1 = new android.content.Intent(this.getApplicationContext(), com.android.insecurebankv2.ChangePassword);\n v0_1.putExtra(uname, this.uname);\n this.startActivity(v0_1);\n return;\n }\n\n protected void onCreate(android.os.Bundle p4)\n {\n super.onCreate(p4);\n this.setContentView(2130968606);\n this.uname = this.getIntent().getStringExtra(uname);\n this.root_status = ((android.widget.TextView) this.findViewById(2131558528));\n this.showRootStatus();\n this.transfer_button = ((android.widget.Button) this.findViewById(2131558525));\n this.transfer_button.setOnClickListener(new com.android.insecurebankv2.PostLogin$1(this));\n this.statement_button = ((android.widget.Button) this.findViewById(2131558526));\n this.statement_button.setOnClickListener(new com.android.insecurebankv2.PostLogin$2(this));\n this.changepasswd_button = ((android.widget.Button) this.findViewById(2131558527));\n this.changepasswd_button.setOnClickListener(new com.android.insecurebankv2.PostLogin$3(this));\n return;\n }\n\n public boolean onCreateOptionsMenu(android.view.Menu p3)\n {\n this.getMenuInflater().inflate(2131623938, p3);\n return 1;\n }\n\n public boolean onOptionsItemSelected(android.view.MenuItem p6)\n {\n boolean v2 = 1;\n int v1 = p6.getItemId();\n if (v1 != 2131558557) {\n if (v1 != 2131558558) {\n v2 = super.onOptionsItemSelected(p6);\n } else {\n android.content.Intent v0_0 = new android.content.Intent(this.getBaseContext(), com.android.insecurebankv2.LoginActivity);\n v0_0.addFlags(67108864);\n this.startActivity(v0_0);\n }\n } else {\n this.callPreferences();\n }\n return v2;\n }\n\n void showRootStatus()\n {\n if ((!this.doesSuperuserApkExist(/system/app/Superuser.apk)) && (!this.doesSUexist())) {\n int v0 = 0;\n } else {\n v0 = 1;\n }\n if (v0 != 1) {\n this.root_status.setText(Device not Rooted!!);\n } else {\n this.root_status.setText(Rooted Device!!);\n }\n return;\n }\n\n protected void viewStatment()\n {\n android.content.Intent v0_1 = new android.content.Intent(this.getApplicationContext(), com.android.insecurebankv2.ViewStatement);\n v0_1.putExtra(uname, this.uname);\n this.startActivity(v0_1);\n return;\n }\n}\n"}}}},{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.android.insecurebankv2.DoTransfer"},"region":{"startLine":1,"snippet":{"text":"package com.android.insecurebankv2;\npublic class DoTransfer extends android.app.Activity {\n public static final String MYPREFS2 = \"mySharedPreferences\";\n String acc1;\n String acc2;\n android.widget.EditText amount;\n android.widget.Button button1;\n android.widget.EditText from;\n android.widget.Button getAccounts;\n java.io.InputStream in;\n org.json.JSONObject jsonObject;\n String number;\n String passNormalized;\n android.widget.EditText phoneNumber;\n String protocol;\n java.io.BufferedReader reader;\n org.apache.http.HttpResponse responseBody;\n String result;\n android.content.SharedPreferences serverDetails;\n String serverip;\n String serverport;\n android.widget.EditText to;\n android.widget.Button transfer;\n String usernameBase64ByteString;\n\n public DoTransfer()\n {\n this.number = 5554;\n this.serverip = ;\n this.serverport = ;\n this.protocol = http://;\n return;\n }\n\n static synthetic String access$000(com.android.insecurebankv2.DoTransfer p1, String p2)\n {\n return p1.getNormalizedPassword(p2);\n }\n\n private String getNormalizedPassword(String p3)\n {\n return new com.android.insecurebankv2.CryptoClass().aesDeccryptedString(p3);\n }\n\n public void callPreferences()\n {\n this.startActivity(new android.content.Intent(this, com.android.insecurebankv2.FilePrefActivity));\n return;\n }\n\n public String convertStreamToString(java.io.InputStream p7)\n {\n try {\n this.reader = new java.io.BufferedReader(new java.io.InputStreamReader(p7, UTF-8));\n } catch (java.io.UnsupportedEncodingException v0) {\n v0.printStackTrace();\n }\n StringBuilder v2_1 = new StringBuilder();\n while(true) {\n String v1 = this.reader.readLine();\n if (v1 == null) {\n break;\n }\n v2_1.append(new StringBuilder().append(v1).append(\n).toString());\n }\n p7.close();\n return v2_1.toString();\n }\n\n protected void onCreate(android.os.Bundle p4)\n {\n super.onCreate(p4);\n this.setContentView(2130968603);\n this.serverDetails = android.preference.PreferenceManager.getDefaultSharedPreferences(this);\n this.serverip = this.serverDetails.getString(serverip, 0);\n this.serverport = this.serverDetails.getString(serverport, 0);\n this.transfer = ((android.widget.Button) this.findViewById(2131558513));\n this.transfer.setOnClickListener(new com.android.insecurebankv2.DoTransfer$1(this));\n this.button1 = ((android.widget.Button) this.findViewById(2131558510));\n this.button1.setOnClickListener(new com.android.insecurebankv2.DoTransfer$2(this));\n return;\n }\n\n public boolean onCreateOptionsMenu(android.view.Menu p3)\n {\n this.getMenuInflater().inflate(2131623938, p3);\n return 1;\n }\n\n public boolean onOptionsItemSelected(android.view.MenuItem p6)\n {\n boolean v2 = 1;\n int v1 = p6.getItemId();\n if (v1 != 2131558557) {\n if (v1 != 2131558558) {\n v2 = super.onOptionsItemSelected(p6);\n } else {\n android.content.Intent v0_0 = new android.content.Intent(this.getBaseContext(), com.android.insecurebankv2.LoginActivity);\n v0_0.addFlags(67108864);\n this.startActivity(v0_0);\n }\n } else {\n this.callPreferences();\n }\n return v2;\n }\n}\n"}}}},{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.android.insecurebankv2.ViewStatement"},"region":{"startLine":1,"snippet":{"text":"package com.android.insecurebankv2;\npublic class ViewStatement extends android.app.Activity {\n String uname;\n\n public ViewStatement()\n {\n return;\n }\n\n public void callPreferences()\n {\n this.startActivity(new android.content.Intent(this, com.android.insecurebankv2.FilePrefActivity));\n return;\n }\n\n protected void onCreate(android.os.Bundle p10)\n {\n super.onCreate(p10);\n this.setContentView(2130968607);\n this.uname = this.getIntent().getStringExtra(uname);\n java.io.File v2_1 = new java.io.File(android.os.Environment.getExternalStorageDirectory(), new StringBuilder().append(Statements_).append(this.uname).append(.html).toString());\n System.out.println(v2_1.toString());\n if (!v2_1.exists()) {\n this.startActivity(new android.content.Intent(this, com.android.insecurebankv2.PostLogin));\n android.widget.Toast.makeText(this, Statement does not Exist!!, 1).show();\n } else {\n android.webkit.WebView v5_1 = ((android.webkit.WebView) this.findViewById(2131558530));\n v5_1.loadUrl(new StringBuilder().append(file://).append(android.os.Environment.getExternalStorageDirectory()).append(/Statements_).append(this.uname).append(.html).toString());\n v5_1.getSettings().setJavaScriptEnabled(1);\n v5_1.getSettings().setSaveFormData(1);\n v5_1.getSettings().setBuiltInZoomControls(1);\n v5_1.setWebViewClient(new com.android.insecurebankv2.MyWebViewClient());\n v5_1.setWebChromeClient(new android.webkit.WebChromeClient());\n }\n return;\n }\n\n public boolean onCreateOptionsMenu(android.view.Menu p3)\n {\n this.getMenuInflater().inflate(2131623938, p3);\n return 1;\n }\n\n public boolean onOptionsItemSelected(android.view.MenuItem p6)\n {\n boolean v2 = 1;\n int v1 = p6.getItemId();\n if (v1 != 2131558557) {\n if (v1 != 2131558558) {\n v2 = super.onOptionsItemSelected(p6);\n } else {\n android.content.Intent v0_0 = new android.content.Intent(this.getBaseContext(), com.android.insecurebankv2.LoginActivity);\n v0_0.addFlags(67108864);\n this.startActivity(v0_0);\n }\n } else {\n this.callPreferences();\n }\n return v2;\n }\n}\n"}}}},{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.android.insecurebankv2.ChangePassword"},"region":{"startLine":1,"snippet":{"text":"package com.android.insecurebankv2;\npublic class ChangePassword extends android.app.Activity {\n private static final String PASSWORD_PATTERN = \"((?=.*\\\\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[@#$%]).{6,20})\";\n android.widget.Button changePassword_button;\n android.widget.EditText changePassword_text;\n private java.util.regex.Matcher matcher;\n private java.util.regex.Pattern pattern;\n String protocol;\n java.io.BufferedReader reader;\n String result;\n android.content.SharedPreferences serverDetails;\n String serverip;\n String serverport;\n android.widget.TextView textView_Username;\n String uname;\n\n public ChangePassword()\n {\n this.serverip = ;\n this.serverport = ;\n this.protocol = http://;\n return;\n }\n\n static synthetic java.util.regex.Pattern access$000(com.android.insecurebankv2.ChangePassword p1)\n {\n return p1.pattern;\n }\n\n static synthetic java.util.regex.Pattern access$002(com.android.insecurebankv2.ChangePassword p0, java.util.regex.Pattern p1)\n {\n p0.pattern = p1;\n return p1;\n }\n\n static synthetic java.util.regex.Matcher access$100(com.android.insecurebankv2.ChangePassword p1)\n {\n return p1.matcher;\n }\n\n static synthetic java.util.regex.Matcher access$102(com.android.insecurebankv2.ChangePassword p0, java.util.regex.Matcher p1)\n {\n p0.matcher = p1;\n return p1;\n }\n\n static synthetic void access$200(com.android.insecurebankv2.ChangePassword p0, String p1, String p2)\n {\n p0.broadcastChangepasswordSMS(p1, p2);\n return;\n }\n\n private void broadcastChangepasswordSMS(String p4, String p5)\n {\n if (!android.text.TextUtils.isEmpty(p4.toString().trim())) {\n android.content.Intent v0_1 = new android.content.Intent();\n v0_1.setAction(theBroadcast);\n v0_1.putExtra(phonenumber, p4);\n v0_1.putExtra(newpass, p5);\n this.sendBroadcast(v0_1);\n } else {\n System.out.println(Phone number Invalid.);\n }\n return;\n }\n\n public void callPreferences()\n {\n this.startActivity(new android.content.Intent(this, com.android.insecurebankv2.FilePrefActivity));\n return;\n }\n\n protected void onCreate(android.os.Bundle p5)\n {\n super.onCreate(p5);\n this.setContentView(2130968601);\n this.serverDetails = android.preference.PreferenceManager.getDefaultSharedPreferences(this);\n this.serverip = this.serverDetails.getString(serverip, 0);\n this.serverport = this.serverDetails.getString(serverport, 0);\n this.changePassword_text = ((android.widget.EditText) this.findViewById(2131558503));\n this.uname = this.getIntent().getStringExtra(uname);\n System.out.println(new StringBuilder().append(newpassword=).append(this.uname).toString());\n this.textView_Username = ((android.widget.TextView) this.findViewById(2131558502));\n this.textView_Username.setText(this.uname);\n this.changePassword_button = ((android.widget.Button) this.findViewById(2131558504));\n this.changePassword_button.setOnClickListener(new com.android.insecurebankv2.ChangePassword$1(this));\n return;\n }\n\n public boolean onCreateOptionsMenu(android.view.Menu p3)\n {\n this.getMenuInflater().inflate(2131623938, p3);\n return 1;\n }\n\n public boolean onOptionsItemSelected(android.view.MenuItem p6)\n {\n boolean v2 = 1;\n int v1 = p6.getItemId();\n if (v1 != 2131558557) {\n if (v1 != 2131558558) {\n v2 = super.onOptionsItemSelected(p6);\n } else {\n android.content.Intent v0_0 = new android.content.Intent(this.getBaseContext(), com.android.insecurebankv2.LoginActivity);\n v0_0.addFlags(67108864);\n this.startActivity(v0_0);\n }\n } else {\n this.callPreferences();\n }\n return v2;\n }\n}\n"}}}}]},{"ruleId":"63d8db78-1e43-ad8f-1243-c49400000000","ruleIndex":12,"message":{"text":"\uD83D\uDFE9 Recommendation

To remove this vulnerability, change the HttpHost scheme from \"DEFAULT_SCHEME_NAME\" which is equivalent to \"http\" or set the explicit \"http\" string to \"https\".

\uD83D\uDFE7 Locations

com.android.insecurebankv2.ChangePassword : postData
com.android.insecurebankv2.DoLogin : postData
com.android.insecurebankv2.DoTransfer : doInBackground

\uD83D\uDFE6 Code Snippets (Showing 2 of 3)

\uD83D\uDFE7 com.android.insecurebankv2.ChangePassword : postData(Showing 11 lines of 22)

  
      public void postData(String p11)
      {
          org.apache.http.impl.client.DefaultHttpClient v0_1 = new org.apache.http.impl.client.DefaultHttpClient();
          org.apache.http.client.methods.HttpPost v1_1 = new org.apache.http.client.methods.HttpPost(new StringBuilde...
          java.util.ArrayList v4_1 = new java.util.ArrayList(2);
          v4_1.add(new org.apache.http.message.BasicNameValuePair(username, this.this$0.uname));
          v4_1.add(new org.apache.http.message.BasicNameValuePair(newpassword, this.this$0.changePassword_text.getTex...
          v1_1.setEntity(new org.apache.http.client.entity.UrlEncodedFormEntity(v4_1));
          com.android.insecurebankv2.ChangePassword.access$002(this.this$0, java.util.regex.Pattern.compile(((?=.*\\d)...
          com.android.insecurebankv2.ChangePassword.access$102(this.this$0, com.android.insecurebankv2.ChangePassword...

\uD83D\uDFE7 com.android.insecurebankv2.DoLogin : postData(Showing 11 lines of 35)

  
      public void postData(String p13)
      {
          org.apache.http.HttpResponse v6;
          org.apache.http.impl.client.DefaultHttpClient v0_1 = new org.apache.http.impl.client.DefaultHttpClient();
          org.apache.http.client.methods.HttpPost v1_1 = new org.apache.http.client.methods.HttpPost(new StringBuilde...
          org.apache.http.client.methods.HttpPost v2_1 = new org.apache.http.client.methods.HttpPost(new StringBuilde...
          java.util.ArrayList v4_1 = new java.util.ArrayList(2);
          v4_1.add(new org.apache.http.message.BasicNameValuePair(username, this.this$0.username));
          v4_1.add(new org.apache.http.message.BasicNameValuePair(password, this.this$0.password));
          if (!this.this$0.username.equals(devadmin)) {

"},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.android.insecurebankv2.ChangePassword+%3A+postData"},"region":{"startLine":5,"snippet":{"text":"\n public void postData(String p11)\n {\n org.apache.http.impl.client.DefaultHttpClient v0_1 = new org.apache.http.impl.client.DefaultHttpClient();\n org.apache.http.client.methods.HttpPost v1_1 = new org.apache.http.client.methods.HttpPost(new StringBuilder().append(this.this$0.protocol).append(this.this$0.serverip).append(:).append(this.this$0.serverport).append(/changepassword).toString());\n java.util.ArrayList v4_1 = new java.util.ArrayList(2);\n v4_1.add(new org.apache.http.message.BasicNameValuePair(username, this.this$0.uname));\n v4_1.add(new org.apache.http.message.BasicNameValuePair(newpassword, this.this$0.changePassword_text.getText().toString()));\n v1_1.setEntity(new org.apache.http.client.entity.UrlEncodedFormEntity(v4_1));\n com.android.insecurebankv2.ChangePassword.access$002(this.this$0, java.util.regex.Pattern.compile(((?=.*\\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[@#$%]).{6,20})));\n com.android.insecurebankv2.ChangePassword.access$102(this.this$0, com.android.insecurebankv2.ChangePassword.access$000(this.this$0).matcher(this.this$0.changePassword_text.getText().toString()));\n if (!com.android.insecurebankv2.ChangePassword.access$100(this.this$0).matches()) {\n this.this$0.runOnUiThread(new com.android.insecurebankv2.ChangePassword$RequestChangePasswordTask$2(this));\n } else {\n this.this$0.result = this.convertStreamToString(v0_1.execute(v1_1).getEntity().getContent());\n this.this$0.result = this.this$0.result.replace(\n, );\n this.this$0.runOnUiThread(new com.android.insecurebankv2.ChangePassword$RequestChangePasswordTask$1(this));\n }\n return;\n }\n"}}}},{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.android.insecurebankv2.DoLogin+%3A+postData"},"region":{"startLine":7,"snippet":{"text":"\n public void postData(String p13)\n {\n org.apache.http.HttpResponse v6;\n org.apache.http.impl.client.DefaultHttpClient v0_1 = new org.apache.http.impl.client.DefaultHttpClient();\n org.apache.http.client.methods.HttpPost v1_1 = new org.apache.http.client.methods.HttpPost(new StringBuilder().append(this.this$0.protocol).append(this.this$0.serverip).append(:).append(this.this$0.serverport).append(/login).toString());\n org.apache.http.client.methods.HttpPost v2_1 = new org.apache.http.client.methods.HttpPost(new StringBuilder().append(this.this$0.protocol).append(this.this$0.serverip).append(:).append(this.this$0.serverport).append(/devlogin).toString());\n java.util.ArrayList v4_1 = new java.util.ArrayList(2);\n v4_1.add(new org.apache.http.message.BasicNameValuePair(username, this.this$0.username));\n v4_1.add(new org.apache.http.message.BasicNameValuePair(password, this.this$0.password));\n if (!this.this$0.username.equals(devadmin)) {\n v1_1.setEntity(new org.apache.http.client.entity.UrlEncodedFormEntity(v4_1));\n v6 = v0_1.execute(v1_1);\n } else {\n v2_1.setEntity(new org.apache.http.client.entity.UrlEncodedFormEntity(v4_1));\n v6 = v0_1.execute(v2_1);\n }\n this.this$0.result = this.convertStreamToString(v6.getEntity().getContent());\n this.this$0.result = this.this$0.result.replace(\n, );\n if (this.this$0.result != null) {\n if (this.this$0.result.indexOf(Correct Credentials) == -1) {\n this.this$0.startActivity(new android.content.Intent(this.this$0.getApplicationContext(), com.android.insecurebankv2.WrongLogin));\n } else {\n android.util.Log.d(Successful Login:, new StringBuilder().append(, account=).append(this.this$0.username).append(:).append(this.this$0.password).toString());\n this.saveCreds(this.this$0.username, this.this$0.password);\n this.trackUserLogins();\n android.content.Intent v5_1 = new android.content.Intent(this.this$0.getApplicationContext(), com.android.insecurebankv2.PostLogin);\n v5_1.putExtra(uname, this.this$0.username);\n this.this$0.startActivity(v5_1);\n }\n }\n return;\n }\n"}}}},{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.android.insecurebankv2.DoTransfer+%3A+doInBackground"},"region":{"startLine":5,"snippet":{"text":"\n protected varargs String doInBackground(String[] p15)\n {\n org.apache.http.impl.client.DefaultHttpClient v2_1 = new org.apache.http.impl.client.DefaultHttpClient();\n org.apache.http.client.methods.HttpPost v3_0 = new org.apache.http.client.methods.HttpPost(new StringBuilder().append(this.this$0.protocol).append(this.this$0.serverip).append(:).append(this.this$0.serverport).append(/dotransfer).toString());\n android.content.SharedPreferences v6 = this.this$0.getSharedPreferences(mySharedPreferences, 0);\n try {\n this.this$0.usernameBase64ByteString = new String(android.util.Base64.decode(v6.getString(EncryptedUsername, 0), 0), UTF-8);\n try {\n this.this$0.passNormalized = com.android.insecurebankv2.DoTransfer.access$000(this.this$0, v6.getString(superSecurePassword, 0));\n } catch (java.io.IOException v1_2) {\n v1_2.printStackTrace();\n } catch (java.io.IOException v1_2) {\n } catch (java.io.IOException v1_2) {\n } catch (java.io.IOException v1_2) {\n } catch (java.io.IOException v1_2) {\n } catch (java.io.IOException v1_2) {\n } catch (java.io.IOException v1_2) {\n }\n java.util.ArrayList v4_1 = new java.util.ArrayList(5);\n v4_1.add(new org.apache.http.message.BasicNameValuePair(username, this.this$0.usernameBase64ByteString));\n v4_1.add(new org.apache.http.message.BasicNameValuePair(password, this.this$0.passNormalized));\n this.this$0.from = ((android.widget.EditText) this.this$0.findViewById(2131558507));\n this.this$0.to = ((android.widget.EditText) this.this$0.findViewById(2131558509));\n this.this$0.amount = ((android.widget.EditText) this.this$0.findViewById(2131558512));\n v4_1.add(new org.apache.http.message.BasicNameValuePair(from_acc, this.this$0.from.getText().toString()));\n v4_1.add(new org.apache.http.message.BasicNameValuePair(to_acc, this.this$0.to.getText().toString()));\n v4_1.add(new org.apache.http.message.BasicNameValuePair(amount, this.this$0.amount.getText().toString()));\n try {\n v3_0.setEntity(new org.apache.http.client.entity.UrlEncodedFormEntity(v4_1));\n try {\n this.this$0.responseBody = v2_1.execute(v3_0);\n try {\n this.this$0.in = this.this$0.responseBody.getEntity().getContent();\n try {\n this.this$0.result = this.this$0.convertStreamToString(this.this$0.in);\n } catch (java.io.IOException v0_1) {\n v0_1.printStackTrace();\n }\n this.this$0.result = this.this$0.result.replace(\n, );\n this.this$0.runOnUiThread(new com.android.insecurebankv2.DoTransfer$RequestDoTransferTask$1(this));\n return dinesh;\n } catch (java.io.IOException v1_1) {\n v1_1.printStackTrace();\n } catch (java.io.IOException v1_1) {\n }\n } catch (java.io.IOException v1_0) {\n v1_0.printStackTrace();\n }\n } catch (java.io.IOException v0_0) {\n v0_0.printStackTrace();\n }\n } catch (java.io.IOException v0_2) {\n v0_2.printStackTrace();\n }\n }\n"}}}}]},{"ruleId":"642b9a5f-f96b-cd69-3591-2a7400000000","ruleIndex":13,"message":{"text":"\uD83D\uDFE9 Recommendation

Do not let any backdoor exist on the server side. In particular, do not leave any code that reveals the backdoor location.

\uD83D\uDFE6 Code Snippet

\uD83D\uDFE7 com.android.insecurebankv2.DoLogin : postData(Showing 11 lines of 35)

  
      public void postData(String p13)
      {
          org.apache.http.HttpResponse v6;
          org.apache.http.impl.client.DefaultHttpClient v0_1 = new org.apache.http.impl.client.DefaultHttpClient();
          org.apache.http.client.methods.HttpPost v1_1 = new org.apache.http.client.methods.HttpPost(new StringBuilde...
          org.apache.http.client.methods.HttpPost v2_1 = new org.apache.http.client.methods.HttpPost(new StringBuilde...
          java.util.ArrayList v4_1 = new java.util.ArrayList(2);
          v4_1.add(new org.apache.http.message.BasicNameValuePair(username, this.this$0.username));
          v4_1.add(new org.apache.http.message.BasicNameValuePair(password, this.this$0.password));
          if (!this.this$0.username.equals(devadmin)) {

"},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.android.insecurebankv2.DoLogin+%3A+postData"},"region":{"startLine":7,"snippet":{"text":"\n public void postData(String p13)\n {\n org.apache.http.HttpResponse v6;\n org.apache.http.impl.client.DefaultHttpClient v0_1 = new org.apache.http.impl.client.DefaultHttpClient();\n org.apache.http.client.methods.HttpPost v1_1 = new org.apache.http.client.methods.HttpPost(new StringBuilder().append(this.this$0.protocol).append(this.this$0.serverip).append(:).append(this.this$0.serverport).append(/login).toString());\n org.apache.http.client.methods.HttpPost v2_1 = new org.apache.http.client.methods.HttpPost(new StringBuilder().append(this.this$0.protocol).append(this.this$0.serverip).append(:).append(this.this$0.serverport).append(/devlogin).toString());\n java.util.ArrayList v4_1 = new java.util.ArrayList(2);\n v4_1.add(new org.apache.http.message.BasicNameValuePair(username, this.this$0.username));\n v4_1.add(new org.apache.http.message.BasicNameValuePair(password, this.this$0.password));\n if (!this.this$0.username.equals(devadmin)) {\n v1_1.setEntity(new org.apache.http.client.entity.UrlEncodedFormEntity(v4_1));\n v6 = v0_1.execute(v1_1);\n } else {\n v2_1.setEntity(new org.apache.http.client.entity.UrlEncodedFormEntity(v4_1));\n v6 = v0_1.execute(v2_1);\n }\n this.this$0.result = this.convertStreamToString(v6.getEntity().getContent());\n this.this$0.result = this.this$0.result.replace(\n, );\n if (this.this$0.result != null) {\n if (this.this$0.result.indexOf(Correct Credentials) == -1) {\n this.this$0.startActivity(new android.content.Intent(this.this$0.getApplicationContext(), com.android.insecurebankv2.WrongLogin));\n } else {\n android.util.Log.d(Successful Login:, new StringBuilder().append(, account=).append(this.this$0.username).append(:).append(this.this$0.password).toString());\n this.saveCreds(this.this$0.username, this.this$0.password);\n this.trackUserLogins();\n android.content.Intent v5_1 = new android.content.Intent(this.this$0.getApplicationContext(), com.android.insecurebankv2.PostLogin);\n v5_1.putExtra(uname, this.this$0.username);\n this.this$0.startActivity(v5_1);\n }\n }\n return;\n }\n"}}}}]},{"ruleId":"642b9a56-f96b-cd69-3591-2a7300000000","ruleIndex":14,"message":{"text":"\uD83D\uDFE9 Recommendation

Avoid saving valuable predicates in accessible places such as the /res/values folder or in shared preferences.

\uD83D\uDFE6 Code Snippet

\uD83D\uDFE7 com.android.insecurebankv2.LoginActivity : onCreate(Showing 11 lines of 17)

  
  protected void onCreate(android.os.Bundle p6)
  {
      super.onCreate(p6);
      this.setContentView(2130968605);
      if (this.getResources().getString(2131165258).equals(no)) {
          this.findViewById(2131558510).setVisibility(8);
      }
      this.login_buttons = ((android.widget.Button) this.findViewById(2131558522));
      this.login_buttons.setOnClickListener(new com.android.insecurebankv2.LoginActivity$1(this));
      this.createuser_buttons = ((android.widget.Button) this.findViewById(2131558510));

"},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.android.insecurebankv2.LoginActivity+%3A+onCreate"},"region":{"startLine":6,"snippet":{"text":"\n protected void onCreate(android.os.Bundle p6)\n {\n super.onCreate(p6);\n this.setContentView(2130968605);\n if (this.getResources().getString(2131165258).equals(no)) {\n this.findViewById(2131558510).setVisibility(8);\n }\n this.login_buttons = ((android.widget.Button) this.findViewById(2131558522));\n this.login_buttons.setOnClickListener(new com.android.insecurebankv2.LoginActivity$1(this));\n this.createuser_buttons = ((android.widget.Button) this.findViewById(2131558510));\n this.createuser_buttons.setOnClickListener(new com.android.insecurebankv2.LoginActivity$2(this));\n this.fillData_button = ((android.widget.Button) this.findViewById(2131558523));\n this.fillData_button.setOnClickListener(new com.android.insecurebankv2.LoginActivity$3(this));\n return;\n }\n"}}}}]},{"ruleId":"54631b68-d8c9-7547-8e76-8efd00000000","ruleIndex":15,"message":{"text":"\uD83D\uDFE9 Recommendation

Zimperium's zShield provides code obfuscation to defend against static analysis of your application. Obfuscation makes reverse engineering more difficult by adding complexity and transforming the appearance of your application without changing the behavior.

"},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+classes.dex"},"region":{"startLine":1}}}]},{"ruleId":"67e11d2b-85c8-6900-ca1e-527b00000000","ruleIndex":16,"message":{"text":"\uD83D\uDFE9 Recommendation

Update the minSdkVersion of the app to a version within the N-2 range. This requires evaluating dependencies and APIs to ensure compatibility with the latest Android features and addressing any deprecated methods. Regular updates to align with new Android releases will not only restore compliance but also enhance user experience, security, and device compatibility.

"},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+classes.dex"},"region":{"startLine":1}}}]},{"ruleId":"667eb2a2-8452-657c-816c-289a00000000","ruleIndex":17,"message":{"text":"\uD83D\uDFE9 Recommendation

To ensure compliance with Google Play Store policies regarding location permissions, clearly justify each permission request and limit access to location data strictly necessary to enhance user experience. Ensure adherence to the requirements specified in the Location Permissions section of Google's Developer Content Policy. Always refer to Google's updated policies for ongoing compliance.

"},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+classes.dex"},"region":{"startLine":1}}}]},{"ruleId":"667eb276-8452-657c-816c-289900000000","ruleIndex":18,"message":{"text":"\uD83D\uDFE9 Recommendation

To comply with Google Play Store policies, it is critical to correctly register the application as the default handler for calls or SMS on the device. Provide a clear and detailed justification for the use of these permissions in the application description on the Play Store, outlining the functionalities that require these permissions and committing to use them solely for their declared purposes. Ensure to include information on how user privacy is protected and how misuse of these permissions is prevented.

"},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+classes.dex"},"region":{"startLine":1}}}]},{"ruleId":"63d8fb8f-b544-7900-1078-5c8400000000","ruleIndex":19,"message":{"text":"\uD83D\uDFE9 Recommendation

Clear the clipboard regularly, use a secure password manager, and avoid copying sensitive information to the clipboard. Additionally, install security updates and avoid downloading and installing untrusted apps to help prevent potential exploitation of the vulnerability. Android 12 notifies users when apps access the clipboard, and with Android 13, the clipboard is empty after a certain period of time.

\uD83D\uDFE7 Locations

com.android.insecurebankv2.LoginActivity
com.android.insecurebankv2.DoTransfer

\uD83D\uDFE6 Code Snippets

\uD83D\uDFE7 com.android.insecurebankv2.LoginActivity

  
  
  public class LoginActivity extends Activity {
              public static final String MYPREFS = \"mySharedPreferences\";
              EditText Password_Text;
‣‣            EditText Username_Text;
              Button createuser_buttons;
              Button fillData_button;
              Button login_buttons;
              String usernameBase64ByteString;
  

\uD83D\uDFE7 com.android.insecurebankv2.DoTransfer

  EditText from;
  Button getAccounts;
  InputStream in;
  JSONObject jsonObject;
  String passNormalized;
‣‣EditText phoneNumber;
  BufferedReader reader;
  HttpResponse responseBody;
  String result;
  SharedPreferences serverDetails;
  EditText to;

"},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.android.insecurebankv2.LoginActivity"},"region":{"startLine":27,"snippet":{"text":"\n\n public class LoginActivity extends Activity {\n public static final String MYPREFS = \"mySharedPreferences\";\n EditText Password_Text;\n EditText Username_Text;\n Button createuser_buttons;\n Button fillData_button;\n Button login_buttons;\n String usernameBase64ByteString;\n"}}}},{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.android.insecurebankv2.DoTransfer"},"region":{"startLine":54,"snippet":{"text":" EditText from;\n Button getAccounts;\n InputStream in;\n JSONObject jsonObject;\n String passNormalized;\n EditText phoneNumber;\n BufferedReader reader;\n HttpResponse responseBody;\n String result;\n SharedPreferences serverDetails;\n EditText to;"}}}}]},{"ruleId":"63ca495c-b0a8-7a00-262f-409800000000","ruleIndex":20,"message":{"text":"\uD83D\uDFE9 Recommendation

The app uses hardcoded symmetric cryptography as the only method of encryption. It is recommended not to use hardcoded keys on the code; furthermore, the use of additional encryption methods is recommended.

Use Zimperium's zKeyBox product to ensure that the implementation of cryptographic algorithms and keys are secure in zero-trust execution environments. zKeyBox is based on white-box cryptography that is designed to protect cryptographic keys, making it extremely difficult for attackers to locate, modify, and extract them. Visit https://www.zimperium.com/zkeybox/ for more information.

\uD83D\uDFE7 Locations

Lcom/android/insecurebankv2/CryptoClass; : aes256decrypt
Lcom/android/insecurebankv2/CryptoClass; : aes256encrypt

"},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+Lcom%2Fandroid%2Finsecurebankv2%2FCryptoClass%3B+%3A+aes256decrypt"},"region":{"startLine":1}}},{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+Lcom%2Fandroid%2Finsecurebankv2%2FCryptoClass%3B+%3A+aes256encrypt"},"region":{"startLine":1}}}]},{"ruleId":"63cff208-1499-a500-0f37-337700000000","ruleIndex":21,"message":{"text":"\uD83D\uDFE9 Recommendation

Use zShield to prevent reverse engineering attempts, as it also provides additional APK signature verification to ensure that your app signing has not been compromised by a malicious exploit and code obfuscation to defend against static analysis of your app.

"},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+classes.dex"},"region":{"startLine":1}}}]},{"ruleId":"63bd9c4e-d6d6-9f00-0f5b-8df700000000","ruleIndex":22,"message":{"text":"\uD83D\uDFE9 Recommendation

Review and accept the minimum set of permissions periodically as the API levels change.

"},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+classes.dex"},"region":{"startLine":1}}}]},{"ruleId":"5b62edc8-bb6e-303a-8140-a37300000000","ruleIndex":23,"message":{"text":"\uD83D\uDFE9 Recommendation

Any app that uses external storage should encrypt any sensitive data that it writes to external storage and perform input validation on any data that is read from external storage.

\uD83D\uDFE7 Locations

com.android.insecurebankv2.DoTransfer
com.android.insecurebankv2.ViewStatement

\uD83D\uDFE6 Code Snippets

\uD83D\uDFE7 com.android.insecurebankv2.DoTransfer

  DoTransfer.this.acc1 = DoTransfer.this.jsonObject.getString(\"from\");
  DoTransfer.this.acc2 = DoTransfer.this.jsonObject.getString(\"to\");
  System.out.println(\"Message:\" + DoTransfer.this.jsonObject.getString(\"message\") + \" From:\" + DoTransfer.this.from.g...
  String status = new String(\"\\nMessage:Success From:\" + DoTransfer.this.from.getText().toString() + \" To:\" + DoTrans...
           try {
‣‣    String MYFILE = Environment.getExternalStorageDirectory() + \"/Statements_\" + DoTransfer.this.usernameBase64Byte...
      BufferedWriter out2 = new BufferedWriter(new FileWriter(MYFILE, true));
      out2.write(status);
      out2.write(\"
\");
      out2.close();
      return;

\uD83D\uDFE7 com.android.insecurebankv2.ViewStatement

  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_view_statement);
  Intent intent = getIntent();
  this.uname = intent.getStringExtra(\"uname\");
  String FILENAME = \"Statements_\" + this.uname + \".html\";
‣‣File fileToCheck = new File(Environment.getExternalStorageDirectory(), FILENAME);
  System.out.println(fileToCheck.toString());
  if (fileToCheck.exists()) {
      WebView mWebView = (WebView) findViewById(R.id.webView1);
      mWebView.loadUrl(\"file://\" + Environment.getExternalStorageDirectory() + \"/Statements_\" + this.uname + \".html\");
      mWebView.getSettings().setJavaScriptEnabled(true);

"},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.android.insecurebankv2.DoTransfer"},"region":{"startLine":162,"snippet":{"text":" DoTransfer.this.acc1 = DoTransfer.this.jsonObject.getString(\"from\");\n DoTransfer.this.acc2 = DoTransfer.this.jsonObject.getString(\"to\");\n System.out.println(\"Message:\" + DoTransfer.this.jsonObject.getString(\"message\") + \" From:\" + DoTransfer.this.from.getText().toString() + \" To:\" + DoTransfer.this.to.getText().toString() + \" Amount:\" + DoTransfer.this.amount.getText().toString());\n String status = new String(\"\\nMessage:Success From:\" + DoTransfer.this.from.getText().toString() + \" To:\" + DoTransfer.this.to.getText().toString() + \" Amount:\" + DoTransfer.this.amount.getText().toString() + \"\\n\");\n try {\n String MYFILE = Environment.getExternalStorageDirectory() + \"/Statements_\" + DoTransfer.this.usernameBase64ByteString + \".html\";\n BufferedWriter out2 = new BufferedWriter(new FileWriter(MYFILE, true));\n out2.write(status);\n out2.write(\"
\");\n out2.close();\n return;"}}}},{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.android.insecurebankv2.ViewStatement"},"region":{"startLine":25,"snippet":{"text":" super.onCreate(savedInstanceState);\n setContentView(R.layout.activity_view_statement);\n Intent intent = getIntent();\n this.uname = intent.getStringExtra(\"uname\");\n String FILENAME = \"Statements_\" + this.uname + \".html\";\n File fileToCheck = new File(Environment.getExternalStorageDirectory(), FILENAME);\n System.out.println(fileToCheck.toString());\n if (fileToCheck.exists()) {\n WebView mWebView = (WebView) findViewById(R.id.webView1);\n mWebView.loadUrl(\"file://\" + Environment.getExternalStorageDirectory() + \"/Statements_\" + this.uname + \".html\");\n mWebView.getSettings().setJavaScriptEnabled(true);"}}}}]},{"ruleId":"5d4203bc-7d33-27a1-308b-456700000000","ruleIndex":24,"message":{"text":"\uD83D\uDFE9 Recommendation

If the data being stored is classified as sensitive, private, proprietary, or confidential, then SharedPreferences is not a recommended solution. When possible, store authentication data in the AccountManager or consider a third-party solution to encrypt the data before being inserted into SharedPrefences.

\uD83D\uDFE7 Locations

com.android.insecurebankv2.FilePrefActivity
com.android.insecurebankv2.DoLogin$RequestTask
com.android.insecurebankv2.DoLogin

\uD83D\uDFE6 Code Snippets

\uD83D\uDFE7 com.android.insecurebankv2.FilePrefActivity

  Matcher m = p.matcher(serveripSaved);
  if (serveripSaved != null && m.matches()) {
      Pattern p2 = Pattern.compile(\"(6553[0-5]|655[0-2]\\\\d|65[0-4]\\\\d{2}|6[0-4]\\\\d{3}|[1-5]\\\\d{4}|[1-9]\\\\d{0,3})\");
      Matcher m2 = p2.matcher(serverportSaved);
      if (serverportSaved != null && m2.matches()) {
‣‣        this.editor.putString(\"serverip\", serveripSaved);
          this.editor.putString(\"serverport\", serverportSaved);
          this.editor.commit();
          Toast.makeText(this, \"Server Configured Successfully!!\", 1).show();
          finish();
          return;

\uD83D\uDFE7 com.android.insecurebankv2.DoLogin

      DoLogin.this.rememberme_username = username;
      DoLogin.this.rememberme_password = password;
      String base64Username = new String(Base64.encodeToString(DoLogin.this.rememberme_username.getBytes(), 4));
      CryptoClass crypt = new CryptoClass();
      DoLogin.this.superSecurePassword = crypt.aesEncryptedString(DoLogin.this.rememberme_password);
‣‣    editor.putString(\"EncryptedUsername\", base64Username);
      editor.putString(\"superSecurePassword\", DoLogin.this.superSecurePassword);
      editor.commit();
           }
  
  private String convertStreamToString(InputStream in) throws IOException {

"},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.android.insecurebankv2.FilePrefActivity"},"region":{"startLine":78,"snippet":{"text":" Matcher m = p.matcher(serveripSaved);\n if (serveripSaved != null && m.matches()) {\n Pattern p2 = Pattern.compile(\"(6553[0-5]|655[0-2]\\\\d|65[0-4]\\\\d{2}|6[0-4]\\\\d{3}|[1-5]\\\\d{4}|[1-9]\\\\d{0,3})\");\n Matcher m2 = p2.matcher(serverportSaved);\n if (serverportSaved != null && m2.matches()) {\n this.editor.putString(\"serverip\", serveripSaved);\n this.editor.putString(\"serverport\", serverportSaved);\n this.editor.commit();\n Toast.makeText(this, \"Server Configured Successfully!!\", 1).show();\n finish();\n return;"}}}},{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.android.insecurebankv2.DoLogin%24RequestTask"},"region":{"startLine":1}}},{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.android.insecurebankv2.DoLogin"},"region":{"startLine":147,"snippet":{"text":" DoLogin.this.rememberme_username = username;\n DoLogin.this.rememberme_password = password;\n String base64Username = new String(Base64.encodeToString(DoLogin.this.rememberme_username.getBytes(), 4));\n CryptoClass crypt = new CryptoClass();\n DoLogin.this.superSecurePassword = crypt.aesEncryptedString(DoLogin.this.rememberme_password);\n editor.putString(\"EncryptedUsername\", base64Username);\n editor.putString(\"superSecurePassword\", DoLogin.this.superSecurePassword);\n editor.commit();\n }\n\n private String convertStreamToString(InputStream in) throws IOException {"}}}}]},{"ruleId":"63921084-031a-7c00-0f01-0dc500000000","ruleIndex":25,"message":{"text":"\uD83D\uDFE9 Recommendation

This app is following the best practice of validating the chain of trust on all secure endpoints.

"},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+classes.dex"},"region":{"startLine":1}}}]},{"ruleId":"5da893bf-7d33-27f0-1f8b-456a00000000","ruleIndex":26,"message":{"text":"\uD83D\uDFE9 Recommendation

This is an informational finding that is used by an evaluator to determine if the application is justified in this usage.

"},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+classes.dex"},"region":{"startLine":1}}}]},{"ruleId":"5da89d29-7d33-27f1-1f8b-456800000000","ruleIndex":27,"message":{"text":"\uD83D\uDFE9 Recommendation

This is an informational finding that is used by an evaluator to determine if the application is justified in this usage.

\uD83D\uDFE7 Locations

com.android.insecurebankv2.ChangePassword
com.android.insecurebankv2.MyBroadCastReceiver

\uD83D\uDFE6 Code Snippets

\uD83D\uDFE7 com.android.insecurebankv2.ChangePassword

  import android.content.Intent;
  import android.content.SharedPreferences;
  import android.os.AsyncTask;
  import android.os.Bundle;
  import android.preference.PreferenceManager;
‣‣import android.telephony.TelephonyManager;
  import android.text.TextUtils;
  import android.view.Menu;
  import android.view.MenuItem;
  import android.view.View;
  import android.widget.Button;

\uD83D\uDFE7 com.android.insecurebankv2.MyBroadCastReceiver

  
          import android.content.BroadcastReceiver;
          import android.content.Context;
          import android.content.Intent;
          import android.content.SharedPreferences;
‣‣        import android.telephony.SmsManager;
          import android.util.Base64;
  
  
  public class MyBroadCastReceiver extends BroadcastReceiver {
              public static final String MYPREFS = \"mySharedPreferences\";

"},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.android.insecurebankv2.ChangePassword"},"region":{"startLine":9,"snippet":{"text":" import android.content.Intent;\n import android.content.SharedPreferences;\n import android.os.AsyncTask;\n import android.os.Bundle;\n import android.preference.PreferenceManager;\n import android.telephony.TelephonyManager;\n import android.text.TextUtils;\n import android.view.Menu;\n import android.view.MenuItem;\n import android.view.View;\n import android.widget.Button;"}}}},{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.android.insecurebankv2.MyBroadCastReceiver"},"region":{"startLine":7,"snippet":{"text":"\n import android.content.BroadcastReceiver;\n import android.content.Context;\n import android.content.Intent;\n import android.content.SharedPreferences;\n import android.telephony.SmsManager;\n import android.util.Base64;\n\n\n public class MyBroadCastReceiver extends BroadcastReceiver {\n public static final String MYPREFS = \"mySharedPreferences\";"}}}}]},{"ruleId":"5db9a286-7d33-277c-4b8b-456700000000","ruleIndex":28,"message":{"text":"\uD83D\uDFE9 Recommendation

If encryption is used in the application, random bit generation should follow the FCS_RBG_EXT.2.1 requirements.

The requirements state the application should perform all deterministic random bit generation (DRBG) services in accordance with NIST Special Publication 800-90A using Hash_DRBG, HMAC_DRBG, or CTR_DRBG. This requirement to implement DRBG functionality is chosen in FCS_RBG_EXT.1.1. While any of the identified hash functions (SHA-1, SHA-224, SHA-256, SHA-384, SHA-512) are allowed for Hash_DRBG or HMAC_DRBG, only AES-based implementations for CTR_DRBG are allowed.

"},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+classes.dex"},"region":{"startLine":1}}}]},{"ruleId":"5e0a1763-a430-cf64-d379-2d6400000000","ruleIndex":29,"message":{"text":"\uD83D\uDFE9 Recommendation

Use certificate pinning anytime you want to be relatively certain of the remote host's identity or when operating in a hostile environment. Since these are almost always true, you should probably pin all the time.

Since Android N, the preferred way for implementing pinning is by leveraging Android's Network Security Configuration feature, which lets apps customize their network security settings in a safe, declarative configuration file without modifying app code.

You can use the configuration setting to enable pinning.

If devices, running a version of Android that is earlier than N, need to be supported, a backport of the Network Security Configuration pinning functionality is available through the TrustKit Android library at https://github.com/datatheorem/TrustKit-Android.

For iOS, you can use TrustKit, an open-source SSL pinning library for iOS and macOS. It is available at https://github.com/datatheorem/TrustKit and provides an easy-to-use API for implementing pinning.

SSL pinning can be bypassed during dynamic analysis of the application when the attacker has full control of the app's environment. zDefend SDK provides a next-generation RASP engine that can be embedded into the mobile application. It provides application runtime defense against Man-in-the-Middle (MITM) attacks, such as SSL proxying, network manipulation, and gateway or proxy changes on the device. Visit https://www.zimperium.com/zdefend/ for more information.

"},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+classes.dex"},"region":{"startLine":1}}}]},{"ruleId":"5fb402d1-d9a8-c20a-5e58-e34300000000","ruleIndex":30,"message":{"text":"\uD83D\uDFE9 Recommendation

The Android SDK tool now generates the v4 signature file if you run it with default parameters. Use the APKSigner with default parameters: apksigner sign --ks debug.keystore {your app}.apk.

Zimperium's zShield provides additional APK Signature Verification to ensure your application signing has not been compromised by a malicious exploit.

"},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+classes.dex"},"region":{"startLine":1}}}]},{"ruleId":"5f7eef85-c098-1160-dd2a-481f00000000","ruleIndex":31,"message":{"text":"\uD83D\uDFE9 Recommendation

Zimperium's zShield provides code obfuscation to defend against static analysis of your application. Obfuscation makes reverse engineering more difficult by adding complexity and transforming the appearance of your application without changing the behavior.

"},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+classes.dex"},"region":{"startLine":1}}}]},{"ruleId":"60faf47f-178b-b800-121f-af7600000000","ruleIndex":32,"message":{"text":"\uD83D\uDFE9 Recommendation

It is recommended that the application be coded to perform countermeasures such as halting application execution when a jailbroken or rooted device is discovered.

\uD83D\uDFE6 Code Snippet

\uD83D\uDFE7 com.android.insecurebankv2.PostLogin

  
  .prologue
  const/4 v1, 0x1
  
  .line 86
‣‣const-string v2, \"/system/app/Superuser.apk\"
  
  invoke-direct {p0, v2}, Lcom/android/insecurebankv2/PostLogin;->doesSuperuserApkExist(Ljava/lang/String;)Z
  
  move-result v2
  

"},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.android.insecurebankv2.PostLogin"},"region":{"startLine":429,"snippet":{"text":"\n .prologue\n const/4 v1, 0x1\n\n .line 86\n const-string v2, \"/system/app/Superuser.apk\"\n\n invoke-direct {p0, v2}, Lcom/android/insecurebankv2/PostLogin;->doesSuperuserApkExist(Ljava/lang/String;)Z\n\n move-result v2\n"}}}}]},{"ruleId":"636277c5-cab2-bc00-0f71-f3f500000000","ruleIndex":33,"message":{"text":"\uD83D\uDFE9 Recommendation

This application is performing the best practice of protecting program data symbols.

"},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+classes.dex"},"region":{"startLine":1}}}]},{"ruleId":"64ad4f15-2e24-ce00-5648-756a00000000","ruleIndex":34,"message":{"text":"\uD83D\uDFE9 Recommendation

No immediate action is required for this finding, as code obfuscation makes reverse engineering difficult. However, it is always recommended to add code obfuscation before releasing the app into the app stores to deter reverse engineering.

"},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+classes.dex"},"region":{"startLine":1}}}]},{"ruleId":"591d4dc1-12df-5b5d-87e6-1e2d00000000","ruleIndex":35,"message":{"text":"\uD83D\uDFE9 Recommendation

Secure data storage, conduct regular security audits, and follow the least privilege principle.

\uD83D\uDFE6 Code Snippet

\uD83D\uDFE7 com.android.insecurebankv2.MyBroadCastReceiver

  public void onReceive(Context context, Intent intent) {
      String phn = intent.getStringExtra(\"phonenumber\");
      String newpass = intent.getStringExtra(\"newpass\");
      if (phn != null) {
                  try {
‣‣            SharedPreferences settings = context.getSharedPreferences(\"mySharedPreferences\", 1);
              String username = settings.getString(\"EncryptedUsername\", null);
              byte[] usernameBase64Byte = Base64.decode(username, 0);
              this.usernameBase64ByteString = new String(usernameBase64Byte, \"UTF-8\");
              String password = settings.getString(\"superSecurePassword\", null);
              CryptoClass crypt = new CryptoClass();

"},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.android.insecurebankv2.MyBroadCastReceiver"},"region":{"startLine":21,"snippet":{"text":" public void onReceive(Context context, Intent intent) {\n String phn = intent.getStringExtra(\"phonenumber\");\n String newpass = intent.getStringExtra(\"newpass\");\n if (phn != null) {\n try {\n SharedPreferences settings = context.getSharedPreferences(\"mySharedPreferences\", 1);\n String username = settings.getString(\"EncryptedUsername\", null);\n byte[] usernameBase64Byte = Base64.decode(username, 0);\n this.usernameBase64ByteString = new String(usernameBase64Byte, \"UTF-8\");\n String password = settings.getString(\"superSecurePassword\", null);\n CryptoClass crypt = new CryptoClass();"}}}}]},{"ruleId":"633d121c-0e3b-5700-1234-49c900000000","ruleIndex":36,"message":{"text":"\uD83D\uDFE9 Recommendation

This is a detection to satisfy the MASVS MSTG-CRYPTO-3 requirements.

\uD83D\uDFE7 Locations

com.android.insecurebankv2.CryptoClass
com.android.insecurebankv2.DoLogin
com.android.insecurebankv2.LoginActivity
com.android.insecurebankv2.ChangePassword
com.android.insecurebankv2.DoTransfer

"},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.android.insecurebankv2.CryptoClass"},"region":{"startLine":1}}},{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.android.insecurebankv2.DoLogin"},"region":{"startLine":1}}},{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.android.insecurebankv2.LoginActivity"},"region":{"startLine":1}}},{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.android.insecurebankv2.ChangePassword"},"region":{"startLine":1}}},{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.android.insecurebankv2.DoTransfer"},"region":{"startLine":1}}}]},{"ruleId":"63eb7110-f141-dd00-6a42-035400000000","ruleIndex":37,"message":{"text":"\uD83D\uDFE9 Recommendation

Clear the WebView resources when the application accesses any sensitive data, which may include any files stored locally, the RAM cache, and any loaded JavaScript. Please note that this presents a potential security risk if any sensitive data is being exposed.

\uD83D\uDFE7 Locations

com.android.insecurebankv2.MyWebViewClient
com.android.insecurebankv2.ViewStatement

\uD83D\uDFE6 Code Snippets

\uD83D\uDFE7 com.android.insecurebankv2.MyWebViewClient

          package com.android.insecurebankv2;
  
‣‣        import android.webkit.WebView;
          import android.webkit.WebViewClient;
  
  
  public class MyWebViewClient extends WebViewClient {
              @Override // android.webkit.WebViewClient

\uD83D\uDFE7 com.android.insecurebankv2.ViewStatement

  import android.content.Intent;
  import android.os.Bundle;
  import android.os.Environment;
  import android.view.Menu;
  import android.view.MenuItem;
‣‣import android.webkit.WebChromeClient;
  import android.webkit.WebView;
  import android.widget.Toast;
  import java.io.File;
  
  

"},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.android.insecurebankv2.MyWebViewClient"},"region":{"startLine":3,"snippet":{"text":" package com.android.insecurebankv2;\n\n import android.webkit.WebView;\n import android.webkit.WebViewClient;\n\n\n public class MyWebViewClient extends WebViewClient {\n @Override // android.webkit.WebViewClient"}}}},{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.android.insecurebankv2.ViewStatement"},"region":{"startLine":9,"snippet":{"text":" import android.content.Intent;\n import android.os.Bundle;\n import android.os.Environment;\n import android.view.Menu;\n import android.view.MenuItem;\n import android.webkit.WebChromeClient;\n import android.webkit.WebView;\n import android.widget.Toast;\n import java.io.File;\n\n"}}}}]},{"ruleId":"558af6f7-3601-8303-e4d5-958000000000","ruleIndex":38,"message":{"text":"\uD83D\uDFE9 Recommendation

JavaScript execution is disabled by default on WebViews. This behavior is enabled with the setJavaScriptEnabled() API, and the first recommendation is to maintain the default behavior if there is no need for client-side scripting. This prevents exposure to potential Cross-Site Scripting (XSS) attacks and reduces the consequences of a Man in the Middle (MITM) attack.

In a scenario where JavaScript is mandatory, all inputs should be sanitized to prevent XSS attacks. Validating the origin of the content being loaded by the WebView is a good security precaution. It can be implemented by overriding the shouldOverrideUrlLoading() and the shouldInterceptRequest() methods.

Additionally, it is recommended to add \"android.webkit.WebView.EnableSafeBrowsing\" in the Android manifest file and also to compile the app against Android API level 17 or above and implementing @JavascriptInterface annotation as this prevents accessing to operating system commands through java.lang.Runtime.

\uD83D\uDFE6 Code Snippet

\uD83D\uDFE7 com.android.insecurebankv2.ViewStatement

  File fileToCheck = new File(Environment.getExternalStorageDirectory(), FILENAME);
  System.out.println(fileToCheck.toString());
  if (fileToCheck.exists()) {
      WebView mWebView = (WebView) findViewById(R.id.webView1);
      mWebView.loadUrl(\"file://\" + Environment.getExternalStorageDirectory() + \"/Statements_\" + this.uname + \".html\");
‣‣    mWebView.getSettings().setJavaScriptEnabled(true);
      mWebView.getSettings().setSaveFormData(true);
      mWebView.getSettings().setBuiltInZoomControls(true);
      mWebView.setWebViewClient(new MyWebViewClient());
      WebChromeClient cClient = new WebChromeClient();
      mWebView.setWebChromeClient(cClient);

"},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.android.insecurebankv2.ViewStatement"},"region":{"startLine":30,"snippet":{"text":" File fileToCheck = new File(Environment.getExternalStorageDirectory(), FILENAME);\n System.out.println(fileToCheck.toString());\n if (fileToCheck.exists()) {\n WebView mWebView = (WebView) findViewById(R.id.webView1);\n mWebView.loadUrl(\"file://\" + Environment.getExternalStorageDirectory() + \"/Statements_\" + this.uname + \".html\");\n mWebView.getSettings().setJavaScriptEnabled(true);\n mWebView.getSettings().setSaveFormData(true);\n mWebView.getSettings().setBuiltInZoomControls(true);\n mWebView.setWebViewClient(new MyWebViewClient());\n WebChromeClient cClient = new WebChromeClient();\n mWebView.setWebChromeClient(cClient);"}}}}]},{"ruleId":"575ecc52-d100-c5af-f726-2ac400000000","ruleIndex":39,"message":{"text":"\uD83D\uDFE9 Recommendation

Because of the potential to abuse the Reflection API, it is strongly recommended that when reflection is used in third-party libraries that those libraries are reviewed to ensure acceptable use of the reflection API.

\uD83D\uDFE6 Code Snippet

\uD83D\uDFE7 com.google.ads.mediation.MediationServerParameters

  import com.google.android.gms.ads.internal.util.client.zzb;
  import java.lang.annotation.ElementType;
  import java.lang.annotation.Retention;
  import java.lang.annotation.RetentionPolicy;
  import java.lang.annotation.Target;
‣‣import java.lang.reflect.Field;
  import java.util.HashMap;
  import java.util.Map;
  
  @Deprecated
  

"},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.google.ads.mediation.MediationServerParameters"},"region":{"startLine":8,"snippet":{"text":" import com.google.android.gms.ads.internal.util.client.zzb;\n import java.lang.annotation.ElementType;\n import java.lang.annotation.Retention;\n import java.lang.annotation.RetentionPolicy;\n import java.lang.annotation.Target;\n import java.lang.reflect.Field;\n import java.util.HashMap;\n import java.util.Map;\n\n @Deprecated\n"}}}}]}]}]} \ No newline at end of file diff --git a/dist/index.js b/dist/index.js index f2a5d7f..6bcb15a 100644 --- a/dist/index.js +++ b/dist/index.js @@ -44545,14 +44545,14 @@ async function uploadApp() { } }); core.info(`Upload successful for ${file}`); - + core.debug(`buildId: ${response.data.buildId}`); core.debug(`zdevAppId: ${response.data.zdevAppId}`); core.debug(`teamId: ${response.data.teamId}`); core.debug(`buildUploadedAt: ${response.data.buildUploadedAt}`); - core.debug(`buildNumber: ${response.data.buildNumber}`); - core.debug(`bundleIdentifier: ${response.data.bundleIdentifier}`); - core.debug(`appVersion: ${response.data.appVersion}`); + core.debug(`buildNumber: ${response.data.zdevUploadResponse.appBuildVersion}`); + core.debug(`bundleIdentifier: ${response.data.zdevUploadResponse.bundleIdentifier}`); + core.debug(`appVersion: ${response.data.zdevUploadResponse.appVersion}`); const result = response.data; result.originalFileName = file; @@ -44605,10 +44605,10 @@ async function pollStatus(buildId) { } } -async function downloadApp(appId, originalFileName) { +async function downloadApp(assessmentId, originalFileName) { const loginResponse = await loginHttpRequest(); try { - const response = await axios.get(`${baseUrl}/api/zdev-app/public/v1/assessments/${appId}/sarif`, { + const response = await axios.get(`${baseUrl}/api/zdev-app/public/v1/assessments/${assessmentId}/sarif`, { headers: { 'Authorization': 'Bearer ' + loginResponse.accessToken }, @@ -44628,12 +44628,14 @@ async function downloadApp(appId, originalFileName) { } } -async function pollDownload(appId, originalFileName) { +async function pollDownload(assessmentId, originalFileName) { await sleep(DOWNLOAD_POLL_TIME); let done = false; let totalTime = 0; while(!done && totalTime < MAX_DOWNLOAD_TIME) { - let result = await downloadApp(appId, originalFileName); + let result = await downloadApp(assessmentId, originalFileName); + core.debug(`Download attempt returned status code: ${result.statusCode}`); + core.debug(`Download result: ${JSON.stringify(result)}`); if(result.statusCode == 200) { core.info(`Sarif file ${result.reportFileName} download complete.`); done = true; diff --git a/src/action.js b/src/action.js index 4ae1d99..8b3be11 100644 --- a/src/action.js +++ b/src/action.js @@ -102,14 +102,14 @@ async function uploadApp() { } }); core.info(`Upload successful for ${file}`); - + core.debug(`buildId: ${response.data.buildId}`); core.debug(`zdevAppId: ${response.data.zdevAppId}`); core.debug(`teamId: ${response.data.teamId}`); core.debug(`buildUploadedAt: ${response.data.buildUploadedAt}`); - core.debug(`buildNumber: ${response.data.buildNumber}`); - core.debug(`bundleIdentifier: ${response.data.bundleIdentifier}`); - core.debug(`appVersion: ${response.data.appVersion}`); + core.debug(`buildNumber: ${response.data.zdevUploadResponse.appBuildVersion}`); + core.debug(`bundleIdentifier: ${response.data.zdevUploadResponse.bundleIdentifier}`); + core.debug(`appVersion: ${response.data.zdevUploadResponse.appVersion}`); const result = response.data; result.originalFileName = file; @@ -162,10 +162,10 @@ async function pollStatus(buildId) { } } -async function downloadApp(appId, originalFileName) { +async function downloadApp(assessmentId, originalFileName) { const loginResponse = await loginHttpRequest(); try { - const response = await axios.get(`${baseUrl}/api/zdev-app/public/v1/assessments/${appId}/sarif`, { + const response = await axios.get(`${baseUrl}/api/zdev-app/public/v1/assessments/${assessmentId}/sarif`, { headers: { 'Authorization': 'Bearer ' + loginResponse.accessToken }, @@ -185,12 +185,13 @@ async function downloadApp(appId, originalFileName) { } } -async function pollDownload(appId, originalFileName) { +async function pollDownload(assessmentId, originalFileName) { await sleep(DOWNLOAD_POLL_TIME); let done = false; let totalTime = 0; while(!done && totalTime < MAX_DOWNLOAD_TIME) { - let result = await downloadApp(appId, originalFileName); + let result = await downloadApp(assessmentId, originalFileName); + core.debug(`Download attempt returned status code: ${result.statusCode}`); if(result.statusCode == 200) { core.info(`Sarif file ${result.reportFileName} download complete.`); done = true; From b8378a4eaba374e86aff13e14082e18bdc88c2e8 Mon Sep 17 00:00:00 2001 From: Igor Matlin Date: Thu, 18 Dec 2025 14:06:34 -0600 Subject: [PATCH 3/5] Removed erroneously added SARIF report --- Sample_Insecure_Bank_App_zscan.sarif | 1 - 1 file changed, 1 deletion(-) delete mode 100644 Sample_Insecure_Bank_App_zscan.sarif diff --git a/Sample_Insecure_Bank_App_zscan.sarif b/Sample_Insecure_Bank_App_zscan.sarif deleted file mode 100644 index 69418ae..0000000 --- a/Sample_Insecure_Bank_App_zscan.sarif +++ /dev/null @@ -1 +0,0 @@ -{"$schema":"https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json","version":"2.1.0","runs":[{"tool":{"driver":{"name":"Zimperium zScan","semanticVersion":"0.0","informationUri":"https://www.zimperium.com/zscan","rules":[{"id":"63ee197a-f141-dd00-ed71-43f400000000","name":"PossibleHardcodedInformation","shortDescription":{"text":"Files may contain hardcoded sensitive information such as user names, passwords, and keys."},"defaultConfiguration":{},"helpUri":"https://www.zimperium.com/zscan","help":{"text":"Files may contain hardcoded sensitive information such as user names, passwords, and keys.\n\nUsing clear text storage for sensitive information in an Android app can have potentially risky results, including exposure of confidential information, violation of compliance requirements, damage to reputation, and the potential for man-in-the-middle (MITM) attacks. To avoid this, app developers should follow best practices for secure data storage and encrypt sensitive information to prevent unauthorized access and potential attacks.","markdown":"

\uD83D\uDFE6  Description

Files may contain hardcoded sensitive information such as user names, passwords, and keys.

\uD83D\uDFE6  Business Impact

Using clear text storage for sensitive information in an Android app can have potentially risky results, including exposure of confidential information, violation of compliance requirements, damage to reputation, and the potential for man-in-the-middle (MITM) attacks. To avoid this, app developers should follow best practices for secure data storage and encrypt sensitive information to prevent unauthorized access and potential attacks.

"},"properties":{"tags":["MASVS MSTG-STORAGE-14","OWASP M9"],"severity":"Low","type":"privacy","category":"Vulnerability","subcategory":"Data Leakage"}},{"id":"63a5da01-834f-de00-1102-f91500000000","name":"SensitiveDataProtection","shortDescription":{"text":"This app is applying properly sensitive data management through the user interface (UI)."},"defaultConfiguration":{"level":"note"},"helpUri":"https://www.zimperium.com/zscan","help":{"text":"This app is applying properly sensitive data management through the user interface (UI).\n\nThe app follows the best practice to prevent sensitive data leakage in the UI.","markdown":"

\uD83D\uDFE6  Description

This app is applying properly sensitive data management through the user interface (UI).

\uD83D\uDFE6  Business Impact

The app follows the best practice to prevent sensitive data leakage in the UI.

"},"properties":{"tags":["MASVS MSTG-STORAGE-7"],"severity":"Best Practices","type":"privacy","category":"Data Leakage","subcategory":"UI"}},{"id":"63a02646-7625-7900-1469-a8d500000000","name":"KeyboardCacheDisabled","shortDescription":{"text":"The keyboard cache is disabled on text inputs that process sensitive data."},"defaultConfiguration":{"level":"note"},"helpUri":"https://www.zimperium.com/zscan","help":{"text":"The keyboard cache is disabled on text inputs that process sensitive data.\n\nThis application follows a best practice to remove sensitive data, which could be read by an attacker, from the keyboard cache.","markdown":"

\uD83D\uDFE6  Description

The keyboard cache is disabled on text inputs that process sensitive data.

\uD83D\uDFE6  Business Impact

This application follows a best practice to remove sensitive data, which could be read by an attacker, from the keyboard cache.

"},"properties":{"tags":["MASVS MSTG-STORAGE-5"],"severity":"Best Practices","type":"privacy","category":"Data Leakage","subcategory":"UI"}},{"id":"61cb48eb-3ea7-004a-a9a1-cd8400000000","name":"UnprotectedComponent","shortDescription":{"text":"The content provider is not protected by signature permission and exported in the AndroidManifest.xml file."},"defaultConfiguration":{},"helpUri":"https://www.zimperium.com/zscan","help":{"text":"The content provider is not protected by signature permission and exported in the AndroidManifest.xml file.\n\nAn attacker can read or write the exported content provider, leading to leakage of sensitive information or unpredictable application behavior. If the content providers are used as interfaces for a database, the attacker can access and potentially extract, update, insert and delete information. In addition, there might be options for SQL injection and path traversal attacks. ","markdown":"

\uD83D\uDFE6  Description

The content provider is not protected by signature permission and exported in the AndroidManifest.xml file.

\uD83D\uDFE6  Business Impact

An attacker can read or write the exported content provider, leading to leakage of sensitive information or unpredictable application behavior. If the content providers are used as interfaces for a database, the attacker can access and potentially extract, update, insert and delete information. In addition, there might be options for SQL injection and path traversal attacks.

"},"properties":{"tags":["CVSS 2.0 score 4.6","CVSS 2.0 vector AV:L/AC:L/Au:N/C:P/I:P/A:P","CVSS 3.1 score 9.8","CVSS 3.1 vector AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H","CWE-359","CWE-926","MASVS MSTG-PLATFORM-4"],"severity":"High","type":"privacy","category":"Vulnerability","subcategory":"Components"}},{"id":"63fe316a-b4ef-1e00-171b-d42800000000","name":"StringxmlFileMayContainHardcodedCredentialsOrSensitiveInformation","shortDescription":{"text":"The string.xml file may contain hardcoded sensitive information such as usernames, passwords, and keys."},"defaultConfiguration":{},"helpUri":"https://www.zimperium.com/zscan","help":{"text":"The string.xml file may contain hardcoded sensitive information such as usernames, passwords, and keys.\n\nUsing clear text storage for sensitive information in an Android app can cause exposure of confidential information, violation of compliance requirements, damage to reputation, and the potential for Man-in-the-Middle (MITM) attacks. To avoid this, app developers should follow best practices for secure data storage and encrypt sensitive information to prevent unauthorized access and potential attacks.","markdown":"

\uD83D\uDFE6  Description

The string.xml file may contain hardcoded sensitive information such as usernames, passwords, and keys.

\uD83D\uDFE6  Business Impact

Using clear text storage for sensitive information in an Android app can cause exposure of confidential information, violation of compliance requirements, damage to reputation, and the potential for Man-in-the-Middle (MITM) attacks. To avoid this, app developers should follow best practices for secure data storage and encrypt sensitive information to prevent unauthorized access and potential attacks.

"},"properties":{"tags":["OWASP M3"],"severity":"Medium","type":"security","category":"Vulnerability","subcategory":"Data Leakage"}},{"id":"5225b063-3a08-f68e-2bad-572100000000","name":"DebuggableApp","shortDescription":{"text":"This app has the \"android:debuggable\" attribute in the Android manifest file set to true."},"defaultConfiguration":{},"helpUri":"https://www.zimperium.com/zscan","help":{"text":"This app has the \"android:debuggable\" attribute in the Android manifest file set to true.\n\nReleasing an app with the “android:debuggable” attribute enabled exposes information that can possibly be used to make reverse engineering of the app much easier (information such as debug log statements, debug symbols etc).","markdown":"

\uD83D\uDFE6  Description

This app has the \"android:debuggable\" attribute in the Android manifest file set to true.

\uD83D\uDFE6  Business Impact

Releasing an app with the “android:debuggable” attribute enabled exposes information that can possibly be used to make reverse engineering of the app much easier (information such as debug log statements, debug symbols etc).

"},"properties":{"tags":["CVSS 2.0 score 3.5","CVSS 2.0 vector AV:N/AC:M/Au:S/C:P/I:N/A:N","CVSS 3.1 score 6.5","CVSS 3.1 vector AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N","MASVS MSTG-CODE-4","OWASP M7"],"severity":"High","type":"security","category":"Code Analysis","subcategory":"Binary Protections Testing"}},{"id":"5b86446e-1cfc-cd18-3a11-f25c00000000","name":"ZimperiumZ9MalwareScan","shortDescription":{"text":"The Zimperium z9 detection engine discovered that this application contains malware and is an active threat."},"defaultConfiguration":{},"helpUri":"https://www.zimperium.com/zscan","help":{"text":"The Zimperium z9 detection engine discovered that this application contains malware and is an active threat.\n\nThe Zimperium z9 engine analyzed the app and determined that it contains malware. This app should be considered as an active threat.","markdown":"

\uD83D\uDFE6  Description

The Zimperium z9 detection engine discovered that this application contains malware and is an active threat.

\uD83D\uDFE6  Business Impact

The Zimperium z9 engine analyzed the app and determined that it contains malware. This app should be considered as an active threat.

"},"properties":{"tags":[],"severity":"Critical","type":"security","category":"Vulnerability","subcategory":"Malware Detection"}},{"id":"60d0b70e-f1a7-9107-8fbb-669900000000","name":"ImplicitActivityStart","shortDescription":{"text":"The application has an implicit activity start vulnerability."},"defaultConfiguration":{},"helpUri":"https://www.zimperium.com/zscan","help":{"text":"The application has an implicit activity start vulnerability.\n\nUsing an implicit activity start is not recommended since the component is not set and the Android OS may ask the user what to start. An attacker could register their own activity with an action from the intent in the AndroidManifest.xml file and specify a 999 priority in the intent-filter attribute.\r\n\r\nAdditionally, using an implicit intent without a signature permission protection level while sending broadcasts enables any third-party application to intercept or hijack information between components. This can lead to the disclosure of application usage statistics and application states.","markdown":"

\uD83D\uDFE6  Description

The application has an implicit activity start vulnerability.

\uD83D\uDFE6  Business Impact

Using an implicit activity start is not recommended since the component is not set and the Android OS may ask the user what to start. An attacker could register their own activity with an action from the intent in the AndroidManifest.xml file and specify a 999 priority in the intent-filter attribute.\r
\r
Additionally, using an implicit intent without a signature permission protection level while sending broadcasts enables any third-party application to intercept or hijack information between components. This can lead to the disclosure of application usage statistics and application states.

"},"properties":{"tags":["CWE-927"],"severity":"Medium","type":"security","category":"Vulnerability","subcategory":"Components"}},{"id":"60e3f8fc-7552-e2f0-e9ca-648e00000000","name":"ImplicitIntent","shortDescription":{"text":"An implicit intent is used without signature permission for broadcasting and sending it to another component of the application. "},"defaultConfiguration":{},"helpUri":"https://www.zimperium.com/zscan","help":{"text":"An implicit intent is used without signature permission for broadcasting and sending it to another component of the application. \n\nUsing an implicit intent without signature protection during the broadcast allows any third-party application installed on the same mobile device to intercept or hijack information between components, leading to leakage of sensitive information or falsification of data broadcasts between components of the application.","markdown":"

\uD83D\uDFE6  Description

An implicit intent is used without signature permission for broadcasting and sending it to another component of the application.

\uD83D\uDFE6  Business Impact

Using an implicit intent without signature protection during the broadcast allows any third-party application installed on the same mobile device to intercept or hijack information between components, leading to leakage of sensitive information or falsification of data broadcasts between components of the application.

"},"properties":{"tags":["CWE-927"],"severity":"Medium","type":"security","category":"Vulnerability","subcategory":"Components"}},{"id":"63c7aacb-f7ca-34f8-62f5-126000000000","name":"HardcodedKeys","shortDescription":{"text":"A hardcoded cryptographic key was found in the app."},"defaultConfiguration":{},"helpUri":"https://www.zimperium.com/zscan","help":{"text":"A hardcoded cryptographic key was found in the app.\n\nHardcoded keys might be leaked or used for seeding. It is important to limit their scope.","markdown":"

\uD83D\uDFE6  Description

A hardcoded cryptographic key was found in the app.

\uD83D\uDFE6  Business Impact

Hardcoded keys might be leaked or used for seeding. It is important to limit their scope.

"},"properties":{"tags":["OWASP M10"],"severity":"Medium","type":"security","category":"Compliance","subcategory":"Cryptography"}},{"id":"63770e36-eff2-45a3-8feb-909900000000","name":"ManifestdeclaredBroadcastReceiverForNonsystemActions","shortDescription":{"text":"This app uses a manifest-declared broadcast receiver for non-system actions. The detected app's broadcast receiver is dynamically registered in the code, is not protected by signature permission in the AndroidManifest.xml file, and is exported.\r\n\r\n"},"defaultConfiguration":{},"helpUri":"https://www.zimperium.com/zscan","help":{"text":"This app uses a manifest-declared broadcast receiver for non-system actions. The detected app's broadcast receiver is dynamically registered in the code, is not protected by signature permission in the AndroidManifest.xml file, and is exported.\r\n\r\n\n\nUsing a malware app, an attacker can broadcast arbitrary data to the exported receiver, which can lead to invocation of different components of the app or to code execution.","markdown":"

\uD83D\uDFE6  Description

This app uses a manifest-declared broadcast receiver for non-system actions. The detected app's broadcast receiver is dynamically registered in the code, is not protected by signature permission in the AndroidManifest.xml file, and is exported.\r
\r

\uD83D\uDFE6  Business Impact

Using a malware app, an attacker can broadcast arbitrary data to the exported receiver, which can lead to invocation of different components of the app or to code execution.

"},"properties":{"tags":[],"severity":"Low","type":"security","category":"Vulnerability","subcategory":"Components"}},{"id":"6268f908-f255-393a-3274-df8500000000","name":"ExposedActivity","shortDescription":{"text":"The Android app exposes an activity component for use by other apps, but might not properly restrict which apps can launch the component or access the data it contains.\r\nAll activities are non-exported by default, unless the android:exported attribute is set to \"true\" or the intent-filter element is defined.\r\nOnce we target Android 12, the system will require us to be explicit about the value for android:exported.\r\nIf the intent-filter is registered to an implicit action intent that can be broadcast by all apps, the activity exposes itself to a third-party app.\r\n\r\nThis activity is exported or registered to standard broadcast actions.\r\nExamples of common implicit actions include:\r\nACTION_EDIT\r\nACTION_VIEW\r\nACTION_ATTACH_DATA\r\nACTION_EDIT\r\nACTION_PICK\r\nACTION_CHOOSER\r\nACTION_GET_CONTENT\r\nACTION_DIAL\r\nACTION_CALL\r\nACTION_SEND\r\n\r\nThe exceptional case of an improper activity exposure is when the activity action starts the main entry point and does not expect to receive data:\r\nACTION_MAIN"},"defaultConfiguration":{},"helpUri":"https://www.zimperium.com/zscan","help":{"text":"The Android app exposes an activity component for use by other apps, but might not properly restrict which apps can launch the component or access the data it contains.\r\nAll activities are non-exported by default, unless the android:exported attribute is set to \"true\" or the intent-filter element is defined.\r\nOnce we target Android 12, the system will require us to be explicit about the value for android:exported.\r\nIf the intent-filter is registered to an implicit action intent that can be broadcast by all apps, the activity exposes itself to a third-party app.\r\n\r\nThis activity is exported or registered to standard broadcast actions.\r\nExamples of common implicit actions include:\r\nACTION_EDIT\r\nACTION_VIEW\r\nACTION_ATTACH_DATA\r\nACTION_EDIT\r\nACTION_PICK\r\nACTION_CHOOSER\r\nACTION_GET_CONTENT\r\nACTION_DIAL\r\nACTION_CALL\r\nACTION_SEND\r\n\r\nThe exceptional case of an improper activity exposure is when the activity action starts the main entry point and does not expect to receive data:\r\nACTION_MAIN\n\nIf access to an exported activity is not restricted, any app (including one that may not be trusted) will be able to launch the activity. This may allow a malicious app to gain access to sensitive information, modify the internal state of the app, or trick a user into interacting with the victim app while believing they are still interacting with the malicious app.","markdown":"

\uD83D\uDFE6  Description

The Android app exposes an activity component for use by other apps, but might not properly restrict which apps can launch the component or access the data it contains.\r
All activities are non-exported by default, unless the android:exported attribute is set to \"true\" or the intent-filter element is defined.\r
Once we target Android 12, the system will require us to be explicit about the value for android:exported.\r
If the intent-filter is registered to an implicit action intent that can be broadcast by all apps, the activity exposes itself to a third-party app.\r
\r
This activity is exported or registered to standard broadcast actions.\r
Examples of common implicit actions include:\r
ACTION_EDIT\r
ACTION_VIEW\r
ACTION_ATTACH_DATA\r
ACTION_EDIT\r
ACTION_PICK\r
ACTION_CHOOSER\r
ACTION_GET_CONTENT\r
ACTION_DIAL\r
ACTION_CALL\r
ACTION_SEND\r
\r
The exceptional case of an improper activity exposure is when the activity action starts the main entry point and does not expect to receive data:\r
ACTION_MAIN

\uD83D\uDFE6  Business Impact

If access to an exported activity is not restricted, any app (including one that may not be trusted) will be able to launch the activity. This may allow a malicious app to gain access to sensitive information, modify the internal state of the app, or trick a user into interacting with the victim app while believing they are still interacting with the malicious app.

"},"properties":{"tags":["CWE-926","OWASP M8"],"severity":"Medium","type":"security","category":"Vulnerability","subcategory":"Components"}},{"id":"63d8db78-1e43-ad8f-1243-c49400000000","name":"UnsafeHttphostScheme","shortDescription":{"text":"The Apache HttpClient uses an HttpHost data structure to describe HTTP and HTTPS connections. HttpHost does not have any internal consistency checks. For example, it allows connections on port 443 to adopt HTTP as their scheme."},"defaultConfiguration":{},"helpUri":"https://www.zimperium.com/zscan","help":{"text":"The Apache HttpClient uses an HttpHost data structure to describe HTTP and HTTPS connections. HttpHost does not have any internal consistency checks. For example, it allows connections on port 443 to adopt HTTP as their scheme.\n\nThe TCP port 443 indicates the traffic is passed over the default port for HTTPS, but the session is not encrypted if the scheme is HTTP. This critical vulnerability allows attackers to monitor a legitimate user's network traffic, exposing any sensitive information the user may supply.","markdown":"

\uD83D\uDFE6  Description

The Apache HttpClient uses an HttpHost data structure to describe HTTP and HTTPS connections. HttpHost does not have any internal consistency checks. For example, it allows connections on port 443 to adopt HTTP as their scheme.

\uD83D\uDFE6  Business Impact

The TCP port 443 indicates the traffic is passed over the default port for HTTPS, but the session is not encrypted if the scheme is HTTP. This critical vulnerability allows attackers to monitor a legitimate user's network traffic, exposing any sensitive information the user may supply.

"},"properties":{"tags":["MASVS MSTG-NETWORK-1"],"severity":"High","type":"security","category":"Communications","subcategory":"Weakness"}},{"id":"642b9a5f-f96b-cd69-3591-2a7400000000","name":"DeveloperBackdoor","shortDescription":{"text":"A backdoor attack is a way to access a computer system or encrypted data while bypassing the system's customary security mechanisms. A developer can create a backdoor so that an application, operating system, or data can be accessed for troubleshooting or other purposes."},"defaultConfiguration":{},"helpUri":"https://www.zimperium.com/zscan","help":{"text":"A backdoor attack is a way to access a computer system or encrypted data while bypassing the system's customary security mechanisms. A developer can create a backdoor so that an application, operating system, or data can be accessed for troubleshooting or other purposes.\n\nA backdoor attack occurs when threat actors create or use a backdoor to gain remote access to a system. \r\n\r\nThis type of attack leads to authentication bypass, which can result in malicious actions by threat actors such as stealing sensitive information, performing fraudulent transactions, launching denial of service (DoS) attacks, hijacking servers, and defacing websites.","markdown":"

\uD83D\uDFE6  Description

A backdoor attack is a way to access a computer system or encrypted data while bypassing the system's customary security mechanisms. A developer can create a backdoor so that an application, operating system, or data can be accessed for troubleshooting or other purposes.

\uD83D\uDFE6  Business Impact

A backdoor attack occurs when threat actors create or use a backdoor to gain remote access to a system. \r
\r
This type of attack leads to authentication bypass, which can result in malicious actions by threat actors such as stealing sensitive information, performing fraudulent transactions, launching denial of service (DoS) attacks, hijacking servers, and defacing websites.

"},"properties":{"tags":[],"severity":"High","type":"security","category":"Code Analysis","subcategory":"Data Protection"}},{"id":"642b9a56-f96b-cd69-3591-2a7300000000","name":"WeakAuthorizationMechanism","shortDescription":{"text":"A value in the app's /res/values directory is used as a predicate for important privileges like being an admin or gaining access to the premium app features."},"defaultConfiguration":{},"helpUri":"https://www.zimperium.com/zscan","help":{"text":"A value in the app's /res/values directory is used as a predicate for important privileges like being an admin or gaining access to the premium app features.\n\nThis value can changed in order to bypass the authorization control, thus gaining admin privileges.","markdown":"

\uD83D\uDFE6  Description

A value in the app's /res/values directory is used as a predicate for important privileges like being an admin or gaining access to the premium app features.

\uD83D\uDFE6  Business Impact

This value can changed in order to bypass the authorization control, thus gaining admin privileges.

"},"properties":{"tags":["OWASP M3"],"severity":"High","type":"security","category":"Code Analysis","subcategory":"App Modification"}},{"id":"54631b68-d8c9-7547-8e76-8efd00000000","name":"StaticDataExposure","shortDescription":{"text":"This application is susceptible to reverse engineering."},"defaultConfiguration":{},"helpUri":"https://www.zimperium.com/zscan","help":{"text":"This application is susceptible to reverse engineering.\n\nDuring analysis, the classes, method names, strings, and resources were available to inspect. It is recommended that you obfuscate all data in the application so it is not easily readable. Good obfuscation practices prevent de-obfuscation by tools such as IDA Pro and Hopper.","markdown":"

\uD83D\uDFE6  Description

This application is susceptible to reverse engineering.

\uD83D\uDFE6  Business Impact

During analysis, the classes, method names, strings, and resources were available to inspect. It is recommended that you obfuscate all data in the application so it is not easily readable. Good obfuscation practices prevent de-obfuscation by tools such as IDA Pro and Hopper.

"},"properties":{"tags":["CWE-200","HIPAA §164.312(a)(1)","PCI 6.5","OWASP M7"],"severity":"Low","type":"security","category":"Code Analysis","subcategory":"Binary Protections Testing"}},{"id":"67e11d2b-85c8-6900-ca1e-527b00000000","name":"MinsdkversionNoncompliance","shortDescription":{"text":"This app does not meet ADA requirement 1.6.1, as its minSdkVersion is set to a platform version older than the N-2 range, where N represents the latest Android release. This lack of compliance means the app does not take full advantage of recent Android updates, potentially exposing users to security vulnerabilities and limiting compatibility with modern features."},"defaultConfiguration":{"level":"note"},"helpUri":"https://www.zimperium.com/zscan","help":{"text":"This app does not meet ADA requirement 1.6.1, as its minSdkVersion is set to a platform version older than the N-2 range, where N represents the latest Android release. This lack of compliance means the app does not take full advantage of recent Android updates, potentially exposing users to security vulnerabilities and limiting compatibility with modern features.\n\nFailing to target an up-to-date platform version leaves the app vulnerable to known security threats that have been addressed in more recent Android versions. Users may experience degraded functionality or issues on newer devices due to deprecated APIs.","markdown":"

\uD83D\uDFE6  Description

This app does not meet ADA requirement 1.6.1, as its minSdkVersion is set to a platform version older than the N-2 range, where N represents the latest Android release. This lack of compliance means the app does not take full advantage of recent Android updates, potentially exposing users to security vulnerabilities and limiting compatibility with modern features.

\uD83D\uDFE6  Business Impact

Failing to target an up-to-date platform version leaves the app vulnerable to known security threats that have been addressed in more recent Android versions. Users may experience degraded functionality or issues on newer devices due to deprecated APIs.

"},"properties":{"tags":[],"severity":"Informational","type":"security","category":"Code Analysis","subcategory":"Content API"}},{"id":"667eb2a2-8452-657c-816c-289a00000000","name":"LocationPermissions","shortDescription":{"text":"The app was found to be using location permissions which, according to Google Play Store policies, require justifying the use of background location, describing the benefits to the user and providing information on how location information is handled securely."},"defaultConfiguration":{"level":"note"},"helpUri":"https://www.zimperium.com/zscan","help":{"text":"The app was found to be using location permissions which, according to Google Play Store policies, require justifying the use of background location, describing the benefits to the user and providing information on how location information is handled securely.\n\nDevice location is regarded as personal and sensitive user data subject to the Personal and Sensitive Information policy and the Background Location policy. Not complying with Google's Play Store specifications may cause the app to be removed. For this policy, e.g:\r\n- Misusing location data without explicit user consent.\r\n- Failing to provide clear disclosures on how location information is used.","markdown":"

\uD83D\uDFE6  Description

The app was found to be using location permissions which, according to Google Play Store policies, require justifying the use of background location, describing the benefits to the user and providing information on how location information is handled securely.

\uD83D\uDFE6  Business Impact

Device location is regarded as personal and sensitive user data subject to the Personal and Sensitive Information policy and the Background Location policy. Not complying with Google's Play Store specifications may cause the app to be removed. For this policy, e.g:\r
- Misusing location data without explicit user consent.\r
- Failing to provide clear disclosures on how location information is used.

"},"properties":{"tags":["GDPR Article 5, Section 1","GDPR Article 13, Section 1","GDPR Article 25, Section 1"],"severity":"Informational","type":"security","category":"Compliance","subcategory":"App Store"}},{"id":"667eb276-8452-657c-816c-289900000000","name":"SmsAndCallLogPermissions","shortDescription":{"text":"The app was found to be using SMS and Call Log permissions that require specific actions according to Google Play Store policies. For the Call Log permission group, the application must be actively registered as the default phone or assistant handler on the device. For the SMS permission group, it must be actively registered as the default SMS or assistant handler on the device. These permissions including READ_CALL_LOG, WRITE_CALL_LOG, READ_SMS, and SEND_SMS, are necessary for specific functionalities such as managing call logs and sending SMS messages."},"defaultConfiguration":{"level":"note"},"helpUri":"https://www.zimperium.com/zscan","help":{"text":"The app was found to be using SMS and Call Log permissions that require specific actions according to Google Play Store policies. For the Call Log permission group, the application must be actively registered as the default phone or assistant handler on the device. For the SMS permission group, it must be actively registered as the default SMS or assistant handler on the device. These permissions including READ_CALL_LOG, WRITE_CALL_LOG, READ_SMS, and SEND_SMS, are necessary for specific functionalities such as managing call logs and sending SMS messages.\n\nFailure to properly register the application as the default handler for calls or SMS may result with the app being non-compliant with Google Play Store requirements and potential expulsion from the store. Moreover, misuse of these permissions to collect user data without consent can lead to serious legal and reputation consequences. Strict adherence to Google's privacy and security policies is essential to avoid any issues.","markdown":"

\uD83D\uDFE6  Description

The app was found to be using SMS and Call Log permissions that require specific actions according to Google Play Store policies. For the Call Log permission group, the application must be actively registered as the default phone or assistant handler on the device. For the SMS permission group, it must be actively registered as the default SMS or assistant handler on the device. These permissions including READ_CALL_LOG, WRITE_CALL_LOG, READ_SMS, and SEND_SMS, are necessary for specific functionalities such as managing call logs and sending SMS messages.

\uD83D\uDFE6  Business Impact

Failure to properly register the application as the default handler for calls or SMS may result with the app being non-compliant with Google Play Store requirements and potential expulsion from the store. Moreover, misuse of these permissions to collect user data without consent can lead to serious legal and reputation consequences. Strict adherence to Google's privacy and security policies is essential to avoid any issues.

"},"properties":{"tags":["GDPR Article 5, Section 1","GDPR Article 13, Section 1","GDPR Article 25, Section 1","GDPR Article 32, Section 1"],"severity":"Informational","type":"security","category":"Compliance","subcategory":"App Store"}},{"id":"63d8fb8f-b544-7900-1078-5c8400000000","name":"ClipboardVulnerability","shortDescription":{"text":"The app can be vulnerable to clipboard attacks."},"defaultConfiguration":{},"helpUri":"https://www.zimperium.com/zscan","help":{"text":"The app can be vulnerable to clipboard attacks.\n\nThe Android clipboard vulnerability is a security issue that allows malicious apps to access sensitive data stored in the clipboard, such as passwords, personal information, and other confidential data. This vulnerability exists because the Android clipboard is not secured and can be accessed by any app, leading to potential data theft.","markdown":"

\uD83D\uDFE6  Description

The app can be vulnerable to clipboard attacks.

\uD83D\uDFE6  Business Impact

The Android clipboard vulnerability is a security issue that allows malicious apps to access sensitive data stored in the clipboard, such as passwords, personal information, and other confidential data. This vulnerability exists because the Android clipboard is not secured and can be accessed by any app, leading to potential data theft.

"},"properties":{"tags":["OWASP M9"],"severity":"Low","type":"security","category":"Vulnerability","subcategory":"Data Leakage"}},{"id":"63ca495c-b0a8-7a00-262f-409800000000","name":"HardcodedSymmetricKey","shortDescription":{"text":"The app exposes symmetric secret key on the code and it is the only encryption method used."},"defaultConfiguration":{},"helpUri":"https://www.zimperium.com/zscan","help":{"text":"The app exposes symmetric secret key on the code and it is the only encryption method used.\n\nHardcoding secret keys, such as encryption keys, in software can pose a significant security risk. If an attacker is able to access the code, they can easily extract the key and use it for unauthorized access or decryption. Additionally, if the code is made public, the key can be discovered easily by anyone. This can lead to data breaches, unauthorized access to systems, and other security incidents. To mitigate this risk, secret keys should be stored in secure, external locations and accessed by the software at runtime, rather than being hardcoded into the codebase.\r\n\r\nThe Cryptographic Key Generation does not satisfy the requeriment for NIAP compliance.","markdown":"

\uD83D\uDFE6  Description

The app exposes symmetric secret key on the code and it is the only encryption method used.

\uD83D\uDFE6  Business Impact

Hardcoding secret keys, such as encryption keys, in software can pose a significant security risk. If an attacker is able to access the code, they can easily extract the key and use it for unauthorized access or decryption. Additionally, if the code is made public, the key can be discovered easily by anyone. This can lead to data breaches, unauthorized access to systems, and other security incidents. To mitigate this risk, secret keys should be stored in secure, external locations and accessed by the software at runtime, rather than being hardcoded into the codebase.\r
\r
The Cryptographic Key Generation does not satisfy the requeriment for NIAP compliance.

"},"properties":{"tags":["MASVS MSTG-CRYPTO-1","NIAP FCS_CKM_EXT.1.1","OWASP M10"],"severity":"High","type":"security","category":"Code Analysis","subcategory":"Cryptography"}},{"id":"63cff208-1499-a500-0f37-337700000000","name":"ApkCanBeEasilyTampered","shortDescription":{"text":"The app can be tampered with easily. It contains readable method names, has been signed with a weak APK Signature Scheme, and appears not to be using any known code obfuscation tools."},"defaultConfiguration":{},"helpUri":"https://www.zimperium.com/zscan","help":{"text":"The app can be tampered with easily. It contains readable method names, has been signed with a weak APK Signature Scheme, and appears not to be using any known code obfuscation tools.\n\nAn adversary can easily use accessible tools to extract this metadata and reveal significant information about sensitive parts of the program. The adversary can find this information useful on its own or use it as a stepping stone to perform unauthorized code modifications. \r\n\r\nAlso, using a weak APK signature makes it difficult to ensure that no one has tampered with the contents of the APK. \r\n\r\nIn addition, this app appears not to be using any known code obfuscation tools. Code obfuscation is the process of modifying an executable so that it is no longer useful to a hacker but remains fully functional. Making an app much more difficult to reverse-engineer helps protect against trade secret (intellectual property) theft, unauthorized access, bypassing licensing or other controls, and vulnerability discovery.\r\n\r\nThese issues combined make it easy for an attacker to modify the APK and add unexpected behaviors, such as bypassing authorization control and gaining privileges.","markdown":"

\uD83D\uDFE6  Description

The app can be tampered with easily. It contains readable method names, has been signed with a weak APK Signature Scheme, and appears not to be using any known code obfuscation tools.

\uD83D\uDFE6  Business Impact

An adversary can easily use accessible tools to extract this metadata and reveal significant information about sensitive parts of the program. The adversary can find this information useful on its own or use it as a stepping stone to perform unauthorized code modifications. \r
\r
Also, using a weak APK signature makes it difficult to ensure that no one has tampered with the contents of the APK. \r
\r
In addition, this app appears not to be using any known code obfuscation tools. Code obfuscation is the process of modifying an executable so that it is no longer useful to a hacker but remains fully functional. Making an app much more difficult to reverse-engineer helps protect against trade secret (intellectual property) theft, unauthorized access, bypassing licensing or other controls, and vulnerability discovery.\r
\r
These issues combined make it easy for an attacker to modify the APK and add unexpected behaviors, such as bypassing authorization control and gaining privileges.

"},"properties":{"tags":["OWASP M7"],"severity":"Medium","type":"security","category":"Code Analysis","subcategory":"Binary Protections Testing"}},{"id":"63bd9c4e-d6d6-9f00-0f5b-8df700000000","name":"BareminimumPermissions","shortDescription":{"text":"The app asks for the minimum set of permissions required for it to fully operate."},"defaultConfiguration":{"level":"note"},"helpUri":"https://www.zimperium.com/zscan","help":{"text":"The app asks for the minimum set of permissions required for it to fully operate.\n\nThis is a best practice.","markdown":"

\uD83D\uDFE6  Description

The app asks for the minimum set of permissions required for it to fully operate.

\uD83D\uDFE6  Business Impact

This is a best practice.

"},"properties":{"tags":["MASVS MSTG-PLATFORM-1"],"severity":"Best Practices","type":"security","category":"Code Analysis","subcategory":"Entitlements"}},{"id":"5b62edc8-bb6e-303a-8140-a37300000000","name":"ExternalStorageAccess","shortDescription":{"text":"Files created on external storage are world readable and writable, meaning any app can read or write to them."},"defaultConfiguration":{},"helpUri":"https://www.zimperium.com/zscan","help":{"text":"Files created on external storage are world readable and writable, meaning any app can read or write to them.\n\nSince external storage can be removed from a device and connected to any other computer, it is not possible to enforce access control for data stored on external storage. \r\n\r\nUsing external storage could open up the app to a Man-in-the-Disk attack which harms apps and data stored in external storage. When an app is downloaded and saved in external storage, updated or received data from an app's server provider is passed through external storage and it gives the adversary an opportunity to manipulate the data held in the external storage.","markdown":"

\uD83D\uDFE6  Description

Files created on external storage are world readable and writable, meaning any app can read or write to them.

\uD83D\uDFE6  Business Impact

Since external storage can be removed from a device and connected to any other computer, it is not possible to enforce access control for data stored on external storage. \r
\r
Using external storage could open up the app to a Man-in-the-Disk attack which harms apps and data stored in external storage. When an app is downloaded and saved in external storage, updated or received data from an app's server provider is passed through external storage and it gives the adversary an opportunity to manipulate the data held in the external storage.

"},"properties":{"tags":["CWE-276","CWE-284","GDPR Article 25, Section 1","HIPAA §164.312(a)(1)","OWASP M9"],"severity":"Low","type":"security","category":"Code Analysis","subcategory":"File Storage"}},{"id":"5d4203bc-7d33-27a1-308b-456700000000","name":"Sharedpreferences","shortDescription":{"text":"This app uses a SharedPreferences instance."},"defaultConfiguration":{},"helpUri":"https://www.zimperium.com/zscan","help":{"text":"This app uses a SharedPreferences instance.\n\nSharedPreferences on Android stores all of your values unencrypted in the /data/data/ location as an XML file, simply protected by the user-restricted file system on Android. If an adversary gains root access to an Android device, they have full read and write access to the application preferences, even if it was created with MODE_PRIV. ","markdown":"

\uD83D\uDFE6  Description

This app uses a SharedPreferences instance.

\uD83D\uDFE6  Business Impact

SharedPreferences on Android stores all of your values unencrypted in the /data/data/ location as an XML file, simply protected by the user-restricted file system on Android. If an adversary gains root access to an Android device, they have full read and write access to the application preferences, even if it was created with MODE_PRIV.

"},"properties":{"tags":["GDPR Article 5, Section 1","GDPR Article 13, Section 1","GDPR Article 25, Section 1"],"severity":"Low","type":"security","category":"Code Analysis","subcategory":"Content API"}},{"id":"63921084-031a-7c00-0f01-0dc500000000","name":"ChainOfTrustValidation","shortDescription":{"text":"All secure endpoints passed the chain of trust validation testing."},"defaultConfiguration":{"level":"note"},"helpUri":"https://www.zimperium.com/zscan","help":{"text":"All secure endpoints passed the chain of trust validation testing.\n\nIf the app does not follow a chain of trust of a certificate to a root server, the certificate loses all value as a metric of trust. This makes the app susceptible to an attack that poisons the DNS cache or uses an Adversary-in-the-Middle (AITM) attack to modify the traffic from server to client.","markdown":"

\uD83D\uDFE6  Description

All secure endpoints passed the chain of trust validation testing.

\uD83D\uDFE6  Business Impact

If the app does not follow a chain of trust of a certificate to a root server, the certificate loses all value as a metric of trust. This makes the app susceptible to an attack that poisons the DNS cache or uses an Adversary-in-the-Middle (AITM) attack to modify the traffic from server to client.

"},"properties":{"tags":["MASVS MSTG-NETWORK-3","NIAP FCS_TLSC_EXT.1.3"],"severity":"Best Practices","type":"security","category":"Communications","subcategory":"SSL Checks"}},{"id":"5da893bf-7d33-27f0-1f8b-456a00000000","name":"NetworkCommunications","shortDescription":{"text":"This application has access to the network communication, which is used to send and receive data to local or remote services and systems."},"defaultConfiguration":{"level":"note"},"helpUri":"https://www.zimperium.com/zscan","help":{"text":"This application has access to the network communication, which is used to send and receive data to local or remote services and systems.\n\nThis finding is a requirement for NIAP compliance.","markdown":"

\uD83D\uDFE6  Description

This application has access to the network communication, which is used to send and receive data to local or remote services and systems.

\uD83D\uDFE6  Business Impact

This finding is a requirement for NIAP compliance.

"},"properties":{"tags":["NIAP FDP_DEC_EXT.1.1, FDP_DEC_EXT.1.2"],"severity":"Informational","type":"security","category":"Compliance","subcategory":"Hardware Access"}},{"id":"5da89d29-7d33-27f1-1f8b-456800000000","name":"TelephonyHardware","shortDescription":{"text":"This application has access to the telephony services that is used to send and receive data to local or remote services and systems."},"defaultConfiguration":{"level":"note"},"helpUri":"https://www.zimperium.com/zscan","help":{"text":"This application has access to the telephony services that is used to send and receive data to local or remote services and systems.\n\nThis finding is a requirement for NIAP compliance.","markdown":"

\uD83D\uDFE6  Description

This application has access to the telephony services that is used to send and receive data to local or remote services and systems.

\uD83D\uDFE6  Business Impact

This finding is a requirement for NIAP compliance.

"},"properties":{"tags":["NIAP FDP_DEC_EXT.1.1, FDP_DEC_EXT.1.2"],"severity":"Informational","type":"security","category":"Compliance","subcategory":"Hardware Access"}},{"id":"5db9a286-7d33-277c-4b8b-456700000000","name":"DbrgCipherSuggestion","shortDescription":{"text":"This application is not using any deterministic random bit generation (DRBG) functionality."},"defaultConfiguration":{"level":"note"},"helpUri":"https://www.zimperium.com/zscan","help":{"text":"This application is not using any deterministic random bit generation (DRBG) functionality.\n\nThis finding is a requirement for NIAP compliance.","markdown":"

\uD83D\uDFE6  Description

This application is not using any deterministic random bit generation (DRBG) functionality.

\uD83D\uDFE6  Business Impact

This finding is a requirement for NIAP compliance.

"},"properties":{"tags":["MASVS MSTG-CRYPTO-2, MSTG-CRYPTO-3","NIAP FCS_RBG_EXT.2.1, FCS_RBG_EXT.1.1"],"severity":"Informational","type":"security","category":"Compliance","subcategory":"Random Bit Generation"}},{"id":"5e0a1763-a430-cf64-d379-2d6400000000","name":"NoCertificatePinningDetected","shortDescription":{"text":"This app has not implemented SSL certificate pinning."},"defaultConfiguration":{},"helpUri":"https://www.zimperium.com/zscan","help":{"text":"This app has not implemented SSL certificate pinning.\n\nPinning leverages knowledge of the pre-existing relationship between the user and an organization or service to help make better security-related decisions. Because you already have information on the server or service, you don't need to rely on generalized mechanisms meant to solve the key distribution problem. ","markdown":"

\uD83D\uDFE6  Description

This app has not implemented SSL certificate pinning.

\uD83D\uDFE6  Business Impact

Pinning leverages knowledge of the pre-existing relationship between the user and an organization or service to help make better security-related decisions. Because you already have information on the server or service, you don't need to rely on generalized mechanisms meant to solve the key distribution problem.

"},"properties":{"tags":["OWASP M5"],"severity":"Medium","type":"security","category":"Communications","subcategory":"SSL Checks"}},{"id":"5fb402d1-d9a8-c20a-5e58-e34300000000","name":"WeakApkSigningScheme","shortDescription":{"text":"This application is signed with version 1 or 2 of the APK Signature Scheme. "},"defaultConfiguration":{},"helpUri":"https://www.zimperium.com/zscan","help":{"text":"This application is signed with version 1 or 2 of the APK Signature Scheme. \n\nThe recommended APK Signature Scheme version 3 was introduced in Android 9. Using the latest APK Signature Scheme helps ensure that no one has tampered with the contents of the APK.","markdown":"

\uD83D\uDFE6  Description

This application is signed with version 1 or 2 of the APK Signature Scheme.

\uD83D\uDFE6  Business Impact

The recommended APK Signature Scheme version 3 was introduced in Android 9. Using the latest APK Signature Scheme helps ensure that no one has tampered with the contents of the APK.

"},"properties":{"tags":["CVSS 2.0 score 3.8","CVSS 2.0 vector AV:L/AC:H/Au:S/C:N/I:C/A:N","CVSS 3.1 score 5.9","CVSS 3.1 vector AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:H/A:N","MASVS MSTG-CODE-1","NIAP FPT_TUD_EXT.1.6"],"severity":"Medium","type":"security","category":"Code Analysis","subcategory":"Cryptography"}},{"id":"5f7eef85-c098-1160-dd2a-481f00000000","name":"NoCodeObfuscation","shortDescription":{"text":"This application appears not to be using any known code obfuscation tools."},"defaultConfiguration":{},"helpUri":"https://www.zimperium.com/zscan","help":{"text":"This application appears not to be using any known code obfuscation tools.\n\nCode obfuscation is the process of modifying an executable so that it is no longer useful to a hacker but remains fully functional. Making an application much more difficult to reverse-engineer helps protect against trade secret (intellectual property) theft, unauthorized access, bypassing licensing or other controls, and vulnerability discovery.","markdown":"

\uD83D\uDFE6  Description

This application appears not to be using any known code obfuscation tools.

\uD83D\uDFE6  Business Impact

Code obfuscation is the process of modifying an executable so that it is no longer useful to a hacker but remains fully functional. Making an application much more difficult to reverse-engineer helps protect against trade secret (intellectual property) theft, unauthorized access, bypassing licensing or other controls, and vulnerability discovery.

"},"properties":{"tags":["MASVS MSTG-RESILIENCE-9","OWASP M7"],"severity":"Medium","type":"security","category":"Code Analysis","subcategory":"Binary Protections Testing"}},{"id":"60faf47f-178b-b800-121f-af7600000000","name":"JailbreakAndRootDetection","shortDescription":{"text":"This application has code to detect if the device the application is executing on is jailbroken or rooted."},"defaultConfiguration":{"level":"note"},"helpUri":"https://www.zimperium.com/zscan","help":{"text":"This application has code to detect if the device the application is executing on is jailbroken or rooted.\n\nThe goal of this detection is to increase the difficulty of running the app on a compromised device. This forces the adversary to defeat the jailbreak and rooted device checks to fully execute the app. ","markdown":"

\uD83D\uDFE6  Description

This application has code to detect if the device the application is executing on is jailbroken or rooted.

\uD83D\uDFE6  Business Impact

The goal of this detection is to increase the difficulty of running the app on a compromised device. This forces the adversary to defeat the jailbreak and rooted device checks to fully execute the app.

"},"properties":{"tags":[],"severity":"Best Practices","type":"security","category":"Code Analysis","subcategory":"Binary Protections Testing"}},{"id":"636277c5-cab2-bc00-0f71-f3f500000000","name":"ProtectedProgramDataSymbols","shortDescription":{"text":"This application has obfuscated or encrypted program data symbols."},"defaultConfiguration":{"level":"note"},"helpUri":"https://www.zimperium.com/zscan","help":{"text":"This application has obfuscated or encrypted program data symbols.\n\nSymbol names and locations reveal the internal assets of the application. Protecting these symbols with encryption or obfuscation make it difficult for an attacker to understand the internal assets of the application.","markdown":"

\uD83D\uDFE6  Description

This application has obfuscated or encrypted program data symbols.

\uD83D\uDFE6  Business Impact

Symbol names and locations reveal the internal assets of the application. Protecting these symbols with encryption or obfuscation make it difficult for an attacker to understand the internal assets of the application.

"},"properties":{"tags":["MASVS MSTG-CODE-3"],"severity":"Best Practices","type":"security","category":"Code Analysis","subcategory":"Binary Protections Testing"}},{"id":"64ad4f15-2e24-ce00-5648-756a00000000","name":"NoObfuscationDetected","shortDescription":{"text":"There is no obfuscation detected in the app."},"defaultConfiguration":{"level":"note"},"helpUri":"https://www.zimperium.com/zscan","help":{"text":"There is no obfuscation detected in the app.\n\nThe app has no code obfuscation. This allows for reverse engineering and full app analysis.","markdown":"

\uD83D\uDFE6  Description

There is no obfuscation detected in the app.

\uD83D\uDFE6  Business Impact

The app has no code obfuscation. This allows for reverse engineering and full app analysis.

"},"properties":{"tags":["NIAP AVA_VAN.1.1C"],"severity":"Informational","type":"security","category":"Code Analysis","subcategory":"Binary Protections Testing"}},{"id":"591d4dc1-12df-5b5d-87e6-1e2d00000000","name":"UnsecuredStorageDataMode","shortDescription":{"text":"This app uses the unsecured storage data mode (WORLD_READABLE, WORLD_WRITABLE), which could allow any app or adversary to access the data."},"defaultConfiguration":{},"helpUri":"https://www.zimperium.com/zscan","help":{"text":"This app uses the unsecured storage data mode (WORLD_READABLE, WORLD_WRITABLE), which could allow any app or adversary to access the data.\n\nUsing unsecured storage data modes in an app can lead to data breaches.","markdown":"

\uD83D\uDFE6  Description

This app uses the unsecured storage data mode (WORLD_READABLE, WORLD_WRITABLE), which could allow any app or adversary to access the data.

\uD83D\uDFE6  Business Impact

Using unsecured storage data modes in an app can lead to data breaches.

"},"properties":{"tags":["CVSS 2.0 score 4.3","CVSS 2.0 vector AV:N/AC:M/Au:N/C:P/I:N/A:N","CVSS 3.1 score 5.9","CVSS 3.1 vector AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N","CWE-276","NIAP FMT_CFG_EXT.1.2"],"severity":"Low","type":"security","category":"Code Analysis","subcategory":"File Storage"}},{"id":"633d121c-0e3b-5700-1234-49c900000000","name":"CryptographicPrimitives","shortDescription":{"text":"This application is using cryptographic primitives."},"defaultConfiguration":{"level":"note"},"helpUri":"https://www.zimperium.com/zscan","help":{"text":"This application is using cryptographic primitives.\n\nCryptographic primitives are well-established, low-level cryptographic algorithms that are frequently used to build cryptographic protocols for computer security systems. These routines include, but are not limited to, one-way hash functions and encryption functions.","markdown":"

\uD83D\uDFE6  Description

This application is using cryptographic primitives.

\uD83D\uDFE6  Business Impact

Cryptographic primitives are well-established, low-level cryptographic algorithms that are frequently used to build cryptographic protocols for computer security systems. These routines include, but are not limited to, one-way hash functions and encryption functions.

"},"properties":{"tags":["MASVS MSTG-CRYPTO-2, MSTG-CRYPTO-3"],"severity":"Informational","type":"security","category":"Compliance","subcategory":"Cryptography"}},{"id":"63eb7110-f141-dd00-6a42-035400000000","name":"WebviewCleanup","shortDescription":{"text":"No WebView cleaning measures are implemented."},"defaultConfiguration":{},"helpUri":"https://www.zimperium.com/zscan","help":{"text":"No WebView cleaning measures are implemented.\n\nData, including sensitive data such as user credentials, financial information, or personal data can be stored insecurely, making them vulnerable to theft or manipulation by adversaries.","markdown":"

\uD83D\uDFE6  Description

No WebView cleaning measures are implemented.

\uD83D\uDFE6  Business Impact

Data, including sensitive data such as user credentials, financial information, or personal data can be stored insecurely, making them vulnerable to theft or manipulation by adversaries.

"},"properties":{"tags":["MASVS MSTG-PLATFORM-10"],"severity":"Medium","type":"security","category":"Vulnerability","subcategory":"WebView"}},{"id":"558af6f7-3601-8303-e4d5-958000000000","name":"JavascriptEnabled","shortDescription":{"text":"The application has been configured to allow JavaScript execution in the WebView control. "},"defaultConfiguration":{},"helpUri":"https://www.zimperium.com/zscan","help":{"text":"The application has been configured to allow JavaScript execution in the WebView control. \n\nA common attack vector for mobile apps is ads. Advertisements from external sources are often loaded in WebViews, and blocking JavaScript execution is a good way to prevent malicious code from being injected and protect the app users.","markdown":"

\uD83D\uDFE6  Description

The application has been configured to allow JavaScript execution in the WebView control.

\uD83D\uDFE6  Business Impact

A common attack vector for mobile apps is ads. Advertisements from external sources are often loaded in WebViews, and blocking JavaScript execution is a good way to prevent malicious code from being injected and protect the app users.

"},"properties":{"tags":["CWE-830","MASVS MSTG-PLATFORM-5"],"severity":"Medium","type":"security","category":"WebView","subcategory":"JavaScript"}},{"id":"575ecc52-d100-c5af-f726-2ac400000000","name":"JavaReflectionApiInvoked","shortDescription":{"text":"Reflection is an API that is used to examine or modify the behavior of methods, classes, and interfaces at runtime. An application can use the Java reflection APIs to access and update fields, and execute methods that are forbidden by normal Java access and visibility rules."},"defaultConfiguration":{},"helpUri":"https://www.zimperium.com/zscan","help":{"text":"Reflection is an API that is used to examine or modify the behavior of methods, classes, and interfaces at runtime. An application can use the Java reflection APIs to access and update fields, and execute methods that are forbidden by normal Java access and visibility rules.\n\nReflection can assist the developer in inspecting a class, interface, class structure, methods, and fields without knowing the names of the classes at compile time. What makes it even more interesting is that developers can manipulate fields, invoke methods, and also instantiate new objects. However, with access to private fields and other items, inspection and modification of internal data is possible and could lead to various malicious exploits and data leakage.","markdown":"

\uD83D\uDFE6  Description

Reflection is an API that is used to examine or modify the behavior of methods, classes, and interfaces at runtime. An application can use the Java reflection APIs to access and update fields, and execute methods that are forbidden by normal Java access and visibility rules.

\uD83D\uDFE6  Business Impact

Reflection can assist the developer in inspecting a class, interface, class structure, methods, and fields without knowing the names of the classes at compile time. What makes it even more interesting is that developers can manipulate fields, invoke methods, and also instantiate new objects. However, with access to private fields and other items, inspection and modification of internal data is possible and could lead to various malicious exploits and data leakage.

"},"properties":{"tags":[],"severity":"Medium","type":"security","category":"Code Analysis","subcategory":"Method"}}],"properties":{"policyName":null,"appRulesVersion":"9943727e80b24e105fd4cb8646a7f084"}}},"artifacts":[{"location":{"uri":"Sample_Insecure_Bank_App.apk","uriBaseId":"%binroot%"},"properties":{"appVersion":"1.0","appPlatform":"android","appMD5Hash":"5ee4829065640f9c936ac861d1650ffc","appName":"InsecureBankv2","appBundle":"com.android.insecurebankv2","appBuild":"1"}}],"results":[{"ruleId":"63ee197a-f141-dd00-ed71-43f400000000","ruleIndex":0,"message":{"text":"\uD83D\uDFE9 Recommendation

Sensitive, hardcoded data (such as private IPs/emails and user/DB details) should not be stored unless secured specifically. An attacker can use that data for further malicious intentions.

\uD83D\uDFE6 Code Snippet

\uD83D\uDFE7 com.android.insecurebankv2.CryptoClass

  public class CryptoClass {
              String base64Text;
              byte[] cipherData;
              String cipherText;
              String plainText;
‣‣            String key = \"This is the super secret key 123\";
              byte[] ivBytes = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
  
      public static byte[] aes256encrypt(byte[] ivBytes, byte[] keyBytes, byte[] textBytes) throws BadPaddingExceptio...
          AlgorithmParameterSpec ivSpec = new IvParameterSpec(ivBytes);
          SecretKeySpec newKey = new SecretKeySpec(keyBytes, \"AES\");

"},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.android.insecurebankv2.CryptoClass"},"region":{"startLine":22,"snippet":{"text":" public class CryptoClass {\n String base64Text;\n byte[] cipherData;\n String cipherText;\n String plainText;\n String key = \"This is the super secret key 123\";\n byte[] ivBytes = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};\n\n public static byte[] aes256encrypt(byte[] ivBytes, byte[] keyBytes, byte[] textBytes) throws BadPaddingException, NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, InvalidKeyException, UnsupportedEncodingException, InvalidAlgorithmParameterException {\n AlgorithmParameterSpec ivSpec = new IvParameterSpec(ivBytes);\n SecretKeySpec newKey = new SecretKeySpec(keyBytes, \"AES\");"}}}}]},{"ruleId":"63a5da01-834f-de00-1102-f91500000000","ruleIndex":1,"message":{"text":"\uD83D\uDFE9 Recommendation

Do not expose sensitive data. Use appropriate EditText attributes to mask potentially sensitive user input (for example, use dots instead of the input characters for password or pins).

"},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+classes.dex"},"region":{"startLine":1}}}]},{"ruleId":"63a02646-7625-7900-1469-a8d500000000","ruleIndex":2,"message":{"text":"\uD83D\uDFE9 Recommendation

Always use appropriate EditText attributes for sensitive data inputs.

\uD83D\uDFE7 Locations

com.android.insecurebankv2.BuildConfig
com.android.insecurebankv2.MyBroadCastReceiver
com.android.insecurebankv2.CryptoClass
com.android.insecurebankv2.FilePrefActivity
com.android.insecurebankv2.DoLogin
com.android.insecurebankv2.MyWebViewClient
com.android.insecurebankv2.LoginActivity
com.android.insecurebankv2.ChangePassword
com.android.insecurebankv2.WrongLogin
com.android.insecurebankv2.PostLogin
com.google.ads.mediation.admob.AdMobAdapter
com.android.insecurebankv2.ViewStatement
com.android.insecurebankv2.TrackUserContentProvider
com.google.ads.mediation.customevent.CustomEventBanner
com.google.ads.mediation.customevent.CustomEventBannerListener
com.google.ads.mediation.customevent.CustomEventAdapter
com.google.ads.mediation.customevent.CustomEventInterstitialListener
com.google.ads.mediation.customevent.CustomEventInterstitial
com.google.ads.mediation.customevent.CustomEvent
com.android.insecurebankv2.R
com.google.ads.mediation.customevent.CustomEventListener
com.google.ads.mediation.EmptyNetworkExtras
com.google.ads.mediation.customevent.CustomEventServerParameters
com.google.ads.mediation.MediationAdapter
com.google.ads.mediation.AdUrlAdapter
com.google.ads.mediation.MediationAdRequest
com.android.insecurebankv2.DoTransfer
com.google.ads.mediation.AbstractAdViewAdapter
com.google.ads.mediation.MediationBannerAdapter
com.google.ads.mediation.MediationBannerListener
com.google.ads.mediation.MediationInterstitialAdapter
com.google.ads.mediation.NetworkExtras
com.google.ads.mediation.MediationInterstitialListener
com.google.ads.mediation.MediationServerParameters
com.google.ads.AdRequest
com.google.ads.AdSize

\uD83D\uDFE6 Code Snippets

\uD83D\uDFE7 com.android.insecurebankv2.LoginActivity

  
  
  public class LoginActivity extends Activity {
              public static final String MYPREFS = \"mySharedPreferences\";
              EditText Password_Text;
‣‣            EditText Username_Text;
              Button createuser_buttons;
              Button fillData_button;
              Button login_buttons;
              String usernameBase64ByteString;
  

\uD83D\uDFE7 com.android.insecurebankv2.DoTransfer

  EditText from;
  Button getAccounts;
  InputStream in;
  JSONObject jsonObject;
  String passNormalized;
‣‣EditText phoneNumber;
  BufferedReader reader;
  HttpResponse responseBody;
  String result;
  SharedPreferences serverDetails;
  EditText to;

"},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.android.insecurebankv2.BuildConfig"},"region":{"startLine":1}}},{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.android.insecurebankv2.MyBroadCastReceiver"},"region":{"startLine":1}}},{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.android.insecurebankv2.CryptoClass"},"region":{"startLine":1}}},{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.android.insecurebankv2.FilePrefActivity"},"region":{"startLine":1}}},{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.android.insecurebankv2.DoLogin"},"region":{"startLine":1}}},{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.android.insecurebankv2.MyWebViewClient"},"region":{"startLine":1}}},{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.android.insecurebankv2.LoginActivity"},"region":{"startLine":27,"snippet":{"text":"\n\n public class LoginActivity extends Activity {\n public static final String MYPREFS = \"mySharedPreferences\";\n EditText Password_Text;\n EditText Username_Text;\n Button createuser_buttons;\n Button fillData_button;\n Button login_buttons;\n String usernameBase64ByteString;\n"}}}},{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.android.insecurebankv2.ChangePassword"},"region":{"startLine":1}}},{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.android.insecurebankv2.WrongLogin"},"region":{"startLine":1}}},{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.android.insecurebankv2.PostLogin"},"region":{"startLine":1}}},{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.google.ads.mediation.admob.AdMobAdapter"},"region":{"startLine":1}}},{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.android.insecurebankv2.ViewStatement"},"region":{"startLine":1}}},{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.android.insecurebankv2.TrackUserContentProvider"},"region":{"startLine":1}}},{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.google.ads.mediation.customevent.CustomEventBanner"},"region":{"startLine":1}}},{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.google.ads.mediation.customevent.CustomEventBannerListener"},"region":{"startLine":1}}},{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.google.ads.mediation.customevent.CustomEventAdapter"},"region":{"startLine":1}}},{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.google.ads.mediation.customevent.CustomEventInterstitialListener"},"region":{"startLine":1}}},{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.google.ads.mediation.customevent.CustomEventInterstitial"},"region":{"startLine":1}}},{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.google.ads.mediation.customevent.CustomEvent"},"region":{"startLine":1}}},{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.android.insecurebankv2.R"},"region":{"startLine":1}}},{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.google.ads.mediation.customevent.CustomEventListener"},"region":{"startLine":1}}},{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.google.ads.mediation.EmptyNetworkExtras"},"region":{"startLine":1}}},{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.google.ads.mediation.customevent.CustomEventServerParameters"},"region":{"startLine":1}}},{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.google.ads.mediation.MediationAdapter"},"region":{"startLine":1}}},{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.google.ads.mediation.AdUrlAdapter"},"region":{"startLine":1}}},{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.google.ads.mediation.MediationAdRequest"},"region":{"startLine":1}}},{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.android.insecurebankv2.DoTransfer"},"region":{"startLine":54,"snippet":{"text":" EditText from;\n Button getAccounts;\n InputStream in;\n JSONObject jsonObject;\n String passNormalized;\n EditText phoneNumber;\n BufferedReader reader;\n HttpResponse responseBody;\n String result;\n SharedPreferences serverDetails;\n EditText to;"}}}},{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.google.ads.mediation.AbstractAdViewAdapter"},"region":{"startLine":1}}},{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.google.ads.mediation.MediationBannerAdapter"},"region":{"startLine":1}}},{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.google.ads.mediation.MediationBannerListener"},"region":{"startLine":1}}},{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.google.ads.mediation.MediationInterstitialAdapter"},"region":{"startLine":1}}},{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.google.ads.mediation.NetworkExtras"},"region":{"startLine":1}}},{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.google.ads.mediation.MediationInterstitialListener"},"region":{"startLine":1}}},{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.google.ads.mediation.MediationServerParameters"},"region":{"startLine":1}}},{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.google.ads.AdRequest"},"region":{"startLine":1}}},{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.google.ads.AdSize"},"region":{"startLine":1}}}]},{"ruleId":"61cb48eb-3ea7-004a-a9a1-cd8400000000","ruleIndex":3,"message":{"text":"\uD83D\uDFE9 Recommendation

If other applications should not have access to this content provider, mark them as \"android:exported=false\" in the application manifest. Otherwise, set the \"android:exported\" attribute to true to allow other apps to access the stored data.

If this app is intentionally exporting the content provider, specify one or more permissions for reading and writing. If the content provider is for sharing data between the same developer across different apps, it is preferable to use the \"android:protectionLevel\" attribute and set it to \"signature\" protection. Signature permissions do not require user confirmation. And they provide a better user experience and more controlled access to the content provider data when the apps accessing the data are signed with the same key.

For applications that set either \"android:minSdkVersion\" or \"android:targetSdkVersion\" to 17 and higher, all of the providers are non-exported by default, unless the \"android:exported\" attribute is set to true or an intent-filter element is defined. For applications that set either \"android:minSdkVersion\" or \"android:targetSdkVersion\" to 16 or lower, a default exported status is true.

When accessing a content provider, use parameterized query methods such as query(), update(), and delete() to avoid potential SQL injection from untrusted sources. Using parameterized methods is insufficient if the selection argument is built by concatenating user data, before submitting it to the method. Check if access to sensitive information is possible or change it to bypass authorization mechanisms.

When creating a content provider that is exported for use by other applications, specify a single permission for reading and writing, or specify distinct permissions for reading and writing. Limit the permissions to those required to accomplish the task. Remember that it's usually easier to add permissions later to expose new functionality, than it is to take them away and impact existing users.

Content providers can also provide more granular access by declaring the \"android:grantUriPermissions\" attribute and using the FLAG_GRANT_READ_URI_PERMISSION and FLAG_GRANT_WRITE_URI_PERMISSION flags in the Intent object that activates the component. The scope of these permissions can be further limited by the element.

\uD83D\uDFE6 Code Snippet

\uD83D\uDFE7 com.android.insecurebankv2.TrackUserContentProvider(Showing 11 lines of 106)

  package com.android.insecurebankv2;
  public class TrackUserContentProvider extends android.content.ContentProvider {
      static final android.net.Uri CONTENT_URI = None;
      static final String CREATE_DB_TABLE = \" CREATE TABLE names (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NO...
      static final String DATABASE_NAME = \"mydb\";
      static final int DATABASE_VERSION = 1;
      static final String PROVIDER_NAME = \"com.android.insecurebankv2.TrackUserContentProvider\";
      static final String TABLE_NAME = \"names\";
      static final String URL = \"content://com.android.insecurebankv2.TrackUserContentProvider/trackerusers\";
      static final String name = \"name\";
      static final int uriCode = 1;

"},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.android.insecurebankv2.TrackUserContentProvider"},"region":{"startLine":1,"snippet":{"text":"package com.android.insecurebankv2;\npublic class TrackUserContentProvider extends android.content.ContentProvider {\n static final android.net.Uri CONTENT_URI = None;\n static final String CREATE_DB_TABLE = \" CREATE TABLE names (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL);\";\n static final String DATABASE_NAME = \"mydb\";\n static final int DATABASE_VERSION = 1;\n static final String PROVIDER_NAME = \"com.android.insecurebankv2.TrackUserContentProvider\";\n static final String TABLE_NAME = \"names\";\n static final String URL = \"content://com.android.insecurebankv2.TrackUserContentProvider/trackerusers\";\n static final String name = \"name\";\n static final int uriCode = 1;\n static final android.content.UriMatcher uriMatcher;\n private static java.util.HashMap values;\n private android.database.sqlite.SQLiteDatabase db;\n\n static TrackUserContentProvider()\n {\n com.android.insecurebankv2.TrackUserContentProvider.CONTENT_URI = android.net.Uri.parse(content://com.android.insecurebankv2.TrackUserContentProvider/trackerusers);\n com.android.insecurebankv2.TrackUserContentProvider.uriMatcher = new android.content.UriMatcher(-1);\n com.android.insecurebankv2.TrackUserContentProvider.uriMatcher.addURI(com.android.insecurebankv2.TrackUserContentProvider, trackerusers, 1);\n com.android.insecurebankv2.TrackUserContentProvider.uriMatcher.addURI(com.android.insecurebankv2.TrackUserContentProvider, trackerusers/*, 1);\n return;\n }\n\n public TrackUserContentProvider()\n {\n return;\n }\n\n public int delete(android.net.Uri p5, String p6, String[] p7)\n {\n switch (com.android.insecurebankv2.TrackUserContentProvider.uriMatcher.match(p5)) {\n case 1:\n int v0 = this.db.delete(names, p6, p7);\n this.getContext().getContentResolver().notifyChange(p5, 0);\n return v0;\n default:\n throw new IllegalArgumentException(new StringBuilder().append(Unknown URI ).append(p5).toString());\n }\n }\n\n public String getType(android.net.Uri p4)\n {\n switch (com.android.insecurebankv2.TrackUserContentProvider.uriMatcher.match(p4)) {\n case 1:\n return vnd.android.cursor.dir/u;\n default:\n throw new IllegalArgumentException(new StringBuilder().append(Unsupported URI: ).append(p4).toString());\n }\n }\n\n public android.net.Uri insert(android.net.Uri p7, android.content.ContentValues p8)\n {\n long v2 = this.db.insert(names, , p8);\n if (v2 <= 0) {\n throw new android.database.SQLException(new StringBuilder().append(Failed to add a record into ).append(p7).toString());\n } else {\n android.net.Uri v0 = android.content.ContentUris.withAppendedId(com.android.insecurebankv2.TrackUserContentProvider.CONTENT_URI, v2);\n this.getContext().getContentResolver().notifyChange(v0, 0);\n return v0;\n }\n }\n\n public boolean onCreate()\n {\n int v2_0;\n this.db = new com.android.insecurebankv2.TrackUserContentProvider$DatabaseHelper(this.getContext()).getWritableDatabase();\n if (this.db == null) {\n v2_0 = 0;\n } else {\n v2_0 = 1;\n }\n return v2_0;\n }\n\n public android.database.Cursor query(android.net.Uri p10, String[] p11, String p12, String[] p13, String p14)\n {\n android.database.sqlite.SQLiteQueryBuilder v0_1 = new android.database.sqlite.SQLiteQueryBuilder();\n v0_1.setTables(names);\n switch (com.android.insecurebankv2.TrackUserContentProvider.uriMatcher.match(p10)) {\n case 1:\n v0_1.setProjectionMap(com.android.insecurebankv2.TrackUserContentProvider.values);\n if ((p14 == null) || (p14 == )) {\n p14 = name;\n }\n android.database.Cursor v8 = v0_1.query(this.db, p11, p12, p13, 0, 0, p14);\n v8.setNotificationUri(this.getContext().getContentResolver(), p10);\n return v8;\n default:\n throw new IllegalArgumentException(new StringBuilder().append(Unknown URI ).append(p10).toString());\n }\n }\n\n public int update(android.net.Uri p5, android.content.ContentValues p6, String p7, String[] p8)\n {\n switch (com.android.insecurebankv2.TrackUserContentProvider.uriMatcher.match(p5)) {\n case 1:\n int v0 = this.db.update(names, p6, p7, p8);\n this.getContext().getContentResolver().notifyChange(p5, 0);\n return v0;\n default:\n throw new IllegalArgumentException(new StringBuilder().append(Unknown URI ).append(p5).toString());\n }\n }\n}\n"}}}}]},{"ruleId":"63fe316a-b4ef-1e00-171b-d42800000000","ruleIndex":4,"message":{"text":"\uD83D\uDFE9 Recommendation

Sensitive, hardcoded data (such as Private IPs/Emails or User/DB details) should not be stored unless secured specifically. An attacker can use that data for further malicious actions.

"},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+common_google_play_services_api_unavailable_text%3A+%251%24s+requires+one+or+more+Google+Play+services+that+are+not+currently+available.+Please+contact+the+developer+for+assistance."},"region":{"startLine":1}}}]},{"ruleId":"5225b063-3a08-f68e-2bad-572100000000","ruleIndex":5,"message":{"text":"\uD83D\uDFE9 Recommendation

Remove the \"android:debuggable=true\" setting from the Android manifest file or set it to “false” to mitigate this threat.

Zimperium's zShield provides debugger detection and tamper resistance to defend against the malicious use of these tools.

"},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+classes.dex"},"region":{"startLine":1}}}]},{"ruleId":"5b86446e-1cfc-cd18-3a11-f25c00000000","ruleIndex":6,"message":{"text":"\uD83D\uDFE9 Recommendation

Review the added code from third-parties to include any advertising libraries for potential malware activity.

"},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+Malware.FakeApp%2F22629"},"region":{"startLine":1}}}]},{"ruleId":"60d0b70e-f1a7-9107-8fbb-669900000000","ruleIndex":7,"message":{"text":"\uD83D\uDFE9 Recommendation

It is recommended to use explicit intents to start activities using the setComponent, setPackage, setClass or setClassName methods of the Intent class. It is also recommended to always use explicit intents to broadcast data within the same application or the LocalBroadcastManager to use a signature-permission protection level.

\uD83D\uDFE7 Locations

com.android.insecurebankv2.ChangePassword : onOptionsItemSelected
com.android.insecurebankv2.DoLogin : postData
com.android.insecurebankv2.DoLogin : onOptionsItemSelected
com.android.insecurebankv2.DoTransfer : onOptionsItemSelected
com.android.insecurebankv2.FilePrefActivity : onOptionsItemSelected
com.android.insecurebankv2.LoginActivity : onOptionsItemSelected
com.android.insecurebankv2.PostLogin : changePasswd
com.android.insecurebankv2.PostLogin : onOptionsItemSelected
com.android.insecurebankv2.PostLogin : viewStatment
com.android.insecurebankv2.ViewStatement : onOptionsItemSelected
com.android.insecurebankv2.WrongLogin : onOptionsItemSelected

\uD83D\uDFE6 Code Snippets (Showing 3 of 11)

\uD83D\uDFE7 com.android.insecurebankv2.ChangePassword : onOptionsItemSelected(Showing 11 lines of 19)

  
  public boolean onOptionsItemSelected(android.view.MenuItem p6)
  {
      boolean v2 = 1;
      int v1 = p6.getItemId();
      if (v1 != 2131558557) {
          if (v1 != 2131558558) {
              v2 = super.onOptionsItemSelected(p6);
          } else {
              android.content.Intent v0_0 = new android.content.Intent(this.getBaseContext(), com.android.insecureban...
              v0_0.addFlags(67108864);

\uD83D\uDFE7 com.android.insecurebankv2.DoLogin : postData(Showing 11 lines of 35)

  
      public void postData(String p13)
      {
          org.apache.http.HttpResponse v6;
          org.apache.http.impl.client.DefaultHttpClient v0_1 = new org.apache.http.impl.client.DefaultHttpClient();
‣‣        org.apache.http.client.methods.HttpPost v1_1 = new org.apache.http.client.methods.HttpPost(new StringBuilde...
          org.apache.http.client.methods.HttpPost v2_1 = new org.apache.http.client.methods.HttpPost(new StringBuilde...
          java.util.ArrayList v4_1 = new java.util.ArrayList(2);
          v4_1.add(new org.apache.http.message.BasicNameValuePair(username, this.this$0.username));
          v4_1.add(new org.apache.http.message.BasicNameValuePair(password, this.this$0.password));
          if (!this.this$0.username.equals(devadmin)) {

\uD83D\uDFE7 com.android.insecurebankv2.PostLogin : changePasswd

  
  protected void changePasswd()
  {
‣‣    android.content.Intent v0_1 = new android.content.Intent(this.getApplicationContext(), com.android.insecurebank...
      v0_1.putExtra(uname, this.uname);
      this.startActivity(v0_1);
      return;
  }
  

"},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.android.insecurebankv2.ChangePassword+%3A+onOptionsItemSelected"},"region":{"startLine":10,"snippet":{"text":"\n public boolean onOptionsItemSelected(android.view.MenuItem p6)\n {\n boolean v2 = 1;\n int v1 = p6.getItemId();\n if (v1 != 2131558557) {\n if (v1 != 2131558558) {\n v2 = super.onOptionsItemSelected(p6);\n } else {\n android.content.Intent v0_0 = new android.content.Intent(this.getBaseContext(), com.android.insecurebankv2.LoginActivity);\n v0_0.addFlags(67108864);\n this.startActivity(v0_0);\n }\n } else {\n this.callPreferences();\n }\n return v2;\n }\n"}}}},{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.android.insecurebankv2.DoLogin+%3A+postData"},"region":{"startLine":28,"snippet":{"text":"\n public void postData(String p13)\n {\n org.apache.http.HttpResponse v6;\n org.apache.http.impl.client.DefaultHttpClient v0_1 = new org.apache.http.impl.client.DefaultHttpClient();\n org.apache.http.client.methods.HttpPost v1_1 = new org.apache.http.client.methods.HttpPost(new StringBuilder().append(this.this$0.protocol).append(this.this$0.serverip).append(:).append(this.this$0.serverport).append(/login).toString());\n org.apache.http.client.methods.HttpPost v2_1 = new org.apache.http.client.methods.HttpPost(new StringBuilder().append(this.this$0.protocol).append(this.this$0.serverip).append(:).append(this.this$0.serverport).append(/devlogin).toString());\n java.util.ArrayList v4_1 = new java.util.ArrayList(2);\n v4_1.add(new org.apache.http.message.BasicNameValuePair(username, this.this$0.username));\n v4_1.add(new org.apache.http.message.BasicNameValuePair(password, this.this$0.password));\n if (!this.this$0.username.equals(devadmin)) {\n v1_1.setEntity(new org.apache.http.client.entity.UrlEncodedFormEntity(v4_1));\n v6 = v0_1.execute(v1_1);\n } else {\n v2_1.setEntity(new org.apache.http.client.entity.UrlEncodedFormEntity(v4_1));\n v6 = v0_1.execute(v2_1);\n }\n this.this$0.result = this.convertStreamToString(v6.getEntity().getContent());\n this.this$0.result = this.this$0.result.replace(\n, );\n if (this.this$0.result != null) {\n if (this.this$0.result.indexOf(Correct Credentials) == -1) {\n this.this$0.startActivity(new android.content.Intent(this.this$0.getApplicationContext(), com.android.insecurebankv2.WrongLogin));\n } else {\n android.util.Log.d(Successful Login:, new StringBuilder().append(, account=).append(this.this$0.username).append(:).append(this.this$0.password).toString());\n this.saveCreds(this.this$0.username, this.this$0.password);\n this.trackUserLogins();\n android.content.Intent v5_1 = new android.content.Intent(this.this$0.getApplicationContext(), com.android.insecurebankv2.PostLogin);\n v5_1.putExtra(uname, this.this$0.username);\n this.this$0.startActivity(v5_1);\n }\n }\n return;\n }\n"}}}},{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.android.insecurebankv2.DoLogin+%3A+onOptionsItemSelected"},"region":{"startLine":10,"snippet":{"text":"\n public boolean onOptionsItemSelected(android.view.MenuItem p6)\n {\n boolean v2 = 1;\n int v1 = p6.getItemId();\n if (v1 != 2131558557) {\n if (v1 != 2131558558) {\n v2 = super.onOptionsItemSelected(p6);\n } else {\n android.content.Intent v0_0 = new android.content.Intent(this.getBaseContext(), com.android.insecurebankv2.LoginActivity);\n v0_0.addFlags(67108864);\n this.startActivity(v0_0);\n }\n } else {\n this.callPreferences();\n }\n return v2;\n }\n"}}}},{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.android.insecurebankv2.DoTransfer+%3A+onOptionsItemSelected"},"region":{"startLine":10,"snippet":{"text":"\n public boolean onOptionsItemSelected(android.view.MenuItem p6)\n {\n boolean v2 = 1;\n int v1 = p6.getItemId();\n if (v1 != 2131558557) {\n if (v1 != 2131558558) {\n v2 = super.onOptionsItemSelected(p6);\n } else {\n android.content.Intent v0_0 = new android.content.Intent(this.getBaseContext(), com.android.insecurebankv2.LoginActivity);\n v0_0.addFlags(67108864);\n this.startActivity(v0_0);\n }\n } else {\n this.callPreferences();\n }\n return v2;\n }\n"}}}},{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.android.insecurebankv2.FilePrefActivity+%3A+onOptionsItemSelected"},"region":{"startLine":10,"snippet":{"text":"\n public boolean onOptionsItemSelected(android.view.MenuItem p6)\n {\n boolean v2 = 1;\n int v1 = p6.getItemId();\n if (v1 != 2131558557) {\n if (v1 != 2131558558) {\n v2 = super.onOptionsItemSelected(p6);\n } else {\n android.content.Intent v0_0 = new android.content.Intent(this.getBaseContext(), com.android.insecurebankv2.LoginActivity);\n v0_0.addFlags(67108864);\n this.startActivity(v0_0);\n }\n } else {\n this.callPreferences();\n }\n return v2;\n }\n"}}}},{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.android.insecurebankv2.LoginActivity+%3A+onOptionsItemSelected"},"region":{"startLine":10,"snippet":{"text":"\n public boolean onOptionsItemSelected(android.view.MenuItem p6)\n {\n boolean v2 = 1;\n int v1 = p6.getItemId();\n if (v1 != 2131558557) {\n if (v1 != 2131558558) {\n v2 = super.onOptionsItemSelected(p6);\n } else {\n android.content.Intent v0_0 = new android.content.Intent(this.getBaseContext(), com.android.insecurebankv2.LoginActivity);\n v0_0.addFlags(67108864);\n this.startActivity(v0_0);\n }\n } else {\n this.callPreferences();\n }\n return v2;\n }\n"}}}},{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.android.insecurebankv2.PostLogin+%3A+changePasswd"},"region":{"startLine":4,"snippet":{"text":"\n protected void changePasswd()\n {\n android.content.Intent v0_1 = new android.content.Intent(this.getApplicationContext(), com.android.insecurebankv2.ChangePassword);\n v0_1.putExtra(uname, this.uname);\n this.startActivity(v0_1);\n return;\n }\n"}}}},{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.android.insecurebankv2.PostLogin+%3A+onOptionsItemSelected"},"region":{"startLine":10,"snippet":{"text":"\n public boolean onOptionsItemSelected(android.view.MenuItem p6)\n {\n boolean v2 = 1;\n int v1 = p6.getItemId();\n if (v1 != 2131558557) {\n if (v1 != 2131558558) {\n v2 = super.onOptionsItemSelected(p6);\n } else {\n android.content.Intent v0_0 = new android.content.Intent(this.getBaseContext(), com.android.insecurebankv2.LoginActivity);\n v0_0.addFlags(67108864);\n this.startActivity(v0_0);\n }\n } else {\n this.callPreferences();\n }\n return v2;\n }\n"}}}},{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.android.insecurebankv2.PostLogin+%3A+viewStatment"},"region":{"startLine":4,"snippet":{"text":"\n protected void viewStatment()\n {\n android.content.Intent v0_1 = new android.content.Intent(this.getApplicationContext(), com.android.insecurebankv2.ViewStatement);\n v0_1.putExtra(uname, this.uname);\n this.startActivity(v0_1);\n return;\n }\n"}}}},{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.android.insecurebankv2.ViewStatement+%3A+onOptionsItemSelected"},"region":{"startLine":10,"snippet":{"text":"\n public boolean onOptionsItemSelected(android.view.MenuItem p6)\n {\n boolean v2 = 1;\n int v1 = p6.getItemId();\n if (v1 != 2131558557) {\n if (v1 != 2131558558) {\n v2 = super.onOptionsItemSelected(p6);\n } else {\n android.content.Intent v0_0 = new android.content.Intent(this.getBaseContext(), com.android.insecurebankv2.LoginActivity);\n v0_0.addFlags(67108864);\n this.startActivity(v0_0);\n }\n } else {\n this.callPreferences();\n }\n return v2;\n }\n"}}}},{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.android.insecurebankv2.WrongLogin+%3A+onOptionsItemSelected"},"region":{"startLine":10,"snippet":{"text":"\n public boolean onOptionsItemSelected(android.view.MenuItem p6)\n {\n boolean v2 = 1;\n int v1 = p6.getItemId();\n if (v1 != 2131558557) {\n if (v1 != 2131558558) {\n v2 = super.onOptionsItemSelected(p6);\n } else {\n android.content.Intent v0_0 = new android.content.Intent(this.getBaseContext(), com.android.insecurebankv2.LoginActivity);\n v0_0.addFlags(67108864);\n this.startActivity(v0_0);\n }\n } else {\n this.callPreferences();\n }\n return v2;\n }\n"}}}}]},{"ruleId":"60e3f8fc-7552-e2f0-e9ca-648e00000000","ruleIndex":8,"message":{"text":"\uD83D\uDFE9 Recommendation

It is recommended to always use explicit intents for the broadcast of data within the same application.

If it is not required to send broadcasts to components outside of the app, then send and receive local broadcasts using the LocalBroadcastManager available in the Support Library. The LocalBroadcastManager is much more efficient because no interprocess communication is needed. Also, this minimizes security issues related to other apps being able to receive or send broadcasts. Local broadcasts can be used as a general-purpose pub/sub event bus in the app without any overheads of system-wide broadcasts.\n\nDo not broadcast sensitive information using an implicit intent. The information can be read by any app that registers to receive the broadcast. There are several ways to control who can receive the broadcasts:\n\nIn Android 4.0 and higher, you can specify a package with setPackage(String) when sending a broadcast. The system restricts the broadcast to the set of apps that match the package.\nAdditionally, send local broadcasts with LocalBroadcastManager.

\uD83D\uDFE6 Code Snippet

\uD83D\uDFE7 com.android.insecurebankv2.ChangePassword : broadcastChangepasswordSMS(Showing 11 lines of 15)

  
  private void broadcastChangepasswordSMS(String p4, String p5)
  {
      if (!android.text.TextUtils.isEmpty(p4.toString().trim())) {
          android.content.Intent v0_1 = new android.content.Intent();
          v0_1.setAction(theBroadcast);
          v0_1.putExtra(phonenumber, p4);
          v0_1.putExtra(newpass, p5);
          this.sendBroadcast(v0_1);
‣‣    } else {
          System.out.println(Phone number Invalid.);

"},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.android.insecurebankv2.ChangePassword+%3A+broadcastChangepasswordSMS"},"region":{"startLine":5,"snippet":{"text":"\n private void broadcastChangepasswordSMS(String p4, String p5)\n {\n if (!android.text.TextUtils.isEmpty(p4.toString().trim())) {\n android.content.Intent v0_1 = new android.content.Intent();\n v0_1.setAction(theBroadcast);\n v0_1.putExtra(phonenumber, p4);\n v0_1.putExtra(newpass, p5);\n this.sendBroadcast(v0_1);\n } else {\n System.out.println(Phone number Invalid.);\n }\n return;\n }\n"}}}}]},{"ruleId":"63c7aacb-f7ca-34f8-62f5-126000000000","ruleIndex":9,"message":{"text":"\uD83D\uDFE9 Recommendation

Hardcoded keys must be avoided. When possible, replace them with ephemeral keys.

\uD83D\uDFE7 Locations

Lcom/android/insecurebankv2/CryptoClass; : aes256decrypt
Lcom/android/insecurebankv2/CryptoClass; : aes256encrypt

\uD83D\uDFE6 Code Snippets

\uD83D\uDFE7 Lcom/android/insecurebankv2/CryptoClass; : aes256decrypt

  
  public static byte[] aes256decrypt(byte[] p4, byte[] p5, byte[] p6)
  {
      javax.crypto.spec.IvParameterSpec v1_1 = new javax.crypto.spec.IvParameterSpec(p4);
‣‣    javax.crypto.spec.SecretKeySpec v2_1 = new javax.crypto.spec.SecretKeySpec(p5, AES);
      javax.crypto.Cipher v0 = javax.crypto.Cipher.getInstance(AES/CBC/PKCS5Padding);
      v0.init(2, v2_1, v1_1);
      return v0.doFinal(p6);
  }
  

\uD83D\uDFE7 Lcom/android/insecurebankv2/CryptoClass; : aes256encrypt

  
  public static byte[] aes256encrypt(byte[] p4, byte[] p5, byte[] p6)
  {
      javax.crypto.spec.IvParameterSpec v1_1 = new javax.crypto.spec.IvParameterSpec(p4);
‣‣    javax.crypto.spec.SecretKeySpec v2_1 = new javax.crypto.spec.SecretKeySpec(p5, AES);
      javax.crypto.Cipher v0 = javax.crypto.Cipher.getInstance(AES/CBC/PKCS5Padding);
      v0.init(1, v2_1, v1_1);
      return v0.doFinal(p6);
  }
  

"},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+Lcom%2Fandroid%2Finsecurebankv2%2FCryptoClass%3B+%3A+aes256decrypt"},"region":{"startLine":2,"snippet":{"text":"\n public static byte[] aes256decrypt(byte[] p4, byte[] p5, byte[] p6)\n {\n javax.crypto.spec.IvParameterSpec v1_1 = new javax.crypto.spec.IvParameterSpec(p4);\n javax.crypto.spec.SecretKeySpec v2_1 = new javax.crypto.spec.SecretKeySpec(p5, AES);\n javax.crypto.Cipher v0 = javax.crypto.Cipher.getInstance(AES/CBC/PKCS5Padding);\n v0.init(2, v2_1, v1_1);\n return v0.doFinal(p6);\n }\n"}}}},{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+Lcom%2Fandroid%2Finsecurebankv2%2FCryptoClass%3B+%3A+aes256encrypt"},"region":{"startLine":2,"snippet":{"text":"\n public static byte[] aes256encrypt(byte[] p4, byte[] p5, byte[] p6)\n {\n javax.crypto.spec.IvParameterSpec v1_1 = new javax.crypto.spec.IvParameterSpec(p4);\n javax.crypto.spec.SecretKeySpec v2_1 = new javax.crypto.spec.SecretKeySpec(p5, AES);\n javax.crypto.Cipher v0 = javax.crypto.Cipher.getInstance(AES/CBC/PKCS5Padding);\n v0.init(1, v2_1, v1_1);\n return v0.doFinal(p6);\n }\n"}}}}]},{"ruleId":"63770e36-eff2-45a3-8feb-909900000000","ruleIndex":10,"message":{"text":"\uD83D\uDFE9 Recommendation

To secure manifest-declared broadcast receivers, you should always declare appropriate permissions during the call to the registerReceiver method.

Broadcast receivers represent a likely exploitable component which is often used to start services, so it is highly recommended to verify that all of the external data is passed to them. To enable the most restrictive (and therefore secure) policy, use the signature permissions to minimize the number of exported intents.

If you do not need to send broadcasts to components outside of your app, then send and receive local broadcasts with the LocalBroadcastManager, which is available in the Support Library. The LocalBroadcastManager is much more efficient (no interprocess communication needed) and allows you to avoid any security issues related to other apps being able to receive or send your broadcasts. Local broadcasts can be used as a general purpose pub/sub event bus in your app without any overheads of system-wide broadcasts.

When you register a receiver, any app can send potentially malicious broadcasts to your app's receiver. Here are some ways to limit the broadcasts that your app receives:

- Specify a permission when registering a broadcast receiver.\r\n- For manifest-declared receivers, set the android:exported attribute to \"false\" in the manifest. The receiver does not receive broadcasts from sources outside of the app.\r\n- Limit yourself to only local broadcasts with LocalBroadcastManager.\r\n- Specify a permission when registering a broadcast receiver.

\uD83D\uDFE6 Code Snippet

\uD83D\uDFE7 com.android.insecurebankv2.MyBroadCastReceiver(Showing 11 lines of 34)

  package com.android.insecurebankv2;
  public class MyBroadCastReceiver extends android.content.BroadcastReceiver {
      public static final String MYPREFS = \"mySharedPreferences\";
      String usernameBase64ByteString;
  
      public MyBroadCastReceiver()
      {
          return;
      }
  
      public void onReceive(android.content.Context p17, android.content.Intent p18)

"},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.android.insecurebankv2.MyBroadCastReceiver"},"region":{"startLine":1,"snippet":{"text":"package com.android.insecurebankv2;\npublic class MyBroadCastReceiver extends android.content.BroadcastReceiver {\n public static final String MYPREFS = \"mySharedPreferences\";\n String usernameBase64ByteString;\n\n public MyBroadCastReceiver()\n {\n return;\n }\n\n public void onReceive(android.content.Context p17, android.content.Intent p18)\n {\n String v12 = p18.getStringExtra(phonenumber);\n String v10 = p18.getStringExtra(newpass);\n if (v12 == null) {\n System.out.println(Phone number is null);\n } else {\n try {\n android.content.SharedPreferences v13 = p17.getSharedPreferences(mySharedPreferences, 1);\n this.usernameBase64ByteString = new String(android.util.Base64.decode(v13.getString(EncryptedUsername, 0), 0), UTF-8);\n String v8 = new com.android.insecurebankv2.CryptoClass().aesDeccryptedString(v13.getString(superSecurePassword, 0));\n String v2 = v12.toString();\n String v4 = new StringBuilder().append(Updated Password from: ).append(v8).append( to: ).append(v10).toString();\n android.telephony.SmsManager v1 = android.telephony.SmsManager.getDefault();\n System.out.println(new StringBuilder().append(For the changepassword - phonenumber: ).append(v2).append( password is: ).append(v4).toString());\n v1.sendTextMessage(v2, 0, v4, 0, 0);\n } catch (Exception v9) {\n v9.printStackTrace();\n }\n }\n return;\n }\n}\n"}}}}]},{"ruleId":"6268f908-f255-393a-3274-df8500000000","ruleIndex":11,"message":{"text":"\uD83D\uDFE9 Recommendation

If the activity does not need to be shared by other applications, explicitly mark components with android:exported=\"false\" in the app manifest.\r\nIf the exported component will only be shared between related apps under your control, use android:protectionLevel=\"signature\" in the XML manifest to restrict access to applications signed by you.

\uD83D\uDFE7 Locations

com.android.insecurebankv2.PostLogin
com.android.insecurebankv2.DoTransfer
com.android.insecurebankv2.ViewStatement
com.android.insecurebankv2.ChangePassword

\uD83D\uDFE6 Code Snippets

\uD83D\uDFE7 com.android.insecurebankv2.PostLogin(Showing 11 lines of 137)

  package com.android.insecurebankv2;
  public class PostLogin extends android.app.Activity {
      android.widget.Button changepasswd_button;
      android.widget.TextView root_status;
      android.widget.Button statement_button;
      android.widget.Button transfer_button;
      String uname;
  
      public PostLogin()
      {
          return;

\uD83D\uDFE7 com.android.insecurebankv2.DoTransfer(Showing 11 lines of 109)

  package com.android.insecurebankv2;
  public class DoTransfer extends android.app.Activity {
      public static final String MYPREFS2 = \"mySharedPreferences\";
      String acc1;
      String acc2;
      android.widget.EditText amount;
      android.widget.Button button1;
      android.widget.EditText from;
      android.widget.Button getAccounts;
      java.io.InputStream in;
      org.json.JSONObject jsonObject;

\uD83D\uDFE7 com.android.insecurebankv2.ViewStatement(Showing 11 lines of 62)

  package com.android.insecurebankv2;
  public class ViewStatement extends android.app.Activity {
      String uname;
  
      public ViewStatement()
      {
          return;
      }
  
      public void callPreferences()
      {

\uD83D\uDFE7 com.android.insecurebankv2.ChangePassword(Showing 11 lines of 114)

  package com.android.insecurebankv2;
  public class ChangePassword extends android.app.Activity {
      private static final String PASSWORD_PATTERN = \"((?=.*\\\\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[@#$%]).{6,20})\";
      android.widget.Button changePassword_button;
      android.widget.EditText changePassword_text;
      private java.util.regex.Matcher matcher;
      private java.util.regex.Pattern pattern;
      String protocol;
      java.io.BufferedReader reader;
      String result;
      android.content.SharedPreferences serverDetails;

"},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.android.insecurebankv2.PostLogin"},"region":{"startLine":1,"snippet":{"text":"package com.android.insecurebankv2;\npublic class PostLogin extends android.app.Activity {\n android.widget.Button changepasswd_button;\n android.widget.TextView root_status;\n android.widget.Button statement_button;\n android.widget.Button transfer_button;\n String uname;\n\n public PostLogin()\n {\n return;\n }\n\n private boolean doesSUexist()\n {\n int v3_0 = 1;\n try {\n String v5_3 = Runtime.getRuntime();\n java.io.InputStream v6_2 = new String[2];\n v6_2[0] = /system/xbin/which;\n v6_2[1] = su;\n Process v1 = v5_3.exec(v6_2);\n } catch (Throwable v2) {\n if (v1 != null) {\n v1.destroy();\n }\n v3_0 = 0;\n return v3_0;\n } catch (int v3_1) {\n if (v1 != null) {\n v1.destroy();\n }\n throw v3_1;\n }\n if (new java.io.BufferedReader(new java.io.InputStreamReader(v1.getInputStream())).readLine() == null) {\n if (v1 != null) {\n v1.destroy();\n }\n v3_0 = 0;\n return v3_0;\n } else {\n if (v1 == null) {\n return v3_0;\n } else {\n v1.destroy();\n return v3_0;\n }\n }\n }\n\n private boolean doesSuperuserApkExist(String p5)\n {\n int v2 = 1;\n if (Boolean.valueOf(new java.io.File(/system/app/Superuser.apk).exists()).booleanValue() != 1) {\n v2 = 0;\n }\n return v2;\n }\n\n public void callPreferences()\n {\n this.startActivity(new android.content.Intent(this, com.android.insecurebankv2.FilePrefActivity));\n return;\n }\n\n protected void changePasswd()\n {\n android.content.Intent v0_1 = new android.content.Intent(this.getApplicationContext(), com.android.insecurebankv2.ChangePassword);\n v0_1.putExtra(uname, this.uname);\n this.startActivity(v0_1);\n return;\n }\n\n protected void onCreate(android.os.Bundle p4)\n {\n super.onCreate(p4);\n this.setContentView(2130968606);\n this.uname = this.getIntent().getStringExtra(uname);\n this.root_status = ((android.widget.TextView) this.findViewById(2131558528));\n this.showRootStatus();\n this.transfer_button = ((android.widget.Button) this.findViewById(2131558525));\n this.transfer_button.setOnClickListener(new com.android.insecurebankv2.PostLogin$1(this));\n this.statement_button = ((android.widget.Button) this.findViewById(2131558526));\n this.statement_button.setOnClickListener(new com.android.insecurebankv2.PostLogin$2(this));\n this.changepasswd_button = ((android.widget.Button) this.findViewById(2131558527));\n this.changepasswd_button.setOnClickListener(new com.android.insecurebankv2.PostLogin$3(this));\n return;\n }\n\n public boolean onCreateOptionsMenu(android.view.Menu p3)\n {\n this.getMenuInflater().inflate(2131623938, p3);\n return 1;\n }\n\n public boolean onOptionsItemSelected(android.view.MenuItem p6)\n {\n boolean v2 = 1;\n int v1 = p6.getItemId();\n if (v1 != 2131558557) {\n if (v1 != 2131558558) {\n v2 = super.onOptionsItemSelected(p6);\n } else {\n android.content.Intent v0_0 = new android.content.Intent(this.getBaseContext(), com.android.insecurebankv2.LoginActivity);\n v0_0.addFlags(67108864);\n this.startActivity(v0_0);\n }\n } else {\n this.callPreferences();\n }\n return v2;\n }\n\n void showRootStatus()\n {\n if ((!this.doesSuperuserApkExist(/system/app/Superuser.apk)) && (!this.doesSUexist())) {\n int v0 = 0;\n } else {\n v0 = 1;\n }\n if (v0 != 1) {\n this.root_status.setText(Device not Rooted!!);\n } else {\n this.root_status.setText(Rooted Device!!);\n }\n return;\n }\n\n protected void viewStatment()\n {\n android.content.Intent v0_1 = new android.content.Intent(this.getApplicationContext(), com.android.insecurebankv2.ViewStatement);\n v0_1.putExtra(uname, this.uname);\n this.startActivity(v0_1);\n return;\n }\n}\n"}}}},{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.android.insecurebankv2.DoTransfer"},"region":{"startLine":1,"snippet":{"text":"package com.android.insecurebankv2;\npublic class DoTransfer extends android.app.Activity {\n public static final String MYPREFS2 = \"mySharedPreferences\";\n String acc1;\n String acc2;\n android.widget.EditText amount;\n android.widget.Button button1;\n android.widget.EditText from;\n android.widget.Button getAccounts;\n java.io.InputStream in;\n org.json.JSONObject jsonObject;\n String number;\n String passNormalized;\n android.widget.EditText phoneNumber;\n String protocol;\n java.io.BufferedReader reader;\n org.apache.http.HttpResponse responseBody;\n String result;\n android.content.SharedPreferences serverDetails;\n String serverip;\n String serverport;\n android.widget.EditText to;\n android.widget.Button transfer;\n String usernameBase64ByteString;\n\n public DoTransfer()\n {\n this.number = 5554;\n this.serverip = ;\n this.serverport = ;\n this.protocol = http://;\n return;\n }\n\n static synthetic String access$000(com.android.insecurebankv2.DoTransfer p1, String p2)\n {\n return p1.getNormalizedPassword(p2);\n }\n\n private String getNormalizedPassword(String p3)\n {\n return new com.android.insecurebankv2.CryptoClass().aesDeccryptedString(p3);\n }\n\n public void callPreferences()\n {\n this.startActivity(new android.content.Intent(this, com.android.insecurebankv2.FilePrefActivity));\n return;\n }\n\n public String convertStreamToString(java.io.InputStream p7)\n {\n try {\n this.reader = new java.io.BufferedReader(new java.io.InputStreamReader(p7, UTF-8));\n } catch (java.io.UnsupportedEncodingException v0) {\n v0.printStackTrace();\n }\n StringBuilder v2_1 = new StringBuilder();\n while(true) {\n String v1 = this.reader.readLine();\n if (v1 == null) {\n break;\n }\n v2_1.append(new StringBuilder().append(v1).append(\n).toString());\n }\n p7.close();\n return v2_1.toString();\n }\n\n protected void onCreate(android.os.Bundle p4)\n {\n super.onCreate(p4);\n this.setContentView(2130968603);\n this.serverDetails = android.preference.PreferenceManager.getDefaultSharedPreferences(this);\n this.serverip = this.serverDetails.getString(serverip, 0);\n this.serverport = this.serverDetails.getString(serverport, 0);\n this.transfer = ((android.widget.Button) this.findViewById(2131558513));\n this.transfer.setOnClickListener(new com.android.insecurebankv2.DoTransfer$1(this));\n this.button1 = ((android.widget.Button) this.findViewById(2131558510));\n this.button1.setOnClickListener(new com.android.insecurebankv2.DoTransfer$2(this));\n return;\n }\n\n public boolean onCreateOptionsMenu(android.view.Menu p3)\n {\n this.getMenuInflater().inflate(2131623938, p3);\n return 1;\n }\n\n public boolean onOptionsItemSelected(android.view.MenuItem p6)\n {\n boolean v2 = 1;\n int v1 = p6.getItemId();\n if (v1 != 2131558557) {\n if (v1 != 2131558558) {\n v2 = super.onOptionsItemSelected(p6);\n } else {\n android.content.Intent v0_0 = new android.content.Intent(this.getBaseContext(), com.android.insecurebankv2.LoginActivity);\n v0_0.addFlags(67108864);\n this.startActivity(v0_0);\n }\n } else {\n this.callPreferences();\n }\n return v2;\n }\n}\n"}}}},{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.android.insecurebankv2.ViewStatement"},"region":{"startLine":1,"snippet":{"text":"package com.android.insecurebankv2;\npublic class ViewStatement extends android.app.Activity {\n String uname;\n\n public ViewStatement()\n {\n return;\n }\n\n public void callPreferences()\n {\n this.startActivity(new android.content.Intent(this, com.android.insecurebankv2.FilePrefActivity));\n return;\n }\n\n protected void onCreate(android.os.Bundle p10)\n {\n super.onCreate(p10);\n this.setContentView(2130968607);\n this.uname = this.getIntent().getStringExtra(uname);\n java.io.File v2_1 = new java.io.File(android.os.Environment.getExternalStorageDirectory(), new StringBuilder().append(Statements_).append(this.uname).append(.html).toString());\n System.out.println(v2_1.toString());\n if (!v2_1.exists()) {\n this.startActivity(new android.content.Intent(this, com.android.insecurebankv2.PostLogin));\n android.widget.Toast.makeText(this, Statement does not Exist!!, 1).show();\n } else {\n android.webkit.WebView v5_1 = ((android.webkit.WebView) this.findViewById(2131558530));\n v5_1.loadUrl(new StringBuilder().append(file://).append(android.os.Environment.getExternalStorageDirectory()).append(/Statements_).append(this.uname).append(.html).toString());\n v5_1.getSettings().setJavaScriptEnabled(1);\n v5_1.getSettings().setSaveFormData(1);\n v5_1.getSettings().setBuiltInZoomControls(1);\n v5_1.setWebViewClient(new com.android.insecurebankv2.MyWebViewClient());\n v5_1.setWebChromeClient(new android.webkit.WebChromeClient());\n }\n return;\n }\n\n public boolean onCreateOptionsMenu(android.view.Menu p3)\n {\n this.getMenuInflater().inflate(2131623938, p3);\n return 1;\n }\n\n public boolean onOptionsItemSelected(android.view.MenuItem p6)\n {\n boolean v2 = 1;\n int v1 = p6.getItemId();\n if (v1 != 2131558557) {\n if (v1 != 2131558558) {\n v2 = super.onOptionsItemSelected(p6);\n } else {\n android.content.Intent v0_0 = new android.content.Intent(this.getBaseContext(), com.android.insecurebankv2.LoginActivity);\n v0_0.addFlags(67108864);\n this.startActivity(v0_0);\n }\n } else {\n this.callPreferences();\n }\n return v2;\n }\n}\n"}}}},{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.android.insecurebankv2.ChangePassword"},"region":{"startLine":1,"snippet":{"text":"package com.android.insecurebankv2;\npublic class ChangePassword extends android.app.Activity {\n private static final String PASSWORD_PATTERN = \"((?=.*\\\\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[@#$%]).{6,20})\";\n android.widget.Button changePassword_button;\n android.widget.EditText changePassword_text;\n private java.util.regex.Matcher matcher;\n private java.util.regex.Pattern pattern;\n String protocol;\n java.io.BufferedReader reader;\n String result;\n android.content.SharedPreferences serverDetails;\n String serverip;\n String serverport;\n android.widget.TextView textView_Username;\n String uname;\n\n public ChangePassword()\n {\n this.serverip = ;\n this.serverport = ;\n this.protocol = http://;\n return;\n }\n\n static synthetic java.util.regex.Pattern access$000(com.android.insecurebankv2.ChangePassword p1)\n {\n return p1.pattern;\n }\n\n static synthetic java.util.regex.Pattern access$002(com.android.insecurebankv2.ChangePassword p0, java.util.regex.Pattern p1)\n {\n p0.pattern = p1;\n return p1;\n }\n\n static synthetic java.util.regex.Matcher access$100(com.android.insecurebankv2.ChangePassword p1)\n {\n return p1.matcher;\n }\n\n static synthetic java.util.regex.Matcher access$102(com.android.insecurebankv2.ChangePassword p0, java.util.regex.Matcher p1)\n {\n p0.matcher = p1;\n return p1;\n }\n\n static synthetic void access$200(com.android.insecurebankv2.ChangePassword p0, String p1, String p2)\n {\n p0.broadcastChangepasswordSMS(p1, p2);\n return;\n }\n\n private void broadcastChangepasswordSMS(String p4, String p5)\n {\n if (!android.text.TextUtils.isEmpty(p4.toString().trim())) {\n android.content.Intent v0_1 = new android.content.Intent();\n v0_1.setAction(theBroadcast);\n v0_1.putExtra(phonenumber, p4);\n v0_1.putExtra(newpass, p5);\n this.sendBroadcast(v0_1);\n } else {\n System.out.println(Phone number Invalid.);\n }\n return;\n }\n\n public void callPreferences()\n {\n this.startActivity(new android.content.Intent(this, com.android.insecurebankv2.FilePrefActivity));\n return;\n }\n\n protected void onCreate(android.os.Bundle p5)\n {\n super.onCreate(p5);\n this.setContentView(2130968601);\n this.serverDetails = android.preference.PreferenceManager.getDefaultSharedPreferences(this);\n this.serverip = this.serverDetails.getString(serverip, 0);\n this.serverport = this.serverDetails.getString(serverport, 0);\n this.changePassword_text = ((android.widget.EditText) this.findViewById(2131558503));\n this.uname = this.getIntent().getStringExtra(uname);\n System.out.println(new StringBuilder().append(newpassword=).append(this.uname).toString());\n this.textView_Username = ((android.widget.TextView) this.findViewById(2131558502));\n this.textView_Username.setText(this.uname);\n this.changePassword_button = ((android.widget.Button) this.findViewById(2131558504));\n this.changePassword_button.setOnClickListener(new com.android.insecurebankv2.ChangePassword$1(this));\n return;\n }\n\n public boolean onCreateOptionsMenu(android.view.Menu p3)\n {\n this.getMenuInflater().inflate(2131623938, p3);\n return 1;\n }\n\n public boolean onOptionsItemSelected(android.view.MenuItem p6)\n {\n boolean v2 = 1;\n int v1 = p6.getItemId();\n if (v1 != 2131558557) {\n if (v1 != 2131558558) {\n v2 = super.onOptionsItemSelected(p6);\n } else {\n android.content.Intent v0_0 = new android.content.Intent(this.getBaseContext(), com.android.insecurebankv2.LoginActivity);\n v0_0.addFlags(67108864);\n this.startActivity(v0_0);\n }\n } else {\n this.callPreferences();\n }\n return v2;\n }\n}\n"}}}}]},{"ruleId":"63d8db78-1e43-ad8f-1243-c49400000000","ruleIndex":12,"message":{"text":"\uD83D\uDFE9 Recommendation

To remove this vulnerability, change the HttpHost scheme from \"DEFAULT_SCHEME_NAME\" which is equivalent to \"http\" or set the explicit \"http\" string to \"https\".

\uD83D\uDFE7 Locations

com.android.insecurebankv2.ChangePassword : postData
com.android.insecurebankv2.DoLogin : postData
com.android.insecurebankv2.DoTransfer : doInBackground

\uD83D\uDFE6 Code Snippets (Showing 2 of 3)

\uD83D\uDFE7 com.android.insecurebankv2.ChangePassword : postData(Showing 11 lines of 22)

  
      public void postData(String p11)
      {
          org.apache.http.impl.client.DefaultHttpClient v0_1 = new org.apache.http.impl.client.DefaultHttpClient();
          org.apache.http.client.methods.HttpPost v1_1 = new org.apache.http.client.methods.HttpPost(new StringBuilde...
          java.util.ArrayList v4_1 = new java.util.ArrayList(2);
          v4_1.add(new org.apache.http.message.BasicNameValuePair(username, this.this$0.uname));
          v4_1.add(new org.apache.http.message.BasicNameValuePair(newpassword, this.this$0.changePassword_text.getTex...
          v1_1.setEntity(new org.apache.http.client.entity.UrlEncodedFormEntity(v4_1));
          com.android.insecurebankv2.ChangePassword.access$002(this.this$0, java.util.regex.Pattern.compile(((?=.*\\d)...
          com.android.insecurebankv2.ChangePassword.access$102(this.this$0, com.android.insecurebankv2.ChangePassword...

\uD83D\uDFE7 com.android.insecurebankv2.DoLogin : postData(Showing 11 lines of 35)

  
      public void postData(String p13)
      {
          org.apache.http.HttpResponse v6;
          org.apache.http.impl.client.DefaultHttpClient v0_1 = new org.apache.http.impl.client.DefaultHttpClient();
          org.apache.http.client.methods.HttpPost v1_1 = new org.apache.http.client.methods.HttpPost(new StringBuilde...
          org.apache.http.client.methods.HttpPost v2_1 = new org.apache.http.client.methods.HttpPost(new StringBuilde...
          java.util.ArrayList v4_1 = new java.util.ArrayList(2);
          v4_1.add(new org.apache.http.message.BasicNameValuePair(username, this.this$0.username));
          v4_1.add(new org.apache.http.message.BasicNameValuePair(password, this.this$0.password));
          if (!this.this$0.username.equals(devadmin)) {

"},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.android.insecurebankv2.ChangePassword+%3A+postData"},"region":{"startLine":5,"snippet":{"text":"\n public void postData(String p11)\n {\n org.apache.http.impl.client.DefaultHttpClient v0_1 = new org.apache.http.impl.client.DefaultHttpClient();\n org.apache.http.client.methods.HttpPost v1_1 = new org.apache.http.client.methods.HttpPost(new StringBuilder().append(this.this$0.protocol).append(this.this$0.serverip).append(:).append(this.this$0.serverport).append(/changepassword).toString());\n java.util.ArrayList v4_1 = new java.util.ArrayList(2);\n v4_1.add(new org.apache.http.message.BasicNameValuePair(username, this.this$0.uname));\n v4_1.add(new org.apache.http.message.BasicNameValuePair(newpassword, this.this$0.changePassword_text.getText().toString()));\n v1_1.setEntity(new org.apache.http.client.entity.UrlEncodedFormEntity(v4_1));\n com.android.insecurebankv2.ChangePassword.access$002(this.this$0, java.util.regex.Pattern.compile(((?=.*\\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[@#$%]).{6,20})));\n com.android.insecurebankv2.ChangePassword.access$102(this.this$0, com.android.insecurebankv2.ChangePassword.access$000(this.this$0).matcher(this.this$0.changePassword_text.getText().toString()));\n if (!com.android.insecurebankv2.ChangePassword.access$100(this.this$0).matches()) {\n this.this$0.runOnUiThread(new com.android.insecurebankv2.ChangePassword$RequestChangePasswordTask$2(this));\n } else {\n this.this$0.result = this.convertStreamToString(v0_1.execute(v1_1).getEntity().getContent());\n this.this$0.result = this.this$0.result.replace(\n, );\n this.this$0.runOnUiThread(new com.android.insecurebankv2.ChangePassword$RequestChangePasswordTask$1(this));\n }\n return;\n }\n"}}}},{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.android.insecurebankv2.DoLogin+%3A+postData"},"region":{"startLine":7,"snippet":{"text":"\n public void postData(String p13)\n {\n org.apache.http.HttpResponse v6;\n org.apache.http.impl.client.DefaultHttpClient v0_1 = new org.apache.http.impl.client.DefaultHttpClient();\n org.apache.http.client.methods.HttpPost v1_1 = new org.apache.http.client.methods.HttpPost(new StringBuilder().append(this.this$0.protocol).append(this.this$0.serverip).append(:).append(this.this$0.serverport).append(/login).toString());\n org.apache.http.client.methods.HttpPost v2_1 = new org.apache.http.client.methods.HttpPost(new StringBuilder().append(this.this$0.protocol).append(this.this$0.serverip).append(:).append(this.this$0.serverport).append(/devlogin).toString());\n java.util.ArrayList v4_1 = new java.util.ArrayList(2);\n v4_1.add(new org.apache.http.message.BasicNameValuePair(username, this.this$0.username));\n v4_1.add(new org.apache.http.message.BasicNameValuePair(password, this.this$0.password));\n if (!this.this$0.username.equals(devadmin)) {\n v1_1.setEntity(new org.apache.http.client.entity.UrlEncodedFormEntity(v4_1));\n v6 = v0_1.execute(v1_1);\n } else {\n v2_1.setEntity(new org.apache.http.client.entity.UrlEncodedFormEntity(v4_1));\n v6 = v0_1.execute(v2_1);\n }\n this.this$0.result = this.convertStreamToString(v6.getEntity().getContent());\n this.this$0.result = this.this$0.result.replace(\n, );\n if (this.this$0.result != null) {\n if (this.this$0.result.indexOf(Correct Credentials) == -1) {\n this.this$0.startActivity(new android.content.Intent(this.this$0.getApplicationContext(), com.android.insecurebankv2.WrongLogin));\n } else {\n android.util.Log.d(Successful Login:, new StringBuilder().append(, account=).append(this.this$0.username).append(:).append(this.this$0.password).toString());\n this.saveCreds(this.this$0.username, this.this$0.password);\n this.trackUserLogins();\n android.content.Intent v5_1 = new android.content.Intent(this.this$0.getApplicationContext(), com.android.insecurebankv2.PostLogin);\n v5_1.putExtra(uname, this.this$0.username);\n this.this$0.startActivity(v5_1);\n }\n }\n return;\n }\n"}}}},{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.android.insecurebankv2.DoTransfer+%3A+doInBackground"},"region":{"startLine":5,"snippet":{"text":"\n protected varargs String doInBackground(String[] p15)\n {\n org.apache.http.impl.client.DefaultHttpClient v2_1 = new org.apache.http.impl.client.DefaultHttpClient();\n org.apache.http.client.methods.HttpPost v3_0 = new org.apache.http.client.methods.HttpPost(new StringBuilder().append(this.this$0.protocol).append(this.this$0.serverip).append(:).append(this.this$0.serverport).append(/dotransfer).toString());\n android.content.SharedPreferences v6 = this.this$0.getSharedPreferences(mySharedPreferences, 0);\n try {\n this.this$0.usernameBase64ByteString = new String(android.util.Base64.decode(v6.getString(EncryptedUsername, 0), 0), UTF-8);\n try {\n this.this$0.passNormalized = com.android.insecurebankv2.DoTransfer.access$000(this.this$0, v6.getString(superSecurePassword, 0));\n } catch (java.io.IOException v1_2) {\n v1_2.printStackTrace();\n } catch (java.io.IOException v1_2) {\n } catch (java.io.IOException v1_2) {\n } catch (java.io.IOException v1_2) {\n } catch (java.io.IOException v1_2) {\n } catch (java.io.IOException v1_2) {\n } catch (java.io.IOException v1_2) {\n }\n java.util.ArrayList v4_1 = new java.util.ArrayList(5);\n v4_1.add(new org.apache.http.message.BasicNameValuePair(username, this.this$0.usernameBase64ByteString));\n v4_1.add(new org.apache.http.message.BasicNameValuePair(password, this.this$0.passNormalized));\n this.this$0.from = ((android.widget.EditText) this.this$0.findViewById(2131558507));\n this.this$0.to = ((android.widget.EditText) this.this$0.findViewById(2131558509));\n this.this$0.amount = ((android.widget.EditText) this.this$0.findViewById(2131558512));\n v4_1.add(new org.apache.http.message.BasicNameValuePair(from_acc, this.this$0.from.getText().toString()));\n v4_1.add(new org.apache.http.message.BasicNameValuePair(to_acc, this.this$0.to.getText().toString()));\n v4_1.add(new org.apache.http.message.BasicNameValuePair(amount, this.this$0.amount.getText().toString()));\n try {\n v3_0.setEntity(new org.apache.http.client.entity.UrlEncodedFormEntity(v4_1));\n try {\n this.this$0.responseBody = v2_1.execute(v3_0);\n try {\n this.this$0.in = this.this$0.responseBody.getEntity().getContent();\n try {\n this.this$0.result = this.this$0.convertStreamToString(this.this$0.in);\n } catch (java.io.IOException v0_1) {\n v0_1.printStackTrace();\n }\n this.this$0.result = this.this$0.result.replace(\n, );\n this.this$0.runOnUiThread(new com.android.insecurebankv2.DoTransfer$RequestDoTransferTask$1(this));\n return dinesh;\n } catch (java.io.IOException v1_1) {\n v1_1.printStackTrace();\n } catch (java.io.IOException v1_1) {\n }\n } catch (java.io.IOException v1_0) {\n v1_0.printStackTrace();\n }\n } catch (java.io.IOException v0_0) {\n v0_0.printStackTrace();\n }\n } catch (java.io.IOException v0_2) {\n v0_2.printStackTrace();\n }\n }\n"}}}}]},{"ruleId":"642b9a5f-f96b-cd69-3591-2a7400000000","ruleIndex":13,"message":{"text":"\uD83D\uDFE9 Recommendation

Do not let any backdoor exist on the server side. In particular, do not leave any code that reveals the backdoor location.

\uD83D\uDFE6 Code Snippet

\uD83D\uDFE7 com.android.insecurebankv2.DoLogin : postData(Showing 11 lines of 35)

  
      public void postData(String p13)
      {
          org.apache.http.HttpResponse v6;
          org.apache.http.impl.client.DefaultHttpClient v0_1 = new org.apache.http.impl.client.DefaultHttpClient();
          org.apache.http.client.methods.HttpPost v1_1 = new org.apache.http.client.methods.HttpPost(new StringBuilde...
          org.apache.http.client.methods.HttpPost v2_1 = new org.apache.http.client.methods.HttpPost(new StringBuilde...
          java.util.ArrayList v4_1 = new java.util.ArrayList(2);
          v4_1.add(new org.apache.http.message.BasicNameValuePair(username, this.this$0.username));
          v4_1.add(new org.apache.http.message.BasicNameValuePair(password, this.this$0.password));
          if (!this.this$0.username.equals(devadmin)) {

"},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.android.insecurebankv2.DoLogin+%3A+postData"},"region":{"startLine":7,"snippet":{"text":"\n public void postData(String p13)\n {\n org.apache.http.HttpResponse v6;\n org.apache.http.impl.client.DefaultHttpClient v0_1 = new org.apache.http.impl.client.DefaultHttpClient();\n org.apache.http.client.methods.HttpPost v1_1 = new org.apache.http.client.methods.HttpPost(new StringBuilder().append(this.this$0.protocol).append(this.this$0.serverip).append(:).append(this.this$0.serverport).append(/login).toString());\n org.apache.http.client.methods.HttpPost v2_1 = new org.apache.http.client.methods.HttpPost(new StringBuilder().append(this.this$0.protocol).append(this.this$0.serverip).append(:).append(this.this$0.serverport).append(/devlogin).toString());\n java.util.ArrayList v4_1 = new java.util.ArrayList(2);\n v4_1.add(new org.apache.http.message.BasicNameValuePair(username, this.this$0.username));\n v4_1.add(new org.apache.http.message.BasicNameValuePair(password, this.this$0.password));\n if (!this.this$0.username.equals(devadmin)) {\n v1_1.setEntity(new org.apache.http.client.entity.UrlEncodedFormEntity(v4_1));\n v6 = v0_1.execute(v1_1);\n } else {\n v2_1.setEntity(new org.apache.http.client.entity.UrlEncodedFormEntity(v4_1));\n v6 = v0_1.execute(v2_1);\n }\n this.this$0.result = this.convertStreamToString(v6.getEntity().getContent());\n this.this$0.result = this.this$0.result.replace(\n, );\n if (this.this$0.result != null) {\n if (this.this$0.result.indexOf(Correct Credentials) == -1) {\n this.this$0.startActivity(new android.content.Intent(this.this$0.getApplicationContext(), com.android.insecurebankv2.WrongLogin));\n } else {\n android.util.Log.d(Successful Login:, new StringBuilder().append(, account=).append(this.this$0.username).append(:).append(this.this$0.password).toString());\n this.saveCreds(this.this$0.username, this.this$0.password);\n this.trackUserLogins();\n android.content.Intent v5_1 = new android.content.Intent(this.this$0.getApplicationContext(), com.android.insecurebankv2.PostLogin);\n v5_1.putExtra(uname, this.this$0.username);\n this.this$0.startActivity(v5_1);\n }\n }\n return;\n }\n"}}}}]},{"ruleId":"642b9a56-f96b-cd69-3591-2a7300000000","ruleIndex":14,"message":{"text":"\uD83D\uDFE9 Recommendation

Avoid saving valuable predicates in accessible places such as the /res/values folder or in shared preferences.

\uD83D\uDFE6 Code Snippet

\uD83D\uDFE7 com.android.insecurebankv2.LoginActivity : onCreate(Showing 11 lines of 17)

  
  protected void onCreate(android.os.Bundle p6)
  {
      super.onCreate(p6);
      this.setContentView(2130968605);
      if (this.getResources().getString(2131165258).equals(no)) {
          this.findViewById(2131558510).setVisibility(8);
      }
      this.login_buttons = ((android.widget.Button) this.findViewById(2131558522));
      this.login_buttons.setOnClickListener(new com.android.insecurebankv2.LoginActivity$1(this));
      this.createuser_buttons = ((android.widget.Button) this.findViewById(2131558510));

"},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.android.insecurebankv2.LoginActivity+%3A+onCreate"},"region":{"startLine":6,"snippet":{"text":"\n protected void onCreate(android.os.Bundle p6)\n {\n super.onCreate(p6);\n this.setContentView(2130968605);\n if (this.getResources().getString(2131165258).equals(no)) {\n this.findViewById(2131558510).setVisibility(8);\n }\n this.login_buttons = ((android.widget.Button) this.findViewById(2131558522));\n this.login_buttons.setOnClickListener(new com.android.insecurebankv2.LoginActivity$1(this));\n this.createuser_buttons = ((android.widget.Button) this.findViewById(2131558510));\n this.createuser_buttons.setOnClickListener(new com.android.insecurebankv2.LoginActivity$2(this));\n this.fillData_button = ((android.widget.Button) this.findViewById(2131558523));\n this.fillData_button.setOnClickListener(new com.android.insecurebankv2.LoginActivity$3(this));\n return;\n }\n"}}}}]},{"ruleId":"54631b68-d8c9-7547-8e76-8efd00000000","ruleIndex":15,"message":{"text":"\uD83D\uDFE9 Recommendation

Zimperium's zShield provides code obfuscation to defend against static analysis of your application. Obfuscation makes reverse engineering more difficult by adding complexity and transforming the appearance of your application without changing the behavior.

"},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+classes.dex"},"region":{"startLine":1}}}]},{"ruleId":"67e11d2b-85c8-6900-ca1e-527b00000000","ruleIndex":16,"message":{"text":"\uD83D\uDFE9 Recommendation

Update the minSdkVersion of the app to a version within the N-2 range. This requires evaluating dependencies and APIs to ensure compatibility with the latest Android features and addressing any deprecated methods. Regular updates to align with new Android releases will not only restore compliance but also enhance user experience, security, and device compatibility.

"},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+classes.dex"},"region":{"startLine":1}}}]},{"ruleId":"667eb2a2-8452-657c-816c-289a00000000","ruleIndex":17,"message":{"text":"\uD83D\uDFE9 Recommendation

To ensure compliance with Google Play Store policies regarding location permissions, clearly justify each permission request and limit access to location data strictly necessary to enhance user experience. Ensure adherence to the requirements specified in the Location Permissions section of Google's Developer Content Policy. Always refer to Google's updated policies for ongoing compliance.

"},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+classes.dex"},"region":{"startLine":1}}}]},{"ruleId":"667eb276-8452-657c-816c-289900000000","ruleIndex":18,"message":{"text":"\uD83D\uDFE9 Recommendation

To comply with Google Play Store policies, it is critical to correctly register the application as the default handler for calls or SMS on the device. Provide a clear and detailed justification for the use of these permissions in the application description on the Play Store, outlining the functionalities that require these permissions and committing to use them solely for their declared purposes. Ensure to include information on how user privacy is protected and how misuse of these permissions is prevented.

"},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+classes.dex"},"region":{"startLine":1}}}]},{"ruleId":"63d8fb8f-b544-7900-1078-5c8400000000","ruleIndex":19,"message":{"text":"\uD83D\uDFE9 Recommendation

Clear the clipboard regularly, use a secure password manager, and avoid copying sensitive information to the clipboard. Additionally, install security updates and avoid downloading and installing untrusted apps to help prevent potential exploitation of the vulnerability. Android 12 notifies users when apps access the clipboard, and with Android 13, the clipboard is empty after a certain period of time.

\uD83D\uDFE7 Locations

com.android.insecurebankv2.LoginActivity
com.android.insecurebankv2.DoTransfer

\uD83D\uDFE6 Code Snippets

\uD83D\uDFE7 com.android.insecurebankv2.LoginActivity

  
  
  public class LoginActivity extends Activity {
              public static final String MYPREFS = \"mySharedPreferences\";
              EditText Password_Text;
‣‣            EditText Username_Text;
              Button createuser_buttons;
              Button fillData_button;
              Button login_buttons;
              String usernameBase64ByteString;
  

\uD83D\uDFE7 com.android.insecurebankv2.DoTransfer

  EditText from;
  Button getAccounts;
  InputStream in;
  JSONObject jsonObject;
  String passNormalized;
‣‣EditText phoneNumber;
  BufferedReader reader;
  HttpResponse responseBody;
  String result;
  SharedPreferences serverDetails;
  EditText to;

"},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.android.insecurebankv2.LoginActivity"},"region":{"startLine":27,"snippet":{"text":"\n\n public class LoginActivity extends Activity {\n public static final String MYPREFS = \"mySharedPreferences\";\n EditText Password_Text;\n EditText Username_Text;\n Button createuser_buttons;\n Button fillData_button;\n Button login_buttons;\n String usernameBase64ByteString;\n"}}}},{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.android.insecurebankv2.DoTransfer"},"region":{"startLine":54,"snippet":{"text":" EditText from;\n Button getAccounts;\n InputStream in;\n JSONObject jsonObject;\n String passNormalized;\n EditText phoneNumber;\n BufferedReader reader;\n HttpResponse responseBody;\n String result;\n SharedPreferences serverDetails;\n EditText to;"}}}}]},{"ruleId":"63ca495c-b0a8-7a00-262f-409800000000","ruleIndex":20,"message":{"text":"\uD83D\uDFE9 Recommendation

The app uses hardcoded symmetric cryptography as the only method of encryption. It is recommended not to use hardcoded keys on the code; furthermore, the use of additional encryption methods is recommended.

Use Zimperium's zKeyBox product to ensure that the implementation of cryptographic algorithms and keys are secure in zero-trust execution environments. zKeyBox is based on white-box cryptography that is designed to protect cryptographic keys, making it extremely difficult for attackers to locate, modify, and extract them. Visit https://www.zimperium.com/zkeybox/ for more information.

\uD83D\uDFE7 Locations

Lcom/android/insecurebankv2/CryptoClass; : aes256decrypt
Lcom/android/insecurebankv2/CryptoClass; : aes256encrypt

"},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+Lcom%2Fandroid%2Finsecurebankv2%2FCryptoClass%3B+%3A+aes256decrypt"},"region":{"startLine":1}}},{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+Lcom%2Fandroid%2Finsecurebankv2%2FCryptoClass%3B+%3A+aes256encrypt"},"region":{"startLine":1}}}]},{"ruleId":"63cff208-1499-a500-0f37-337700000000","ruleIndex":21,"message":{"text":"\uD83D\uDFE9 Recommendation

Use zShield to prevent reverse engineering attempts, as it also provides additional APK signature verification to ensure that your app signing has not been compromised by a malicious exploit and code obfuscation to defend against static analysis of your app.

"},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+classes.dex"},"region":{"startLine":1}}}]},{"ruleId":"63bd9c4e-d6d6-9f00-0f5b-8df700000000","ruleIndex":22,"message":{"text":"\uD83D\uDFE9 Recommendation

Review and accept the minimum set of permissions periodically as the API levels change.

"},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+classes.dex"},"region":{"startLine":1}}}]},{"ruleId":"5b62edc8-bb6e-303a-8140-a37300000000","ruleIndex":23,"message":{"text":"\uD83D\uDFE9 Recommendation

Any app that uses external storage should encrypt any sensitive data that it writes to external storage and perform input validation on any data that is read from external storage.

\uD83D\uDFE7 Locations

com.android.insecurebankv2.DoTransfer
com.android.insecurebankv2.ViewStatement

\uD83D\uDFE6 Code Snippets

\uD83D\uDFE7 com.android.insecurebankv2.DoTransfer

  DoTransfer.this.acc1 = DoTransfer.this.jsonObject.getString(\"from\");
  DoTransfer.this.acc2 = DoTransfer.this.jsonObject.getString(\"to\");
  System.out.println(\"Message:\" + DoTransfer.this.jsonObject.getString(\"message\") + \" From:\" + DoTransfer.this.from.g...
  String status = new String(\"\\nMessage:Success From:\" + DoTransfer.this.from.getText().toString() + \" To:\" + DoTrans...
           try {
‣‣    String MYFILE = Environment.getExternalStorageDirectory() + \"/Statements_\" + DoTransfer.this.usernameBase64Byte...
      BufferedWriter out2 = new BufferedWriter(new FileWriter(MYFILE, true));
      out2.write(status);
      out2.write(\"
\");
      out2.close();
      return;

\uD83D\uDFE7 com.android.insecurebankv2.ViewStatement

  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_view_statement);
  Intent intent = getIntent();
  this.uname = intent.getStringExtra(\"uname\");
  String FILENAME = \"Statements_\" + this.uname + \".html\";
‣‣File fileToCheck = new File(Environment.getExternalStorageDirectory(), FILENAME);
  System.out.println(fileToCheck.toString());
  if (fileToCheck.exists()) {
      WebView mWebView = (WebView) findViewById(R.id.webView1);
      mWebView.loadUrl(\"file://\" + Environment.getExternalStorageDirectory() + \"/Statements_\" + this.uname + \".html\");
      mWebView.getSettings().setJavaScriptEnabled(true);

"},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.android.insecurebankv2.DoTransfer"},"region":{"startLine":162,"snippet":{"text":" DoTransfer.this.acc1 = DoTransfer.this.jsonObject.getString(\"from\");\n DoTransfer.this.acc2 = DoTransfer.this.jsonObject.getString(\"to\");\n System.out.println(\"Message:\" + DoTransfer.this.jsonObject.getString(\"message\") + \" From:\" + DoTransfer.this.from.getText().toString() + \" To:\" + DoTransfer.this.to.getText().toString() + \" Amount:\" + DoTransfer.this.amount.getText().toString());\n String status = new String(\"\\nMessage:Success From:\" + DoTransfer.this.from.getText().toString() + \" To:\" + DoTransfer.this.to.getText().toString() + \" Amount:\" + DoTransfer.this.amount.getText().toString() + \"\\n\");\n try {\n String MYFILE = Environment.getExternalStorageDirectory() + \"/Statements_\" + DoTransfer.this.usernameBase64ByteString + \".html\";\n BufferedWriter out2 = new BufferedWriter(new FileWriter(MYFILE, true));\n out2.write(status);\n out2.write(\"
\");\n out2.close();\n return;"}}}},{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.android.insecurebankv2.ViewStatement"},"region":{"startLine":25,"snippet":{"text":" super.onCreate(savedInstanceState);\n setContentView(R.layout.activity_view_statement);\n Intent intent = getIntent();\n this.uname = intent.getStringExtra(\"uname\");\n String FILENAME = \"Statements_\" + this.uname + \".html\";\n File fileToCheck = new File(Environment.getExternalStorageDirectory(), FILENAME);\n System.out.println(fileToCheck.toString());\n if (fileToCheck.exists()) {\n WebView mWebView = (WebView) findViewById(R.id.webView1);\n mWebView.loadUrl(\"file://\" + Environment.getExternalStorageDirectory() + \"/Statements_\" + this.uname + \".html\");\n mWebView.getSettings().setJavaScriptEnabled(true);"}}}}]},{"ruleId":"5d4203bc-7d33-27a1-308b-456700000000","ruleIndex":24,"message":{"text":"\uD83D\uDFE9 Recommendation

If the data being stored is classified as sensitive, private, proprietary, or confidential, then SharedPreferences is not a recommended solution. When possible, store authentication data in the AccountManager or consider a third-party solution to encrypt the data before being inserted into SharedPrefences.

\uD83D\uDFE7 Locations

com.android.insecurebankv2.FilePrefActivity
com.android.insecurebankv2.DoLogin$RequestTask
com.android.insecurebankv2.DoLogin

\uD83D\uDFE6 Code Snippets

\uD83D\uDFE7 com.android.insecurebankv2.FilePrefActivity

  Matcher m = p.matcher(serveripSaved);
  if (serveripSaved != null && m.matches()) {
      Pattern p2 = Pattern.compile(\"(6553[0-5]|655[0-2]\\\\d|65[0-4]\\\\d{2}|6[0-4]\\\\d{3}|[1-5]\\\\d{4}|[1-9]\\\\d{0,3})\");
      Matcher m2 = p2.matcher(serverportSaved);
      if (serverportSaved != null && m2.matches()) {
‣‣        this.editor.putString(\"serverip\", serveripSaved);
          this.editor.putString(\"serverport\", serverportSaved);
          this.editor.commit();
          Toast.makeText(this, \"Server Configured Successfully!!\", 1).show();
          finish();
          return;

\uD83D\uDFE7 com.android.insecurebankv2.DoLogin

      DoLogin.this.rememberme_username = username;
      DoLogin.this.rememberme_password = password;
      String base64Username = new String(Base64.encodeToString(DoLogin.this.rememberme_username.getBytes(), 4));
      CryptoClass crypt = new CryptoClass();
      DoLogin.this.superSecurePassword = crypt.aesEncryptedString(DoLogin.this.rememberme_password);
‣‣    editor.putString(\"EncryptedUsername\", base64Username);
      editor.putString(\"superSecurePassword\", DoLogin.this.superSecurePassword);
      editor.commit();
           }
  
  private String convertStreamToString(InputStream in) throws IOException {

"},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.android.insecurebankv2.FilePrefActivity"},"region":{"startLine":78,"snippet":{"text":" Matcher m = p.matcher(serveripSaved);\n if (serveripSaved != null && m.matches()) {\n Pattern p2 = Pattern.compile(\"(6553[0-5]|655[0-2]\\\\d|65[0-4]\\\\d{2}|6[0-4]\\\\d{3}|[1-5]\\\\d{4}|[1-9]\\\\d{0,3})\");\n Matcher m2 = p2.matcher(serverportSaved);\n if (serverportSaved != null && m2.matches()) {\n this.editor.putString(\"serverip\", serveripSaved);\n this.editor.putString(\"serverport\", serverportSaved);\n this.editor.commit();\n Toast.makeText(this, \"Server Configured Successfully!!\", 1).show();\n finish();\n return;"}}}},{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.android.insecurebankv2.DoLogin%24RequestTask"},"region":{"startLine":1}}},{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.android.insecurebankv2.DoLogin"},"region":{"startLine":147,"snippet":{"text":" DoLogin.this.rememberme_username = username;\n DoLogin.this.rememberme_password = password;\n String base64Username = new String(Base64.encodeToString(DoLogin.this.rememberme_username.getBytes(), 4));\n CryptoClass crypt = new CryptoClass();\n DoLogin.this.superSecurePassword = crypt.aesEncryptedString(DoLogin.this.rememberme_password);\n editor.putString(\"EncryptedUsername\", base64Username);\n editor.putString(\"superSecurePassword\", DoLogin.this.superSecurePassword);\n editor.commit();\n }\n\n private String convertStreamToString(InputStream in) throws IOException {"}}}}]},{"ruleId":"63921084-031a-7c00-0f01-0dc500000000","ruleIndex":25,"message":{"text":"\uD83D\uDFE9 Recommendation

This app is following the best practice of validating the chain of trust on all secure endpoints.

"},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+classes.dex"},"region":{"startLine":1}}}]},{"ruleId":"5da893bf-7d33-27f0-1f8b-456a00000000","ruleIndex":26,"message":{"text":"\uD83D\uDFE9 Recommendation

This is an informational finding that is used by an evaluator to determine if the application is justified in this usage.

"},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+classes.dex"},"region":{"startLine":1}}}]},{"ruleId":"5da89d29-7d33-27f1-1f8b-456800000000","ruleIndex":27,"message":{"text":"\uD83D\uDFE9 Recommendation

This is an informational finding that is used by an evaluator to determine if the application is justified in this usage.

\uD83D\uDFE7 Locations

com.android.insecurebankv2.ChangePassword
com.android.insecurebankv2.MyBroadCastReceiver

\uD83D\uDFE6 Code Snippets

\uD83D\uDFE7 com.android.insecurebankv2.ChangePassword

  import android.content.Intent;
  import android.content.SharedPreferences;
  import android.os.AsyncTask;
  import android.os.Bundle;
  import android.preference.PreferenceManager;
‣‣import android.telephony.TelephonyManager;
  import android.text.TextUtils;
  import android.view.Menu;
  import android.view.MenuItem;
  import android.view.View;
  import android.widget.Button;

\uD83D\uDFE7 com.android.insecurebankv2.MyBroadCastReceiver

  
          import android.content.BroadcastReceiver;
          import android.content.Context;
          import android.content.Intent;
          import android.content.SharedPreferences;
‣‣        import android.telephony.SmsManager;
          import android.util.Base64;
  
  
  public class MyBroadCastReceiver extends BroadcastReceiver {
              public static final String MYPREFS = \"mySharedPreferences\";

"},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.android.insecurebankv2.ChangePassword"},"region":{"startLine":9,"snippet":{"text":" import android.content.Intent;\n import android.content.SharedPreferences;\n import android.os.AsyncTask;\n import android.os.Bundle;\n import android.preference.PreferenceManager;\n import android.telephony.TelephonyManager;\n import android.text.TextUtils;\n import android.view.Menu;\n import android.view.MenuItem;\n import android.view.View;\n import android.widget.Button;"}}}},{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.android.insecurebankv2.MyBroadCastReceiver"},"region":{"startLine":7,"snippet":{"text":"\n import android.content.BroadcastReceiver;\n import android.content.Context;\n import android.content.Intent;\n import android.content.SharedPreferences;\n import android.telephony.SmsManager;\n import android.util.Base64;\n\n\n public class MyBroadCastReceiver extends BroadcastReceiver {\n public static final String MYPREFS = \"mySharedPreferences\";"}}}}]},{"ruleId":"5db9a286-7d33-277c-4b8b-456700000000","ruleIndex":28,"message":{"text":"\uD83D\uDFE9 Recommendation

If encryption is used in the application, random bit generation should follow the FCS_RBG_EXT.2.1 requirements.

The requirements state the application should perform all deterministic random bit generation (DRBG) services in accordance with NIST Special Publication 800-90A using Hash_DRBG, HMAC_DRBG, or CTR_DRBG. This requirement to implement DRBG functionality is chosen in FCS_RBG_EXT.1.1. While any of the identified hash functions (SHA-1, SHA-224, SHA-256, SHA-384, SHA-512) are allowed for Hash_DRBG or HMAC_DRBG, only AES-based implementations for CTR_DRBG are allowed.

"},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+classes.dex"},"region":{"startLine":1}}}]},{"ruleId":"5e0a1763-a430-cf64-d379-2d6400000000","ruleIndex":29,"message":{"text":"\uD83D\uDFE9 Recommendation

Use certificate pinning anytime you want to be relatively certain of the remote host's identity or when operating in a hostile environment. Since these are almost always true, you should probably pin all the time.

Since Android N, the preferred way for implementing pinning is by leveraging Android's Network Security Configuration feature, which lets apps customize their network security settings in a safe, declarative configuration file without modifying app code.

You can use the configuration setting to enable pinning.

If devices, running a version of Android that is earlier than N, need to be supported, a backport of the Network Security Configuration pinning functionality is available through the TrustKit Android library at https://github.com/datatheorem/TrustKit-Android.

For iOS, you can use TrustKit, an open-source SSL pinning library for iOS and macOS. It is available at https://github.com/datatheorem/TrustKit and provides an easy-to-use API for implementing pinning.

SSL pinning can be bypassed during dynamic analysis of the application when the attacker has full control of the app's environment. zDefend SDK provides a next-generation RASP engine that can be embedded into the mobile application. It provides application runtime defense against Man-in-the-Middle (MITM) attacks, such as SSL proxying, network manipulation, and gateway or proxy changes on the device. Visit https://www.zimperium.com/zdefend/ for more information.

"},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+classes.dex"},"region":{"startLine":1}}}]},{"ruleId":"5fb402d1-d9a8-c20a-5e58-e34300000000","ruleIndex":30,"message":{"text":"\uD83D\uDFE9 Recommendation

The Android SDK tool now generates the v4 signature file if you run it with default parameters. Use the APKSigner with default parameters: apksigner sign --ks debug.keystore {your app}.apk.

Zimperium's zShield provides additional APK Signature Verification to ensure your application signing has not been compromised by a malicious exploit.

"},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+classes.dex"},"region":{"startLine":1}}}]},{"ruleId":"5f7eef85-c098-1160-dd2a-481f00000000","ruleIndex":31,"message":{"text":"\uD83D\uDFE9 Recommendation

Zimperium's zShield provides code obfuscation to defend against static analysis of your application. Obfuscation makes reverse engineering more difficult by adding complexity and transforming the appearance of your application without changing the behavior.

"},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+classes.dex"},"region":{"startLine":1}}}]},{"ruleId":"60faf47f-178b-b800-121f-af7600000000","ruleIndex":32,"message":{"text":"\uD83D\uDFE9 Recommendation

It is recommended that the application be coded to perform countermeasures such as halting application execution when a jailbroken or rooted device is discovered.

\uD83D\uDFE6 Code Snippet

\uD83D\uDFE7 com.android.insecurebankv2.PostLogin

  
  .prologue
  const/4 v1, 0x1
  
  .line 86
‣‣const-string v2, \"/system/app/Superuser.apk\"
  
  invoke-direct {p0, v2}, Lcom/android/insecurebankv2/PostLogin;->doesSuperuserApkExist(Ljava/lang/String;)Z
  
  move-result v2
  

"},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.android.insecurebankv2.PostLogin"},"region":{"startLine":429,"snippet":{"text":"\n .prologue\n const/4 v1, 0x1\n\n .line 86\n const-string v2, \"/system/app/Superuser.apk\"\n\n invoke-direct {p0, v2}, Lcom/android/insecurebankv2/PostLogin;->doesSuperuserApkExist(Ljava/lang/String;)Z\n\n move-result v2\n"}}}}]},{"ruleId":"636277c5-cab2-bc00-0f71-f3f500000000","ruleIndex":33,"message":{"text":"\uD83D\uDFE9 Recommendation

This application is performing the best practice of protecting program data symbols.

"},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+classes.dex"},"region":{"startLine":1}}}]},{"ruleId":"64ad4f15-2e24-ce00-5648-756a00000000","ruleIndex":34,"message":{"text":"\uD83D\uDFE9 Recommendation

No immediate action is required for this finding, as code obfuscation makes reverse engineering difficult. However, it is always recommended to add code obfuscation before releasing the app into the app stores to deter reverse engineering.

"},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+classes.dex"},"region":{"startLine":1}}}]},{"ruleId":"591d4dc1-12df-5b5d-87e6-1e2d00000000","ruleIndex":35,"message":{"text":"\uD83D\uDFE9 Recommendation

Secure data storage, conduct regular security audits, and follow the least privilege principle.

\uD83D\uDFE6 Code Snippet

\uD83D\uDFE7 com.android.insecurebankv2.MyBroadCastReceiver

  public void onReceive(Context context, Intent intent) {
      String phn = intent.getStringExtra(\"phonenumber\");
      String newpass = intent.getStringExtra(\"newpass\");
      if (phn != null) {
                  try {
‣‣            SharedPreferences settings = context.getSharedPreferences(\"mySharedPreferences\", 1);
              String username = settings.getString(\"EncryptedUsername\", null);
              byte[] usernameBase64Byte = Base64.decode(username, 0);
              this.usernameBase64ByteString = new String(usernameBase64Byte, \"UTF-8\");
              String password = settings.getString(\"superSecurePassword\", null);
              CryptoClass crypt = new CryptoClass();

"},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.android.insecurebankv2.MyBroadCastReceiver"},"region":{"startLine":21,"snippet":{"text":" public void onReceive(Context context, Intent intent) {\n String phn = intent.getStringExtra(\"phonenumber\");\n String newpass = intent.getStringExtra(\"newpass\");\n if (phn != null) {\n try {\n SharedPreferences settings = context.getSharedPreferences(\"mySharedPreferences\", 1);\n String username = settings.getString(\"EncryptedUsername\", null);\n byte[] usernameBase64Byte = Base64.decode(username, 0);\n this.usernameBase64ByteString = new String(usernameBase64Byte, \"UTF-8\");\n String password = settings.getString(\"superSecurePassword\", null);\n CryptoClass crypt = new CryptoClass();"}}}}]},{"ruleId":"633d121c-0e3b-5700-1234-49c900000000","ruleIndex":36,"message":{"text":"\uD83D\uDFE9 Recommendation

This is a detection to satisfy the MASVS MSTG-CRYPTO-3 requirements.

\uD83D\uDFE7 Locations

com.android.insecurebankv2.CryptoClass
com.android.insecurebankv2.DoLogin
com.android.insecurebankv2.LoginActivity
com.android.insecurebankv2.ChangePassword
com.android.insecurebankv2.DoTransfer

"},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.android.insecurebankv2.CryptoClass"},"region":{"startLine":1}}},{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.android.insecurebankv2.DoLogin"},"region":{"startLine":1}}},{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.android.insecurebankv2.LoginActivity"},"region":{"startLine":1}}},{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.android.insecurebankv2.ChangePassword"},"region":{"startLine":1}}},{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.android.insecurebankv2.DoTransfer"},"region":{"startLine":1}}}]},{"ruleId":"63eb7110-f141-dd00-6a42-035400000000","ruleIndex":37,"message":{"text":"\uD83D\uDFE9 Recommendation

Clear the WebView resources when the application accesses any sensitive data, which may include any files stored locally, the RAM cache, and any loaded JavaScript. Please note that this presents a potential security risk if any sensitive data is being exposed.

\uD83D\uDFE7 Locations

com.android.insecurebankv2.MyWebViewClient
com.android.insecurebankv2.ViewStatement

\uD83D\uDFE6 Code Snippets

\uD83D\uDFE7 com.android.insecurebankv2.MyWebViewClient

          package com.android.insecurebankv2;
  
‣‣        import android.webkit.WebView;
          import android.webkit.WebViewClient;
  
  
  public class MyWebViewClient extends WebViewClient {
              @Override // android.webkit.WebViewClient

\uD83D\uDFE7 com.android.insecurebankv2.ViewStatement

  import android.content.Intent;
  import android.os.Bundle;
  import android.os.Environment;
  import android.view.Menu;
  import android.view.MenuItem;
‣‣import android.webkit.WebChromeClient;
  import android.webkit.WebView;
  import android.widget.Toast;
  import java.io.File;
  
  

"},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.android.insecurebankv2.MyWebViewClient"},"region":{"startLine":3,"snippet":{"text":" package com.android.insecurebankv2;\n\n import android.webkit.WebView;\n import android.webkit.WebViewClient;\n\n\n public class MyWebViewClient extends WebViewClient {\n @Override // android.webkit.WebViewClient"}}}},{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.android.insecurebankv2.ViewStatement"},"region":{"startLine":9,"snippet":{"text":" import android.content.Intent;\n import android.os.Bundle;\n import android.os.Environment;\n import android.view.Menu;\n import android.view.MenuItem;\n import android.webkit.WebChromeClient;\n import android.webkit.WebView;\n import android.widget.Toast;\n import java.io.File;\n\n"}}}}]},{"ruleId":"558af6f7-3601-8303-e4d5-958000000000","ruleIndex":38,"message":{"text":"\uD83D\uDFE9 Recommendation

JavaScript execution is disabled by default on WebViews. This behavior is enabled with the setJavaScriptEnabled() API, and the first recommendation is to maintain the default behavior if there is no need for client-side scripting. This prevents exposure to potential Cross-Site Scripting (XSS) attacks and reduces the consequences of a Man in the Middle (MITM) attack.

In a scenario where JavaScript is mandatory, all inputs should be sanitized to prevent XSS attacks. Validating the origin of the content being loaded by the WebView is a good security precaution. It can be implemented by overriding the shouldOverrideUrlLoading() and the shouldInterceptRequest() methods.

Additionally, it is recommended to add \"android.webkit.WebView.EnableSafeBrowsing\" in the Android manifest file and also to compile the app against Android API level 17 or above and implementing @JavascriptInterface annotation as this prevents accessing to operating system commands through java.lang.Runtime.

\uD83D\uDFE6 Code Snippet

\uD83D\uDFE7 com.android.insecurebankv2.ViewStatement

  File fileToCheck = new File(Environment.getExternalStorageDirectory(), FILENAME);
  System.out.println(fileToCheck.toString());
  if (fileToCheck.exists()) {
      WebView mWebView = (WebView) findViewById(R.id.webView1);
      mWebView.loadUrl(\"file://\" + Environment.getExternalStorageDirectory() + \"/Statements_\" + this.uname + \".html\");
‣‣    mWebView.getSettings().setJavaScriptEnabled(true);
      mWebView.getSettings().setSaveFormData(true);
      mWebView.getSettings().setBuiltInZoomControls(true);
      mWebView.setWebViewClient(new MyWebViewClient());
      WebChromeClient cClient = new WebChromeClient();
      mWebView.setWebChromeClient(cClient);

"},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.android.insecurebankv2.ViewStatement"},"region":{"startLine":30,"snippet":{"text":" File fileToCheck = new File(Environment.getExternalStorageDirectory(), FILENAME);\n System.out.println(fileToCheck.toString());\n if (fileToCheck.exists()) {\n WebView mWebView = (WebView) findViewById(R.id.webView1);\n mWebView.loadUrl(\"file://\" + Environment.getExternalStorageDirectory() + \"/Statements_\" + this.uname + \".html\");\n mWebView.getSettings().setJavaScriptEnabled(true);\n mWebView.getSettings().setSaveFormData(true);\n mWebView.getSettings().setBuiltInZoomControls(true);\n mWebView.setWebViewClient(new MyWebViewClient());\n WebChromeClient cClient = new WebChromeClient();\n mWebView.setWebChromeClient(cClient);"}}}}]},{"ruleId":"575ecc52-d100-c5af-f726-2ac400000000","ruleIndex":39,"message":{"text":"\uD83D\uDFE9 Recommendation

Because of the potential to abuse the Reflection API, it is strongly recommended that when reflection is used in third-party libraries that those libraries are reviewed to ensure acceptable use of the reflection API.

\uD83D\uDFE6 Code Snippet

\uD83D\uDFE7 com.google.ads.mediation.MediationServerParameters

  import com.google.android.gms.ads.internal.util.client.zzb;
  import java.lang.annotation.ElementType;
  import java.lang.annotation.Retention;
  import java.lang.annotation.RetentionPolicy;
  import java.lang.annotation.Target;
‣‣import java.lang.reflect.Field;
  import java.util.HashMap;
  import java.util.Map;
  
  @Deprecated
  

"},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"zscan%3A+com.google.ads.mediation.MediationServerParameters"},"region":{"startLine":8,"snippet":{"text":" import com.google.android.gms.ads.internal.util.client.zzb;\n import java.lang.annotation.ElementType;\n import java.lang.annotation.Retention;\n import java.lang.annotation.RetentionPolicy;\n import java.lang.annotation.Target;\n import java.lang.reflect.Field;\n import java.util.HashMap;\n import java.util.Map;\n\n @Deprecated\n"}}}}]}]}]} \ No newline at end of file From f6f5c8506cffc4f23b36c8d43a673e11d5c6ba5c Mon Sep 17 00:00:00 2001 From: Igor Matlin Date: Fri, 19 Dec 2025 08:33:29 -0600 Subject: [PATCH 4/5] Removed non-working test action --- .github/workflows/zScanAction.yml | 60 ------------------------------- 1 file changed, 60 deletions(-) delete mode 100644 .github/workflows/zScanAction.yml diff --git a/.github/workflows/zScanAction.yml b/.github/workflows/zScanAction.yml deleted file mode 100644 index d13259d..0000000 --- a/.github/workflows/zScanAction.yml +++ /dev/null @@ -1,60 +0,0 @@ -# This workflow uses actions that are not certified by GitHub. -# They are provided by a third-party and are governed by -# separate terms of service, privacy policy, and support -# documentation. -# -# The zimperium-zscan GitHub action scans your mobile app binary (iOS or Android) -# and identifies security, privacy, and compliance-related vulnerabilities. ​ -# -# Prerequisites: -# * An active Zimperium zScan account is required. If you are not an existing Zimperium -# zScan customer, please request a zSCAN demo by visiting https://www.zimperium.com/contact-us. -# * Either GitHub Advanced Security (GHAS) or a public repository is required to display -# issues and view the remediation information inside of GitHub code scanning alerts.​ -# -# For additional information and setup instructions -# please visit: https://github.com/Zimperium/zScanMarketplace#readme - -name: "Zimperium zScan" - -on: - push: - branches: [ "master" ] - pull_request: - branches: [ "master" ] - -permissions: - contents: read - -jobs: - zscan: - name: zScan - runs-on: ubuntu-latest - permissions: - contents: read # for actions/checkout to fetch code - security-events: write # for github/codeql-action/upload-sarif to upload SARIF results - actions: read # only required for a private repository by github/codeql-action/upload-sarif to get the Action run status - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Run Zimperium zScan - uses: zimperium/zscanmarketplace@v1 - timeout-minutes: 60 - with: - # REPLACE: Zimperium Console URL - console_url: "https://zc202.zimperium.com" - # REPLACE: Zimperium Client ID - client_id: ${{ vars.ZSCAN_CLIENT_ID }} - # REPLACE: Zimperium Client Secret - client_secret: ${{ secrets.ZSCAN_CLIENT_SECRET }} - # REPLACE: The path to an .ipa or .apk - app_file: ./Sample_Insecure_Bank_App.apk - # REPLACE: Team name to assign the app to (default: Default) - team_name: "Americas" - - - name: Upload SARIF file - uses: github/codeql-action/upload-sarif@v4 - with: - sarif_file: Sample_Insecure_Bank_App_zscan.sarif - \ No newline at end of file From c01b0b296ce1a6b235e9223741f08a75e04a21e1 Mon Sep 17 00:00:00 2001 From: Igor Matlin Date: Fri, 19 Dec 2025 10:56:37 -0600 Subject: [PATCH 5/5] Sleep before trying to assign app to a team --- dist/index.js | 4 ++-- src/action.js | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/dist/index.js b/dist/index.js index 6bcb15a..f78b6f3 100644 --- a/dist/index.js +++ b/dist/index.js @@ -44635,7 +44635,6 @@ async function pollDownload(assessmentId, originalFileName) { while(!done && totalTime < MAX_DOWNLOAD_TIME) { let result = await downloadApp(assessmentId, originalFileName); core.debug(`Download attempt returned status code: ${result.statusCode}`); - core.debug(`Download result: ${JSON.stringify(result)}`); if(result.statusCode == 200) { core.info(`Sarif file ${result.reportFileName} download complete.`); done = true; @@ -44700,7 +44699,8 @@ uploadApp().then(uploadResults => { // Check if app needs to be assigned to a team if (result.teamId === null || result.teamId === undefined) { core.info(`App ${result.zdevAppId} not assigned to a team, attempting to assign to team: ${teamName}`); - + // Wait for a short time to ensure the app is available for team assignment + await sleep(STATUS_POLL_TIME); try { const teams = await getTeams(); let targetTeamId = null; diff --git a/src/action.js b/src/action.js index 8b3be11..0f4124f 100644 --- a/src/action.js +++ b/src/action.js @@ -256,7 +256,8 @@ uploadApp().then(uploadResults => { // Check if app needs to be assigned to a team if (result.teamId === null || result.teamId === undefined) { core.info(`App ${result.zdevAppId} not assigned to a team, attempting to assign to team: ${teamName}`); - + // Wait for a short time to ensure the app is available for team assignment + await sleep(STATUS_POLL_TIME); try { const teams = await getTeams(); let targetTeamId = null;