Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add option --log-level to filt output report #327

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
2 changes: 1 addition & 1 deletion qark/apk_builder.py
Expand Up @@ -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:
Expand Down
12 changes: 8 additions & 4 deletions qark/decompiler/decompiler.py
Expand Up @@ -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

Expand Down Expand Up @@ -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")
Expand Down
8 changes: 4 additions & 4 deletions 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"
}
Expand All @@ -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'
}
}
2 changes: 1 addition & 1 deletion qark/plugins/file/api_keys.py
Expand Up @@ -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."

Expand Down
4 changes: 2 additions & 2 deletions qark/plugins/helpers.py
Expand Up @@ -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
Expand All @@ -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


Expand Down
4 changes: 3 additions & 1 deletion qark/plugins/manifest_helpers.py
Expand Up @@ -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.
Expand All @@ -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)

Expand Down
12 changes: 9 additions & 3 deletions qark/qark.py
Expand Up @@ -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())
Expand Down Expand Up @@ -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:
Expand Down
4 changes: 2 additions & 2 deletions qark/report.py
Expand Up @@ -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'.
Expand All @@ -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
4 changes: 2 additions & 2 deletions qark/scanner/plugin.py
Expand Up @@ -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:
Expand All @@ -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
Expand Down
12 changes: 9 additions & 3 deletions qark/scanner/scanner.py
Expand Up @@ -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.

Expand All @@ -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.
Expand All @@ -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]:
Expand All @@ -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)
Expand Down
4 changes: 3 additions & 1 deletion qark/templates/html_report.jinja
Expand Up @@ -3,9 +3,11 @@
<body>
<h1>Issues</h1>
{% for issue in issues %}
<section class="{{ issue.name }}">
<h2>{{ issue.severity.name }} {{ issue.name }}</h2>
{{ issue.description }} <br/><br/>
File: <a href="file://{{ issue.file_object }}">{{ issue.file_object }}{% if issue.line_number %}:{{ issue.line_number[0] }}:{{ issue.line_number[1] }} {% endif %}</a><br/>
</section>
{% endfor %}
</body>
</html>
</html>