Projektowanie stron WWW od podszewki

Artykuły na każdy temat

[PHP] Pseudo zdalny pulpit aka remote access

Dodano 04.04.2018r. o 17:24
Z natury nie mam zaufania do rozwiązań pokroju RDP czy TeamViewer choć czasami są pomocne. Może dlatego, że wykonywanie audytu bezpieczeństwa wyżej wymienionych jest "średnio" legalne, marnuje zasoby, czas i inne bzdury? Chyba tak... stąd pisanie swojej aplikacji w np. C++ to już kompletne marnotrawienie możliwości szczególnie jeżeli chce się uzyskać prosty efekt jakim jest zdalne sprawdzenie statusu maszyny, cykniecie screenshota i ewentualny zdalny shutdown. Odnosi się to do sytuacji kiedy odpalamy coś na dłuższy przebieg i np. musimy pilnie wyjść. Oczywiście w przypadku kiedy to tylko np. skrypt/program to można dodać OnExit prosty shutdown jednak jeżeli "wątków" jest więcej to sprawa się komplikuje. Właśnie dlatego napisałem mały skrypt do takich działań. Przy okazji jak słusznie kolega Sobak zauważył wykorzystałem PHP w dość nieszablonowy sposób. A ponieważ jest multiplatformowy to można łatwo przeportować na inny system.

Dość tego pierdolenia stąd czas przejść do rzeczy. Przedstawiony poniżej projekt to prototyp, który można dowolnie rozwinąć o kilka aspektów. Moja podpowiedź brzmi następująco:
  • udoskonalić metodę autoryzacji o np. port knocking, etc,
  • zaprojektować swój system szyfrowania komunikacji albo nie jeżeli Wam nie zależy i nie macie nic do ukrycia,
  • dodać metodę do usuwania IP z pliku, który jest wysyłany po uruchomieniu serwera,
  • unowocześnić metodę screenshot() dodając do niej np. parametr $mime_type,
  • w przypadku braku szyfrowania komunikacji nie trzeba bawić sie w bufor w metodzie screenshot() i wystarczy użyć stream_copy_to_stream() - aż dziwne, że w GD nie można wypluć jakąś funkcją grafiki do zmiennej,
  • poprawić warunki php_sapi_name() === 'apache2handler' na np. PHP_SAPI !== 'cli' - nginx, lighttpd i te sprawy,
  • napisać swoją binarke lub plugin do PHP w związku z feature funkcji imagegrabscreen() czyli sprawdź manuala aka problem więcej niż jednego monitora,
  • przerzucenie zapisu plików screenshot i ip piętro wyżej,
  • zastanowić się nad swoimi grzechami Wink

Jak to wygląda?

remote-access-small.png

Kod źródłowy

Plik index.php czyli panel:
Kod:
<!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">
  <link rel="shortcut icon" href="./favicon.ico">
  <title>Remote access</title>
  <link href="./css/bootstrap.min.css" type="text/css" rel="stylesheet">
  <link href="./css/style.css" type="text/css" rel="stylesheet">
  <!--[if lt IE 9]>
   <script src="./js/html5shiv.min.js"></script>
   <script src="./js/respond.min.js"></script>
  <![endif]-->
 </head>
 <body>
  <div class="container">
   <div class="jumbotron">
    <h1>Remote access</h1>
<?php
/**
 * @package Remote access
 * @subpackage Panel
 * @version: 1.00rc-1
 * @author CapaciousCore
 * @copyright Copyright (C) 2018 CapaciousCore
 * @link http://www.capaciouscore.pl/
 * @license Beerware
 */

define('BLOCK_INCLUDE'true);
// Load the necessary things
require './remote_access.class.php';
// Create remote access object
$remote_access = new remote_access();

if($remote_access -> is_authorized() === true)
{
?>
    <div class="panel-group">
     <div class="btn-group">
      <a href="status" class="btn btn-default">Status</a>
     </div>
     <div class="btn-group">
      <a href="take-screenshot" class="btn btn-success" role="button"><span class="glyphicon glyphicon-arrow-down"></span> Take screenshot</a>
      <a href="view-screenshot" class="btn btn-primary" role="button"><span class="glyphicon glyphicon-picture"></span> View screenshot</a>
      <a href="remove-screenshot" class="btn btn-danger" role="button"><span class="glyphicon glyphicon-remove"></span> Remove screenshot</a>
     </div>
     <div class="btn-group">
      <a href="shutdown" class="btn btn-warning" role="button"><span class="glyphicon glyphicon-off"></span> Shutdown</a>
      <a href="force-shutdown" class="btn btn-danger" role="button"><span class="glyphicon glyphicon-off"></span> Force shutdown</a>
     </div>
    </div>
<?php
 switch($_GET['arg'])
 {
  case 'status':
   if($remote_access -> send_request('ping') === true)
   {
    $statement 'Machine is online';
    $alert_type 'success';
   }
   else
   {
    $statement 'Machine is offline';
   }
  break;

  case 'take-screenshot':
   if($remote_access -> send_request('screenshot') === true)
   {
    $statement 'Screenshot has been taken';
    $alert_type 'success';
   }
   else
   {
    $statement 'Screenshot has not been taken';
   }
  break;

  case 'view-screenshot':
   if(file_exists('./screenshot') === true)
   {
    $screenshot base64_encode(file_get_contents('./screenshot'));
    $file_info = new finfo();
    $mime_type $file_info -> file('./screenshot'FILEINFO_MIME_TYPE);
   }
   else
   {
    $statement 'Screenshot does not exist';
   }
  break;

  case 'remove-screenshot':
   if(file_exists('./screenshot') === true)
   {
    if(unlink('./screenshot') === true)
    {
     $statement 'Screenshot has been removed';
     $alert_type 'success';
    }
    else
    {
     $statement 'Screenshot has not been removed';
    }
   }
   else
   {
    $statement 'Screenshot does not exist';
   }
  break;

  case 'shutdown':
   if($remote_access -> send_request('shutdown') === true)
   {
    $statement 'Shutdown in progress';
    $alert_type 'success';
   }
   else
   {
    $statement 'Shutdown failed';
   }
  break;

  case 'force-shutdown':
   if($remote_access -> send_request('force shutdown') === true)
   {
    $statement 'Forced shutdown in progress';
    $alert_type 'success';
   }
   else
   {
    $statement 'Force shutdown failed';
   }
  break;
 }
}
else
{
 $statement 'No authorization';
}

if($statement !== null)
{
 if($alert_type === null)
 {
  $alert_type 'danger';
 }

 // str_repeat() just for fun
 echo str_repeat(' '4).'<div class="alert alert-'.$alert_type.'" role="alert"><p class="text-center">'.$statement.'</p></div>'.PHP_EOL;
}
else if($screenshot !== null)
{
 echo str_repeat(' '4).'<img src="data:'.$mime_type.';base64,'.$screenshot.'" class="img-responsive img-thumbnail" alt="Screenshot">'.PHP_EOL;
}
?>
   </div>
  </div>
  <script src="./js/jquery.min.js"></script>
  <script src="./js/bootstrap.min.js"></script>
  <!-- IE10 viewport hack for Surface/desktop Windows 8 bug -->
  <script src="./js/ie10-viewport-bug-workaround.js"></script>
 </body>
</html>
Plik .htaccess:
Kod:
Options +FollowSymLinks -Indexes -MultiViews
RewriteEngine On
# Jump to non www address
# RewriteCond %{HTTP_HOST} ^www\.(.*)$ [NC]
# RewriteRule ^(.*)$ http://%1/$1 [R=301,L]
# Main address handling
RewriteRule ^([^.]*)$ index.php?arg=$1 [L]

<FilesMatch "^screenshot|ip$">
Order Allow,Deny
Deny from all
</FilesMatch 
Plik config.php:
Kod:
<?php
/**
 * @package Remote access
 * @subpackage Configuration file
 * @version: 1.00rc-1
 * @author CapaciousCore
 * @copyright Copyright (C) 2018 CapaciousCore
 * @link http://www.capaciouscore.pl/
 * @license Beerware
 */

if(defined('BLOCK_INCLUDE') === false)
{
 die('Restricted access');
}

$config['local']['protocol'] = 'tcp';
$config['local']['ip'] = '0.0.0.0';
$config['local']['port'] = 666;
$config['local']['allowed_ip'] = '127.0.0.1';

$config['remote']['protocol'] = 'tcp';
// $config['remote']['ip'] = '127.0.0.1';
$config['remote']['port'] = 666;

$config['system']['is_debugging_mode'] = true;
$config['system']['secret_cookie'] = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa';
$config['system']['remote_address'] = 'http://127.0.0.1/remote-access/';
$config['system']['api_key'] = 'zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz';
?>
Plik server.php (CLI):
Kod:
<?php
/**
 * @package Remote access
 * @subpackage CLI server
 * @version: 1.00rc-1
 * @author CapaciousCore
 * @copyright Copyright (C) 2018 CapaciousCore
 * @link http://www.capaciouscore.pl/
 * @license Beerware
 */

if(php_sapi_name() === 'cli')
{
 define('BLOCK_INCLUDE'true);
 // Load the necessary things
 chdir(__DIR__);
 require './remote_access.class.php';
 // Create remote access object
 $remote_access = new remote_access();
 // Run server
 $remote_access -> run();
}
?>
Plik client.php (CLI):
Kod:
<?php
/**
 * @package Remote access
 * @subpackage CLI client
 * @version: 1.00rc-1
 * @author CapaciousCore
 * @copyright Copyright (C) 2018 CapaciousCore
 * @link http://www.capaciouscore.pl/
 * @license Beerware
 */

if(php_sapi_name() === 'cli')
{
 if($argc === 2)
 {
  define('BLOCK_INCLUDE'true);
  // Load the necessary things
  chdir(__DIR__);
  require './remote_access.class.php';
  // Create remote access object
  $remote_access = new remote_access();
  // Send request
  $remote_access -> send_request($argv[1]);
 }
 else
 {
  echo 'Incorrect number of arguments '.PHP_EOL;
 }
}
?>
Plik remote_access.class.php:
Kod:
<?php
/**
 * @package Remote access
 * @subpackage Core
 * @version: 1.00rc-1
 * @author CapaciousCore
 * @copyright Copyright (C) 2018 CapaciousCore
 * @link http://www.capaciouscore.pl/
 * @license Beerware
 */

if(defined('BLOCK_INCLUDE') === false)
{
 die('Restricted access');
}

class remote_access
{
 private $config;
 private $crypt;

 public function __construct()
 {
  // Load config file and crypt class
  require './config.php';
  require './crypt.class.php';
  // Place configuration and crypt in class property
  $this -> config $config;
  $this -> crypt = new crypt();
 }

 public function run()
 {
  if(php_sapi_name() === 'cli')
  {
   set_time_limit(0);
   $this -> config['system']['mode'] = 'server';
   $address $this -> config['local']['protocol'].'://'.$this -> config['local']['ip'].':'.$this -> config['local']['port'];
   $socket stream_socket_server($address$socket_error_id$socket_error_message);

   if($socket !== false)
   {
    $this -> log('Server socket established on specified address '.$address);
    stream_set_timeout($socket5);
    $this -> register_server_ip();
    $this -> log('Server sent IP address');

    while($client stream_socket_accept($socket, -1$peer))
    {
     if($this -> is_valid_address($peer) === true)
     {
      $this -> log('Connection accepted from '.$peer);
      $command $this -> crypt -> decode(fread($client0xFF)); // or 0x400 if you like
      $this -> log('Received command: '.$command);

      switch($command)
      {
       case 'ping':
        $response 'pong';
        $message 'Pong sent';
       break;

       case 'screenshot':
        $response $this -> screenshot();
        $message 'Screenshot sent';
       break;

       case 'shutdown':
        $this -> shutdown();
        $response 'shutdown executed';
        $message 'Shutdown in progress';
       break;

       case 'force shutdown':
        $this -> shutdown(true);
        $response 'forced shutdown executed';
        $message 'Forced shutdown in progress';
       break;

       default:
        $response $message 'Unknown command';
       break;
      }

      fputs($client$this -> crypt -> encode($response));
      $this -> log($message);
     }
      else
     {
      // Log unauthorized packets?
     }

     fclose($client);
     $this -> log('Client disconnected');
    }

    fclose($socket);
    // stream_socket_shutdown($socket, STREAM_SHUT_RDWR);
    $this -> log('Server socket closed');
   }
   else
   {
    $this -> log($socket_error_message.' ('.$socket_error_id.')');
   }
  }
 }

 public function send_request($command)
 {
  $this -> config['system']['mode'] = 'client';
  $remote_ip long2ip(file_get_contents('./ip'));
  $address $this -> config['remote']['protocol'].'://'.$remote_ip.':'.$this -> config['remote']['port'];
  $socket stream_socket_client($address$socket_error_id$socket_error_message);

  if($socket !== false)
  {
   $this -> log('Established connection with server on specified address '.$address);
   fwrite($socket$this -> crypt -> encode($command));
   $this -> log('Command sent: '.$command);

   while(feof($socket) !== true)
   {
    $data .= fgets($socket1024);
   }

   $data $this -> crypt -> decode($data);
   // $this -> log('Received data: '.$data);
   fclose($socket);

   if(strlen($data) > 0)
   {
    switch($command)
    {
     case 'ping':
      if($data === 'pong')
      {
       $status 'online';
       $message 'Pong received';
       $return true;
      }
      else
      {
       $status 'offline';
       $message 'Pong not received';
      }
     break;

     case 'screenshot':
      file_put_contents('./screenshot'$dataLOCK_EX);
      $response 'screenshot received';
      $message 'Screenshot received';
      $return true;
     break;

     case 'shutdown':
      if($data === 'shutdown executed')
      {
       $response 'shutdown executed';
       $message 'Shutdown in progress';
       $return true;
      }
      else
      {
       $response 'shutdown failed';
       $message 'Shutdown failed';
      }
     break;

     case 'force shutdown':
      if($data === 'forced shutdown executed')
      {
       $response 'forced shutdown executed';
       $message 'Forced shutdown in progress';
       $return true;
      }
      else
      {
       $response 'forced shutdown failed';
       $message 'Forced shutdown failed';
      }
     break;

     default:
      $message 'Unknown command';
     break;
    }
   }
   else
   {
    $message 'No data was received';
   }

   $this -> log($message);
   $this -> log('Server connection closed'); // This should be after fclose($socket)
  }
  else
  {
   // Failed to establish a connection
   $this -> log($socket_error_message.' ('.$socket_error_id.')');
  }

  if(php_sapi_name() === 'apache2handler')
  {
   return $return;
  }
 }

 public function is_authorized()
 {
  // Other ways omitted
  return $this -> config['system']['secret_cookie'] === $_COOKIE['auth'];
 }

 private function register_server_ip()
 {
  $ch curl_init($this -> config['system']['remote_address'].'register_server.php');
  curl_setopt($chCURLOPT_RETURNTRANSFERtrue);
  curl_setopt($chCURLOPT_POSTFIELDShttp_build_query(['api_key' => $this -> config['system']['api_key']]));
  $data curl_exec($ch);
  $info curl_getinfo($ch);

  if($info['http_code'] === 200)
  {
   if($data === 'saved')
   {
    return true;
   }
  }

  curl_close($ch);

  return false;
 }

 private function screenshot()
 {
  // Pathetic solution
  ob_start();
  imagepng(imagegrabscreen(), null9);
  $screenshot ob_get_contents();
  ob_end_clean();

  return $screenshot;
 }

 private function shutdown($is_forced false)
 {
  if($this -> config['system']['is_debugging_mode'] === false)
  {
   if($is_forced === true)
   {
    $command 'shutdown /s /d p /c "Executed shutdown via remote access"';
   }
   else
   {
    $command 'shutdown /s /f /t 30 /d p /c "Executed forced shutdown via remote access"';
   }

   exec($command);
  }
 }

 private function is_valid_address($address)
 {
  $ip parse_url($addressPHP_URL_HOST);

  if(is_array($this -> config['local']['allowed_ip']) === true)
  {
   return in_array($ip$this -> config['local']['allowed_ip']);
  }
  else
  {
   return $this -> config['local']['allowed_ip'] === $ip;
  }
 }

 private function log($message)
 {
  if(php_sapi_name() === 'cli')
  {
   echo $message.PHP_EOL;
  }

  file_put_contents('./logs/'.$this -> config['system']['mode'].'_'.date('d-m-Y').'.log'date('[H:i:s]').' '.$message.PHP_EOLFILE_APPEND LOCK_EX);
 }
}
?>
Plik crypt.class.php (wrapper):
Kod:
<?php
/**
 * @package Remote access
 * @subpackage Crypt module
 * @version: 1.00rc-1
 * @author CapaciousCore
 * @copyright Copyright (C) 2018 CapaciousCore
 * @link http://www.capaciouscore.pl/
 * @license Beerware
 */

class crypt extends remote_access
{
 private $key;

 public function __construct()
 {

 }

 public function encode($data)
 {
  // Do it yourself
  return $data;
 }

 public function decode($data)
 {
  // Do it yourself
  return $data;
 }

 private function encrypt($plain)
 {
  // Do it yourself
  return $cipher;
 }

 private function decrypt($cipher)
 {
  // Do it yourself
  return $plain;
 }
}
?>
Plik register_server.php:
Kod:
<?php
/**
 * @package Remote access
 * @subpackage Register server IP address
 * @version: 1.00rc-1
 * @author CapaciousCore
 * @copyright Copyright (C) 2018 CapaciousCore
 * @link http://www.capaciouscore.pl/
 * @license Beerware
 */

if(php_sapi_name() === 'apache2handler')
{
 define('BLOCK_INCLUDE'true);
 // Load the necessary things
 require './config.php';

 if($config['system']['api_key'] === $_POST['api_key'])
 {
  echo (file_put_contents('./ip'ip2long($_SERVER['REMOTE_ADDR'])) > 'saved' 'not_saved');
 }
}
?>
Plik set_admin_cookie.php czyli ustawienie pseudo autoryzacji:
Kod:
<?php
/**
 * @package Remote access
 * @subpackage Set admin "cookie"
 * @version: 1.00rc-1
 * @author CapaciousCore
 * @copyright Copyright (C) 2018 CapaciousCore
 * @link http://www.capaciouscore.pl/
 * @license Beerware
 */

if($_SERVER['REMOTE_ADDR'] === '127.0.0.1')
{
 define('BLOCK_INCLUDE'true);
 // Load the necessary things
 require './config.php';
 // Execute necessary things
 echo (setcookie('auth'$config['system']['secret_cookie'], time() + 31536000'/') ? 'ok' 'fail'); // Expire in 1 year
}
?>

Opcja dla leniwca

Gotową paczkę można pobrać stąd.

Jak używać?

  1. Skonfigurować skrypt edytując config.php,
  2. Odpalić set_admin_cookie.php z urządzenia które ma być dopuszczone do panelu,
  3. Uruchomić server.php z poziomu CLI.

Komentarze

Brak komentarzy

Dodaj komentarz

Zostaw komentarz jeżeli możesz! Nie bądź przysłowiowym botem! Nie bądź obojętny! Ciebie to nic nie kosztuje, a mi sprawi uśmiech na twarzy.
Zezwolono używać: BBCode
Zabroniono używać:
znaczników HTML

(Wymagany)

(Wymagany, niepublikowany)

(Nie wymagana)

Token:

Obrazek dla bota

(Przepisz tylko cyfry!)

(Wymagana)