Webshell on TCL, for Cisco IOS and more

    I have long wanted to put into practice the capabilities of Cisco IOS, which are hidden behind the tclsh command and are present in almost every router and switch. But unfortunately, maybe fortunately it wasn’t necessary to solve problems where using automation by means of the device itself could at least somehow help, however, there have never been a lot of devices from Cisco under my control. Finally, fate threw me on a business trip from where it was necessary to manage the network, and in my hands only a tablet with Wi-Fi and the 80th TCP port. This time I had to dictate the commands by voice over the phone, but upon arrival the task was solved using Cisco IOS Scripting with Tcl . Fortunately, the implementation of TCL in Cisco IOS is functional enough to solve such a problem.
    First, I went in search of a ready-made solution. Actually, Cisco itself offers access to its devices via the web-based interface, but such access does not fully have all the capabilities of the console interface (as Cisco itself indicates), requires preliminary configuration (for example, Java for SDM), affects security, and is not convenient to use ( for me) - in general, on all devices no ip http server . It was also possible to map the SSH / telnet port to HTTP, but this is only a problem with the tablet: telnet has already been gone from Windows for a long time, and ssh. We take into account that we may need to log in as quickly as possible from any device with a web browser, even if it is a telephone from the early / mid-2000s.
    We look at third-party ready-made solutions for remote access to the Cisco console -here is probably the most popular article on this topic , but remember that we need at least telnet. As a result, we will formulate the requirements: access via the web interface, as simple as possible to launch on the device (ideally with one command) - and write everything yourself.
    First, what happened - using the link at the end of the post you can pick up a script that should be run on a remote device. Due to the fact that there are TCL implementations for so many systems, that’s why you can get access to Cisco IOS, Windows, Linux, FreeBSD (this is just what I checked myself). In the parameters, you need to specify the address at which we will listen to incoming requests, and the port. If you do not specify parameters, we listen to all addresses and the first free port allocated to us by the system. There is a semblance of a hint (as in Cisco) when a question mark appears in the parameters, the irony is that the hint does not work in the Cisco console, the Cisco console itself intercepts the question mark. The first line does not contain the standard #! <Path to the script> , therefore, we explicitly call tclsh (for Cisco it is impossible in another way):
    win>tclsh cws.tcl ?
    A.B.C.D or * 		Listen ip address
    win>tclsh cws.tcl * ?
    <0-65535>		Listen tcp port, 0 for first free
    win>tclsh cws.tcl * 8181
    Listen on

    For the Cisco console, write the full path (via tftp), and you must be in privileged mode. In tclsh mode, the script is called through source , but command line parameters cannot be passed to the script:
    cisco#tclsh tftp:// * 8181
    Listen on
    cisco(tcl)#source tftp://
    Listen on

    For nix consoles, the parameter "*" is perceived as a wildcard parameter, so you need to escape it "\ *" or enclose it in quotation marks.
    After starting, you can enter the browser at the listening address. The interface is ascetic - a line for entering commands, buttons for executing and stopping the script, and also a tclsh mode switch.
    If the executed command has something to output, then the output of the command will be shown at the beginning of the page. Tclsh mode executes the command as a TCL script - you can use the entire set of language commands. The input field is limited to 160 characters, size 40 characters (convenient for the tablet). The button to stop the script will complete its work on the device, we will not need to ask anyone else when we finish our actions. Errors in the execution of the command itself are processed and output back to the web interface, communication and script errors are not caught in any way, so that the standard errors of the TCL interpreter can come out in the device’s console (I tried to catch everything as much as possible, but there may be something left). It is also worth remembering that at a time the script processes only one request, and if you execute a command that requires interactive actions or a long period of execution, then access to further control the script will be lost. You can end the command with the "&" character after a space, which will run it in the background and return control back to the script; it does not work for Cisco IOS.
    The main feature of tclsh mode on Cisco is that all of the Cisco commands are interpreted by the interpreter as native, that is, in fact, we supplement the TCL language with all the functionality that has a specific device command interface - we do not need to explicitly call the commands using exec , they are already are being implemented. Also, we will not be able to enter the configuration mode from the conf t terminal , for this we need to use the Cisco TCL add-on command - ios_config . For example, turn off the interface:
    cisco#conf t
    cisco(config)#int fa0/0
    cisco(tcl)#ios_config "int fa 0/0" "shutdown"

    Very detailed about this as always at cisco.com .
    On the contrary, in Windows all commands are not executed in the console, that is, they are only perceived as a separate executable file, therefore, to execute, for example, dir, you must explicitly call the console:
    win>cmd /c dir c:\
     Том в устройстве C имеет метку SYSTEM
     Серийный номер тома: A073-3CE1
     Содержимое папки c:\

    We’ve finished the functionality, now a little about the implementation. To accept TCP connections, use the socket command in server mode, the -server switch . If necessary, we pass the address for listening (if it is not, then all addresses will be listened) -myaddr key . Mandatory parameters is the name of the callback procedure that will be called when data is transferred in the established connection, in our case it is get_http . As I already wrote, errors when creating a connection are not checked, if something goes wrong the script will swear at the console. The open socket is saved in the wsh variable , from which we get its parameters with the fconfigure $ wsh -sockname command - the listened address and port to display them on the screen.
    if { $argc == $i } then {
    	if [string length $listenaddr] then {
    		set wsh [socket -server get_http -myaddr $listenaddr $listenport]
    	} else {
    		set wsh [socket -server get_http $listenport]
    	set sockparam [fconfigure $wsh -sockname]
    	puts "Listen on http://[lindex $sockparam 0]:[lindex $sockparam 2]"
    	after $connwait set stopsrv 1
    	vwait stopsrv
    	close $wsh
    	set retcode $stopsrv
    } else {
    	set retcode [expr $i + 100]
    return $retcode

    Then we call vwait which waits for the variable stopsrv to change . Without this callback command, the procedure will not accept connections, the server itself will not listen on the port at all. In fact, only after calling this command we switch to server mode: TCL goes into the internal loop where connection requests are processed. The variable stopsrv is needed in order to stop processing connections - stop the server, we will set it when you click on the stop button in the web interface - TCL will exit the listen loop, end the callback procedure get_http and only then execute the close command , which follows vwait , which will close the open one socket. Before vwait, a protective mechanism: if we change our minds about going in or don’t take any action for a long time, then after the time set in the variable connwait = 15 minutes, stopsrv will be set and vwait will continue to execute. If we logged in, then after is reinitialized again in the get_http procedure .
    The wrapper from if is needed for the correct transfer of command line parameters to this block, the parameters themselves are checked earlier, which is how you can see it in the script itself.
    Keep in mind the features of Cisco tclsh that the Cisco commands are native to it, so instead of exit which refers specifically to Cisco and not TCL, we use return- after running the script on Cisco, a completion code will be issued to the console.
    All the main work is done in the get_http procedure , where the input parameters of HTTP requests are analyzed and the procedures are called to generate HTTP responses:
    proc get_http { sockaddr ipaddr portaddr } {	
    	global stopsrv
    	global connwait
    	gets $sockaddr r
    	flush $sockaddr
    	set rs [string tolower [string trim $r]]
    	after cancel set stopsrv 2
    	after $connwait set stopsrv 2
    	switch -regexp -- $rs {
    		{^get\s*/close} { set body 1; set stopsrv 0 }
    		{^get\s*/\s+} { puts "GET from $ipaddr:$portaddr"; after cancel set stopsrv 1 }
    		{^get\s*/\?cmd=} { if { [regexp {/\?cmd=([^[:space:]^\&]*)(&tclsh)?} $r opt cmdline checked]
    				&& [string length $cmdline] } then {
    				set cmdline [expandPercent $cmdline]
    				if { ! [string length $checked] } then { catch "exec $cmdline" msgout
    				} else { catch $cmdline msgout
    					set checked {checked} } } }
    		default {set mode 1; set body 2 } }
    	response_http $sockaddr $mode
    	response_html $sockaddr $body $cmdline $msgout $checked
    	close $sockaddr }

    The parameters of this procedure are: the open sockaddr socket , where we will read from and where we will write, as well as the portaddr port and ipaddr address of the joined client. We define the global variable stopsrv to see it outside this procedure, it is needed for vwait . We read only the first line (we do not need the rest - flush ), we expect that there will be a GET request. We check this in the switch and act in accordance with what the client passes to us. If we don’t know something, then we will return “HTTP / 1.0 501” (we cannot display the requested content). We respond to the correct request with "HTTP / 1.0 200".
    All HTTP responses are generated in the procedure.response_http , and the HTML page in response_html . I will not describe these procedures, in them the linear code for outputting page layout is just a few lines puts $ sockaddr <text> and checking the conditions of what to display.
    We respond to the following requested data:
    • / - root - display our page, and in the device’s console we write who came to us - address and port. Also cancel the pause after ;
    • / close - we finish the script work - we set the variable stopsrv (in fact, "/ close?" arrives from the client, like a request from a form without parameters);
    • / cmd = <command> & tclsh - first, we will open the text from the HTTP request - a processed line arrives to us where all spaces are replaced by "+", all non-standard characters (almost all characters except letters and numbers) are represented as% XX. For this we use expandPercent , almost invariably with the Wiki TCL . Next, we execute the cleaned command through catch and save the results in msgout , for output on our page. By calling the command in this way we catch possible errors of its execution. If the "tclsh" parameter is present, then execute it as is, if it is not present, execute it through exec - which gives us the effect of executing a command in the device’s console.

    Immediately after we send the responses, we close the socket. The fact that we do not support “keep-alive” connections is reported in each HTTP response with the “Connection: close” option and indicating the version of “HTTP / 1.0”. The script does not try to meet the standards in any way, I did the smallest possible processing so that those browsers that were at hand (Opera 12, IE7, Chrome) responded to the transmitted data normally, in those that were not at hand there might be surprises.
    The peculiarity of the language syntax directly on Cisco IOS is associated mainly with the fact that TCL version 8.3.4 is implemented and the latest version is 8.5.12. For example, the convenient switch , -matchvar, and -nocase optionsnot implemented. In any case, you can write on any platform, it’s just more strictly related to the syntax and then there will be no problems with the transfer.
    In the code, I could screw up a little, somewhere went too far with the style, somewhere on the contrary, on some devices it might not start up in view of the features of these devices, but overall it looked pretty stable to me. The main thing when using it is to remember that this is just a tool, and how to attach a head to it depends only on who puts this head.

    The script can be taken from the link - cws.tcl
    TCL on Cisco - www.cisco.com/en/US/docs/ios/12_3t/12_3t2/feature/guide/gt_tcl.html
    TCL Commands - www.tcl.tk/man/tcl8 .5 / TclCmd / contents.htm

    Also popular now: