How to have access to a shell without ssh or telnet using jQuery terminal

If you have, like I do, a virtual server on hosting service, that support cgi but don’t have access to a shell and php have passthru disabled you still can have access to a shell, even if you can’t install anything on that machine – like anyterm.

There is this project, which I did, called jQuery terminal, that emulate a Unix terminal. All you need is interpreter that will execute shell commands.

Lots of hosting services disable php passthru but leave cgi script, so if you have them you can create simple shell like this one:

UPDATE: If you don’t want to code it yourself (or look at working solution), you can check my project LEASH – Browser Shell. You if you prefer there is also available single file Shell using same library.

#!/bin/bash
echo -en "Content-Type: text/plain\r\n\r\n"
query=$(/usr/bin/python -c "import urllib; print urllib.unquote_plus('$QUERY_STRING')")
eval $query 2>&1

I use python subprocess to decode QUERY_STRING. Probably it can be done using some Unix tools.

On my server I use Python CGI script like this one:.

#!/usr/bin/python
import cgi
import subprocess as sub

try:
    from json import dumps as json_serialize
except ImportError:
    def json_serialize(obj):
        result = ''
        t = type(obj)
        if t == types.StringType:
            result += '"%s"' % obj.replace('\\', '\\\\').replace('"', '\\"').replace('\n', '\\n')
        elif t == types.NoneType:
            result += 'null'
        elif t == types.IntType or t == types.FloatType:
            result += str(obj)
        elif t == types.LongType:
            result += str(int(obj))
        elif t == types.TupleType:
            result += '[' + ','.join(map(json_serialize, list(obj))) + ']'
        elif t == types.ListType:
            result += '[' + ','.join(map(json_serialize, obj)) + ']'
        elif t == types.DictType:
            array = ['"%s":%s' % (k,json_serialize(v)) for k,v in obj.iteritems()]
            result += '{' + ','.join(array) + '}'
        else:
            result += '"unknown type - ' + type(obj).__name__ + '"'
        return result

class Singleton(object):
    __single = None
    def __init__(self):
        Singleton.__single = self

    def __new__(cls):
        if Singleton.__single:
            return Singleton.__single
        else:
            return super(Singleton, cls).__new__(cls)


class Form(Singleton):
    def __init__(self):
        super(Form)
        self.form = cgi.FieldStorage()

    def __getitem__(self, name):
        return self.form.getvalue(name)

def execv(command, path):
    command = 'cd %s && %s && pwd 1>&2' % (path, command)
    proc = sub.Popen(['/bin/bash', '-c', command],
                     stdout=sub.PIPE, stderr=sub.PIPE)
    stderr = proc.stderr.read()[:-1]
    stdout = proc.stdout.read()[:-1]
    if not os.path.exists(stderr):
        raise Exception(stdout, stderr)
    return {
        "cwd": stderr,
        "stdout": re.sub('.\x08', '', stdout)
    }

if __name__ == '__main__':
    print "Content-Type: text/plain"
    print
    form = Form()
    if token() == form['token']:
        response = {}
        try:
            response['result'] = execv(form['command'], form['path'])
        except Exception, e:
            response['error'] = e.args[1]
            response['result'] = {'stdout': e.args[0], 'cwd': form['path']}
        else:
            response['error'] = None
    else:
        response = {'error': 'You are not authorized', 'result': None}
    stdout.write(json_serialize(response))

You have enabled passthru in php you can write your shell in php.

Now when you have access to a shell you need to setup interface using jQuery terminal.

<!DOCTYPE HTML>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="utf-8" />
    <title>Shell</title>
    http://code.jquery.com/jquery-1.7.1.min.js
    http://terminal/js/jquery.mousewheel-min.js
    http://terminal/js/jquery.terminal.js
    <link href="terminal/css/jquery.terminal.css" rel="stylesheet"/>
    <style>
    .terminal a.ui-slider-handle:focus { outline: none; }
    body { margin: 0; padding: 0; }
    html { background-color: #000; }
    .clear { clear: both; }
    /* This works only in Safari and Google Chrome */
    @media screen and (-webkit-min-device-pixel-ratio:0) {
        .terminal, .terminal .terminal-output, .terminal .terminal-output div,
        .terminal .terminal-output div div, .cmd, .terminal .cmd span, .terminal .cmd div {
            font-weight: bold;
        }
    }
    </style>
<script>
$(function() {
    var pwd, last_dir, home_dir;
    var terminal = $('#shell').terminal(function(command, term) {
        if (command.replace(/^ *(.*) *$/, '$1') == '-') {
            pwd = last_dir;
            return;
        }
        term.pause();
        $.post("/cgi-bin/cmd.py", {
            token: term.token(),
            command: command.replace(/(\\)?~/, function($0,$1) {
                return $1 ? $0 : home_dir;
            }),
            path: pwd
        }, function(response) {
            term.echo(response.result.stdout);
            pwd = response.result.cwd;
            term.resume();
        });
    }, {
        login: function(user, password, authenticate) {
            $.post("/cgi-bin/cmd.py", {
                user: user,
                password: password
            }, function(token) {
                if (token) {
                    home_dir = pwd = last_dir = '/home/ + user;
                }
                authenticate(token);
            });
        },
        prompt: function(callback) {
            var username = terminal.login_name();
            var re = new RegExp("^" + '/home/' + username);
            var username = '[[;#44D544;]' + username + ']';
            var path = '[[;#5555FF;]' + pwd.replace(re, '~') + ']';
            callback(username + '[[;#989898;]:]' + path + '[[;#989898;]$] ');
        },
        name: 'shell'
    }).css({
        overflow: 'auto'
    });

    $(window).resize(function() {
       terminal.css('height', $(window).height()-20);
    }).resize();

});
</script>
</head>
<body>
  <div id="shell"></div>
</body>
</html>

JQuery Terminal Emulator Plugin

My new project JQuery Terminal Emulator. It’s a plug-in which can be used to add Command Line interface to your application. You can use it to easily create server configuration tool or can be help in debugging or testing server side of AJAX applications. You can put lots of options in one place.

You can create command line interface to JSON-RPC in one line of code. Just set the path to rpc service.

$('body').terminal("json-rpc-service.php");

If you want to use authentication.

$('body').terminal("json-rpc-service.php", {
    login:true
});

And when user type user and password it will call login rpc method, get the token and pass that token to all methods on the server when user type command. So when user type for example add-user foo foo@bar.com it will call json-rpc add-user with parameters [token, “foo”, “foo@bar.com”].