├── img └── terminal.png ├── gotty.conf ├── listCommandSet.php ├── deleteCommand.php ├── addCommand.php ├── config.php ├── css └── style.css ├── runCommand.php ├── lib └── dbManager.php ├── install.php ├── readme.md └── index.php /img/terminal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Arno0x/TermGate/HEAD/img/terminal.png -------------------------------------------------------------------------------- /gotty.conf: -------------------------------------------------------------------------------- 1 | preferences { 2 | use_default_window_copy = true 3 | copy_on_select = false 4 | ctrl_c_copy = true 5 | ctrl_v_paste = true 6 | font_familly = "Lucida Console" 7 | } -------------------------------------------------------------------------------- /listCommandSet.php: -------------------------------------------------------------------------------- 1 | getMessage(); 27 | exit(); 28 | } 29 | 30 | // Retrieve all commands stored in the database 31 | $commandList = $dbManager->getCommandList(); 32 | 33 | // Check if we have at least one element (faster than counting the number of elements in the array) 34 | if (!isset($commandList['0'])) { 35 | echo ''; 36 | } else { 37 | foreach ($commandList as $command) { 38 | echo "
  • "; 39 | echo "".$command['LABEL']." "; 40 | if ($command['ISDYNAMIC'] === 1) { 41 | echo ""; 42 | } 43 | echo ""; 44 | echo "
  • "; 45 | } 46 | } 47 | $dbManager->close(); 48 | ?> -------------------------------------------------------------------------------- /deleteCommand.php: -------------------------------------------------------------------------------- 1 | getMessage(); 41 | exit(); 42 | } 43 | 44 | // Add the command to the database and close the database 45 | if ($dbManager->deleteCommand($commandID)) { 46 | echo "success"; 47 | } else { 48 | echo "failure"; 49 | } 50 | 51 | $dbManager->close(); 52 | } 53 | else { 54 | echo "failure"; 55 | } 56 | ?> -------------------------------------------------------------------------------- /addCommand.php: -------------------------------------------------------------------------------- 1 | getMessage(); 45 | exit(); 46 | } 47 | 48 | // Add the command to the database and close the database 49 | if ($dbManager->addCommand($command, $label, $isDynamic)) { 50 | echo "success"; 51 | } else { 52 | echo "failure"; 53 | } 54 | $dbManager->close(); 55 | } 56 | else { 57 | echo "failure"; 58 | } 59 | } 60 | else { 61 | echo "failure"; 62 | } 63 | ?> -------------------------------------------------------------------------------- /config.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /css/style.css: -------------------------------------------------------------------------------- 1 | .brand-title { 2 | font-family: monospace; 3 | color: #00FF00 !important; 4 | font-size: 2em; 5 | text-align: center; 6 | } 7 | 8 | .terminal { 9 | width: 100%; 10 | height: 100%; 11 | } 12 | 13 | .maindiv { 14 | position : absolute; 15 | top : 50px; 16 | left : 0; 17 | right : 0; 18 | bottom : 0; 19 | background : #fff; 20 | } 21 | 22 | /* Required to center horizontally and vertically the main image*/ 23 | .img-container { 24 | position: absolute; 25 | top: 0; 26 | bottom: 0; 27 | left: 0; 28 | right: 0; 29 | 30 | text-align:center; /* Align center inline elements */ 31 | font: 0/0 a; 32 | } 33 | 34 | .img-container:before { 35 | content: ' '; 36 | display: inline-block; 37 | vertical-align: middle; 38 | height: 100%; 39 | } 40 | 41 | .img-container img { 42 | vertical-align: middle; 43 | display: inline-block; 44 | } 45 | 46 | /* Allow two clickable element per dropdown row */ 47 | .open> ul>li.hz{ 48 | display: inline-flex !important; 49 | } 50 | 51 | /* Make blinking cursor */ 52 | .blinking-cursor { 53 | font-family: monospace; 54 | font-weight: normal; 55 | font-size: 1em; 56 | color: #00FF00; 57 | -webkit-animation: 1s blink step-end infinite; 58 | -moz-animation: 1s blink step-end infinite; 59 | -ms-animation: 1s blink step-end infinite; 60 | -o-animation: 1s blink step-end infinite; 61 | animation: 1s blink step-end infinite; 62 | } 63 | 64 | @keyframes "blink" { 65 | from, to { 66 | color: #222; 67 | } 68 | 50% { 69 | color: #00FF00; 70 | } 71 | } 72 | 73 | @-moz-keyframes blink { 74 | from, to { 75 | color: #333; 76 | } 77 | 50% { 78 | color: #00FF00; 79 | } 80 | } 81 | 82 | @-webkit-keyframes "blink" { 83 | from, to { 84 | color: #222; 85 | } 86 | 50% { 87 | color: #00FF00; 88 | } 89 | } 90 | 91 | @-o-keyframes "blink" { 92 | from, to { 93 | color: #222; 94 | } 95 | 50% { 96 | color: #00FF00; 97 | } 98 | } 99 | 100 | /* SHOW TOOLTIPS */ 101 | a.tooltips { 102 | position: relative; 103 | display: inline; 104 | color: inherit; 105 | text-decoration: none; 106 | } 107 | a.tooltips span { 108 | position: absolute; 109 | color: #000000; 110 | background: #aaa; 111 | visibility: hidden; 112 | border-radius: 5px; 113 | padding: 3px; 114 | width: 200px; 115 | } 116 | a.tooltips span:after { 117 | content: ''; 118 | position: absolute; 119 | bottom: 100%; 120 | left: 50%; 121 | margin-left: -8px; 122 | width: 0; height: 0; 123 | border-bottom: 8px solid #aaa; 124 | border-right: 8px solid transparent; 125 | border-left: 8px solid transparent; 126 | } 127 | a:hover.tooltips span { 128 | visibility: visible; 129 | opacity: 0.95; 130 | top: 30px; 131 | left: 50%; 132 | margin-left: -100px; 133 | z-index: 999; 134 | } 135 | 136 | /* Overlay div for Add command form */ 137 | .overlay-div { 138 | border: solid; 139 | border-width: 1px; 140 | border-radius: 5px; 141 | padding: 2px; 142 | display: none; 143 | background-color: #ffffff; 144 | left:0; 145 | right:0; 146 | margin-left:auto; 147 | margin-right: auto; 148 | width: 350px; 149 | height:280px; 150 | z-index: 10; 151 | position: absolute; 152 | /*overflow: auto; */ 153 | } 154 | 155 | .overlay-back { 156 | position : absolute; 157 | top : 0; 158 | left : 0; 159 | width : 100%; 160 | height : 100%; 161 | background : #000; 162 | opacity : 0.7; 163 | filter : alpha(opacity=70); 164 | z-index : 5; 165 | display : none; 166 | } -------------------------------------------------------------------------------- /runCommand.php: -------------------------------------------------------------------------------- 1 | getMessage(); 45 | exit(); 46 | } 47 | 48 | if($ret=$dbManager->getCommandAndIsDynamicByID($commandID)) { 49 | $command = escapeshellcmd($ret['COMMAND']); 50 | $isDynamic = $ret['ISDYNAMIC']; 51 | } 52 | 53 | $dbManager->close(); 54 | } 55 | 56 | // If it's not a dynamic/interactive command simply call the command and return the result 57 | if (!$isDynamic) { 58 | echo "
    Command: ".$command."
    "; 59 | 60 | // Prepare the command 61 | $command = "sudo -u ".RUN_AS_USER." -i ".$command; 62 | exec ($command, $return); 63 | 64 | echo "
    ";
    65 | 	echo implode("\n", $return);
    66 | 	echo "
    "; 67 | } 68 | // Else, if it's a dynamic/interactive command, we have to instantiate Gotty along with the proper command. 69 | // Note: commands are executed as the user specified in the RUN_AS_USER setting 70 | else { 71 | // Prepare the Gotty command 72 | // ** A bit of explanation about the use of the DISPLAY environment ** 73 | // ** Most SUDOERS environment will have the 'env_reset' option set, especially for untrusted account such as ones used to run the web server. 74 | // ** With this option set, the environment variables that can be set by the sudo command is restricted to a small set, including the DISPLAY variable. 75 | // ** Because in a TermGate + Gotty typical usage, this variable is most probably unused or, if required, has 90% of chances to point back to the client IP 76 | // ** I'm using it to store the client IP, as it can be a useful information once in a Gotty shell. 77 | $gottyCommand = "sudo -b -u ".RUN_AS_USER." DISPLAY=".$_SERVER['REMOTE_ADDR']." TERM=".GOTTY_TERM." -i ".GOTTY_PATH." --once -w -p ".GOTTY_TCP_PORT." -a ".GOTTY_BIND_INTERFACE." ".$command." > /dev/null 2>&1"; 78 | 79 | 80 | // Execute the command 81 | exec($gottyCommand); 82 | 83 | // DIRTY (!): Wait 500ms, just the time for gotty to be ready to accept incoming connections 84 | usleep(500000); 85 | 86 | echo ""; 87 | } 88 | ?> -------------------------------------------------------------------------------- /lib/dbManager.php: -------------------------------------------------------------------------------- 1 | exec($sqlQuery))) { 40 | return false; 41 | } 42 | else { 43 | return true; 44 | } 45 | } 46 | 47 | //-------------------------------------------------------- 48 | // Delete a command 49 | // @param commandID : The command ID 50 | // @return bool : TRUE if the command was deleted, FALSE otherwise 51 | public function deleteCommand ($commandID) { 52 | 53 | // Prepare variables before the query 54 | $commandID = SQLite3::escapeString ($commandID); 55 | 56 | // Prepare SQL query 57 | $sqlQuery = "DELETE from COMMANDS where ID='".$commandID."';"; 58 | 59 | // Perform SQL query 60 | if(!($ret = $this->exec($sqlQuery))) { 61 | return false; 62 | } 63 | else { 64 | return true; 65 | } 66 | } 67 | 68 | //-------------------------------------------------------- 69 | // Get the list of commands in the database 70 | // @return array : an array of all commands in the set (ie the database), or FALSE if there was an error 71 | public function getCommandList () { 72 | 73 | // Prepare SQL query 74 | $sqlQuery = "SELECT * from COMMANDS;"; 75 | 76 | // Perform SQL query 77 | if(!($ret = $this->query($sqlQuery))) { 78 | return false; 79 | } 80 | else { 81 | $result = array(); 82 | $i=0; 83 | while ($row = $ret->fetchArray(SQLITE3_ASSOC)) { 84 | $result[$i++] = $row; 85 | } 86 | return $result; 87 | } 88 | } 89 | 90 | //-------------------------------------------------------- 91 | // Get a command and its IsDynamic status from the database by its ID 92 | // @return array : an array containing the command, or FALSE if there was an error 93 | public function getCommandAndIsDynamicByID ($commandID) { 94 | 95 | // Prepare SQL query 96 | $sqlQuery = "SELECT COMMAND,ISDYNAMIC from COMMANDS where ID='".$commandID."';"; 97 | 98 | // Perform SQL query 99 | if(!($ret = $this->querySingle($sqlQuery, true))) { 100 | return false; 101 | } 102 | else { 103 | return $ret; 104 | } 105 | } 106 | } 107 | ?> -------------------------------------------------------------------------------- /install.php: -------------------------------------------------------------------------------- 1 | ERROR ] Database already installed. If you want to start the installation process over again, delete the command set database file, and then call this page again."; 21 | } else { 22 | 23 | //========================================== 24 | // Proceed with the installation 25 | //========================================== 26 | 27 | //------------------------------------------------------ 28 | // On first installation, create some directories 29 | if (!file_exists(COMMANDSET_SQL_DATABASE_DIRECTORY)) { mkdir(COMMANDSET_SQL_DATABASE_DIRECTORY); } 30 | 31 | //------------------------------------------------------ 32 | // Import the DBManager library 33 | require_once(DBMANAGER_LIB); 34 | 35 | // Allow included script to be included from this script 36 | define('INCLUSION_ENABLED',true); 37 | 38 | //------------------------------------------------------ 39 | // Check for SQLite3 support 40 | if(!class_exists('SQLite3')) { 41 | exit ("SQLite 3 NOT supported"); 42 | } 43 | 44 | //------------------------------------------------------ 45 | // Create and open the database 46 | try { 47 | $dbManager = new DBManager (COMMANDSET_SQL_DATABASE_FILE, SQLITE3_OPEN_READWRITE | SQLITE3_OPEN_CREATE); 48 | } catch (Exception $e) { 49 | echo "[ERROR] Could not create database. Exception received : " . $e->getMessage(); 50 | exit(); 51 | } 52 | 53 | //------------------------------------------------------ 54 | // Drop the COMMANDS table 55 | $sql = "DROP TABLE COMMANDS;"; 56 | 57 | if(!($ret = $dbManager->exec($sql))) { 58 | } else { 59 | $message = $message."[OK ] Previous COMMANDS table dropped successfully
    "; 60 | } 61 | 62 | //------------------------------------------------------ 63 | // Create the COMMANDS table 64 | $sql =<<exec($sql))) { 73 | $message = $message."[ERROR ] Could not create table COMMANDS
    "; 74 | echo $dbManager->lastErrorMsg(); 75 | } else { 76 | $message = $message."[OK ] Table created successfully
    "; 77 | } 78 | 79 | //------------------------------------------------------ 80 | // Check GoTTY is present on the system and executable 81 | if (is_executable(GOTTY_PATH)) { 82 | $message = $message."[OK ] GoTTY binary found and is executable
    "; 83 | } else { 84 | $message = $message."[ERROR ] GoTTY binary has not been found or is not executable. Please check the config file.
    "; 85 | } 86 | 87 | //------------------------------------------------------ 88 | // Check the web server user can sudo to the configured user 89 | $command = "sudo -u ".RUN_AS_USER." -i id"; 90 | $serverUser = posix_getpwuid(posix_geteuid())['name']; 91 | 92 | exec ($command, $return, $returnValue); 93 | 94 | if ($returnValue === 0) { 95 | $message = $message."[OK ] Current web server user ".$serverUser." is allowed to sudo to user ".RUN_AS_USER."
    "; 96 | }else { 97 | $message = $message."[ERROR ] Current web server user ".$serverUser." is NOT allowed to sudo to user ".RUN_AS_USER."
    "; 98 | $message = $message."You must add the following line to your /etc/sudoers file :
    "; 99 | $message = $message."".$serverUser." ALL=(".RUN_AS_USER.") NOPASSWD:/bin/bash
    "; 100 | } 101 | 102 | // Close the database 103 | $dbManager->close(); 104 | } 105 | 106 | echo << 108 | 109 | 110 | TermGate 111 | 112 | 113 | 114 | 115 | 116 | 123 |
    124 |

    125 | EOT; 126 | 127 | // Echoing all installation messages 128 | echo $message; 129 | 130 | echo << 132 |
    133 | If there's no error messages, you can proceed with TermGate here. 134 |

    135 | 136 | EOT; 137 | ?> -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | TermGate 2 | ============ 3 | 4 | Author: Arno0x0x - [@Arno0x0x](http://twitter.com/Arno0x0x) 5 | 6 | TermGate is a web application that allows you to run console (*shell*) commands on your remote server, including getting an interactive shell, directly from a web browser. 7 | 8 | The initial idea was to create a PHP wrapper around the wonderful job done on the [GoTTY project](https://github.com/yudai/gotty) writen by Iwasaki Yudai. I decided to add a simple shell command call, much faster to execute for simple commands that don't need interaction or dynamic updates. 9 | 10 | That means you can get a shell, or even an ssh client into your browser. 11 | 12 | Check out this demo : 13 | [![Demo](https://dl.dropboxusercontent.com/s/qglm4gvjjbritb6/termgate_video.png?dl=0)](https://vimeo.com/137886386) 14 | 15 | The app is distributed under the terms of the [GPLv3 licence](http://www.gnu.org/copyleft/gpl.html). 16 | 17 | Dependencies 18 | ---------------- 19 | 20 | On the server side, TermGate requires : 21 | * A Unix like system (*Linux, BSD like, MAC OSX*) 22 | * The `sudo` package, which is either installed by default on most Unix systems, or that you'll have to install yourself 23 | * PHP5 (*or more recent*) 24 | * GoTTY 25 | - Go grab a [GoTTY](https://github.com/yudai/gotty/releases) binary release for your system. It's just one binary file, no dependencies or complex installation. Get the one file binary and drop it onto your system. It's available for almost every Unix flavor (including Raspberry Pi and MAC OSX). 26 | 27 | TermGate also relies on one PHP5 library that you'll have to install on your own: 28 | 29 | * The SQLite3 library (*Example on debian like systems*: sudo apt-get install php5-sqlite) 30 | 31 | 32 | Security Aspects 33 | ----------- 34 | 35 | TermGate doesn't handle any user authentication or authorization. So you should put it behind some kind of authentication at the web server level. It is advisable to use a two factor authentication portal such as [TwoFactorAuth](https://github.com/Arno0x/TwoFactorAuth) (*that I wrote :-)*). 36 | 37 | TermGate can be configured to only accept HTTPS (HTTP over SSL) connections, see the configuration file. 38 | 39 | TermGate can be configured to allow only commands that are already stored in the "command set" database. Although it's not really a security feature, this might help controlling which commands are made available in the interface. 40 | 41 | 42 | Installation & Configuration 43 | ------------ 44 | 45 | * Unzip the TermGate package in your web server's directory and ensure all files and folders have appropriate *user:group* ownership, depending on your installation (*might be something like www-data:www-data*). 46 | 47 | * **Edit the configuration file config.php** at the root path of TermGate directory and make it match your needs and your installation. Main parameters are : 48 | - GOTTY\_PATH : Set it to the full path of the GoTTY binary you've previously installed. 49 | - GOTTY\_TCP_PORT : Set the TCP listening port that will be used by GoTTY. 50 | - GOTTY\_BIND_INTERFACE : Set the IP address GoTTY should bind on. If you choose to make GoTTY reachable behind an Nginx reverse-proxy (*see section below*), it is safer to set it to `127.0.0.1`. 51 | - GOTTY\_TERM : Set the TERM environment GoTTY will use (*can be sometthing like 'vt100', 'xterm', etc.*). 52 | - GOTTY\_URL : Set the URL at which GoTTY will be reachable. Again this depends on whether or not you'll make it reachable behind Nginx, which GOTTY\_TCP_PORT you set etc. 53 | - HTTPS\_ONLY : If set to true, the application will only allow HTTPS connections. 54 | - RESTRICTED\_COMMAND\_SET : If set to `true`, only commands previously saved in the command set database can be executed. Also, no new command can be added, no command can be deleted. Initially, you must set it to `false` in order to add commands to you command set database. 55 | - **RUN\_AS_USER** : By default, your web server and PHP server runs as a dedicated user (*such as www-data*) which is probably not the one you want to run commands as. This parameter allows you to tell which user will be used to execute commands. TermGate will need to be able to `sudo` to another user to execute all commands **under the user's login shell**. In order for this to work, it is required to modify the `/etc/sudoers` file. For example, if your web server is running as user `www-data` and you've set RUN\_AS_USER to user `pi`, and this user's login shell is `/bin/bash` : 56 | 57 | ``` 58 | sudo echo "www-data ALL=(pi) NOPASSWD:/bin/bash" >> /etc/sudoers 59 | ``` 60 | 61 | * Next, open a browser and **FIRST** navigate the TermGate **install.php** page (*exact path will vary depending on where you installed the TermGate application*): 62 | https://www.example.com/termgate/install.php . This page will finalize the installation process by creating the SQLite3 command set database. 63 | 64 | * Eventually, navigate to the home page, eg : https://www.example.com/termgate/index.php 65 | 66 | 67 | [OPTIONNAL] NGINX integration 68 | --------------------- 69 | Nginx can be used as a reverse-proxy to access GoTTY. It prevents from opening another TCP port on the public facing interface of the web server. 70 | You'll have to edit your Nginx site configuration file. 71 | 72 | Assuming the TermGate application was deployed in a location named `/termgate/` on your webserver, that you set GOTTY\_TCP\_PORT to `3850`, and that you set GOTTY\_BIND_INTERFACE to `127.0.0.1`, just add the following section within the "server" directive of your config file: 73 | 74 | ``` 75 | location /gotty/ { 76 | proxy_buffering off; 77 | proxy_pass_header Server; 78 | proxy_set_header Host $http_host; 79 | proxy_redirect off; 80 | proxy_set_header X-Real-IP $remote_addr; 81 | proxy_set_header X-Scheme $scheme; 82 | proxy_pass http://127.0.0.1:3850/; 83 | proxy_http_version 1.1; 84 | proxy_set_header Upgrade $http_upgrade; 85 | proxy_set_header Connection "upgrade"; 86 | } 87 | ``` 88 | 89 | Credits 90 | -------- 91 | Iwasaki Yudai for his fantastic [GoTTY project](https://github.com/yudai/gotty). 92 | 93 | If you have a feature request, bug report, feel free to contact me on my twitter page. 94 | 95 | ![bitcoin](https://dl.dropboxusercontent.com/s/imckco5cg0llfla/bitcoin-icon.png?dl=0) Like this tool ? Tip me with bitcoins ! 96 | ![address](https://dl.dropboxusercontent.com/s/9bd5p45xmqz72vw/bc_tipping_address.png?dl=0) -------------------------------------------------------------------------------- /index.php: -------------------------------------------------------------------------------- 1 | "; 18 | echo "You can disable this by editing the configuration file (not advised) or ensure you're running TermGate over an SSL connection."; 19 | exit(); 20 | } 21 | ?> 22 | 23 | 24 | 25 | 26 | 27 | TermGate 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 75 | 76 | 79 |
    80 |
    81 | 82 |
    83 |
    84 | Add a command to the set 85 |
    86 |
    87 |
    88 | 89 | 90 |
    91 | 92 |
    93 |
    94 | 95 | 96 |
    97 | 98 |
    99 | 106 |
    107 |
    108 |
    109 |
    110 |
    111 | EOT; 112 | } ?> 113 |
    114 |
    115 | 116 |
    117 |
    118 | 119 | 120 | 121 | 122 | 256 | 257 | --------------------------------------------------------------------------------