Hi,
our team member, Shi Weiming, would like to report the following bug in GNU
inetutils telnetd.
# GNU inetutils telnetd Local Privilege Escalation via GCONV_PATH
Environment Variable Injection
## Summary
| Field | Value |
|-------|-------|
| Vulnerability Type | CWE-269: Improper Privilege Management |
| Affected Software | GNU inetutils telnetd ≤ 2.7 |
| Severity | High (CVSS 3.1: 7.8) |
| Attack Vector | Local (requires valid local account + telnetd running) |
| Impact | Local Privilege Escalation to root |
| Related CVE | CVE-2026-28372 (CREDENTIALS_DIRECTORY vector, same root
cause) |
| Related CVE | CVE-2026-24061 (Remote auth bypass via USER, same root
cause) |
| Fix Status | **Unpatched** as of inetutils 2.7 (latest release,
2025-12-14) |
**Note:** CVE-2026-28372 officially describes only the
CREDENTIALS_DIRECTORY attack vector. The GCONV_PATH exploitation path
documented in this report shares the same root cause (incomplete
`scrub_env()` blacklist) but represents a **distinct and independently
exploitable attack vector** that has not been assigned a separate CVE as of
this writing. Unlike the CREDENTIALS_DIRECTORY vector which requires
util-linux ≥ 2.40 login, the GCONV_PATH vector works against **any** login
implementation on any glibc-based system.
## Affected Versions
| Distribution | Package Version | Upstream Version | Status |
|---|---|---|---|
| GNU inetutils upstream | - | ≤ 2.7 | Vulnerable |
| Debian sid (unstable) | 2:2.7-3 | 2.7 | **Confirmed Exploitable** |
| Debian bullseye (11) | 2:2.0-1+deb11u3 | 2.0 | **Confirmed Exploitable** |
| Debian bookworm (12) | 2:2.4-2+deb12u2 | 2.4 | Vulnerable |
| Debian trixie (13) | 2:2.6-3+deb13u1 | 2.6 | Vulnerable |
| Ubuntu 24.04 | 2:2.5-3ubuntu4 | 2.5 | Vulnerable |
| Ubuntu 24.10 | 2:2.5-5ubuntu1 | 2.5 | Vulnerable |
## Comparison with CVE-2026-28372 (CREDENTIALS_DIRECTORY)
| Aspect | CVE-2026-28372 (CREDENTIALS_DIRECTORY) | This Report
(GCONV_PATH) |
|--------|----------------------------------------|--------------------------|
| Root Cause | scrub_env() blacklist incomplete | scrub_env() blacklist
incomplete |
| Injected Variables | CREDENTIALS_DIRECTORY | GCONV_PATH, OUTPUT_CHARSET,
LANG, LANGUAGE |
| Prerequisite | util-linux login ≥ 2.40 (systemd credential support) | Any
glibc-based login (no version restriction) |
| Trigger Mechanism | login reads login.noauth from CREDENTIALS_DIRECTORY |
gettext → iconv_open → dlopen(evil.so) |
| Applicable Systems | Only systems with util-linux ≥ 2.40 | **All
glibc-based Linux systems** |
| CVE Assigned | Yes (CVE-2026-28372) | **No** |
**The GCONV_PATH vector has a broader attack surface** because it does not
depend on a specific version of util-linux login — it exploits glibc's
iconv mechanism which is universally present on Linux systems.
## Vulnerability Description
GNU inetutils telnetd contains a local privilege escalation vulnerability
caused by an insufficient environment variable blacklist in its
`scrub_env()` function. The Telnet protocol's NEW-ENVIRON option (RFC 1572)
allows clients to set arbitrary environment variables on the server side.
Before executing `/bin/login`, telnetd attempts to sanitize the environment
by removing dangerous variables, but the blacklist only covers:
- `LD_*` (Linux dynamic linker)
- `_RLD_*` (IRIX dynamic linker)
- `LIBPATH` (AIX shared library path)
- `IFS` (shell field separator)
This blacklist, dating back to David Borman's 1995 telnet security fix,
fails to filter modern glibc-sensitive variables including `GCONV_PATH`,
`OUTPUT_CHARSET`, `LANG`, and `LANGUAGE`.
Since telnetd runs as root and executes `/bin/login` as root (a
root-to-root transition), the Linux kernel does not set `AT_SECURE=1`.
Without `AT_SECURE`, glibc does not enter secure-execution mode and honors
`GCONV_PATH`, allowing an attacker to load arbitrary shared objects into
the login process.
## Root Cause Analysis
### scrub_env() — Incomplete Blacklist
Source: `telnetd/pty.c` (GNU inetutils ≤ 2.7)
```c
static void
scrub_env (void)
{
char **cpp, **cpp2;
for (cpp2 = cpp = environ; *cpp; cpp++)
{
if (strncmp (*cpp, "LD_", 3)
&& strncmp (*cpp, "_RLD_", 5)
&& strncmp (*cpp, "LIBPATH=", 8)
&& strncmp (*cpp, "IFS=", 4))
*cpp2++ = *cpp;
}
*cpp2 = 0;
}
```
Variables NOT filtered: `GCONV_PATH`, `OUTPUT_CHARSET`, `LANG`, `LANGUAGE`,
`GLIBC_TUNABLES`, `CREDENTIALS_DIRECTORY`, `BASH_ENV`, `ENV`, `PYTHONPATH`,
`PERL5LIB`, `RUBYLIB`, and 90+ others.
Our comprehensive testing injected 115 environment variables via
NEW-ENVIRON. **101 variables survived** into the login process. Only 14
were blocked (LD_*, _RLD_*, LIBPATH, IFS, and a few others filtered by
login itself like PATH, PS1).
### AT_SECURE=0 — No glibc Secure Mode
The Linux kernel sets `AT_SECURE=1` in the auxiliary vector when:
- The real UID ≠ effective UID (setuid programs)
- The real GID ≠ effective GID (setgid programs)
- Process lacks capabilities that the new program gains
telnetd runs as root (uid=0) and executes login as root (uid=0). Since
there is no UID/GID transition, `AT_SECURE` remains 0. glibc's
secure-execution mode, which would normally ignore `GCONV_PATH`, is
therefore **not activated**.
### glibc iconv — GCONV_PATH Module Loading
When `GCONV_PATH` is set and `AT_SECURE=0`, glibc's iconv implementation
searches the specified directory for `gconv-modules` files. If a requested
charset is not found in the system `gconv-modules.cache`, glibc falls back
to scanning `GCONV_PATH` directories, reads the `gconv-modules` file, and
`dlopen()`s the corresponding shared object.
## Exploitation Technique
This exploit adapts the technique from CVE-2021-4034 (PwnKit) to the
telnetd context.
### Attack Chain
```
Attacker's telnet client
│
│ Telnet NEW-ENVIRON option (RFC 1572)
│ Injects: GCONV_PATH, OUTPUT_CHARSET, LANG, LANGUAGE
▼
telnetd (running as root, pid=N)
│
│ scrub_env() — does NOT filter GCONV_PATH
│ execv("/bin/login", ...) — root→root, AT_SECURE=0
▼
/bin/login (running as root, pid=N)
│
│ gettext() → detects LANG=ja_JP.eucjp, LANGUAGE=ja
│ Loads Japanese translation → charset mismatch detected
│ iconv_open("PWNKIT", "EUC-JP")
│ "PWNKIT" not in gconv-modules.cache
│ Searches GCONV_PATH=/home/attacker/.gconv_pwn/
│ Reads gconv-modules → maps PWNKIT to evil.so
│ dlopen("evil.so") → __attribute__((constructor)) runs as root
▼
evil.so constructor (uid=0, euid=0)
│
│ Creates /tmp/rootbash (setuid root copy of /bin/bash)
▼
Attacker runs: /tmp/rootbash -p → euid=0 (root)
```
### Key Environment Variables
| Variable | Value | Purpose |
|----------|-------|---------|
| `GCONV_PATH` | `/home/attacker/.gconv_pwn` | Points glibc to attacker's
gconv-modules + evil.so |
| `OUTPUT_CHARSET` | `PWNKIT` | Fake charset name not in system gconv cache
|
| `LANG` | `ja_JP.eucjp` | Triggers Japanese locale with EUC-JP encoding |
| `LANGUAGE` | `ja` | Forces gettext to load Japanese translations,
requiring charset conversion |
### Why "PWNKIT" Charset?
The system's `gconv-modules.cache` contains mappings for all standard
charsets (UTF-8, ISO-8859-1, EUC-JP, etc.). If the attacker used a real
charset name, glibc would find it in the cache and use the system's
conversion module, never searching `GCONV_PATH`.
By using a non-existent charset name ("PWNKIT"), the cache lookup fails,
and glibc falls back to scanning `GCONV_PATH` directories for a
`gconv-modules` file that maps "PWNKIT" to a shared object.
### Tested Versions
| Version | Result |
|---------|--------|
| inetutils 2.0 (Debian bullseye) | **uid=0 euid=0 — Exploited** |
| inetutils 2.7 (Debian sid, latest) | **uid=0 euid=0 — Exploited** |
## Impact
An unprivileged local user with a valid account on a system running GNU
inetutils telnetd can escalate privileges to root by:
1. Preparing a malicious gconv shared object in their home directory (no
special permissions needed)
2. Connecting to the local telnetd via the telnet protocol
3. Injecting `GCONV_PATH` and charset-related environment variables via the
NEW-ENVIRON option
4. Logging in normally — the login process loads the malicious .so as root
**No password for the root account is needed.** The attacker only needs
their own valid credentials. The exploit is reliable, deterministic, and
works regardless of which login implementation (shadow-utils, util-linux)
is used.
Unlike CVE-2026-28372's CREDENTIALS_DIRECTORY vector (which requires
util-linux ≥ 2.40), the GCONV_PATH vector exploits a fundamental glibc
mechanism present on virtually all Linux systems.
## Remediation
### Current Upstream Status (Unpatched)
As of GNU inetutils 2.7 (latest release, 2025-12-14), `GCONV_PATH` is **not
filtered** by `scrub_env()`. The only post-disclosure change is an
`unsetenv("CREDENTIALS_DIRECTORY")` call added to `start_login()` on
2026-02-15, which addresses CVE-2026-28372 but does **not** mitigate the
GCONV_PATH vector.
The `scrub_env()` function still uses the original blacklist approach from
1995. No whitelist-based fix has been committed or released.
### Recommended Fix
Replace the blacklist in `scrub_env()` with a whitelist that only preserves
known-safe variables (e.g., `TERM`, `DISPLAY`, `USER`, `LOGNAME`,
`POSIXLY_CORRECT`). This is the approach recommended by Solar Designer on
the oss-security mailing list.
### Workarounds
1. **Disable telnetd**: Stop and disable the telnetd service. Use SSH
instead.
2. **Network isolation**: Restrict telnetd access to trusted networks via
firewall rules.
3. **Manual patch**: Add `GCONV_PATH`, `GCONV_MODULE_PATH`,
`OUTPUT_CHARSET`, `LANGUAGE`, `GLIBC_TUNABLES`, and other dangerous
variables to the `scrub_env()` blacklist (quick fix, but whitelist approach
is strongly preferred).
## Timeline
| Date | Event |
|------|-------|
| 2026-01-20 | telnetd vulnerabilities disclosed on oss-security mailing
list |
| 2026-01-26 | CISA adds CVE-2026-24061 (remote auth bypass) to KEV catalog
|
| 2026-02 | CVE-2026-28372 assigned (CREDENTIALS_DIRECTORY vector) |
| 2026-02-15 | Upstream adds unsetenv("CREDENTIALS_DIRECTORY") — GCONV_PATH
still unpatched |
| 2026-03-03 | This report: GCONV_PATH vector independently confirmed on
inetutils 2.0 and 2.7 |
## References
- [GNU inetutils official site](https://www.gnu.org/software/inetutils/)
- [GNU Savannah inetutils git — telnetd/pty.c scrub_env()](
https://git.savannah.gnu.org/cgit/inetutils.git/tree/telnetd/pty.c)
- [NVD — CVE-2026-28372](https://nvd.nist.gov/vuln/detail/CVE-2026-28372)
- [NVD — CVE-2026-24061](https://nvd.nist.gov/vuln/detail/CVE-2026-24061)
- [oss-security: Series of new vulnerabilities in telnetd](
https://www.openwall.com/lists/oss-security/2026/01/20/2)
- [CVE-2021-4034 (PwnKit) — Original GCONV_PATH exploitation technique](
https://blog.qualys.com/vulnerabilities-threat-research/2022/01/25/pwnkit-local-privilege-escalation-vulnerability-discovered-in-polkits-pkexec-cve-2021-4034
)
- [RFC 1572 — Telnet Environment Option](https://tools.ietf.org/html/rfc1572
)
- [glibc iconv / gconv internals](https://sourceware.org/glibc/wiki/Gconv)