├── .github └── FUNDING.yml ├── JqueryTerminalAsset.php ├── Module.php ├── README.md ├── WebshellAsset.php ├── assets └── webshell.css ├── composer.json ├── controllers └── DefaultController.php ├── screenshot.png └── views ├── default └── index.php └── layouts └── shell.php /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: samdark 4 | patreon: samdark 5 | -------------------------------------------------------------------------------- /JqueryTerminalAsset.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | class JqueryTerminalAsset extends AssetBundle 13 | { 14 | public $sourcePath = '@bower/jquery.terminal'; 15 | 16 | public $js = [ 17 | 'js/jquery.terminal-min.js', 18 | ]; 19 | 20 | public $css = [ 21 | 'css/jquery.terminal.css', 22 | ]; 23 | 24 | public $depends = [ 25 | 'yii\web\JqueryAsset', 26 | ]; 27 | } 28 | -------------------------------------------------------------------------------- /Module.php: -------------------------------------------------------------------------------- 1 | [ 17 | * 'webshell' => ['class' => 'samdark\webshell\Module'], 18 | * ], 19 | * ] 20 | * ~~~ 21 | * 22 | * With the above configuration, you will be able to access web shell in your browser using 23 | * the URL `http://localhost/path/to/index.php?r=webshell` 24 | * 25 | * @author Alexander Makarov 26 | */ 27 | class Module extends \yii\base\Module 28 | { 29 | /** 30 | * @inheritdoc 31 | */ 32 | public $controllerNamespace = 'samdark\webshell\controllers'; 33 | 34 | /** 35 | * @var string console greetings 36 | */ 37 | public $greetings = 'Yii 2.0 web shell'; 38 | 39 | /** 40 | * @var array URL to use for `quit` command. If not set, `quit` command will do nothing. 41 | */ 42 | public $quitUrl; 43 | 44 | /** 45 | * @var string path to `yii` script 46 | */ 47 | public $yiiScript = '@app/yii'; 48 | 49 | /** 50 | * @var array the list of IPs that are allowed to access this module. 51 | * Each array element represents a single IP filter which can be either an IP address 52 | * or an address with wildcard (e.g. 192.168.0.*) to represent a network segment. 53 | * The default value is `['127.0.0.1', '::1']`, which means the module can only be accessed 54 | * by localhost. 55 | */ 56 | public $allowedIPs = ['127.0.0.1', '::1']; 57 | 58 | /** 59 | * @var callable A valid PHP callback that returns true if user is allowed to use web shell and false otherwise 60 | * 61 | * The signature is the following: 62 | * 63 | * function (Action $action) 64 | * 65 | * @since 2.0.0 66 | */ 67 | public $checkAccessCallback; 68 | 69 | /** 70 | * @inheritdoc 71 | */ 72 | public function init() 73 | { 74 | parent::init(); 75 | set_time_limit(0); 76 | Yii::$app->getResponse()->format = Response::FORMAT_HTML; 77 | } 78 | 79 | /** 80 | * @inheritdoc 81 | */ 82 | public function beforeAction($action) 83 | { 84 | if (!parent::beforeAction($action)) { 85 | return false; 86 | } 87 | 88 | if (Yii::$app instanceof \yii\web\Application && !$this->checkAccess($action)) { 89 | throw new ForbiddenHttpException('You are not allowed to access this page.'); 90 | } 91 | 92 | return true; 93 | } 94 | 95 | /** 96 | * @return boolean whether the module can be accessed by the current user 97 | */ 98 | protected function checkAccess(Action $action) 99 | { 100 | $allowed = false; 101 | 102 | $ip = Yii::$app->getRequest()->getUserIP(); 103 | foreach ($this->allowedIPs as $filter) { 104 | if ($filter === '*' || $filter === $ip || (($pos = strpos($filter, '*')) !== false && !strncmp($ip, $filter, $pos))) { 105 | $allowed = true; 106 | break; 107 | } 108 | } 109 | 110 | if ($allowed === false) { 111 | Yii::warning('Access to web shell is denied due to IP address restriction. The requested IP is ' . $ip, __METHOD__); 112 | return false; 113 | } 114 | 115 | if ($this->checkAccessCallback !== null && call_user_func_array($this->checkAccessCallback, [$action]) !== true) { 116 | Yii::warning('Access to web shell is denied due to checkAccessCallback.', __METHOD__); 117 | return false; 118 | } 119 | 120 | return true; 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Yii 2.0 web shell 2 | ================= 3 | 4 | Web shell allows to run `yii` console commands using a browser. 5 | 6 | 7 | 8 | Installation 9 | ------------ 10 | 11 | The preferred way to install this extension is through [composer](http://getcomposer.org/download/). 12 | 13 | Either run 14 | 15 | ``` 16 | php composer.phar require --prefer-dist samdark/yii2-webshell "~2.0" 17 | ``` 18 | 19 | or add 20 | 21 | ``` 22 | "samdark/yii2-webshell": "~2.0" 23 | ``` 24 | 25 | to the require section of your `composer.json` file. 26 | 27 | 28 | Configuration 29 | ------------- 30 | 31 | To use web shell, include it as a module in the application configuration like the following: 32 | 33 | ```php 34 | return [ 35 | 'modules' => [ 36 | 'webshell' => [ 37 | 'class' => 'samdark\webshell\Module', 38 | // 'yiiScript' => Yii::getAlias('@root'). '/yii', // adjust path to point to your ./yii script 39 | ], 40 | ], 41 | 42 | // ... other application configuration 43 | ] 44 | ``` 45 | 46 | With the above configuration, you will be able to access web shell in your browser using 47 | the URL `http://localhost/path/to/index.php?r=webshell` 48 | 49 | Access control 50 | -------------- 51 | 52 | By default access is restricted to local IPs. It could be changed via `allowedIPs` property. Additionally, 53 | `checkAccessCallback` is available to be able to introduce custom access control: 54 | 55 | ```php 56 | return [ 57 | 'modules' => [ 58 | 'webshell' => [ 59 | 'class' => 'samdark\webshell\Module', 60 | // 'yiiScript' => Yii::getAlias('@root'). '/yii', // adjust path to point to your ./yii script 61 | 'allowedIPs' => ['127.0.0.1', '::1', '192.168.0.2'], 62 | 'checkAccessCallback' => function (\yii\base\Action $action) { 63 | // return true if access is granted or false otherwise 64 | return true; 65 | } 66 | ], 67 | ], 68 | 69 | // ... other application configuration 70 | ] 71 | ``` 72 | 73 | Limitations 74 | ----------- 75 | 76 | Web shell is unable to work interactively because of request-response nature of web. Therefore you should disable interactive mode for commands. 77 | -------------------------------------------------------------------------------- /WebshellAsset.php: -------------------------------------------------------------------------------- 1 | 10 | */ 11 | class WebshellAsset extends AssetBundle 12 | { 13 | public $sourcePath = '@samdark/webshell/assets'; 14 | 15 | public $css = [ 16 | 'webshell.css', 17 | ]; 18 | 19 | public $depends = [ 20 | 'samdark\webshell\JqueryTerminalAsset', 21 | ]; 22 | } 23 | -------------------------------------------------------------------------------- /assets/webshell.css: -------------------------------------------------------------------------------- 1 | body { 2 | background: black; 3 | } -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "samdark/yii2-webshell", 3 | "description": "A web shell that allows to run yii console commands and create your own commands.", 4 | "type": "yii2-extension", 5 | "keywords": ["yii2","extension","webshell","console","shell"], 6 | "license": "BSD-3-Clause", 7 | "authors": [ 8 | { 9 | "name": "Alexander Makarov", 10 | "email": "sam@rmcreative.ru" 11 | } 12 | ], 13 | "require": { 14 | "yiisoft/yii2": "~2.0.0", 15 | "bower-asset/jquery.terminal": "~0.8.8" 16 | }, 17 | "autoload": { 18 | "psr-4": { 19 | "samdark\\webshell\\": "" 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /controllers/DefaultController.php: -------------------------------------------------------------------------------- 1 | 14 | * 15 | * @property \samdark\webshell\Module $module 16 | */ 17 | class DefaultController extends Controller 18 | { 19 | /** 20 | * @inheritdoc 21 | */ 22 | public function init() 23 | { 24 | Yii::$app->request->enableCsrfValidation = false; 25 | parent::init(); 26 | } 27 | 28 | /** 29 | * Displays initial HTML markup 30 | * @return string 31 | */ 32 | public function actionIndex() 33 | { 34 | $this->layout = 'shell'; 35 | return $this->render('index', [ 36 | 'quitUrl' => $this->module->quitUrl ? Url::toRoute($this->module->quitUrl) : null, 37 | 'greetings' => $this->module->greetings 38 | ]); 39 | } 40 | 41 | /** 42 | * RPC handler 43 | * @return array 44 | */ 45 | public function actionRpc() 46 | { 47 | Yii::$app->response->format = Response::FORMAT_JSON; 48 | 49 | $options = Json::decode(Yii::$app->request->getRawBody()); 50 | 51 | switch ($options['method']) { 52 | case 'yii': 53 | list ($status, $output) = $this->runConsole(implode(' ', $options['params'])); 54 | return ['result' => $output]; 55 | } 56 | } 57 | 58 | /** 59 | * Runs console command 60 | * 61 | * @param string $command 62 | * 63 | * @return array [status, output] 64 | */ 65 | private function runConsole($command) 66 | { 67 | $cmd = Yii::getAlias($this->module->yiiScript) . ' ' . $command . ' 2>&1'; 68 | 69 | $handler = popen($cmd, 'r'); 70 | $output = ''; 71 | while (!feof($handler)) { 72 | $output .= fgets($handler); 73 | } 74 | 75 | $output = trim($output); 76 | $status = pclose($handler); 77 | 78 | return [$status, $output]; 79 | } 80 | } -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/samdark/yii2-webshell/fd0ac74c409ae13dd5ff89a22ef844fdd903cdb9/screenshot.png -------------------------------------------------------------------------------- /views/default/index.php: -------------------------------------------------------------------------------- 1 | title = $greetings; 12 | 13 | $this->registerJs( 14 | << 66 |
67 | -------------------------------------------------------------------------------- /views/layouts/shell.php: -------------------------------------------------------------------------------- 1 | 6 | beginPage() ?> 7 | 8 | 9 | 10 | 11 | 12 | 13 | <?= Html::encode($this->title) ?> 14 | head() ?> 15 | 16 | 17 | beginBody() ?> 18 | 19 | endBody() ?> 20 | 21 | 22 | endPage() ?> 23 | 24 | --------------------------------------------------------------------------------