Connecting to remote SSH server (via Node.js/html5 console)
This is easily doable with modules like ssh2
, xterm
, and socket.io
.
Here's an example:
npm install ssh2 xterm socket.io
- Create
index.html
:
<html> <head> <title>SSH Terminal</title> <link rel="stylesheet" href="/src/xterm.css" /> <script src="/src/xterm.js"></script> <script src="/addons/fit/fit.js"></script> <script src="/socket.io/socket.io.js"></script> <script> window.addEventListener('load', function() { var terminalContainer = document.getElementById('terminal-container'); var term = new Terminal({ cursorBlink: true }); term.open(terminalContainer); term.fit(); var socket = io.connect(); socket.on('connect', function() { term.write('\r\n*** Connected to backend***\r\n'); // Browser -> Backend term.on('data', function(data) { socket.emit('data', data); }); // Backend -> Browser socket.on('data', function(data) { term.write(data); }); socket.on('disconnect', function() { term.write('\r\n*** Disconnected from backend***\r\n'); }); }); }, false); </script> <style> body { font-family: helvetica, sans-serif, arial; font-size: 1em; color: #111; } h1 { text-align: center; } #terminal-container { width: 960px; height: 600px; margin: 0 auto; padding: 2px; } #terminal-container .terminal { background-color: #111; color: #fafafa; padding: 2px; } #terminal-container .terminal:focus .terminal-cursor { background-color: #fafafa; } </style> </head> <body> <div id="terminal-container"></div> </body></html>
- Create
server.js
:
var fs = require('fs');var path = require('path');var server = require('http').createServer(onRequest);var io = require('socket.io')(server);var SSHClient = require('ssh2').Client;// Load static files into memoryvar staticFiles = {};var basePath = path.join(require.resolve('xterm'), '..');[ 'addons/fit/fit.js', 'src/xterm.css', 'src/xterm.js'].forEach(function(f) { staticFiles['/' + f] = fs.readFileSync(path.join(basePath, f));});staticFiles['/'] = fs.readFileSync('index.html');// Handle static file servingfunction onRequest(req, res) { var file; if (req.method === 'GET' && (file = staticFiles[req.url])) { res.writeHead(200, { 'Content-Type': 'text/' + (/css$/.test(req.url) ? 'css' : (/js$/.test(req.url) ? 'javascript' : 'html')) }); return res.end(file); } res.writeHead(404); res.end();}io.on('connection', function(socket) { var conn = new SSHClient(); conn.on('ready', function() { socket.emit('data', '\r\n*** SSH CONNECTION ESTABLISHED ***\r\n'); conn.shell(function(err, stream) { if (err) return socket.emit('data', '\r\n*** SSH SHELL ERROR: ' + err.message + ' ***\r\n'); socket.on('data', function(data) { stream.write(data); }); stream.on('data', function(d) { socket.emit('data', d.toString('binary')); }).on('close', function() { conn.end(); }); }); }).on('close', function() { socket.emit('data', '\r\n*** SSH CONNECTION CLOSED ***\r\n'); }).on('error', function(err) { socket.emit('data', '\r\n*** SSH CONNECTION ERROR: ' + err.message + ' ***\r\n'); }).connect({ host: '192.168.100.105', username: 'foo', password: 'barbaz' });});server.listen(8000);
- Edit the SSH server configuration passed to
.connect()
inserver.js
node server.js
- Visit http://localhost:8000 in your browser
Just adding updated code to @mscdex great answer because the libraries have changed over the years.
Libraries:
npm install express socket.io ssh2 xterm xterm-addon-fit
index.html:
<html> <head> <title>SSH Terminal</title> <link rel="stylesheet" href="/xterm.css" /> <script src="/xterm.js"></script> <script src="/xterm-addon-fit.js"></script> <script src="/socket.io/socket.io.js"></script> <script> window.addEventListener('load', function() { var terminalContainer = document.getElementById('terminal-container'); const term = new Terminal({ cursorBlink: true }); const fitAddon = new FitAddon.FitAddon(); term.loadAddon(fitAddon); term.open(terminalContainer); fitAddon.fit(); var socket = io() //.connect(); socket.on('connect', function() { term.write('\r\n*** Connected to backend ***\r\n'); }); // Browser -> Backend term.onKey(function (ev) { socket.emit('data', ev.key); }); // Backend -> Browser socket.on('data', function(data) { term.write(data); }); socket.on('disconnect', function() { term.write('\r\n*** Disconnected from backend ***\r\n'); }); }, false); </script> <style> body { font-family: helvetica, sans-serif, arial; font-size: 1em; color: #111; } h1 { text-align: center; } #terminal-container { width: 960px; height: 600px; margin: 0 auto; padding: 2px; } #terminal-container .terminal { background-color: #111; color: #fafafa; padding: 2px; } #terminal-container .terminal:focus .terminal-cursor { background-color: #fafafa; } </style> </head> <body> <h3>WebSSH</h3> <div id="terminal-container"></div> </body></html>
server.js:
var fs = require('fs');var path = require('path');var server = require('http').createServer(onRequest);var io = require('socket.io')(server);var SSHClient = require('ssh2').Client;// Load static files into memoryvar staticFiles = {};var basePath = path.join(require.resolve('xterm'), '..');staticFiles['/xterm.css'] = fs.readFileSync(path.join(basePath, '../css/xterm.css'));staticFiles['/xterm.js'] = fs.readFileSync(path.join(basePath, 'xterm.js'));basePath = path.join(require.resolve('xterm-addon-fit'), '..');staticFiles['/xterm-addon-fit.js'] = fs.readFileSync(path.join(basePath, 'xterm-addon-fit.js'));staticFiles['/'] = fs.readFileSync('index.html');// Handle static file servingfunction onRequest(req, res) { var file; if (req.method === 'GET' && (file = staticFiles[req.url])) { res.writeHead(200, { 'Content-Type': 'text/' + (/css$/.test(req.url) ? 'css' : (/js$/.test(req.url) ? 'javascript' : 'html')) }); return res.end(file); } res.writeHead(404); res.end();}io.on('connection', function(socket) { var conn = new SSHClient(); conn.on('ready', function() { socket.emit('data', '\r\n*** SSH CONNECTION ESTABLISHED ***\r\n'); conn.shell(function(err, stream) { if (err) return socket.emit('data', '\r\n*** SSH SHELL ERROR: ' + err.message + ' ***\r\n'); socket.on('data', function(data) { stream.write(data); }); stream.on('data', function(d) { socket.emit('data', d.toString('binary')); }).on('close', function() { conn.end(); }); }); }).on('close', function() { socket.emit('data', '\r\n*** SSH CONNECTION CLOSED ***\r\n'); }).on('error', function(err) { socket.emit('data', '\r\n*** SSH CONNECTION ERROR: ' + err.message + ' ***\r\n'); }).connect({ host: 'domain.tld', port: 22, username: 'root', privateKey: require('fs').readFileSync('path/to/keyfile') });});let port = 8000;console.log('Listening on port', port)server.listen(port);
Same as the above answer but actually using express and modern syntax and libraries
const express = require('express');const app = express();const http = require('http').Server(app);const io = require('socket.io')(http, { cors: { origin: "*" }});app.set('view engine', 'ejs');app.use(express.urlencoded({ extended: false, limit: '150mb'}));app.use(express.static(__dirname + '/public'));app.use('/xterm.css', express.static(require.resolve('xterm/css/xterm.css')));app.use('/xterm.js', express.static(require.resolve('xterm')));app.use('/xterm-addon-fit.js', express.static(require.resolve('xterm-addon-fit')));const SSHClient = require('ssh2').Client;app.get('/', (req, res) => { // res.sendFile(__dirname + '/index.html'); res.render('index'); // I am using ejs as my templating engine but HTML file work just fine.});io.on('connection', function(socket) { var conn = new SSHClient(); conn.on('ready', function() { socket.emit('data', '\r\n*** SSH CONNECTION ESTABLISHED ***\r\n'); conn.shell(function(err, stream) { if (err) return socket.emit('data', '\r\n*** SSH SHELL ERROR: ' + err.message + ' ***\r\n'); socket.on('data', function(data) { stream.write(data); }); stream.on('data', function(d) { socket.emit('data', d.toString('binary')); }).on('close', function() { conn.end(); }); }); }).on('close', function() { socket.emit('data', '\r\n*** SSH CONNECTION CLOSED ***\r\n'); }).on('error', function(err) { socket.emit('data', '\r\n*** SSH CONNECTION ERROR: ' + err.message + ' ***\r\n'); }).connect({ host: '192.168.0.103', port: 22, username: 'kali', password: 'kali' });});http.listen(3000, () => { console.log('Listening on http://localhost:3000');});
* { padding: 0%; margin: 0%; box-sizing: border-box;}body { font-family: Helvetica, sans-serif, arial; font-size: 1em; color: #111;}h1 { text-align: center;}#terminal-container { width: 960px; height: 600px; margin: 0 auto; padding: 2px;}#terminal-container .terminal { background-color: #111; color: #fafafa; padding: 2px;}#terminal-container .terminal:focus .terminal-cursor { background-color: #fafafa;}
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>SSH SERVER</title> <link rel="stylesheet" href="/xterm.css" /> <script defer src="/xterm.js"></script> <script defer src="/xterm-addon-fit.js"></script> <script defer src="/socket.io/socket.io.js"></script> <script defer src='/js/app.js'></script> <link rel='stylesheet' href='/css/main.css'></head><body> <h3>WebSSH</h3> <div id="terminal-container"></div> <script> // PLEASE USE A SEPERATE FILE FOR THE JS and defer it // like the above app.js file window.addEventListener('load', function() { const terminalContainer = document.getElementById('terminal-container'); const term = new Terminal({ cursorBlink: true }); const fitAddon = new FitAddon.FitAddon(); term.loadAddon(fitAddon); term.open(terminalContainer); fitAddon.fit(); const socket = io() //.connect(); socket.on('connect', function() { term.write('\r\n*** Connected to backend ***\r\n'); }); // Browser -> Backend term.onKey(function(ev) { socket.emit('data', ev.key); }); // Backend -> Browser socket.on('data', function(data) { term.write(data); }); socket.on('disconnect', function() { term.write('\r\n*** Disconnected from backend ***\r\n'); }); }, false); </script></body></html>