Greetings,

I have been reviewing the recent vulnerability report by Ron Ben Yizhak 
regarding CREDENTIALS_DIRECTORY, as well as commit 4db2f19f which introduces 
unsetenv("CREDENTIALS_DIRECTORY") to address the problem.

After becoming aware of CVE-2026-24061 (telnetd in GNU Inetutils through 2.7 
allows remote authentication bypass via a "-f root" value for the USER 
environment variable), I was curious to find out whether there'd also been a 
potential regression of CVE-1999-0073, described as: telnet allows a remote 
client to specify environment variables including LD_LIBRARY_PATH, allowing an 
attacker to bypass the normal system libraries and gain root access. I can 
confirm that this is still an issue 27 years later, despite attempts at 
blacklisting environment variables by prefix or full name.

The problem stems from telnetd executing /bin/login in a root-to-root context, 
which means that AT_SECURE is set to 0 by the kernel in the process's auxiliary 
vector. When AT_SECURE holds a positive value, it informs the dynamic linker 
(ld-linux.so) and libc to enter a "secure-execution mode" where a bunch of 
interesting environment variables are discarded or, at least, defanged if 
present. In other words, the responsibility is on telnetd itself to ensure that 
none of those potentially interesting, and attacker controlled, variables make 
their way to /bin/login.

While using unsetenv() negates a user's ability to exploit the login.noauth 
vector, the possibility still exists for the inclusion of variables of interest 
to GNU gettext (such as OUTPUT_CHARSET or LANGUAGE) and glibc (such as 
GCONV_PATH) via the telnet protocol itself.

For example, by injecting OUTPUT_CHARSET and LANGUAGE, an attacker can persuade 
gettext that a character set conversion is necessary. This forces gettext to 
call libc's iconv_open(), and because AT_SECURE is 0, iconv_open() will use an 
injected GCONV_PATH in its quest for a gconv-modules file. Assuming the 
attacker already has a local unprivileged account, or at least a means of 
uploading files to the host (and knowing the location of the uploaded files), a 
custom gconv-modules file will allow arbitrary shared objects to be loaded soon 
after /bin/login attempts to print a localized prompt.

For proof of concept, I've declared a broad selection of LANGUAGE codes for the 
best chance of matching an installed locale. An attacker with local access 
could simply determine what's actually installed and select only one that 
doesn't match the system's default locale instead. Similarly, OUTPUT_CHARSET 
has been chosen as a deliberate mismatch against the very common choice of 
UTF-8:

  [email protected]:~$ ls -al .gconv
  total 184
  drwxr-xr-x 2 abuser abuser   4096 Jan  1  1970 .
  drwxr-x--- 5 abuser abuser  36864 Jan  1  1970 ..
  -rw-r--r-- 1 abuser abuser    256 Jan  1  1970 gconv-modules
  -rw-r--r-- 1 abuser abuser  15568 Jan  1  1970 libcash2trash.so


  [email protected]:~$ telnet -l abuser
  telnet> environ define GCONV_PATH /home/abuser/.gconv
  telnet> environ export GCONV_PATH
  telnet> environ define LANGUAGE fr:de:es:it:pt:nl:sv:pl:uk:ru:zh_CN:ko:ja
  telnet> environ export LANGUAGE
  telnet> environ define OUTPUT_CHARSET ISO-8859-1
  telnet> environ export OUTPUT_CHARSET
  telnet> open 127.0.0.1
  Trying 127.0.0.1...
  Connected to 127.0.0.1.
  Escape character is '^]'.
  
  Linux (localhost) (pts/6)
  
  Connection closed by foreign host.


  [email protected]:~$ ls -al .gconv
  total 184
  drwxr-xr-x 2 abuser abuser   4096 Jan  1  1970 .
  drwxr-x--- 5 abuser abuser  36864 Jan  1  1970 ..
  -rw-r--r-- 1 abuser abuser    256 Jan  1  1970 gconv-modules
  -rw-r--r-- 1 abuser abuser  15568 Jan  1  1970 libcash2trash.so
  -rwsr-sr-x 1 root   root   125640 Jan  1  1970 trash


  [email protected]:~$ .gconv/trash -p
  # id
  uid=1001(abuser) gid=1002(abuser) euid=0(root) egid=0(root) 
groups=0(root),1002(abuser)


Once the telnet connection opens, /bin/login tries to print the localized 
prompt but gettext recognizes the encoding mismatch and calls iconv_open() to 
parse the gconv-modules file in the directory referenced by the injected path 
before loading the shared object that turns cash ($) to trash (#). The 
connection drops because I included a call to exit() once the payload has 
executed. As illustrated above, the payload effectively asserts root privilege 
and makes a copy of /bin/sh with SUID/SGID permissions. Note that no 
authentication via telnetd was required, nor performed, for this privilege 
escalation trick to occur. Also note that this is just one of many possible 
methods that may be used to exploit this condition.

In my opinion, to fix this issue and finally put the ghost of CVE-1999-0073 to 
rest: telnetd must drop the blacklist approach and adopt the OpenSSH 
AcceptEnv-style approach suggested by Simon Josefsson [1], which amounts to 
preparing a brand new environment for /bin/login based on a strict whitelist of 
variables names considered to be "safe", and perhaps a healthy dose of input 
sanitization for their respective values.

In terms of the CVE that Ron Ben Yizhak had asked about earlier in the thread: 
I think it might make the most sense to co-ordinate a single CVE for "Improper 
environment sanitization in telnetd" that comprehensively covers both the 
CREDENTIALS_DIRECTORY vector and this dynamic linker escape.

I'm happy to share the intentionally redacted payload privately with the 
maintainers should any help be required to reproduce the proof of concept.

Regards,
Justin

---

[1] https://lists.gnu.org/archive/html/bug-inetutils/2026-02/msg00002.html

Reply via email to