Страницы

воскресенье, 15 декабря 2013 г.

WebSocket как инструмент удаленного администрирования.

Сегодня познакомился с одной интересной разработкой по имени websocketd, и первое, что пришло в голову после знакомства - использовать эту замечательную вещь совместно с Windows Script Host в качестве инструмента удаленного выполнения кода. Расскажу как собрать такой велосипед за 15 минут. Рассмотрим реализацию для VBScript и JScript. В качестве клиента нам потребуется браузер, который поддерживает протокол WebSocket. Системным администраторам посвящается...

Для начала скачаем бинарник websocketd.

Пишем код:
WScript.Echo(WScript.StdIn.ReadLine());

Сохраним файл с именем console1.js.

Пишем еще один не менее сложный код:
@echo off
cscript /nologo console1.js

Сохраняем с именем console.cmd.

Итак, все три файла у нас находятся в одном каталоге: websocketd.exe,  console.cmd,  console1.js.
Открываем консоль, запускаем демона:
websocketd --port 8081 console.cmd

Пишем код клиента:
<!DOCTYPE html>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<textarea id="txt" cols="60" rows="10"></textarea><br/>
<input id="btn" type="button" value="R U N"/>
<pre id="log"></pre>
<script>
  btn.addEventListener('click', function(e) {    
    var ws = new WebSocket('ws://localhost:8081/');
    ws.onopen = function() {
      log('CONNECTED');          
      ws.send(txt.value);      
    };    
    ws.onmessage = function(e) {
      log('MESSAGE: ' + e.data);      
    };
    ws.onclose = function(e) {
      log('DISCONNECTED: code = ' + e.code);
    };    
  });  
  function log(msg) {
    document.getElementById('log').innerText += msg + '\n';
  }
</script>

Сохраняем файл с расширением html, открываем в браузере, отправляем серверу приветствие, получаем ответ:


Такой эхо-сервер получился.

Дальше - интереснее. На счет 15 минут я точно поторопился. И вот спустя пару часов я могу рассказать, как собрать заявленный в заголовке велосипед за 15 минут :).

Веб-интерфейс велосипеда, который я назвал WS-console, выглядит следующим образом:

Просто, без изысков.

Исходный код клиентской части:
<!DOCTYPE html>
<html>
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
  <title>WS-console</title>
</head>
<body>  
  IP: <input id="ip" type="text" value="127.0.0.1">
  Port: <input id="port" type="text" value="8081"><br/>  
  <textarea id="txt" cols="60" rows="10"></textarea><br/>
  <input id="btn" type="button" value="R U N"/>
  <input id="vbs" type="radio" name="code" checked> VBScript
  <input id="js" type="radio" name="code"> JScript  
  <pre id="log"></pre>
  <script>
    document.getElementById('txt').focus();
    btn.addEventListener('click', function(e) {
      var ip = document.getElementById('ip').value,
          port = document.getElementById('port').value,
          ws = new WebSocket('ws://' + ip + ':' + port);
      ws.onopen = function() {
        log('CONNECTED');
        var d = document.all ? '\r\n' : '\n';   
        var arr = txt.value.split(d);
        var js = document.getElementById('js'), msg;
        if (js.checked) {
          for (var i=0; i<arr.length; i++) {
            arr[i] = arr[i].replace(/(\/\*([\s\S]*?)\*\/)|(\/\/(.*)$)/g, '');
          }
          msg = arr.join(' ');
        } else {
          var und = false;
          for (var i=0; i<arr.length; i++) {
            if (arr[i].search(/ _$/) != -1) {
              arr[i] = arr[i].replace(/ _$/, '');
              arr[i] += arr[i+1];
              arr.splice(i+1, 1);
              i--;
              continue;            
            }            
            arr[i] = arr[i].replace(/^\s+|^\s*(?:'|\brem\b).*$|(?:'|\brem\b)[^(?:\"|_$)].*$|\s+$/gi, '');          
          }
          msg = arr.join(' : ');
        }              
        ws.send(msg);      
      };    
      ws.onmessage = function(e) {
        log('MESSAGE:   ' + e.data);
      };
      ws.onclose = function(e) {
        log('DISCONNECTED');
        log('Clean = ' + e.wasClean + ', Code = ' + e.code + ', Reason = ' + (e.reason || 'none'));
      };
    });  
    function log(msg) {
      document.getElementById('log').innerText += msg + '\n';
    }
  </script>
</body>
</html>

Здесь у меня возникли трудности с нижним подчеркиванием, которое используется в VBScript для переноса кода на новую строку. Пришлось писать дополнительный код на JavaScript, чтобы просечь этот момент. Регулярное выражение для удаления комментариев у меня уже было: VBShaker - мое творение :).

Код, на котором я тестировал, выглядит так:
- VBScript:
Set wshNetwork = WScript.CreateObject( "WScript.Network" )
For i=0 To 4 'тестовый цикл
  j = j & i
Next
WScript.Echo("ComputerName: " & wshNetwork.ComputerName & vbCrLf _
    & "UserName: " & wshNetwork.UserName & vbCrLf & "j = " & j)

-JScript:
var wshNetwork = new ActiveXObject( 'WScript.Network' );
var j = '';
for (var i=0;  i<5; i++) { // тестовый цикл
  j = j + i;
}
WScript.Echo('ComputerName: ' + wshNetwork.ComputerName +
  '\nUserName: ' + wshNetwork.UserName + '\nj = ' + j);

Выполнение любого из приведенных кодов (русский не родной :), главное понятно) дает нам такой результат:

Приступаем к тестированию.
Начнем с VBScript. Пишем код файла console.vbs:
Call Execute(WScript.StdIn.ReadLine())

Метод Execute выполнит все, что прилетит к нему в стандартном входном потоке.

Редактируем код файла console.cmd:
@echo off
cscript /nologo console.vbs

Запускаем websocketd (если еще не запущен), открываем нашу консоль WS-console, выполняем код VBScript:

Все OK. Переходим к JScript. Пишем код файла console.js:
eval(WScript.StdIn.ReadLine());

Не менее трудный для понимания код :). По аналогии с VBScript в JScript мы используем метод eval().

Редактируем код файла console.cmd:
@echo off
cscript /nologo console.js

Копируем в нашу консоль код JScript, выполняем:

Все в елочку.

В завершение.
Для того, чтобы выполнить код на удаленной машине необходимо доставить на нее файлы  websocketd.exe,  console.cmd и  console.js (или console.vbs), а также запустить демона websocketd. Как это сделать - не моя забота - есть варианты.
Вероятно придется открыть соответствующий порт. Я тестировал на локальной машине и при первом запуске websocketd мой межсетевой экран ругался.
Если в процессе тестирования кода у вас возникнут ошибки, дайте мне знать, и я постараюсь их исправить.
Я привел самый простой пример удаленного выполнения кода, без авторизации, шифрования и пр. плюшек, реализацию которых оставляю на откуп любознательному читателю, осилившему этот пост до самого конца :).

UPD: На следующий день я создал репозиторий на GitHub, сделал pull request, автор websocketd заценил и добавил ссылку на созданный проект в раздел Example Project. Happy End :).