diff --git a/qark/apk_builder.py b/qark/apk_builder.py index b9e31f1d..db767fbf 100644 --- a/qark/apk_builder.py +++ b/qark/apk_builder.py @@ -110,7 +110,7 @@ def _build_apk(self): os.chdir(self.exploit_apk_path) write_key_value_to_xml('packageName', self.package_name, self.strings_xml_path) self._write_properties_file({"sdk.dir": self.sdk_path}) - command = "./gradlew assembleDebug" + command = "sh -x ./gradlew assembleDebug" try: subprocess.call(shlex.split(command)) except Exception: diff --git a/qark/decompiler/decompiler.py b/qark/decompiler/decompiler.py index 216362c2..6a4822d0 100644 --- a/qark/decompiler/decompiler.py +++ b/qark/decompiler/decompiler.py @@ -68,7 +68,11 @@ def __init__(self, path_to_source, build_directory=None): if os.path.isdir(path_to_source) or is_java_file(path_to_source): self.source_code = True - self.manifest_path = None + if os.path.isdir(path_to_source): + self.manifest_path = os.path.join(path_to_source, 'AndroidManifest.xml') + else: + self.manifest_path = None + self.apk_name = os.path.splitext(os.path.basename(path_to_source))[0].split('/')[-1] log.debug("Decompiler got directory to run on, assuming Java source code") return @@ -159,13 +163,13 @@ def run_apktool(self): log.debug("APKTool finish executing, trying to move manifest into proper location") # copy valid XML file to correct location - shutil.move(os.path.join(self.build_directory, "apktool", "AndroidManifest.xml"), - os.path.join(self.build_directory, "AndroidManifest.xml")) +# shutil.move(os.path.join(self.build_directory, "apktool", "AndroidManifest.xml"), +# os.path.join(self.build_directory, "AndroidManifest.xml")) log.debug("Manifest moved successfully") log.debug("Removing apktool subdirectory of build") # remove the apktool generated files (only needed manifest file) - shutil.rmtree(os.path.join(self.build_directory, "apktool")) +# shutil.rmtree(os.path.join(self.build_directory, "apktool")) log.debug("Removed apktool directory") return os.path.join(self.build_directory, "AndroidManifest.xml") diff --git a/qark/exploit_apk/app/build.gradle b/qark/exploit_apk/app/build.gradle index ab7d8bba..e45255bc 100644 --- a/qark/exploit_apk/app/build.gradle +++ b/qark/exploit_apk/app/build.gradle @@ -1,12 +1,12 @@ apply plugin: 'com.android.application' android { - compileSdkVersion 21 - buildToolsVersion "21.1.2" + compileSdkVersion 28 + buildToolsVersion "28.0.3" defaultConfig { applicationId 'com.secbro.qark' minSdkVersion 7 - targetSdkVersion 21 + targetSdkVersion 28 versionCode 1 versionName "1.0" } @@ -26,4 +26,4 @@ dependencies { compile 'com.android.support:appcompat-v7:22.2.1' compile 'com.android.support:design:22.2.1' compile 'com.android.support:recyclerview-v7:22.2.1' -} \ No newline at end of file +} diff --git a/qark/plugins/file/api_keys.py b/qark/plugins/file/api_keys.py index 3988742d..dd1a7228 100644 --- a/qark/plugins/file/api_keys.py +++ b/qark/plugins/file/api_keys.py @@ -11,7 +11,7 @@ API_KEY_REGEX = re.compile(r'(?=.{20,})(?=.+\d)(?=.+[a-z])(?=.+[A-Z])(?=.+[-_])') SPECIAL_CHARACTER_REGEX = re.compile(r'(?=.+[!$%^&*()_+|~=`{}\[\]:<>?,./])') -BLACKLISTED_EXTENSIONS = (".apk", ".dex", ".png", ".jar") +BLACKLISTED_EXTENSIONS = (".apk", ".dex", ".png", ".jar", ".jpg") API_KEY_DESCRIPTION = "Please confirm and investigate the API key to determine its severity." diff --git a/qark/plugins/helpers.py b/qark/plugins/helpers.py index 8836db04..adfa090f 100644 --- a/qark/plugins/helpers.py +++ b/qark/plugins/helpers.py @@ -75,7 +75,7 @@ def valid_method_invocation(method_invocation, method_name, num_arguments): and len(method_invocation.arguments) == num_arguments) -def get_min_sdk_from_files(files, apk_constants=None): +def get_min_sdk_from_files(files, apk_constants=None, min_sdk=None): """ Get the min_sdk from either the `apk_constants` if it exists, or the manifest file in `files` if it exists. If neither exists, return 1 as the default minimum SDK @@ -91,7 +91,7 @@ def get_min_sdk_from_files(files, apk_constants=None): except (KeyError, TypeError): for decompiled_file in files: if decompiled_file.lower().endswith("{separator}androidmanifest.xml".format(separator=os.sep)): - return get_min_sdk(decompiled_file) + return get_min_sdk(decompiled_file, min_sdk=min_sdk) return 1 diff --git a/qark/plugins/manifest_helpers.py b/qark/plugins/manifest_helpers.py index e2737679..24ed9b2d 100644 --- a/qark/plugins/manifest_helpers.py +++ b/qark/plugins/manifest_helpers.py @@ -22,7 +22,7 @@ def get_package_from_manifest(manifest_path): return manifest_xml.getroot().attrib.get("package") -def get_min_sdk(manifest_xml, files=None): +def get_min_sdk(manifest_xml, files=None, min_sdk=None): """ Given the manifest as a `minidom.parse`'d object or path to manifest, try to get the minimum SDK the manifest specifies. @@ -31,6 +31,8 @@ def get_min_sdk(manifest_xml, files=None): :param Set[str] files: list of files received from Scanner :return: int of the version if it exists, else 1 (the default) """ + if min_sdk: + return int(min_sdk) if manifest_xml is None and files: manifest_xml = get_manifest_out_of_files(files) diff --git a/qark/qark.py b/qark/qark.py index 88cf6a2a..38804717 100644 --- a/qark/qark.py +++ b/qark/qark.py @@ -49,9 +49,15 @@ help="report output path.", show_default=True) @click.option("--keep-report/--no-keep-report", default=False, help="Append to final report file.", show_default=True) +@click.option("--log-level", default=0, + help="Minimum issue output log level, default INFO(0), can be [0(INFO), 1(WARNING), 2(ERROR), 3(VULNERABILITY)].") +@click.option("--disable-plugins", default='', + help="Disable plugins seperated by commas.") +@click.option("--min-sdk", default=None, + help="Override min-sdk which getting from AndroidManifest.xml.") @click.version_option() @click.pass_context -def cli(ctx, sdk_path, build_path, debug, source, report_type, exploit_apk, report_path, keep_report): +def cli(ctx, sdk_path, build_path, debug, source, report_type, exploit_apk, report_path, keep_report, log_level, disable_plugins, min_sdk): if not source: click.secho("Please pass a source for scanning through either --java or --apk") click.secho(ctx.get_help()) @@ -90,13 +96,13 @@ def cli(ctx, sdk_path, build_path, debug, source, report_type, exploit_apk, repo click.secho("Running scans...") path_to_source = decompiler.path_to_source if decompiler.source_code else decompiler.build_directory - scanner = Scanner(manifest_path=decompiler.manifest_path, path_to_source=path_to_source) + scanner = Scanner(manifest_path=decompiler.manifest_path, path_to_source=path_to_source, disable_plugins=disable_plugins.split(","), min_sdk=min_sdk) scanner.run() click.secho("Finish scans...") click.secho("Writing report...") report = Report(issues=set(scanner.issues), report_path=report_path, keep_report=keep_report) - report_path = report.generate(file_type=report_type) + report_path = report.generate(file_type=report_type, log_level=log_level) click.secho("Finish writing report to {report_path} ...".format(report_path=report_path)) if exploit_apk: diff --git a/qark/report.py b/qark/report.py index f9300283..8731d3de 100644 --- a/qark/report.py +++ b/qark/report.py @@ -47,7 +47,7 @@ def __init__(self, issues=None, report_path=None, keep_report=False): self.report_path = report_path or DEFAULT_REPORT_PATH self.keep_report = keep_report - def generate(self, file_type='html', template_file=None): + def generate(self, file_type='html', template_file=None, log_level=0): """This method uses Jinja2 to generate a standalone HTML version of the report. :param str file_type: The type of file for the report. Defaults to 'html'. @@ -67,7 +67,7 @@ def generate(self, file_type='html', template_file=None): template = jinja_env.get_template('{file_type}_report.jinja'.format(file_type=file_type)) else: template = Template(template_file) - report_file.write(template.render(issues=list(self.issues))) + report_file.write(template.render(issues=[x for x in list(self.issues) if x.severity._value_ >= log_level])) report_file.write('\n') return full_report_path diff --git a/qark/scanner/plugin.py b/qark/scanner/plugin.py index 63ac0193..d1041281 100644 --- a/qark/scanner/plugin.py +++ b/qark/scanner/plugin.py @@ -249,7 +249,7 @@ class ManifestPlugin(BasePlugin): package_name = "PACKAGE_NOT_FOUND" @classmethod - def update_manifest(cls, path_to_manifest): + def update_manifest(cls, path_to_manifest, min_sdk): """Users of this class should call this method instead of changing class attributes directly""" cls.manifest_path = path_to_manifest try: @@ -261,7 +261,7 @@ def update_manifest(cls, path_to_manifest): return try: - cls.min_sdk = get_min_sdk(cls.manifest_path) + cls.min_sdk = get_min_sdk(cls.manifest_path, min_sdk=min_sdk) cls.target_sdk = get_target_sdk(cls.manifest_path) except AttributeError: # manifest path is not set, assume min_sdk and target_sdk diff --git a/qark/scanner/scanner.py b/qark/scanner/scanner.py index dd7abf81..839424d9 100644 --- a/qark/scanner/scanner.py +++ b/qark/scanner/scanner.py @@ -20,7 +20,7 @@ class Scanner(object): - def __init__(self, manifest_path, path_to_source): + def __init__(self, manifest_path, path_to_source, disable_plugins=[''], min_sdk=None): """ Creates the scanner. @@ -29,12 +29,16 @@ def __init__(self, manifest_path, path_to_source): """ self.files = set() self.issues = [] - self.manifest_path = manifest_path + self.manifest_path = manifest_path if manifest_path else path_to_source + '/AndroidManifest.xml' self.path_to_source = path_to_source self._gather_files() + self.disable_plugins = disable_plugins + + self.min_sdk = min_sdk + def run(self): """ Runs all the plugin checks by category. @@ -46,7 +50,7 @@ def run(self): if category == "manifest": # Manifest plugins only need to run once, so we run them and continue manifest_plugins = get_plugins(category) - ManifestPlugin.update_manifest(self.manifest_path) + ManifestPlugin.update_manifest(self.manifest_path, self.min_sdk) if ManifestPlugin.manifest_xml is not None: for plugin in [plugin_source.load_plugin(plugin_name).plugin for plugin_name in manifest_plugins]: @@ -59,6 +63,8 @@ def run(self): continue for plugin_name in get_plugins(category): + if plugin_name in self.disable_plugins: + continue plugins.append(plugin_source.load_plugin(plugin_name).plugin) self._run_checks(plugins) diff --git a/qark/templates/html_report.jinja b/qark/templates/html_report.jinja index 2c5acde9..3f09fc53 100644 --- a/qark/templates/html_report.jinja +++ b/qark/templates/html_report.jinja @@ -3,9 +3,11 @@

Issues

{% for issue in issues %} +

{{ issue.severity.name }} {{ issue.name }}

{{ issue.description }}

File: {{ issue.file_object }}{% if issue.line_number %}:{{ issue.line_number[0] }}:{{ issue.line_number[1] }} {% endif %}
+
{% endfor %} - \ No newline at end of file +