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)

Reply via email to