1
0
mirror of https://gitlab.com/fdroid/fdroidserver.git synced 2024-11-14 19:10:11 +01:00

Merge branch 'bandit-scanner-and-fixes' into 'master'

Bandit scanner and fixes

See merge request fdroid/fdroidserver!567
This commit is contained in:
Hans-Christoph Steiner 2018-08-30 13:06:07 +00:00
commit c7d5050091
10 changed files with 75 additions and 42 deletions

View File

@ -119,16 +119,22 @@ pip_install:
- fdroid readmeta - fdroid readmeta
- fdroid update --help - fdroid update --help
lint_format_safety_checks: lint_format_safety_bandit_checks:
image: alpine:3.7 image: alpine:3.7
variables: variables:
LANG: C.UTF-8 LANG: C.UTF-8
script: script:
- apk add --no-cache bash dash ca-certificates python3 - apk add --no-cache bash dash ca-certificates python3
- python3 -m ensurepip - python3 -m ensurepip
- pip3 install pycodestyle pyflakes 'pylint<2.0' safety - pip3 install bandit pycodestyle pyflakes 'pylint<2.0' safety
- export EXITVALUE=0 - export EXITVALUE=0
- ./hooks/pre-commit || export EXITVALUE=1 - ./hooks/pre-commit || export EXITVALUE=1
- bandit
-ii
-s B110,B310,B322,B404,B408,B410,B603,B607
-x fdroidserver/dscanner.py,docker/install_agent.py,docker/drozer.py
-r $CI_PROJECT_DIR
|| export EXITVALUE=1
- safety check --full-report || export EXITVALUE=1 - safety check --full-report || export EXITVALUE=1
- pylint --rcfile=.pylint-rcfile --output-format=colorized --reports=n - pylint --rcfile=.pylint-rcfile --output-format=colorized --reports=n
fdroid fdroid

View File

@ -28,6 +28,7 @@
import collections import collections
import defusedxml.minidom
import git import git
import glob import glob
import os import os
@ -36,7 +37,6 @@ import logging
import requests import requests
import shutil import shutil
import tempfile import tempfile
import xml.dom.minidom
import zipfile import zipfile
from argparse import ArgumentParser from argparse import ArgumentParser
@ -94,7 +94,7 @@ For more info on this idea:
continue continue
dest = os.path.join(cpdir, f) dest = os.path.join(cpdir, f)
if f.endswith('.xml'): if f.endswith('.xml'):
doc = xml.dom.minidom.parse(repof) doc = defusedxml.minidom.parse(repof)
output = doc.toprettyxml(encoding='utf-8') output = doc.toprettyxml(encoding='utf-8')
with open(dest, 'wb') as f: with open(dest, 'wb') as f:
f.write(output) f.write(output)

View File

@ -21,6 +21,7 @@ import os
import shutil import shutil
import glob import glob
import subprocess import subprocess
import posixpath
import re import re
import resource import resource
import sys import sys
@ -92,7 +93,7 @@ def build_server(app, build, vcs, build_dir, output_dir, log_dir, force):
port=sshinfo['port'], timeout=300, port=sshinfo['port'], timeout=300,
look_for_keys=False, key_filename=sshinfo['idfile']) look_for_keys=False, key_filename=sshinfo['idfile'])
homedir = '/home/' + sshinfo['user'] homedir = posixpath.join('/home', sshinfo['user'])
# Get an SFTP connection... # Get an SFTP connection...
ftp = sshs.open_sftp() ftp = sshs.open_sftp()
@ -126,8 +127,8 @@ def build_server(app, build, vcs, build_dir, output_dir, log_dir, force):
ftp.chdir('fdroidserver') ftp.chdir('fdroidserver')
ftp.put(os.path.join(serverpath, '..', 'fdroid'), 'fdroid') ftp.put(os.path.join(serverpath, '..', 'fdroid'), 'fdroid')
ftp.put(os.path.join(serverpath, '..', 'gradlew-fdroid'), 'gradlew-fdroid') ftp.put(os.path.join(serverpath, '..', 'gradlew-fdroid'), 'gradlew-fdroid')
ftp.chmod('fdroid', 0o755) ftp.chmod('fdroid', 0o755) # nosec B103 permissions are appropriate
ftp.chmod('gradlew-fdroid', 0o755) ftp.chmod('gradlew-fdroid', 0o755) # nosec B103 permissions are appropriate
send_dir(os.path.join(serverpath)) send_dir(os.path.join(serverpath))
ftp.chdir(homedir) ftp.chdir(homedir)
@ -159,7 +160,7 @@ def build_server(app, build, vcs, build_dir, output_dir, log_dir, force):
ftp.mkdir('srclib') ftp.mkdir('srclib')
# Copy any extlibs that are required... # Copy any extlibs that are required...
if build.extlibs: if build.extlibs:
ftp.chdir(homedir + '/build/extlib') ftp.chdir(posixpath.join(homedir, 'build', 'extlib'))
for lib in build.extlibs: for lib in build.extlibs:
lib = lib.strip() lib = lib.strip()
libsrc = os.path.join('build/extlib', lib) libsrc = os.path.join('build/extlib', lib)
@ -186,20 +187,20 @@ def build_server(app, build, vcs, build_dir, output_dir, log_dir, force):
srclibpaths.append(basesrclib) srclibpaths.append(basesrclib)
for name, number, lib in srclibpaths: for name, number, lib in srclibpaths:
logging.info("Sending srclib '%s'" % lib) logging.info("Sending srclib '%s'" % lib)
ftp.chdir(homedir + '/build/srclib') ftp.chdir(posixpath.join(homedir, 'build', 'srclib'))
if not os.path.exists(lib): if not os.path.exists(lib):
raise BuildException("Missing srclib directory '" + lib + "'") raise BuildException("Missing srclib directory '" + lib + "'")
fv = '.fdroidvcs-' + name fv = '.fdroidvcs-' + name
ftp.put(os.path.join('build/srclib', fv), fv) ftp.put(os.path.join('build/srclib', fv), fv)
send_dir(lib) send_dir(lib)
# Copy the metadata file too... # Copy the metadata file too...
ftp.chdir(homedir + '/srclibs') ftp.chdir(posixpath.join(homedir, 'srclibs'))
ftp.put(os.path.join('srclibs', name + '.txt'), ftp.put(os.path.join('srclibs', name + '.txt'),
name + '.txt') name + '.txt')
# Copy the main app source code # Copy the main app source code
# (no need if it's a srclib) # (no need if it's a srclib)
if (not basesrclib) and os.path.exists(build_dir): if (not basesrclib) and os.path.exists(build_dir):
ftp.chdir(homedir + '/build') ftp.chdir(posixpath.join(homedir, 'build'))
fv = '.fdroidvcs-' + app.id fv = '.fdroidvcs-' + app.id
ftp.put(os.path.join('build', fv), fv) ftp.put(os.path.join('build', fv), fv)
send_dir(build_dir) send_dir(build_dir)
@ -208,7 +209,7 @@ def build_server(app, build, vcs, build_dir, output_dir, log_dir, force):
logging.info("Starting build...") logging.info("Starting build...")
chan = sshs.get_transport().open_session() chan = sshs.get_transport().open_session()
chan.get_pty() chan.get_pty()
cmdline = os.path.join(homedir, 'fdroidserver', 'fdroid') cmdline = posixpath.join(homedir, 'fdroidserver', 'fdroid')
cmdline += ' build --on-server' cmdline += ' build --on-server'
if force: if force:
cmdline += ' --force --test' cmdline += ' --force --test'
@ -219,7 +220,7 @@ def build_server(app, build, vcs, build_dir, output_dir, log_dir, force):
if options.notarball: if options.notarball:
cmdline += ' --no-tarball' cmdline += ' --no-tarball'
cmdline += " %s:%s" % (app.id, build.versionCode) cmdline += " %s:%s" % (app.id, build.versionCode)
chan.exec_command('bash --login -c "' + cmdline + '"') chan.exec_command('bash --login -c "' + cmdline + '"') # nosec B601 inputs are sanitized
# Fetch build process output ... # Fetch build process output ...
try: try:
@ -255,7 +256,7 @@ def build_server(app, build, vcs, build_dir, output_dir, log_dir, force):
# Retreive logs... # Retreive logs...
toolsversion_log = common.get_toolsversion_logname(app, build) toolsversion_log = common.get_toolsversion_logname(app, build)
try: try:
ftp.chdir(os.path.join(homedir, log_dir)) ftp.chdir(posixpath.join(homedir, log_dir))
ftp.get(toolsversion_log, os.path.join(log_dir, toolsversion_log)) ftp.get(toolsversion_log, os.path.join(log_dir, toolsversion_log))
logging.debug('retrieved %s', toolsversion_log) logging.debug('retrieved %s', toolsversion_log)
except Exception as e: except Exception as e:
@ -264,9 +265,9 @@ def build_server(app, build, vcs, build_dir, output_dir, log_dir, force):
# Retrieve the built files... # Retrieve the built files...
logging.info("Retrieving build output...") logging.info("Retrieving build output...")
if force: if force:
ftp.chdir(homedir + '/tmp') ftp.chdir(posixpath.join(homedir, 'tmp'))
else: else:
ftp.chdir(homedir + '/unsigned') ftp.chdir(posixpath.join(homedir, 'unsigned'))
apkfile = common.get_release_filename(app, build) apkfile = common.get_release_filename(app, build)
tarball = common.getsrcname(app, build) tarball = common.getsrcname(app, build)
try: try:

View File

@ -57,7 +57,7 @@ def check_http(app):
if not parsed.netloc or not parsed.scheme or parsed.scheme != 'https': if not parsed.netloc or not parsed.scheme or parsed.scheme != 'https':
raise FDroidException(_('UpdateCheckData has invalid URL: {url}').format(url=urlcode)) raise FDroidException(_('UpdateCheckData has invalid URL: {url}').format(url=urlcode))
vercode = "99999999" vercode = None
if len(urlcode) > 0: if len(urlcode) > 0:
logging.debug("...requesting {0}".format(urlcode)) logging.debug("...requesting {0}".format(urlcode))
req = urllib.request.Request(urlcode, None) req = urllib.request.Request(urlcode, None)

View File

@ -39,7 +39,7 @@ import base64
import zipfile import zipfile
import tempfile import tempfile
import json import json
import xml.etree.ElementTree as XMLElementTree import defusedxml.ElementTree as XMLElementTree
from binascii import hexlify from binascii import hexlify
from datetime import datetime, timedelta from datetime import datetime, timedelta
@ -71,7 +71,9 @@ CERT_PATH_REGEX = re.compile(r'^META-INF/.*\.(DSA|EC|RSA)$')
APK_NAME_REGEX = re.compile(r'^([a-zA-Z][\w.]*)_(-?[0-9]+)_?([0-9a-f]{7})?\.apk') APK_NAME_REGEX = re.compile(r'^([a-zA-Z][\w.]*)_(-?[0-9]+)_?([0-9a-f]{7})?\.apk')
STANDARD_FILE_NAME_REGEX = re.compile(r'^(\w[\w.]*)_(-?[0-9]+)\.\w+') STANDARD_FILE_NAME_REGEX = re.compile(r'^(\w[\w.]*)_(-?[0-9]+)\.\w+')
XMLElementTree.register_namespace('android', 'http://schemas.android.com/apk/res/android') MAX_VERSION_CODE = 0x7fffffff # Java's Integer.MAX_VALUE (2147483647)
XMLNS_ANDROID = '{http://schemas.android.com/apk/res/android}'
config = None config = None
options = None options = None
@ -281,7 +283,7 @@ def read_config(opts, config_file='config.py'):
logging.debug(_("Reading '{config_file}'").format(config_file=config_file)) logging.debug(_("Reading '{config_file}'").format(config_file=config_file))
with io.open(config_file, "rb") as f: with io.open(config_file, "rb") as f:
code = compile(f.read(), config_file, 'exec') code = compile(f.read(), config_file, 'exec')
exec(code, None, config) exec(code, None, config) # nosec TODO switch to YAML file
else: else:
logging.warning(_("No 'config.py' found, using defaults.")) logging.warning(_("No 'config.py' found, using defaults."))
@ -1287,9 +1289,9 @@ def fetch_real_name(app_dir, flavours):
app = xml.find('application') app = xml.find('application')
if app is None: if app is None:
continue continue
if "{http://schemas.android.com/apk/res/android}label" not in app.attrib: if XMLNS_ANDROID + "label" not in app.attrib:
continue continue
label = app.attrib["{http://schemas.android.com/apk/res/android}label"] label = app.attrib[XMLNS_ANDROID + "label"]
result = retrieve_string_singleline(app_dir, label) result = retrieve_string_singleline(app_dir, label)
if result: if result:
result = result.strip() result = result.strip()
@ -1469,12 +1471,12 @@ def parse_androidmanifests(paths, app):
s = xml.attrib["package"] s = xml.attrib["package"]
if app_matches_packagename(app, s): if app_matches_packagename(app, s):
package = s package = s
if "{http://schemas.android.com/apk/res/android}versionName" in xml.attrib: if XMLNS_ANDROID + "versionName" in xml.attrib:
version = xml.attrib["{http://schemas.android.com/apk/res/android}versionName"] version = xml.attrib[XMLNS_ANDROID + "versionName"]
base_dir = os.path.dirname(path) base_dir = os.path.dirname(path)
version = retrieve_string_singleline(base_dir, version) version = retrieve_string_singleline(base_dir, version)
if "{http://schemas.android.com/apk/res/android}versionCode" in xml.attrib: if XMLNS_ANDROID + "versionCode" in xml.attrib:
a = xml.attrib["{http://schemas.android.com/apk/res/android}versionCode"] a = xml.attrib[XMLNS_ANDROID + "versionCode"]
if string_is_integer(a): if string_is_integer(a):
vercode = a vercode = a
except Exception: except Exception:
@ -3275,8 +3277,12 @@ def get_git_describe_link():
def calculate_math_string(expr): def calculate_math_string(expr):
ops = {ast.Add: operator.add, ast.Sub: operator.sub, ops = {
ast.Mult: operator.mul} ast.Add: operator.add,
ast.Mult: operator.mul,
ast.Sub: operator.sub,
ast.USub: operator.neg,
}
def execute_ast(node): def execute_ast(node):
if isinstance(node, ast.Num): # <number> if isinstance(node, ast.Num): # <number>
@ -3285,7 +3291,7 @@ def calculate_math_string(expr):
return ops[type(node.op)](execute_ast(node.left), return ops[type(node.op)](execute_ast(node.left),
execute_ast(node.right)) execute_ast(node.right))
elif isinstance(node, ast.UnaryOp): # <operator> <operand> e.g., -1 elif isinstance(node, ast.UnaryOp): # <operator> <operand> e.g., -1
return ops[type(node.op)](eval(node.operand)) return ops[type(node.op)](ast.literal_eval(node.operand))
else: else:
raise SyntaxError(node) raise SyntaxError(node)

View File

@ -60,12 +60,12 @@ def key_alias(appid):
# For this particular app, the key alias is overridden... # For this particular app, the key alias is overridden...
keyalias = config['keyaliases'][appid] keyalias = config['keyaliases'][appid]
if keyalias.startswith('@'): if keyalias.startswith('@'):
m = hashlib.md5() m = hashlib.md5() # nosec just used to generate a keyalias
m.update(keyalias[1:].encode('utf-8')) m.update(keyalias[1:].encode('utf-8'))
keyalias = m.hexdigest()[:8] keyalias = m.hexdigest()[:8]
return keyalias return keyalias
else: else:
m = hashlib.md5() m = hashlib.md5() # nosec just used to generate a keyalias
m.update(appid.encode('utf-8')) m.update(appid.encode('utf-8'))
return m.hexdigest()[:8] return m.hexdigest()[:8]
@ -197,7 +197,7 @@ def main():
vercodes = common.read_pkg_args(options.appid, True) vercodes = common.read_pkg_args(options.appid, True)
allaliases = [] allaliases = []
for appid in allapps: for appid in allapps:
m = hashlib.md5() m = hashlib.md5() # nosec just used to generate a keyalias
m.update(appid.encode('utf-8')) m.update(appid.encode('utf-8'))
keyalias = m.hexdigest()[:8] keyalias = m.hexdigest()[:8]
if keyalias in allaliases: if keyalias in allaliases:
@ -307,11 +307,11 @@ def main():
# For this particular app, the key alias is overridden... # For this particular app, the key alias is overridden...
keyalias = config['keyaliases'][appid] keyalias = config['keyaliases'][appid]
if keyalias.startswith('@'): if keyalias.startswith('@'):
m = hashlib.md5() m = hashlib.md5() # nosec just used to generate a keyalias
m.update(keyalias[1:].encode('utf-8')) m.update(keyalias[1:].encode('utf-8'))
keyalias = m.hexdigest()[:8] keyalias = m.hexdigest()[:8]
else: else:
m = hashlib.md5() m = hashlib.md5() # nosec just used to generate a keyalias
m.update(appid.encode('utf-8')) m.update(appid.encode('utf-8'))
keyalias = m.hexdigest()[:8] keyalias = m.hexdigest()[:8]
logging.info("Key alias: " + keyalias) logging.info("Key alias: " + keyalias)

View File

@ -192,7 +192,7 @@ def update_awsbucket_libcloud(repo_section):
upload = True upload = True
else: else:
# if the sizes match, then compare by MD5 # if the sizes match, then compare by MD5
md5 = hashlib.md5() md5 = hashlib.md5() # nosec AWS uses MD5
with open(file_to_upload, 'rb') as f: with open(file_to_upload, 'rb') as f:
while True: while True:
data = f.read(8192) data = f.read(8192)

View File

@ -27,7 +27,7 @@ import re
import socket import socket
import zipfile import zipfile
import hashlib import hashlib
import pickle import pickle # nosec TODO
import time import time
import copy import copy
from datetime import datetime from datetime import datetime
@ -434,7 +434,7 @@ def getsig(apkpath):
cert_encoded = common.get_certificate(cert) cert_encoded = common.get_certificate(cert)
return hashlib.md5(hexlify(cert_encoded)).hexdigest() return hashlib.md5(hexlify(cert_encoded)).hexdigest() # nosec just used as ID for signing key
def get_cache_file(): def get_cache_file():
@ -461,7 +461,7 @@ def get_cache():
ada = options.allow_disabled_algorithms or config['allow_disabled_algorithms'] ada = options.allow_disabled_algorithms or config['allow_disabled_algorithms']
if not options.clean and os.path.exists(apkcachefile): if not options.clean and os.path.exists(apkcachefile):
with open(apkcachefile, 'rb') as cf: with open(apkcachefile, 'rb') as cf:
apkcache = pickle.load(cf, encoding='utf-8') apkcache = pickle.load(cf, encoding='utf-8') # nosec TODO
if apkcache.get("METADATA_VERSION") != METADATA_VERSION \ if apkcache.get("METADATA_VERSION") != METADATA_VERSION \
or apkcache.get('allow_disabled_algorithms') != ada: or apkcache.get('allow_disabled_algorithms') != ada:
apkcache = {} apkcache = {}
@ -1237,7 +1237,7 @@ def scan_apk_androguard(apk, apkfile):
androidmanifest_xml = apkobject.xml['AndroidManifest.xml'] androidmanifest_xml = apkobject.xml['AndroidManifest.xml']
if len(xml.nsmap) > 0: if len(xml.nsmap) > 0:
# one of them surely will be the Android one, or its corrupt # one of them surely will be the Android one, or its corrupt
xmlns = '{http://schemas.android.com/apk/res/android}' xmlns = common.XMLNS_ANDROID
else: else:
# strange but sometimes the namespace is blank. This seems to # strange but sometimes the namespace is blank. This seems to
# only happen with the Bromite/Chromium APKs # only happen with the Bromite/Chromium APKs

View File

@ -69,6 +69,7 @@ setup(name='fdroidserver',
install_requires=[ install_requires=[
'androguard >= 3.1.0rc2', 'androguard >= 3.1.0rc2',
'clint', 'clint',
'defusedxml',
'GitPython', 'GitPython',
'mwclient', 'mwclient',
'paramiko', 'paramiko',

View File

@ -681,6 +681,12 @@ class CommonTest(unittest.TestCase):
sig = fdroidserver.common.metadata_find_developer_signature('org.smssecure.smssecure') sig = fdroidserver.common.metadata_find_developer_signature('org.smssecure.smssecure')
self.assertEqual('b30bb971af0d134866e158ec748fcd553df97c150f58b0a963190bbafbeb0868', sig) self.assertEqual('b30bb971af0d134866e158ec748fcd553df97c150f58b0a963190bbafbeb0868', sig)
def test_parse_xml(self):
manifest = os.path.join('source-files', 'fdroid', 'fdroidclient', 'AndroidManifest.xml')
parsed = fdroidserver.common.parse_xml(manifest)
self.assertIsNotNone(parsed)
self.assertEqual(str(type(parsed)), "<class 'xml.etree.ElementTree.Element'>")
def test_parse_androidmanifests(self): def test_parse_androidmanifests(self):
app = fdroidserver.metadata.App() app = fdroidserver.metadata.App()
app.id = 'org.fdroid.fdroid' app.id = 'org.fdroid.fdroid'
@ -800,13 +806,26 @@ class CommonTest(unittest.TestCase):
fdroidserver.common.parse_androidmanifests(paths, app)) fdroidserver.common.parse_androidmanifests(paths, app))
def test_calculate_math_string(self): def test_calculate_math_string(self):
self.assertEqual(1234, fdroidserver.common.calculate_math_string('1234')) self.assertEqual(1234,
self.assertEqual(4, fdroidserver.common.calculate_math_string('(1+1)*2')) fdroidserver.common.calculate_math_string('1234'))
self.assertEqual(2, fdroidserver.common.calculate_math_string('(1-1)*2+3*1-1')) self.assertEqual((1 + 1) * 2,
fdroidserver.common.calculate_math_string('(1 + 1) * 2'))
self.assertEqual((1 - 1) * 2 + 3 * 1 - 1,
fdroidserver.common.calculate_math_string('(1 - 1) * 2 + 3 * 1 - 1'))
self.assertEqual(0 - 12345,
fdroidserver.common.calculate_math_string('0 - 12345'))
self.assertEqual(0xffff,
fdroidserver.common.calculate_math_string('0xffff'))
self.assertEqual(0xcafe * 123,
fdroidserver.common.calculate_math_string('0xcafe * 123'))
self.assertEqual(-1,
fdroidserver.common.calculate_math_string('-1'))
with self.assertRaises(SyntaxError): with self.assertRaises(SyntaxError):
fdroidserver.common.calculate_math_string('__import__("urllib")') fdroidserver.common.calculate_math_string('__import__("urllib")')
with self.assertRaises(SyntaxError): with self.assertRaises(SyntaxError):
fdroidserver.common.calculate_math_string('self') fdroidserver.common.calculate_math_string('self')
with self.assertRaises(SyntaxError):
fdroidserver.common.calculate_math_string('Ox9()')
with self.assertRaises(SyntaxError): with self.assertRaises(SyntaxError):
fdroidserver.common.calculate_math_string('1+1; print(1)') fdroidserver.common.calculate_math_string('1+1; print(1)')
with self.assertRaises(SyntaxError): with self.assertRaises(SyntaxError):