From bde0558d82eb68c39d6c95eb80ed9c2eddea6ae6 Mon Sep 17 00:00:00 2001 From: Hans-Christoph Steiner Date: Mon, 11 Dec 2017 18:36:21 +0100 Subject: [PATCH] update: reject APKs with invalid file sig, probably Janus exploits This just checks the first four bytes of the APK file, aka the "file signature", to make sure it is the ZIP signature and not the DEX signature. This was checked against the test APK, and I ran it against some known malware and all of f-droid.org to make sure it works. All valid ZIP files (therefore APK files) should start with the ZIP Local File Header of four bytes. https://www.guardsquare.com/en/blog/new-android-vulnerability-allows-attackers-modify-apps-without-affecting-their-signatures --- fdroidserver/update.py | 11 ++++++++++- tests/janus.apk | Bin 0 -> 10067 bytes tests/run-tests | 2 +- tests/update.TestCase | 29 +++++++++++++++++++++++++++++ 4 files changed, 40 insertions(+), 2 deletions(-) create mode 100644 tests/janus.apk diff --git a/fdroidserver/update.py b/fdroidserver/update.py index e548df43..f90e4993 100644 --- a/fdroidserver/update.py +++ b/fdroidserver/update.py @@ -496,8 +496,10 @@ def has_known_vulnerability(filename): Checks whether there are more than one classes.dex or AndroidManifest.xml files, which is invalid and an essential part of the "Master Key" attack. - http://www.saurik.com/id/17 + + Janus is similar to Master Key but is perhaps easier to scan for. + https://www.guardsquare.com/en/blog/new-android-vulnerability-allows-attackers-modify-apps-without-affecting-their-signatures """ found_vuln = False @@ -506,6 +508,13 @@ def has_known_vulnerability(filename): if not hasattr(has_known_vulnerability, "pattern"): has_known_vulnerability.pattern = re.compile(b'.*OpenSSL ([01][0-9a-z.-]+)') + with open(filename.encode(), 'rb') as fp: + first4 = fp.read(4) + if first4 != b'\x50\x4b\x03\x04': + raise FDroidException(_('{path} has bad file signature "{pattern}", possible Janus exploit!') + .format(path=filename, pattern=first4.decode().replace('\n', ' ')) + '\n' + + 'https://www.guardsquare.com/en/blog/new-android-vulnerability-allows-attackers-modify-apps-without-affecting-their-signatures') + files_in_apk = set() with zipfile.ZipFile(filename) as zf: for name in zf.namelist(): diff --git a/tests/janus.apk b/tests/janus.apk new file mode 100644 index 0000000000000000000000000000000000000000..ed4eed96c01647047e6420b778e885236b833c67 GIT binary patch literal 10067 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 literal 0 HcmV?d00001 diff --git a/tests/run-tests b/tests/run-tests index 696bcd75..af29f471 100755 --- a/tests/run-tests +++ b/tests/run-tests @@ -9,7 +9,7 @@ echo_header() { copy_apks_into_repo() { set +x find $APKDIR -type f -name '*.apk' -print0 | while IFS= read -r -d '' f; do - echo $f | grep -F -v -e unaligned -e unsigned -e badsig -e badcert -e bad-unicode || continue + echo $f | grep -F -v -e unaligned -e unsigned -e badsig -e badcert -e bad-unicode -e janus.apk || continue apk=`$aapt dump badging "$f" | sed -n "s,^package: name='\(.*\)' versionCode='\([0-9][0-9]*\)' .*,\1_\2.apk,p"` test "$f" -nt repo/$apk && rm -f repo/$apk # delete existing if $f is newer if [ ! -e repo/$apk ] && [ ! -e archive/$apk ]; then diff --git a/tests/update.TestCase b/tests/update.TestCase index e49e1bf0..db463a89 100755 --- a/tests/update.TestCase +++ b/tests/update.TestCase @@ -601,6 +601,35 @@ class UpdateTest(unittest.TestCase): self.assertEqual('urzip', data['Name']) self.assertEqual('urzip', data['Summary']) + def test_has_known_vulnerability(self): + good = [ + 'org.bitbucket.tickytacky.mirrormirror_1.apk', + 'org.bitbucket.tickytacky.mirrormirror_2.apk', + 'org.bitbucket.tickytacky.mirrormirror_3.apk', + 'org.bitbucket.tickytacky.mirrormirror_4.apk', + 'org.dyndns.fules.ck_20.apk', + 'urzip.apk', + 'urzip-badcert.apk', + 'urzip-badsig.apk', + 'urzip-release.apk', + 'urzip-release-unsigned.apk', + 'repo/com.politedroid_3.apk', + 'repo/com.politedroid_4.apk', + 'repo/com.politedroid_5.apk', + 'repo/com.politedroid_6.apk', + 'repo/obb.main.oldversion_1444412523.apk', + 'repo/obb.mainpatch.current_1619_another-release-key.apk', + 'repo/obb.mainpatch.current_1619.apk', + 'repo/obb.main.twoversions_1101613.apk', + 'repo/obb.main.twoversions_1101615.apk', + 'repo/obb.main.twoversions_1101617.apk', + 'repo/urzip-; Рахма́нинов, [rɐxˈmanʲɪnəf] سيرجي_رخمانينوف 谢尔盖·.apk', + ] + for f in good: + self.assertFalse(fdroidserver.update.has_known_vulnerability(f)) + with self.assertRaises(fdroidserver.exception.FDroidException): + fdroidserver.update.has_known_vulnerability('janus.apk') + if __name__ == "__main__": parser = optparse.OptionParser()