Browse Source

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
merge-requests/409/head
Hans-Christoph Steiner 3 years ago
parent
commit
bde0558d82
4 changed files with 40 additions and 2 deletions
  1. +10
    -1
      fdroidserver/update.py
  2. BIN
      tests/janus.apk
  3. +1
    -1
      tests/run-tests
  4. +29
    -0
      tests/update.TestCase

+ 10
- 1
fdroidserver/update.py View File

@ -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():

BIN
tests/janus.apk View File


+ 1
- 1
tests/run-tests View File

@ -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

+ 29
- 0
tests/update.TestCase View File

@ -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()

Loading…
Cancel
Save