Skip to content

Commit 829453f

Browse files
committed
[Security] Address critical flaw in console rendering that allowed arbitrary command execution
1 parent ddb98df commit 829453f

File tree

11 files changed

+515
-548
lines changed

11 files changed

+515
-548
lines changed

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,13 @@ This file is a running track of new features and fixes to each version of the pa
33

44
This project follows [Semantic Versioning](http://semver.org) guidelines.
55

6+
## v0.6.3 (Courageous Carniadactylus)
7+
### Fixed
8+
* **[Security]** — Addresses an oversight in how the terminal rendered information sent from the server feed which allowed a malicious user to execute arbitrary commands on the game-server process itself by using a specifically crafted in-game command.
9+
10+
### Changed
11+
* Removed `jquery.terminal` and replaced it with an in-house developed terminal with less potential for security issues.
12+
613
## v0.6.2 (Courageous Carniadactylus)
714
### Fixed
815
* Fixes a few typos throughout the panel, there are more don't worry.

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ AdminLTE - [license](https://github.com/almasaeed2010/AdminLTE/blob/master/LICEN
3939

4040
Animate.css - [license](https://github.com/daneden/animate.css/blob/master/LICENSE) - [homepage](http://daneden.github.io/animate.css/)
4141

42+
AnsiUp - [license](https://github.com/drudru/ansi_up/blob/master/Readme.md#license) - [homepage](https://github.com/drudru/ansi_up)
43+
4244
Async.js - [license](https://github.com/caolan/async/blob/master/LICENSE) - [homepage](https://github.com/caolan/async/)
4345

4446
Bootstrap - [license](https://github.com/twbs/bootstrap/blob/master/LICENSE) - [homepage](http://getbootstrap.com)
@@ -53,8 +55,6 @@ FontAwesome Animations - [license](https://github.com/l-lin/font-awesome-animati
5355

5456
jQuery - [license](https://github.com/jquery/jquery/blob/master/LICENSE.txt) - [homepage](http://jquery.com)
5557

56-
jQuery Terminal - [license](https://github.com/jcubic/jquery.terminal/blob/master/LICENSE) - [homepage](http://terminal.jcubic.pl)
57-
5858
Laravel Framework - [license](https://github.com/laravel/framework/blob/5.4/LICENSE.md) - [homepage](https://laravel.com)
5959

6060
Lodash - [license](https://github.com/lodash/lodash/blob/master/LICENSE) - [homepage](https://lodash.com/)

public/themes/pterodactyl/css/pterodactyl.css

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -311,3 +311,90 @@ input.form-autocomplete-stop[readonly] {
311311
background: white;
312312
box-shadow: none !important;
313313
}
314+
315+
#terminal {
316+
font-family: monospace;
317+
color: #aaa;
318+
background: #000;
319+
font-size: 12px;
320+
line-height: 14px;
321+
padding: 10px 10px 0;
322+
box-sizing: border-box;
323+
min-height: 30px;
324+
max-height: 500px;
325+
overflow-y: auto;
326+
overflow-x: hidden;
327+
border-radius: 5px 5px 0 0;
328+
}
329+
330+
#terminal > .cmd {
331+
padding: 1px 0;
332+
}
333+
334+
@keyframes blinky {
335+
0% {
336+
background: transparent;
337+
}
338+
100% {
339+
background: rgba(170, 170, 170, 0.9);
340+
}
341+
}
342+
343+
@-webkit-keyframes blinky {
344+
0% {
345+
background: transparent;
346+
}
347+
100% {
348+
background: rgba(170, 170, 170, 0.9);
349+
}
350+
}
351+
352+
#terminal_input {
353+
width: 100%;
354+
background: #000;
355+
border-radius: 0 0 5px 5px;
356+
padding: 5px 10px;
357+
}
358+
359+
.terminal_input--input {
360+
height: 0;
361+
width: 0;
362+
position: absolute;
363+
top: -20px;
364+
}
365+
366+
.terminal_input--text, .terminal_input--prompt {
367+
line-height: 14px;
368+
width: 100%;
369+
vertical-align: middle;
370+
font-size: 12px;
371+
font-family: monospace;
372+
margin-bottom: 0;
373+
background: transparent;
374+
color: #aaa;
375+
}
376+
377+
.terminal_input--text:before, .terminal_input--text:after {
378+
content: "";
379+
display: inline-block;
380+
width: 7px;
381+
height: 14px;
382+
margin: 0 0 -12px -6px;
383+
vertical-align: middle;
384+
}
385+
386+
.terminal_input--text:after {
387+
position: relative;
388+
bottom: 8px;
389+
left: 8px;
390+
background: #ff00;
391+
animation: blinky 0.6s linear infinite alternate;
392+
-webkit-animation: blinky 0.6s linear infinite alternate;
393+
}
394+
395+
.terminal_input--input {
396+
color: transparent;
397+
background-color: transparent;
398+
border: 0;
399+
outline: none;
400+
}

public/themes/pterodactyl/js/frontend/console.js

Lines changed: 64 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -20,39 +20,56 @@
2020
var CONSOLE_PUSH_COUNT = Pterodactyl.config.console_count || 10;
2121
var CONSOLE_PUSH_FREQ = Pterodactyl.config.console_freq || 200;
2222
var CONSOLE_OUTPUT_LIMIT = Pterodactyl.config.console_limit || 2000;
23+
2324
var InitialLogSent = false;
25+
var AnsiUp = new AnsiUp;
2426

25-
(function initConsole() {
26-
window.TerminalQueue = [];
27-
window.ConsoleServerStatus = 0;
28-
window.Terminal = $('#terminal').terminal(function (command, term) {
29-
Socket.emit((ConsoleServerStatus !== 0) ? 'send command' : 'set status', command);
30-
}, {
31-
greetings: '',
32-
name: Pterodactyl.server.uuid,
33-
height: 450,
34-
exit: false,
35-
echoCommand: false,
36-
outputLimit: CONSOLE_OUTPUT_LIMIT,
37-
prompt: Pterodactyl.server.username + ':~$ ',
38-
scrollOnEcho: false,
39-
scrollBottomOffset: 5,
40-
onBlur: function (terminal) {
41-
return false;
42-
}
27+
var $terminal = $('#terminal');
28+
var $ghostInput = $('.terminal_input--input');
29+
var $visibleInput = $('.terminal_input--text');
30+
var $scrollNotify = $('#terminalNotify');
31+
32+
$(document).ready(function () {
33+
$ghostInput.focus();
34+
$('.terminal_input--text, #terminal_input, #terminal, #terminalNotify').on('click', function () {
35+
$ghostInput.focus();
4336
});
4437

45-
window.TerminalNotifyElement = $('#terminalNotify');
46-
TerminalNotifyElement.on('click', function () {
47-
Terminal.scroll_to_bottom();
48-
TerminalNotifyElement.addClass('hidden');
49-
})
38+
$ghostInput.on('input', function () {
39+
$visibleInput.html($(this).val());
40+
});
41+
42+
$ghostInput.on('keyup', function (e) {
43+
if (e.which === 13) {
44+
Socket.emit((ConsoleServerStatus !== 0) ? 'send command' : 'set status', $(this).val());
5045

51-
Terminal.on('scroll', function () {
52-
if (Terminal.is_bottom()) {
53-
TerminalNotifyElement.addClass('hidden');
46+
$(this).val('');
47+
$visibleInput.html('');
5448
}
55-
})
49+
});
50+
});
51+
52+
$terminal.on('scroll', function () {
53+
if ($(this).scrollTop() + $(this).innerHeight() < $(this)[0].scrollHeight) {
54+
$scrollNotify.removeClass('hidden');
55+
} else {
56+
$scrollNotify.addClass('hidden');
57+
}
58+
});
59+
60+
window.scrollToBottom = function () {
61+
$terminal.scrollTop($terminal[0].scrollHeight);
62+
};
63+
64+
(function initConsole() {
65+
window.TerminalQueue = [];
66+
window.ConsoleServerStatus = 0;
67+
window.ConsoleElements = 0;
68+
69+
$scrollNotify.on('click', function () {
70+
window.scrollToBottom();
71+
$scrollNotify.addClass('hidden');
72+
});
5673
})();
5774

5875
(function pushOutputQueue() {
@@ -62,16 +79,22 @@ var InitialLogSent = false;
6279

6380
if (TerminalQueue.length > 0) {
6481
for (var i = 0; i < CONSOLE_PUSH_COUNT && TerminalQueue.length > 0; i++) {
65-
Terminal.echo(TerminalQueue[0], { flush: false });
82+
$terminal.append(
83+
'<div class="cmd">' + AnsiUp.ansi_to_html(TerminalQueue[0] + '\u001b[0m') + '</div>'
84+
);
85+
86+
if (! $scrollNotify.is(':visible')) {
87+
window.scrollToBottom();
88+
}
89+
90+
window.ConsoleElements++;
6691
TerminalQueue.shift();
6792
}
6893

69-
// Flush after looping through all.
70-
Terminal.flush();
71-
72-
// Show Warning
73-
if (! Terminal.is_bottom()) {
74-
TerminalNotifyElement.removeClass('hidden');
94+
var removeElements = window.ConsoleElements - CONSOLE_OUTPUT_LIMIT;
95+
if (removeElements > 0) {
96+
$('#terminal').find('.cmd').slice(0, removeElements).remove();
97+
window.ConsoleElements = window.ConsoleElements - removeElements;
7598
}
7699
}
77100

@@ -99,14 +122,18 @@ var InitialLogSent = false;
99122

100123
Socket.on('server log', function (data) {
101124
if (! InitialLogSent) {
102-
Terminal.clear();
103-
TerminalQueue.push(data);
125+
$('#terminal').html('');
126+
data.split(/\n/g).forEach(function (item) {
127+
TerminalQueue.push(item);
128+
});
104129
InitialLogSent = true;
105130
}
106131
});
107132

108133
Socket.on('console', function (data) {
109-
TerminalQueue.push(data.line);
134+
data.line.split(/\n/g).forEach(function (item) {
135+
TerminalQueue.push(item);
136+
});
110137
});
111138
})();
112139

0 commit comments

Comments
 (0)