Follow @Openwall on Twitter for new release announcements and other news
[<prev] [next>] [day] [month] [year] [list]
Message-ID: <Z3bEuhwZJuIYpTqg@nihonium>
Date: Thu, 2 Jan 2025 17:54:18 +0100
From: Fay Stegerman <flx@...usk.net>
To: oss-security@...ts.openwall.com
Subject: Another fdroidserver AllowedAPKSigningKeys certificate pinning bypass

Hi!

Another update for [1,2], still published at [3]: two more PoCs (bringing
the total to 5), patches for the 5th PoC, and an updated script to scan for
potentially affected APKs.  I've attached the new and updated files and
included the new sections from the README with the updates below.

- Fay

[1] https://www.openwall.com/lists/oss-security/2024/04/08/8
[2] https://www.openwall.com/lists/oss-security/2024/04/20/3
[3] https://github.com/obfusk/fdroid-fakesigner-poc

============================================================================

# F-Droid Fake Signer PoC

PoC for fdroidserver AllowedAPKSigningKeys certificate pinning bypass.

Published: 2024-04-08; updated: 2024-04-14, 2024-04-20, 2024-12-30.

[...]

### [Observations] Update (2024-12-30 #1)

Instead of adopting the fixes we proposed, F-Droid wrote and merged their
own patch [10], ignoring repeated warnings it had significant flaws
(including an incorrect implementation of v1 signature verification and
making it impossible to have APKs with rotated keys in a repository).  As a
result it is possible to construct a valid v1 signature that fdroidserver
matches to the wrong certificate.

We do this by simply creating and prepending a second SignerInfo using our
own certificate, which has the same serial number and an almost identical
issuer -- e.g. a common name with a space (0x20) replaced by a tab (0x09) or
a DEL (0x7f) appended -- to exploit an implementation that will match the
SignerInfo against the wrong certificate through incorrect canonicalisation.

Luckily, the impact is lower than that of the other vulnerabilities as it
does require a valid signature from the certificate one wishes to spoof.

### [Observations] Update (2024-12-30 #2)

Unfortunately, we found another more severe vulnerability as well, caused by
a regex incorrectly handling newlines in filenames.  This allows another
trivial bypass of certificate pinning, as we can once again make
fdroidserver see whatever certificate we want instead of the one
Android/apksigner does (as long as we have a valid v1 signature for some
other APK).

The regex in question -- ^META-INF/.*\.(DSA|EC|RSA)$ -- is supposed to match
all filenames that start with META-INF/ and end with .DSA, .EC, or .RSA.
Unfortunately, the ".*" does not match newlines, and the "$" matches not
just the end of the string but "the end of the string or just before the
newline at the end of the string".  As a result we can use a newline in the
filename of the real signature files (before the extension), which
Android/apksigner see but fdroidserver does not, and a newline after the
.RSA extension for the spoofed signature files, which fdroidserver will see
but Android/apksigner will not.

NB: androguard seems to use a similarly incorrect regex.

We can do almost the exact same thing with NUL bytes instead of newlines,
independently of the flawed regex, because Python's ZipInfo.filename is
sanitised by removing any NUL byte and everything after it.  This will have
the same result for fdroidserver and apksigner (which happily accepts NUL
bytes in filenames) as above, but luckily Android rejects APKs with NUL
bytes in filenames, and such an APK will thus fail to install.

NB: in light of all of the above we reiterate that we strongly recommend
using the official apksig library (used by apksigner) to both verify APK
signatures and return the first signer's certificate to avoid these kind of
implementation mistakes and inconsistencies and thus further
vulnerabilities.  Handling common cases correctly is fairly easy, but
handling edge cases correctly is hard; rolling your own implementation
without the required expertise and care to get it right is irresponsible.

[...]

### [PoC] Update (2024-12-30 #1)

NB: for convenience we generate our own key for the spoofed certificate as
well; for a real exploit we'd have a v1-signed APK to use here instead of
signing one ourselves.

```bash
$ ./make-key-v4.sh          # generates a dummy key
$ sha256sum cert-rsa-fake.der cert-rsa-orig.der
29c6fc6cfa20c2726721944a659a4293c5ac7e8090ab5faa8e26f64ba007bea4  cert-rsa-fake.der
1e8a45fa677f82755b63edee209fee92081ba822d4f425c3792a1980bfa3fca9  cert-rsa-orig.der
$ python3 make-poc-v4.py    # uses app3.apk (needs minSdk >= 24 & targetSdk < 30)
$ python3 fdroid.py         # verifies and has the wrong signer according to F-Droid
True
ERROR:root:"Signature is invalid", skipping:
  1e8a45fa677f82755b63edee209fee92081ba822d4f425c3792a1980bfa3fca9
  Common Name: Foo Bar
1e8a45fa677f82755b63edee209fee92081ba822d4f425c3792a1980bfa3fca9
$ apksigner verify -v --print-certs poc.apk | grep -E '^Verified using|Signer #1 certificate (DN|SHA-256)'
Verified using v1 scheme (JAR signing): true
Verified using v2 scheme (APK Signature Scheme v2): false
Verified using v3 scheme (APK Signature Scheme v3): false
Verified using v4 scheme (APK Signature Scheme v4): false
Signer #1 certificate DN: CN=Foo        Bar
Signer #1 certificate SHA-256 digest: 29c6fc6cfa20c2726721944a659a4293c5ac7e8090ab5faa8e26f64ba007bea4
```

### [PoC] Update (2024-12-30 #2)

NB: version A uses newlines, version B NUL bytes (which makes it fail to
actually install on Android devices despite verifying with apksigner).

```bash
$ python3 make-poc-v5a.py   # uses app3.apk (needs targetSdk < 30) as base, adds fake.apk .RSA
$ python3 fdroid.py         # verifies and has fake.apk as signer according to F-Droid
True
43238d512c1e5eb2d6569f4a3afbf5523418b82e0a3ed1552770abb9a9c9ccab
```

```bash
$ python3 make-poc-v5b.py   # uses app3.apk (needs targetSdk < 30) as base, adds fake.apk .RSA
$ python3 fdroid.py         # verifies and has fake.apk as signer according to F-Droid
True
43238d512c1e5eb2d6569f4a3afbf5523418b82e0a3ed1552770abb9a9c9ccab
```

[...]

### [Patch] Update (2024-12-30)

The fdroidserver-regex.patch fixes the regex to correctly handle newlines.

The fdroidserver-null-v1.patch (for fdroidserver before the changes we
recommended against) and fdroidserver-null-v2.patch (for current
fdroidserver) use ZipInfo.orig_filename to handle NUL bytes properly (and
avoid other potential issues).

[...]

### [Scanner] Update (2024-12-30)

The scan.py script has been updated to check for APK Signature Scheme v3.1
blocks (which will likely give false positives needing manual inspection as
those are expected to differ with key rotation) as well as NUL/LF/CR in
filenames and to use ZipInfo.orig_filename.

NB: currently, neither fdroidserver nor androguard will see APK Signature
Scheme v3.1 blocks.

```bash
$ python3 scan.py poc[45]*.apk
'poc4.apk': Multiple certificates in signature block file
'poc5a.apk': NUL, LF, or CR in filename
'poc5b.apk': NUL, LF, or CR in filename
```

## References

[...]

* [10] https://gitlab.com/fdroid/fdroidserver/-/merge_requests/1466

[...]

View attachment "fdroidserver-null-v1.patch" of type "text/x-diff" (1054 bytes)

View attachment "fdroidserver-null-v2.patch" of type "text/x-diff" (2172 bytes)

View attachment "fdroidserver-regex.patch" of type "text/x-diff" (757 bytes)

Download attachment "make-key-v4.sh" of type "application/x-sh" (458 bytes)

View attachment "make-poc-v4.py" of type "text/x-python" (4087 bytes)

View attachment "make-poc-v5a.py" of type "text/x-python" (1095 bytes)

View attachment "make-poc-v5b.py" of type "text/x-python" (1180 bytes)

View attachment "scan.py" of type "text/x-python" (6336 bytes)

Powered by blists - more mailing lists

Please check out the Open Source Software Security Wiki, which is counterpart to this mailing list.

Confused about mailing lists and their use? Read about mailing lists on Wikipedia and check out these guidelines on proper formatting of your messages.