<story><title>Remote code execution in OpenSSH’s forwarded SSH-agent</title><url>https://blog.qualys.com/vulnerabilities-threat-research/2023/07/19/cve-2023-38408-remote-code-execution-in-opensshs-forwarded-ssh-agent</url></story><parent_chain><item><author>tptacek</author><text>This is the bug of the year.&lt;p&gt;It&amp;#x27;s well established that if Alice forwards an SSH agent to Bob, Bob can use the SSH agent protocol to make Alice open DLLs, because there&amp;#x27;s an agent protocol command (SSH_AGENTC_ADD_SMARTCARD_KEY) that OpenSSH implements with dlopen: when you ask the agent to access a smart card, OpenSSH dlopen()&amp;#x27;s the library corresponding to the `id` of the device. This is a Jann Horn bug from 2016, and OpenSSH fixed it by whitelisting DLLs to &amp;#x2F;usr&amp;#x2F;lib and directories like it.&lt;p&gt;The Qualys bug builds on Horn&amp;#x27;s bug. When OpenSSH dlopen()&amp;#x27;s the library, it then tries to look up a PKCS#11 entry point function, and, when it doesn&amp;#x27;t find it, it dlclose()&amp;#x27;s the library and returns an error.&lt;p&gt;The issue is that most of the libraries in system library paths were never intended to be opened maliciously, and so they do all sorts of stuff in their constructors and destructors (any function marked `__attribute__((constructor))` or `destructor` is called by dlopen and dlclose respectively). In particular, they register callbacks and signal handlers. Most of these libraries are never expected to dlclose at all, so they tend not to be great about cleaning up. Better still, if you randomly load oddball libraries into random programs, some of them crash, generating SIGBUS and SIGSEGV.&lt;p&gt;So you&amp;#x27;ve got a classic UAF situation here: (1) force Alice to load a library that registers a SIGBUS handler; it won&amp;#x27;t be a PKCS#11 handler so it&amp;#x27;ll get immediately dlclose()&amp;#x27;d, but won&amp;#x27;t clean up the handler. (2) Load another library, which will take over the program text address the signal handler points to. (3) Finally, load a library that SIGBUS&amp;#x27;s. If you manage to get a controlled jump swapped into place in step (2), you win.&lt;p&gt;If you&amp;#x27;re thinking &amp;quot;it&amp;#x27;s pretty unlikely you&amp;#x27;re going to be able to line up a controlled jump at exactly the address previously registered as a signal handler&amp;quot;, you&amp;#x27;re right, but there&amp;#x27;s another quirk of dlclose() they take advantage of: there&amp;#x27;s an ELF flag, NODELETE, that instructs the linker not to unmap a library when it&amp;#x27;s unloaded, and a bunch of standard libraries set it, so you can use those libraries to groom the address space.&lt;p&gt;Finally, because some runtimes require executable stacks, there are standard libraries with an ELF flag that instructs the process to make the stack executable. If you load one of these libraries, and you have a controlled jump, you can write shellcode into the stack like it&amp;#x27;s 1998.&lt;p&gt;To figure out the right sequence of steps, they basically recapitulated the original ROP gadget research idea: they swept all the standard Ubuntu libraries with a fuzzer to find combinations of loads that produced controlled jumps (ie, that died trying to execute stack addresses).&lt;p&gt;A working exploit loads a pattern of &amp;quot;smartcards&amp;quot; that looks like this (all in &amp;#x2F;usr&amp;#x2F;lib):&lt;p&gt;&lt;pre&gt;&lt;code&gt;    syslinux&amp;#x2F;modules&amp;#x2F;efi64&amp;#x2F;gfxboot.c32 (execstack)
    pulse-15.0+dfsg1&amp;#x2F;modules&amp;#x2F;module-remap-sink.so (groom)
    x86_64-linux-gnu&amp;#x2F;libgnatcoll_postgres.so.1 (SIGBUS handler)
    pulse-15.0+dfsg1&amp;#x2F;modules&amp;#x2F;module-http-protocol-unix.so (groom)
    x86_64-linux-gnu&amp;#x2F;sane&amp;#x2F;libsane-hp.so.1.0.32 (groom)
    libreoffice&amp;#x2F;program&amp;#x2F;libindex_data.so (groom)
    x86_64-linux-gnu&amp;#x2F;gstreamer-1.0&amp;#x2F;libgstaudiorate.so (groom)
    libreoffice&amp;#x2F;program&amp;#x2F;libscriptframe.so (groom)
    x86_64-linux-gnu&amp;#x2F;libisccc-9.16.15-Ubuntu.so (groom)
    x86_64-linux-gnu&amp;#x2F;libxkbregistry.so.0.0.0 (groom)
    debug&amp;#x2F;.build-id&amp;#x2F;15&amp;#x2F;c0bee6bcb06fbf381d0e0e6c52f71e1d1bd694.debug (SIGBUS)
&lt;/code&gt;&lt;/pre&gt;
The paper goes on to classify like 4 more patterns whereby you can get unexpected control transfers by dlopen() and immediately dlclosing() libraries. The kicker:&lt;p&gt;&lt;pre&gt;&lt;code&gt;    we noticed that one shared library&amp;#x27;s constructor function
    (which can be invoked by a remote attacker via an ssh-agent forwarding)
    starts a server thread that listens on a TCP port, and we discovered a
    remotely exploitable vulnerability (a heap-based buffer overflow) in
    this server&amp;#x27;s implementation.&lt;/code&gt;&lt;/pre&gt;</text></item></parent_chain><comment><author>jeffbee</author><text>This is pretty wild, but if anyone had asked me previously &amp;quot;should the SSH agent be allowed to dlopen anything?&amp;quot; I would have said &amp;quot;no&amp;quot;. It really seems like for something this sensitive, running it in an empty namespace with no abilities of any kind, unless or until those abilities prove necessary, would be a good approach for those who have high security requirements. If I know I am going to use a smartcard, then I can make the PKCS library visible inside the agent&amp;#x27;s sandbox. But I can see no reason why I would have given it access to libsane, or whatever libenca is, or any other library for that matter.</text></comment>
<story><title>Remote code execution in OpenSSH’s forwarded SSH-agent</title><url>https://blog.qualys.com/vulnerabilities-threat-research/2023/07/19/cve-2023-38408-remote-code-execution-in-opensshs-forwarded-ssh-agent</url></story><parent_chain><item><author>tptacek</author><text>This is the bug of the year.&lt;p&gt;It&amp;#x27;s well established that if Alice forwards an SSH agent to Bob, Bob can use the SSH agent protocol to make Alice open DLLs, because there&amp;#x27;s an agent protocol command (SSH_AGENTC_ADD_SMARTCARD_KEY) that OpenSSH implements with dlopen: when you ask the agent to access a smart card, OpenSSH dlopen()&amp;#x27;s the library corresponding to the `id` of the device. This is a Jann Horn bug from 2016, and OpenSSH fixed it by whitelisting DLLs to &amp;#x2F;usr&amp;#x2F;lib and directories like it.&lt;p&gt;The Qualys bug builds on Horn&amp;#x27;s bug. When OpenSSH dlopen()&amp;#x27;s the library, it then tries to look up a PKCS#11 entry point function, and, when it doesn&amp;#x27;t find it, it dlclose()&amp;#x27;s the library and returns an error.&lt;p&gt;The issue is that most of the libraries in system library paths were never intended to be opened maliciously, and so they do all sorts of stuff in their constructors and destructors (any function marked `__attribute__((constructor))` or `destructor` is called by dlopen and dlclose respectively). In particular, they register callbacks and signal handlers. Most of these libraries are never expected to dlclose at all, so they tend not to be great about cleaning up. Better still, if you randomly load oddball libraries into random programs, some of them crash, generating SIGBUS and SIGSEGV.&lt;p&gt;So you&amp;#x27;ve got a classic UAF situation here: (1) force Alice to load a library that registers a SIGBUS handler; it won&amp;#x27;t be a PKCS#11 handler so it&amp;#x27;ll get immediately dlclose()&amp;#x27;d, but won&amp;#x27;t clean up the handler. (2) Load another library, which will take over the program text address the signal handler points to. (3) Finally, load a library that SIGBUS&amp;#x27;s. If you manage to get a controlled jump swapped into place in step (2), you win.&lt;p&gt;If you&amp;#x27;re thinking &amp;quot;it&amp;#x27;s pretty unlikely you&amp;#x27;re going to be able to line up a controlled jump at exactly the address previously registered as a signal handler&amp;quot;, you&amp;#x27;re right, but there&amp;#x27;s another quirk of dlclose() they take advantage of: there&amp;#x27;s an ELF flag, NODELETE, that instructs the linker not to unmap a library when it&amp;#x27;s unloaded, and a bunch of standard libraries set it, so you can use those libraries to groom the address space.&lt;p&gt;Finally, because some runtimes require executable stacks, there are standard libraries with an ELF flag that instructs the process to make the stack executable. If you load one of these libraries, and you have a controlled jump, you can write shellcode into the stack like it&amp;#x27;s 1998.&lt;p&gt;To figure out the right sequence of steps, they basically recapitulated the original ROP gadget research idea: they swept all the standard Ubuntu libraries with a fuzzer to find combinations of loads that produced controlled jumps (ie, that died trying to execute stack addresses).&lt;p&gt;A working exploit loads a pattern of &amp;quot;smartcards&amp;quot; that looks like this (all in &amp;#x2F;usr&amp;#x2F;lib):&lt;p&gt;&lt;pre&gt;&lt;code&gt;    syslinux&amp;#x2F;modules&amp;#x2F;efi64&amp;#x2F;gfxboot.c32 (execstack)
    pulse-15.0+dfsg1&amp;#x2F;modules&amp;#x2F;module-remap-sink.so (groom)
    x86_64-linux-gnu&amp;#x2F;libgnatcoll_postgres.so.1 (SIGBUS handler)
    pulse-15.0+dfsg1&amp;#x2F;modules&amp;#x2F;module-http-protocol-unix.so (groom)
    x86_64-linux-gnu&amp;#x2F;sane&amp;#x2F;libsane-hp.so.1.0.32 (groom)
    libreoffice&amp;#x2F;program&amp;#x2F;libindex_data.so (groom)
    x86_64-linux-gnu&amp;#x2F;gstreamer-1.0&amp;#x2F;libgstaudiorate.so (groom)
    libreoffice&amp;#x2F;program&amp;#x2F;libscriptframe.so (groom)
    x86_64-linux-gnu&amp;#x2F;libisccc-9.16.15-Ubuntu.so (groom)
    x86_64-linux-gnu&amp;#x2F;libxkbregistry.so.0.0.0 (groom)
    debug&amp;#x2F;.build-id&amp;#x2F;15&amp;#x2F;c0bee6bcb06fbf381d0e0e6c52f71e1d1bd694.debug (SIGBUS)
&lt;/code&gt;&lt;/pre&gt;
The paper goes on to classify like 4 more patterns whereby you can get unexpected control transfers by dlopen() and immediately dlclosing() libraries. The kicker:&lt;p&gt;&lt;pre&gt;&lt;code&gt;    we noticed that one shared library&amp;#x27;s constructor function
    (which can be invoked by a remote attacker via an ssh-agent forwarding)
    starts a server thread that listens on a TCP port, and we discovered a
    remotely exploitable vulnerability (a heap-based buffer overflow) in
    this server&amp;#x27;s implementation.&lt;/code&gt;&lt;/pre&gt;</text></item></parent_chain><comment><author>brasic</author><text>How would this even be mitigated while preserving the (wacky) existing support for runtime-selected PKCS#11 provider libraries? It strikes me that the most compatible way might be to double down on the wackiness and try to perform the required feature detection in some more indirect way like parsing the named lib with readelf(1) or the platform equivalent.&lt;p&gt;The sensible thing would be to force users to register available provider shared libraries in an ssh-agent config file, but that feels like a pretty big breaking change.&lt;p&gt;Edit: Didn’t realize a patch was already available. I see that they did in fact fix this with a breaking change, by simply disabling the functionality by default, and recommending that users allowlist their specific libraries:&lt;p&gt;&lt;pre&gt;&lt;code&gt;  Potentially-incompatible changes
  --------------------------------
  
   * ssh-agent(8): the agent will now refuse requests to load PKCS#11
     modules issued by remote clients by default. A flag has been added
     to restore the previous behaviour &amp;quot;-Oallow-remote-pkcs11&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;a href=&quot;https:&amp;#x2F;&amp;#x2F;www.openssh.com&amp;#x2F;releasenotes.html#9.3p2&quot; rel=&quot;nofollow noreferrer&quot;&gt;https:&amp;#x2F;&amp;#x2F;www.openssh.com&amp;#x2F;releasenotes.html#9.3p2&lt;/a&gt;</text></comment>