├── .gitignore ├── README.md ├── config.js ├── controllers ├── index.js └── main.js ├── entry.js ├── package.json ├── pm2.js ├── public ├── assets │ ├── 448c34a56d699c29117adc64c43affeb.woff2 │ ├── 674f50d287a8c48dc19ba404d20fe713.eot │ ├── 89889688147bd7575d6327160d64e760.svg │ ├── 912ec66d7572ff821749319396470bde.svg │ ├── af7ae505a9eed503f8b8e6982036873e.woff2 │ ├── b06871f281fee6b241d60582ae9369b9.ttf │ ├── bundle.js │ ├── e18bbf611f2a2e43afc071aa2f4e1512.ttf │ ├── f4769f9bdb7466be65088239c12046d1.eot │ ├── fa2772327f55d8198301fdb8bcfc8158.woff │ └── fee66e712a8a08eef5805a46892932ad.woff └── index.html ├── routes └── index.js ├── screenshot └── screenshot.png ├── server.js ├── style ├── custom.css └── dashboard.css ├── tmux └── webpack.config.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore Vim temp files 2 | 3 | *~ 4 | *.swp 5 | *.swo 6 | 7 | # Ignore Node.JS packages 8 | 9 | /node_modules 10 | 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Reverse-Shell-Listener 2 | 3 | reverse-shell-listener is Node.JS script that listens on port 1337 as a TCP server on which you can reply from WebUI. 4 | 5 | ![Screenshot](https://raw.githubusercontent.com/nemanjan00/reverse-shell-listener/master/screenshot/screenshot.png) 6 | 7 | ## Getting Started 8 | Create a new new user for running this application. This should not be run as root. 9 | ``` 10 | useradd -s /bin/bash -m -d /home/safeuser -c "safe user" safeuser 11 | passwd safeuser 12 | usermod -aG sudo safeuser 13 | ``` 14 | Next, login as the new user you have just created to finish the installation. 15 | ``` 16 | sudo apt-get install nodejs 17 | sudo ln -s "$(which nodejs)" /usr/bin/node 18 | git clone https://github.com/nemanjan00/reverse-shell-listener.git 19 | cd reverse-shell-listener/ 20 | sudo apt-get install npm 21 | npm install 22 | npm install pm2 23 | sudo npm install pm2 -g 24 | pm2 start server.js 25 | ``` 26 | Finally remove sudo from the application user to limit access if someone manages to compromise tha pplication. 27 | ``` 28 | sudo deluser safeuser sudo 29 | ``` -------------------------------------------------------------------------------- /config.js: -------------------------------------------------------------------------------- 1 | const settings = { 2 | "PORT": 8080 3 | }; 4 | 5 | module.exports = function(app){ 6 | Object.keys(settings).forEach(function(key){ 7 | try { 8 | app.set(key, JSON.parse(process.env[key])); 9 | } catch (e) { 10 | app.set(key, process.env[key] || settings[key]); 11 | } 12 | }); 13 | } 14 | -------------------------------------------------------------------------------- /controllers/index.js: -------------------------------------------------------------------------------- 1 | module.exports = function(app){ 2 | require('./main')(app); 3 | } 4 | 5 | -------------------------------------------------------------------------------- /controllers/main.js: -------------------------------------------------------------------------------- 1 | module.exports = function(app){ 2 | app.controller("MainController", function($scope, $timeout, $rootScope, $websocket, $filter){ 3 | $scope.currentShell = { 4 | ip: "Not connected" 5 | }; 6 | 7 | $scope.currentShellId = undefined; 8 | 9 | $scope.noneSelected = true; 10 | 11 | $scope.shells = {}; 12 | 13 | $scope.haveNonDeadShells = function(){ 14 | return Object.keys($filter('notDead')($scope.shells)).length != 0; 15 | } 16 | 17 | $scope.haveDeadShells = function(){ 18 | return Object.keys($filter('dead')($scope.shells)).length != 0; 19 | } 20 | 21 | $scope.selectShell = function(id){ 22 | $scope.currentShell = $scope.shells[id]; 23 | $scope.currentShellId = id; 24 | 25 | $timeout(function(){ 26 | window.resize(); 27 | }, 0); 28 | } 29 | 30 | $scope.shellSelected = function(){ 31 | return $scope.currentShellId != undefined; 32 | } 33 | 34 | $rootScope.$on('terminal.main', function (e, input, terminal) { 35 | $rootScope.$emit('terminal.main.echo', input); 36 | }); 37 | 38 | connection = $websocket(window.location.href.replace("http", "ws")+"servers"); 39 | 40 | connection.onMessage(function(message) { 41 | message = JSON.parse(message.data); 42 | 43 | if(message.message == "newServer"){ 44 | $scope.shells[message.id] = { 45 | ip: message.ip, 46 | dead: message.dead 47 | }; 48 | 49 | var newConnection = $websocket(window.location.href.replace("http", "ws")+"server/"+message.id); 50 | 51 | var id = message.id; 52 | 53 | newConnection.onMessage(function(message){ 54 | message = JSON.parse(message.data); 55 | $rootScope.$emit('terminal.main-'+id+'.echo', message.data.trim()); 56 | }); 57 | 58 | $rootScope.$on('terminal.main-'+id, function (e, input, terminal) { 59 | newConnection.send(JSON.stringify({ 60 | message: "command", 61 | command: input+"\n" 62 | })); 63 | }); 64 | } 65 | }); 66 | 67 | $timeout(function(){ 68 | window.resize(); 69 | }, 0); 70 | }); 71 | 72 | app.filter('notDead', function() { 73 | return function(items) { 74 | var output = {}; 75 | 76 | Object.keys(items).forEach(function(key){ 77 | if(items[key].dead != true){ 78 | output[key] = items[key]; 79 | } 80 | }); 81 | 82 | return output; 83 | }; 84 | }); 85 | 86 | app.filter('dead', function() { 87 | return function(items) { 88 | var output = {}; 89 | 90 | Object.keys(items).forEach(function(key){ 91 | if(items[key].dead != false){ 92 | output[key] = items[key]; 93 | } 94 | }); 95 | 96 | return output; 97 | }; 98 | }); 99 | } 100 | -------------------------------------------------------------------------------- /entry.js: -------------------------------------------------------------------------------- 1 | // Styles 2 | 3 | require('bootstrap/dist/css/bootstrap.min.css'); 4 | require('font-awesome/css/font-awesome.css'); 5 | 6 | require('jquery.terminal/css/jquery.terminal.css'); 7 | 8 | require('./style/dashboard.css'); 9 | require('./style/custom.css'); 10 | 11 | // jQuery, Bootstrap & Angular 12 | 13 | window.jQuery = require('jquery'); 14 | window.$ = window.jQuery; 15 | 16 | require('bootstrap'); 17 | 18 | require('angular'); 19 | 20 | require('jquery.mousewheel'); 21 | require('jquery.terminal'); 22 | require('angular-terminal'); 23 | 24 | require('angular-websocket'); 25 | 26 | // App 27 | 28 | var app = angular.module('top.nemanja.reverse-shell-listener', ['angular-terminal', 'ngWebSocket']); 29 | 30 | require("./controllers")(app); 31 | 32 | window.resize = function() { 33 | $(".fluid").each(function(id, element){ 34 | $(element).height($(window).height() - $(element).offset().top - 50); 35 | }); 36 | }; 37 | 38 | resize(); 39 | 40 | $(window).resize(resize); 41 | 42 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "reverse-shell-listener", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "node pm2.js", 8 | "monitor": "nodemon server.js", 9 | "test": "echo \"Error: no test specified\" && exit 1" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "git+https://github.com/nemanjan00/reverse-shell-listener.git" 14 | }, 15 | "author": "Nemanja Nedeljković (https://nemanja.top/)", 16 | "license": "MIT", 17 | "bugs": { 18 | "url": "https://github.com/nemanjan00/reverse-shell-listener/issues" 19 | }, 20 | "homepage": "https://github.com/nemanjan00/reverse-shell-listener#readme", 21 | "devDependencies": { 22 | "angular": "^1.6.1", 23 | "angular-terminal": "git+https://github.com/nemanjan00/angular-terminal.git", 24 | "angular-websocket": "^2.0.1", 25 | "bootstrap": "^3.3.7", 26 | "css-loader": "^0.26.1", 27 | "file-loader": "^0.9.0", 28 | "font-awesome": "^4.7.0", 29 | "jquery": "^3.1.1", 30 | "jquery.mousewheel": "^3.1.9", 31 | "jquery.terminal": "^0.11.23", 32 | "style-loader": "^0.13.1", 33 | "webpack": "^1.14.0", 34 | "webpack-livereload-plugin": "^0.10.0" 35 | }, 36 | "dependencies": { 37 | "express": "^4.14.0", 38 | "express-ws": "^2.0.0", 39 | "pm2": "^2.2.3" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /pm2.js: -------------------------------------------------------------------------------- 1 | var pm2 = require('pm2'); 2 | 3 | pm2.connect(function() { 4 | pm2.start({ 5 | script: 'server.js' 6 | }, function(err) { 7 | if (err) return console.error('Error while launching applications', err.stack || err); 8 | console.log('PM2 and application has been succesfully started'); 9 | 10 | // Display logs in standard output 11 | pm2.launchBus(function(err, bus) { 12 | console.log('[PM2] Log streaming started'); 13 | 14 | bus.on('log:out', function(packet) { 15 | console.log('[App:%s] %s', packet.process.name, packet.data); 16 | }); 17 | 18 | bus.on('log:err', function(packet) { 19 | console.error('[App:%s][Err] %s', packet.process.name, packet.data); 20 | }); 21 | }); 22 | 23 | }); 24 | }); 25 | 26 | -------------------------------------------------------------------------------- /public/assets/448c34a56d699c29117adc64c43affeb.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nemanjan00/reverse-shell-listener/bcd071eabf38b3bccbae6d07e79a3ff8a61cc945/public/assets/448c34a56d699c29117adc64c43affeb.woff2 -------------------------------------------------------------------------------- /public/assets/674f50d287a8c48dc19ba404d20fe713.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nemanjan00/reverse-shell-listener/bcd071eabf38b3bccbae6d07e79a3ff8a61cc945/public/assets/674f50d287a8c48dc19ba404d20fe713.eot -------------------------------------------------------------------------------- /public/assets/89889688147bd7575d6327160d64e760.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | -------------------------------------------------------------------------------- /public/assets/af7ae505a9eed503f8b8e6982036873e.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nemanjan00/reverse-shell-listener/bcd071eabf38b3bccbae6d07e79a3ff8a61cc945/public/assets/af7ae505a9eed503f8b8e6982036873e.woff2 -------------------------------------------------------------------------------- /public/assets/b06871f281fee6b241d60582ae9369b9.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nemanjan00/reverse-shell-listener/bcd071eabf38b3bccbae6d07e79a3ff8a61cc945/public/assets/b06871f281fee6b241d60582ae9369b9.ttf -------------------------------------------------------------------------------- /public/assets/e18bbf611f2a2e43afc071aa2f4e1512.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nemanjan00/reverse-shell-listener/bcd071eabf38b3bccbae6d07e79a3ff8a61cc945/public/assets/e18bbf611f2a2e43afc071aa2f4e1512.ttf -------------------------------------------------------------------------------- /public/assets/f4769f9bdb7466be65088239c12046d1.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nemanjan00/reverse-shell-listener/bcd071eabf38b3bccbae6d07e79a3ff8a61cc945/public/assets/f4769f9bdb7466be65088239c12046d1.eot -------------------------------------------------------------------------------- /public/assets/fa2772327f55d8198301fdb8bcfc8158.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nemanjan00/reverse-shell-listener/bcd071eabf38b3bccbae6d07e79a3ff8a61cc945/public/assets/fa2772327f55d8198301fdb8bcfc8158.woff -------------------------------------------------------------------------------- /public/assets/fee66e712a8a08eef5805a46892932ad.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nemanjan00/reverse-shell-listener/bcd071eabf38b3bccbae6d07e79a3ff8a61cc945/public/assets/fee66e712a8a08eef5805a46892932ad.woff -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | Reverse Shell Listener 13 | 14 | 15 | 16 | 17 | 35 | 36 |
37 |
38 | 48 |
49 |

Shell - {{currentShell.ip}} #{{currentShellId}}

50 | 51 |
52 |
53 |
54 |
55 |
56 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /routes/index.js: -------------------------------------------------------------------------------- 1 | module.exports = function(app) { 2 | var router = require('express').Router(); 3 | 4 | var sockets = []; 5 | app.set("sockets", sockets); 6 | 7 | var socketsByServer = {}; 8 | app.set("socketsByServer", socketsByServer); 9 | 10 | app.ws('/servers', function(ws, req) { 11 | app.get("servers").forEach(function(server){ 12 | ws.send(JSON.stringify({ 13 | message: "newServer", 14 | id: server.id, 15 | ip: server.ip, 16 | dead: server.dead 17 | })); 18 | }); 19 | 20 | sockets.push(ws); 21 | app.set("sockets", sockets); 22 | }); 23 | 24 | app.ws('/server/:id', function(ws, req) { 25 | var messagesByServer = app.get("messagesByServer"); 26 | 27 | if(messagesByServer[req.params.id] == undefined){ 28 | messagesByServer[req.params.id] = []; 29 | } 30 | 31 | app.set("messagesByServer", messagesByServer) 32 | 33 | messagesByServer[req.params.id].forEach(function(data){ 34 | ws.send(JSON.stringify({ 35 | message: "data", 36 | data: data+"" 37 | })); 38 | }); 39 | 40 | if(socketsByServer[req.params.id] == undefined){ 41 | socketsByServer[req.params.id] = []; 42 | } 43 | 44 | socketsByServer[req.params.id].push(ws); 45 | app.set("socketsByServer", socketsByServer); 46 | 47 | ws.on("message", function(message){ 48 | message = JSON.parse(message); 49 | 50 | var servers = app.get("servers"); 51 | 52 | try { 53 | servers[req.params.id].socket.write(message.command); 54 | } catch(e) {} 55 | 56 | app.set("servers", servers); 57 | 58 | 59 | var messagesByServer = app.get("messagesByServer"); 60 | 61 | messagesByServer[req.params.id].push(">"+message.command); 62 | 63 | app.set("messagesByServer", messagesByServer) 64 | 65 | var socketsByServer = app.get("socketsByServer"); 66 | 67 | socketsByServer[req.params.id].filter(function(wsNew){ 68 | if(wsNew == ws){ 69 | return true; 70 | } 71 | 72 | try{ 73 | wsNew.send(JSON.stringify({ 74 | message: "data", 75 | data: ">"+message.command 76 | })); 77 | 78 | return true; 79 | }catch(e){ 80 | return false; 81 | } 82 | }); 83 | 84 | app.set("socketsByServer", socketsByServer); 85 | }); 86 | }); 87 | 88 | return router; 89 | }; 90 | -------------------------------------------------------------------------------- /screenshot/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nemanjan00/reverse-shell-listener/bcd071eabf38b3bccbae6d07e79a3ff8a61cc945/screenshot/screenshot.png -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | // Express init 2 | 3 | var express = require('express'); 4 | 5 | var app = express(); 6 | var expressWs = require('express-ws')(app); 7 | 8 | var config = require('./config')(app); 9 | 10 | // Routing 11 | 12 | app.use(express.static('public')); 13 | 14 | app.use('/', require('./routes')(app)); 15 | 16 | // Start 17 | 18 | app.listen(app.get('PORT'), function () { 19 | console.log('PPS app listening on port '+app.get('PORT')+'!'); 20 | }); 21 | 22 | var net = require('net'); 23 | 24 | var id = 0; 25 | 26 | var servers = []; 27 | app.set("servers", servers); 28 | 29 | var messagesByServer = {}; 30 | app.set("messagesByServer", messagesByServer); 31 | 32 | var server = net.createServer(function(socket) { 33 | var currentId = id+""; 34 | var currentIP = socket.address().address+""; 35 | 36 | socket.on("data", function(data){ 37 | if(messagesByServer[currentId] == undefined){ 38 | messagesByServer[currentId] = []; 39 | } 40 | 41 | messagesByServer[currentId].push(data); 42 | 43 | app.set("messagesByServer", messagesByServer); 44 | 45 | var socketsByServer = app.get("socketsByServer"); 46 | 47 | if(socketsByServer[currentId] == undefined){ 48 | socketsByServer[currentId] = []; 49 | } 50 | 51 | socketsByServer[currentId].filter(function(ws){ 52 | try{ 53 | ws.send(JSON.stringify({ 54 | message: "data", 55 | data: data+"" 56 | })); 57 | 58 | return true; 59 | }catch(e){ 60 | return false; 61 | } 62 | }); 63 | 64 | app.set("socketsByServer", socketsByServer); 65 | }); 66 | 67 | socket.on("error", function(data){ 68 | if(messagesByServer[currentId] == undefined){ 69 | messagesByServer[currentId] = []; 70 | } 71 | 72 | messagesByServer[currentId].push("connection closed"); 73 | 74 | app.set("messagesByServer", messagesByServer); 75 | 76 | var socketsByServer = app.get("socketsByServer"); 77 | 78 | if(socketsByServer[currentId] == undefined){ 79 | socketsByServer[currentId] = []; 80 | } 81 | 82 | socketsByServer[currentId].filter(function(ws){ 83 | try{ 84 | ws.send(JSON.stringify({ 85 | message: "data", 86 | data: "connection closed" 87 | })); 88 | 89 | return true; 90 | }catch(e){ 91 | return false; 92 | } 93 | }); 94 | 95 | app.set("socketsByServer", socketsByServer); 96 | 97 | var servers = app.get("servers"); 98 | 99 | servers[currentId].dead = true; 100 | 101 | 102 | var sockets = app.get("sockets"); 103 | 104 | sockets.filter(function(ws){ 105 | try{ 106 | ws.send(JSON.stringify({ 107 | message: "newServer", 108 | id: currentId, 109 | ip: currentIP, 110 | dead: true 111 | })); 112 | 113 | return true; 114 | }catch(e){ 115 | return false; 116 | } 117 | }); 118 | 119 | app.set("sockets", sockets); 120 | }); 121 | 122 | servers.push({ 123 | socket: socket, 124 | id: id, 125 | ip: socket.address().address, 126 | dead: false 127 | }); 128 | 129 | app.set("servers", servers); 130 | 131 | var sockets = app.get("sockets"); 132 | 133 | sockets.filter(function(ws){ 134 | try{ 135 | ws.send(JSON.stringify({ 136 | message: "newServer", 137 | id: id, 138 | ip: socket.address().address, 139 | dead: false 140 | })); 141 | 142 | return true; 143 | }catch(e){ 144 | return false; 145 | } 146 | }); 147 | 148 | app.set("sockets", sockets); 149 | 150 | id++; 151 | }); 152 | 153 | server.listen(1337, '0.0.0.0'); 154 | 155 | -------------------------------------------------------------------------------- /style/custom.css: -------------------------------------------------------------------------------- 1 | .nav-sidebar > .header > a, .nav-sidebar > .header > a:hover, .nav-sidebar > .header > a:focus { 2 | color: #fff; 3 | background-color: #5cb85c; 4 | } 5 | -------------------------------------------------------------------------------- /style/dashboard.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Base structure 3 | */ 4 | 5 | /* Move down content because we have a fixed navbar that is 50px tall */ 6 | body { 7 | padding-top: 50px; 8 | } 9 | 10 | 11 | /* 12 | * Global add-ons 13 | */ 14 | 15 | .sub-header { 16 | padding-bottom: 10px; 17 | border-bottom: 1px solid #eee; 18 | } 19 | 20 | /* 21 | * Top navigation 22 | * Hide default border to remove 1px line. 23 | */ 24 | .navbar-fixed-top { 25 | border: 0; 26 | } 27 | 28 | /* 29 | * Sidebar 30 | */ 31 | 32 | /* Hide for mobile, show later */ 33 | .sidebar { 34 | display: none; 35 | } 36 | @media (min-width: 768px) { 37 | .sidebar { 38 | position: fixed; 39 | top: 51px; 40 | bottom: 0; 41 | left: 0; 42 | z-index: 1000; 43 | display: block; 44 | padding: 20px; 45 | overflow-x: hidden; 46 | overflow-y: auto; /* Scrollable contents if viewport is shorter than content. */ 47 | background-color: #f5f5f5; 48 | border-right: 1px solid #eee; 49 | } 50 | } 51 | 52 | /* Sidebar navigation */ 53 | .nav-sidebar { 54 | margin-right: -21px; /* 20px padding + 1px border */ 55 | margin-bottom: 20px; 56 | margin-left: -20px; 57 | } 58 | .nav-sidebar > li > a { 59 | padding-right: 20px; 60 | padding-left: 20px; 61 | } 62 | .nav-sidebar > .active > a, 63 | .nav-sidebar > .active > a:hover, 64 | .nav-sidebar > .active > a:focus { 65 | color: #fff; 66 | background-color: #428bca; 67 | } 68 | 69 | 70 | /* 71 | * Main content 72 | */ 73 | 74 | .main { 75 | padding: 20px; 76 | } 77 | @media (min-width: 768px) { 78 | .main { 79 | padding-right: 40px; 80 | padding-left: 40px; 81 | } 82 | } 83 | .main .page-header { 84 | margin-top: 0; 85 | } 86 | 87 | 88 | /* 89 | * Placeholder dashboard ideas 90 | */ 91 | 92 | .placeholders { 93 | margin-bottom: 30px; 94 | text-align: center; 95 | } 96 | .placeholders h4 { 97 | margin-bottom: 0; 98 | } 99 | .placeholder { 100 | margin-bottom: 20px; 101 | } 102 | .placeholder img { 103 | display: inline-block; 104 | border-radius: 50%; 105 | } 106 | -------------------------------------------------------------------------------- /tmux: -------------------------------------------------------------------------------- 1 | rename-session reverse-shell-listener 2 | send "npm run monitor" C-m 3 | new-window 4 | send "webpack --progress --colors --watch" C-m 5 | new-window 6 | send "vim ." C-m 7 | 8 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | var LiveReloadPlugin = require('webpack-livereload-plugin'); 2 | 3 | module.exports = { 4 | entry: "./entry.js", 5 | output: { 6 | path: __dirname+"/public/assets", 7 | publicPath: "assets/", 8 | filename: "bundle.js" 9 | }, 10 | module: { 11 | loaders: [ 12 | { 13 | test: /\.css$/, 14 | loader: "style-loader!css-loader" 15 | }, 16 | { 17 | test : /\.(png|ttf|eot|svg|woff(2)?)(\?[a-z0-9=&.]+)?$/, 18 | loader : 'file-loader' 19 | } 20 | ] 21 | }, 22 | plugins: [ 23 | new LiveReloadPlugin({appendScriptTag: true}) 24 | ], 25 | node: { 26 | fs: 'empty', 27 | tls: 'empty' 28 | }, 29 | target: 'web' 30 | }; 31 | 32 | --------------------------------------------------------------------------------