Security News
Research
Data Theft Repackaged: A Case Study in Malicious Wrapper Packages on npm
The Socket Research Team breaks down a malicious wrapper package that uses obfuscation to harvest credentials and exfiltrate sensitive data.
telnet-stream
Advanced tools
Transform streams that emit TELNET negotiations as events
Although venerable, the TELNET protocol is still in use by some services and expected by some clients. If you need to connect to something that "speaks TELNET", this module offers a pair of bare-bones Transform streams for that purpose.
If you need to connect to something that speaks TELNET, but you don't care about options or negotiations, then simply use the stream as-is. It will filter out all the "TELNET stuff" and pass the rest on to you.
Because TelnetInput and TelnetOutput are Node.js Transform streams, they support all the same operations that regular streams do. See the Node.js Stream API for more details.
var net = require('net');
var TelnetInput = require('telnet-stream').TelnetInput;
var TelnetOutput = require('telnet-stream').TelnetOutput;
var socket = net.createConnection(3000, 'godwars2.org', function() {
var telnetInput = new TelnetInput();
var telnetOutput = new TelnetOutput();
socket.pipe(telnetInput).pipe(process.stdout);
process.stdin.pipe(telnetOutput).pipe(socket);
});
Maybe you have more complex needs. Perhaps you need certain options to be turned on or off, or have important information to pull from a subnegotiation. telnet-stream has you covered in those situations.
TelnetInput is a Transform stream for the input side of TELNET. TELNET commands, options, and negotiations are emitted as events. Non-TELNET data is passed transparently as input data.
When the remote system issues a TELNET command that is not option negotiation, TelnetInput will emit a 'command' event.
var telnetIn = new TelnetInput();
telnetIn.on('command', function(command) {
// Received: IAC <command> - See RFC 854
});
When the remote system wants to request that the local system perform some function or obey some protocol, TelnetInput will emit a 'do' event:
var telnetIn = new TelnetInput();
telnetIn.on('do', function(option) {
// Received: IAC DO <option> - See RFC 854
});
When the remote system wants to request that the local system NOT perform some function or NOT obey some protocol, TelnetInput will emit a 'dont' event:
var telnetIn = new TelnetInput();
telnetIn.on('dont', function(option) {
// Received: IAC DONT <option> - See RFC 854
});
After negotiating an option, either the local or remote system may engage in a more complex subnegotiation. For example, the server and client may agree to use encryption, and then use subnegotiation to agree on the parameters of that encryption.
var telnetIn = new TelnetInput();
telnetIn.on('sub', function(option, buffer) {
// Received: IAC SB <option> <buffer> IAC SE - See RFC 855
});
When the remote system wants to offer that it will perform some function or obey some protocol for the local system, TelnetInput will emit a 'will' event:
var telnetIn = new TelnetInput();
telnetIn.on('will', function(option) {
// Received: IAC WILL <option> - See RFC 854
});
When the remote system wants to refuse to perform some function or obey some protocol for the local system, TelnetInput will emit a 'wont' event:
var telnetIn = new TelnetInput();
telnetIn.on('wont', function(option) {
// Received: IAC WONT <option> - See RFC 854
});
TelnetOutput is a Transform stream for the output side of TELNET. Data written to TelnetOutput is properly escaped to ensure that it isn't interpreted as a TELNET command. It also has methods for sending TELNET option negotiations and subnegotiations.
TELNET commands start with the Interpret as Command (IAC) octet. In order to send a literal IAC octet (one that is intended as data, not as a TELNET command), it must be sent as IAC IAC. TelnetOutput takes care of this transformation automatically.
Call this method to send a TELNET command to the remote system.
var NOP = 241; // No operation. -- See RFC 854
var telnetOut = new TelnetOutput();
// Sends: IAC NOP
telnetOut.writeCommand(NOP);
Call this method to send a TELNET DO option negotiation to the remote system. A DO request is sent when the local system wants the remote system to perform some function or obey some protocol.
var NAWS = 31; // Negotiate About Window Size -- See RFC 1073
var telnetOut = new TelnetOutput();
// Sends: IAC DO NAWS
telnetOut.writeDo(NAWS);
Call this method to send a TELNET DONT option negotiation to the remote system. A DONT request is sent when the local system wants the remote system to NOT perform some function or NOT obey some protocol.
var NAWS = 31; // Negotiate About Window Size -- See RFC 1073
var telnetOut = new TelnetOutput();
// Sends: IAC DONT NAWS
telnetOut.writeDont(NAWS);
Call this method to send a TELNET subnegotiation to the remote system. After the local and remote system have negotiated an option, then subnegotiation information can be sent.
See Example #2: Negotiate About Window Size (NAWS) below.
Call this method to send a TELNET WILL option negotiation to the remote system. A WILL offer is sent when the local system wants to inform the remote system that it will perform some function or obey some protocol.
var NAWS = 31; // Negotiate About Window Size -- See RFC 1073
var telnetOut = new TelnetOutput();
// Sends: IAC WILL NAWS
telnetOut.writeWill(NAWS);
Call this method to send a TELNET WONT option negotiation to the remote system. A WONT refusal is sent when the remote system has requested that the local system perform some function or obey some protocol, and the local system is refusing to do so.
var NAWS = 31; // Negotiate About Window Size -- See RFC 1073
var telnetOut = new TelnetOutput();
// Sends: IAC WONT NAWS
telnetOut.writeWont(NAWS);
The simple example above provided a simple TELNET client. However, all TELNET commands were filtered and ignored. A service might think one was using a very dumb client, because it refuses to acknowledge TELNET negotiations.
This example does the same thing, but actively refuses all TELNET options. If the remote service offers something, we decline to take advantage of it. If the remote service requests that we do something, we refuse to do it.
var net = require('net');
var TelnetInput = require('telnet-stream').TelnetInput;
var TelnetOutput = require('telnet-stream').TelnetOutput;
var socket = net.createConnection(3000, 'godwars2.org', function() {
var telnetInput = new TelnetInput();
var telnetOutput = new TelnetOutput();
telnetInput.on('do', function(option) {
telnetOutput.writeWont(option);
});
telnetInput.on('will', function(option) {
telnetOutput.writeDont(option);
});
socket.pipe(telnetInput).pipe(process.stdout);
process.stdin.pipe(telnetOutput).pipe(socket);
});
Note that incoming 'dont' and 'wont' events are ignored. This is OK because they are the expected state. TELNET negotiations involve changes to the current state. As a rule, we don't acknowledge things that we already expect.
There is a TELNET option called "Negotiate About Window Size" (NAWS) that allows the server to learn the dimensions of the client's output window. This is useful is some cases, as the server can wrap text output at an appropriate boundary, implement a text windowing system, or other things that depend on client metrics.
This code implements a simple TELNET server that listens for NAWS subnegotiations and reports the client's window size to the console.
var NAWS = 31; // Negotiate About Window Size -- See RFC 1073
var net = require('net');
var TelnetInput = require('telnet-stream').TelnetInput;
var TelnetOutput = require('telnet-stream').TelnetOutput;
var serverSocket = net.createServer(function(connection) {
var telnetInput = new TelnetInput();
var telnetOutput = new TelnetOutput();
telnetInput.on('sub', function(option, buffer) {
if(option === NAWS) {
var width = buffer.readInt16BE(0);
var height = buffer.readInt16BE(2);
console.log( 'Client window: ' + width + 'x' + height);
}
});
connection.pipe(telnetInput).pipe(process.stdout);
process.stdin.pipe(telnetOutput).pipe(connection);
telnetOutput.writeDo(NAWS);
});
serverSocket.listen(3000);
This code implements a simple TELNET client that sends NAWS subnegotiations when the output window is resized. Note that it only sends NAWS subnegotiations after it has confirmed that the server supports and wants to hear about them.
var NAWS = 31; // Negotiate About Window Size -- See RFC 1073
var net = require('net');
var TelnetInput = require('telnet-stream').TelnetInput;
var TelnetOutput = require('telnet-stream').TelnetOutput;
var socket = net.createConnection(3000, function() {
var telnetInput = new TelnetInput();
var telnetOutput = new TelnetOutput();
var serverNawsOk = false;
var sendWindowSize = function() {
var nawsBuffer = new Buffer(4);
nawsBuffer.writeInt16BE(process.stdout.columns, 0);
nawsBuffer.writeInt16BE(process.stdout.rows, 2);
telnetOutput.writeSub(NAWS, nawsBuffer);
};
telnetInput.on('do', function(option) {
if(option === NAWS) {
serverNawsOk = true;
telnetOutput.writeWill(NAWS);
sendWindowSize();
}
});
process.stdout.on('resize', function() {
if(serverNawsOk) {
sendWindowSize();
}
});
socket.pipe(telnetInput).pipe(process.stdout);
process.stdin.pipe(telnetOutput).pipe(socket);
telnetOutput.writeWill(NAWS);
});
telnet-stream is not 100% faithful to the TELNET specification.
The input buffer size for TELNET subnegotiations is 8192 bytes. For most cases, this buffer should suffice. If you are running millions of simultaneous connections, you may need to reduce it. If your subnegotiations are HUGE then you may need to increase it.
The size of the buffer is defined at the top of telnetInput.coffee:
SUBNEG_BUFFER_SIZE = 8192
During a subnegotiation, there are two valid sequences that begin with IAC. One is to escape another IAC intended as a literal data octet:
IAC IAC // this is a literal IAC [Hex 0xFF, Dec 255] octet
The other is to end the ongoing subnegotiation:
IAC SE // this signals the end of the subnegotiation
No other sequence is specified in the RFC. No mention is made of the state of the protocol after receiving an unknown sequence.
telnet-stream will silently consume both the IAC and following octet and return to consuming subnegotiation data. The final effect is as if those two octets never existed in the stream.
This error might occur when talking to faulty TELNET implementations that fail to properly escape IAC bytes as IAC IAC in subnegotiations.
In addition to TELNET negotiation, RFC 854 specifies a Network Virtual Terminal (NVT). Among other things in the NVT specification, a Carriage Return (CR) [Hex 0x0C, Dec 13] octet must be followed by either a Line Feed (LF) [Hex 0x0A, Dec 10] octet or a Null (NUL) [Hex 0x00, Dec 0] octet. It says "the CR character must be avoided in other contexts".
Furthermore, it goes on to specify: "Even though it may be known in some situations that characters are not being sent to an actual printer, nonetheless, for the sake of consistency, the protocol requires that a NUL be inserted following a CR not followed by a LF in the data stream."
telnet-stream DOES NOT respect this part of the specification. The character following a CR in the data stream is not modified in any way. If you want or need this behavior, you will need to modify telnet-stream.
telnet-stream is Copyright 2013 Patrick Meade.
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License along with this program. If not, see http://www.gnu.org/licenses/.
FAQs
TELNET protocol support library
The npm package telnet-stream receives a total of 1,609 weekly downloads. As such, telnet-stream popularity was classified as popular.
We found that telnet-stream demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
Research
The Socket Research Team breaks down a malicious wrapper package that uses obfuscation to harvest credentials and exfiltrate sensitive data.
Research
Security News
Attackers used a malicious npm package typosquatting a popular ESLint plugin to steal sensitive data, execute commands, and exploit developer systems.
Security News
The Ultralytics' PyPI Package was compromised four times in one weekend through GitHub Actions cache poisoning and failure to rotate previously compromised API tokens.