netconenum
Hello there my friends! My name is Dot. And I am here, to tell you, how I am going to improve a tool that I already had created, which was used to enumerate which user was running a service. The plus that I am going to add, will allow us to see the content of these services, to see if they are of our interest to enumerate them as attacker or not.
POC:
The first thing I am going to focus on, is to give shape to the program, so I have thought more or less how it will work and how I plan to code it.
My original idea is, that first, before anything else, check if curl, nc, or python/3 are installed in the machine. Then with one of them, the one that the user chooses (I will do this with a case statement and functions) make a request to each of the sockets and print the result.
I know that many things could be done more simply, as for example the first part of checking the paths of the applications, with a simple if condition, comparing the execution state of the which command with a 0, could be done. But the point of this post, is to get our hands dirty, have fun, and above all, to learn.
So let's start
Checking if the applications are installed on the system.
To do this, I have thought of storing in a variable the path of each application (with each application I mean, curl, nc, python and python3) and then with an if statement check with grep if the path is in the global variable PATH system, if this, we will set a variable with the name of the application with the value true, and if not, as false.
The way I came up with to check if a route is in the global $PATH variable, was the following (from now on, I will omit the word global, and I will call it PATH):
awk
The first problem that I thought, was: How am I going to do the grep? If I have the complete path. Let me explain, if the curl path is: /usr/bin/curl
How am I supposed to do a grep with PATH? if PATH looks like this: /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
Do you see the problem? In order to grep, I would have to delete the last part, which corresponds to the name of the executable, in this case curl
.
So I remedied it easily with awk, for that I will make it, to replace the last field (using separator /
) by a "" which is the same as deleting it, since we are not really replacing it by any character. The reason why I have used awk instead of cut, is that with awk I can easily specify the last field, since with cut we would have to specify a field with a numerical value, and this can vary, since some path can be /usr/bin/binary and others /usr/local/bin/binary.
However, since we are using the / as separator, the path will be spaced, so I simply added a tr to change the spaces to / and it was perfect.
root@kali:~# which curl | awk -F"/" '{$NF = ""; print}'
usr bin
root@kali:~# which curl | awk -F"/" '{$NF = ""; print}' | tr -d " " "/"
/usr/bin/
Finally we store this result in a variable. So for the moment, the script would look like this:
As we can see, we have the path of the applications /usr/bin
which are the same for all of them, but this does not always have to be the case.
grep
Well, we already have one part done, next, is to do the grep, which we will have to do in a special way.
As we know, grep is used to find patterns in files, but not on strings, because if we pass it a string, it takes it as a file.
So, I thought of doing it in the following way:
The -c option returns the number of lines of matches. We will now use this for the if statement. The idea here, is to make a conditional, that compares the value of the grep, with 1, if the condition is true, it means that the application is in the path, and exists, so we will create a new variable with the value true, otherwise, we will create a variable and assign it the value false.
Let's try it out:
How? All values in false? How is that possible? Let's take a closer look with bash -x
Do you see what is happening? Look at the grep part, something is causing it not to be satisfied. Correct, the string being grepped ends with a /
like this /usr/bin/
, so it does not find the pattern. Therefore, it will be necessary to delete the last character of all the variables of the routes, this can be easily done with sed.
So, adding this to all the path variables of the applications, if we run the program again...
It seems to be working as planned... are we sure? I realized this when I was further ahead, and I moved the curl binary to another path, so it wouldn't find it, and the result was as follows:
How? curl is set to true? Why? Let's take a closer look.
Look at the curl path, it is empty. And the condition (1=1) is satisfied. Doing the following test, I realized that grep returns a match if no string is passed.
We're going to have to edit the script a bit, so that if it doesn't find a path, it doesn't do the if condition. What I came up with here, was to check with an if condition, the execution status of running which application
. If the execution status is 0 it means that the which has been done successfully, therefore the applicaion exists in the PATH.
So if we introduce this inside the conditional, it would look something like this:
And if we execute it:
The line break below the error message is due to the echo $curl
in line 15. But it is a debugging thing, it will be fixed later.
Well, once that part is done, let's move on to the next part.
The next thing, will be to print, the applications that are installed among curl, nc, python, python3. For it, I have thought of creating an array and adding to the array the applications that are installed, later, with a for loop, iterate over the array, and display its content along with a number. That way, the user can choose which application he wants to use, based on the number he inputs.
Choosing which app we want to use.
From here, I have switched to bash, since arrays and zsh don't get along very well, and they have a totally different syntax in zsh. So from now on, I will continue programming in bash. So the program will only work in bash.
First of all, let's declare an empty array. I will call it "apps". We can create an empty array as follows:
apps=()
Elements can be added to an array in different ways, but the simplest way I can think of, is as follows:
apps=("${apps[@]}" value) --> In this way the value will be added to the last position.
apps=(value "${apps[@]}") --> This way, the value will be added to the first potition
I will use the first way, since I want to keep the order of the applications. If we were to put all the values at the beginning (2º way), we would be doing like little endian, and store the first value, in the last position of the array.
Now, we will remove the part where we set the variables to true, and change it, so that they are added to the array. Looking something like this: (Note that this has to be done in all 4 conditionals).
Okay, next we'll do the for loop, and print each element, along with a number. And then, using the command, read
, we will ask the user to enter a number
This is easy, we simply have to create a variable with value 1 before starting the "for loop". And then, print the value of this variable, adding 1 for each iteration.
And if we run it, this would be the result:
It seems that for the moment, the program is working well, the next thing we will do is to save the for loop in a variable, so that later we can play with the output of the "for loop". By saving the output of a loop I mean the following:
In this way, we can now echo the "for loop", to grep the line number, and see the application that has that number associated with it, all this will be stored in a variable which will correspond to the application name.
The idea now, is to store the value of the application in a variable, and then create a case state. The actions that we will put in each case, will be some functions that we will have to create of each application. We also have to implement this code with the original code of the application, in order to receive the active sockets of the system and make a request with the chosen application to each of the sockets.
Creating the application flow
First of all, we are going to store in a variable ($app_name), the chosen application, for it if we look at it, we have to eliminate the number and the ")". We will do it easily with awk, printing the second field, which is going to be the application name.
case
To develop the flow I have thought of the following:
If we run it, this is the output. The wildcard that I put in the last case, serves to say that if it is not any of the previous cases, executes the following, in my case, an error message and exit with status code 1.
In this way, depending on the value of the variable $app_name, one function or another will be executed. Now, we have to move to the beginning of the script, and create the functions for each application, but this, we will do it individually, one by one. Let's start with curl. First of all, I will start the apache2, MySQL, SMB and ssh services, to test with the sockets created by these services, which are:
127.0.0.1:22 --> SSH
127.0.0.1:80 --> Apache
127.0.0.1:445 --> SMB
127.0.0.1:3306 --> Mysql
curl
Curl, mainly it will be useful to see the content of the web services, and to be able to have a quick look at them. Since we can't do much with the other services, as you will see below:
My biggest dilemma here was whether to create a general command for all services, including SSH, MySQL... This means the http0.9 plus the binary output (this could case problems, as it is not in all versions of curl)... Or just limit to a curl without arguments for the web services. Buuut... What are we here for? To make easy things, or to get our hands dirty? So let's begin :)
Well, first of all, is to save the sockets in a file, for practice and more comfortable, because when we implement this code with the original code, the sockets are stored in a variable
root@kali:~# cat sockets.txt
127.0.0.1:22
127.0.0.1:80
127.0.0.1:445
127.0.0.1:3306
Once we have that there, the next thing to do is to make a for loop, in which for each socket there is, we will print which socket it is, then with a timeout of three seconds, we will make the curl request to the socket. To finish, with an OR condition, if the timeout is finished, we will print "Timeout out", and finally, we print a line break, for visual reasons.
Although it may seem a very long and difficult process, it is not. I leave here the whole command, and below, the result.
for i in $(cat sockets.txt); do echo -e "[+]$i[+]\n" ; timeout 2 curl -s --http0.9 "$i" --output - || echo -e "Timeout out\n" ; echo ""; done
However, there is a problem, and is, that in this way, the user will have to wait 2 seconds for each timeout. Let's imagine that there are 5 ports that give timeout, then the user would have to wait 10 seconds. And this makes the script slow and annoying.
So, we are going to put in background the requests, so that they are made all at the same time, this way, the timeout will start in all the requests at the same time. And instead of putting the echo and the ; we are going to play with $() and execute the requests directly.
for i in $(cat sockets.txt); do echo -e "[+]$i[+]\n\n$(timeout 2 curl -s --http0.9 $i --output - || echo -e -----Timeout $i-----)\n" & done
Let's put this inside the curl function, let's run it, and see how it goes:
And the output:
Whoa-la, it works perfectly :) Let's move to the netcat part
netcat
For netcat, we will have to do a little trick, since it does not send a request like curl does, nc simply establishes a connection and listens, but since it does not make a request, it does not receive anything, that is why we will have to do a little trick.
As you can see, we are making a request directly from nc, so we print the body of the request with echo, and pass it with the pipe (|) to nc.
If you are wondering what are the \r\n
, in this post is clearly explained. But basically, this is known as CRLF (CR = Carriage Return (\r) and LF = Line Feed (\n) ), and it is part of the request structure. Each line of a request ends with a CRLF. And to indicate where the request ends, we dot it with a bank line (an extra <CR><LF>
or \r\n
).
Also, if you have observed, you will have noticed that the socket structure is different, it is no longer IP:Port, now it is IP Port. This can be easily remedied by using tr:
root@kali:~# echo "127.0.0.1:80" | tr ":" " "
127.0.0.1 80
So we are going to take the curl structure, and replace it with the nc one.
for i in $(cat sockets.txt) ; do echo -e "[+]$i[+]\n\n$(echo -e 'GET / HTTP/1.1\r\nHost: localhost\r\n\r\n' | timeout 2 nc $(echo $i | tr ":" " ") || echo -e -----Timeout $i-----)\n" & done
We create the nc function, and we add that
So let's run it...
Great, one more thing finished. Now, let's move to the Python part, which I will do both together (Python and Python3), it just change one small thing in the syntax and that's it.
Python and Python3
One tip that I want to give you, that I use very often: When in a box we want to make an internal request to some IP address and it is not installed neither curl, nor nc nor wget nor anything else. We can make a request with Python(3), in the following way:
Python 2.7
python -c 'import requests; r = requests.get("http://IP:Port"); print r.text'
Python3
python -c 'import requests; r = requests.get("http://IP:Port"); print(r.text)'
But there is a problem, and that is that this only works with web services, it does not work with any other service.
And this time, before copying the structure, we have to keep one thing in mind. And it is, that the variable i of the for loop, is inside bash, so python will not know what we are referring to, for this we can export it, and call it with os.environ
.
So we will have to change the loop structure a bit, so that it looks like this:
python2.7
for i in $(cat sockets.txt) ; do export i=$i; echo -e "[+]$i[+]\n\n$(timeout 2 python -c 'import os; import requests; r = requests.get("http://" + os.environ["i"]); print r.text' || echo -e -----Timeout $i-----)\n" & done 2>/dev/null
python3
for i in $(cat sockets.txt) ; do export i=$i; echo -e "[+]$i[+]\n\n$(timeout 2 python3 -c 'import os; import requests; r = requests.get("http://" + os.environ["i"]); print(r.text)' || echo -e -----Timeout $i-----)\n" & done 2>/dev/null
In this way, the variable $i will be exported for each socket that exists, and the request will be made with Python.
Let's write our Python functions and let's test it.
python2
python3
As you can see, using python, we can only enumerate the http services, since we cannot get anything else from the other services, as for example we do with curl or with nc.
Well, once done the flow of the program, now we have to integrate all this code, with the original application.
Merging the two applications
For this I have created two more functions, one which includes all the previous program (services_uids), and another which includes this new program (checking_content).
Then, to store the sockets in a variable, I did the following:
sockets=$(echo "$(netstat -ante | grep '[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}' | awk '{print $4}' | cut -d "/" -f1 | sort -k2 -n | sort -t " " -u)" | while read socket; do
echo $(echo $socket | cut -d " " -f1)
done)
And I changed, in all application functions, $(cat sockets.txt) by $(echo "$sockets"). The final result is as follows:
#!/bin/bash
#ip_puerto_servico=$(netstat -ant | awk '{print $4,$5}' | awk '{print $1}' | tr -d "a-zA-Z\-\_*()")
#pid_servicios=$(netstat -anp | grep '[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}' | awk '{print $NF}' | cut -d "/" -f1 | tr -d "-" | sort -nu)
#ip_mas_pid=$(netstat -anp | grep '[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}' | awk '{print $4,$NF}' | cut -d "/" -f1 | sort -u)
#users_uid=$(cat /etc/passwd | cut -d ":" -f1,3 | tr ':' ' ')
#Colores
greenColour="\e[0;32m\033[1m"
endColour="\033[0m\e[0m"
redColour="\e[0;31m\033[1m"
blueColour="\e[0;34m\033[1m"
services_uids(){
echo "$(netstat -ante | grep '[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}' | awk '{print $4,$7}' | cut -d "/" -f1 | sort -k1 -u)" | while read line; do
echo "Local Address: "$(echo $line | cut -d " " -f1) "--> User's UID:" $(echo $line | awk '{print $NF}')
uids=$(echo $line | awk '{print $NF}')
user=$(cat /etc/passwd | cut -d ":" -f1)
for i in $uids; do
if [[ "$(echo $i)" =~ ^[0-9]+$ ]] ; then
user=$(cat /etc/passwd | cut -d ":" -f1,3 | tr ':' ' '| grep $i)
if [ $(echo $user | awk '{print $1}') == "root" ]; then
echo -e "User: ${redColour}\e[103m$(echo $user | awk '{print $1}')\e[49m${endColour}\n"
elif [ $(echo $user | awk '{print $1}') == "mysql" ]; then
echo -e "User: ${greenColour} $(echo $user | awk '{print $1}') ${endColour}\n"
else
echo -e "User: ${blueColour} $(echo $user | awk '{print $1}')${endColour}\n"
fi
if ! [[ "$(echo $i)" =~ ^[0-9]+$ ]] ; then
echo -e "No UID \n"
fi
fi
done
done
}
checking_content(){
echo "Checking Content"
sockets=$(echo "$(netstat -ante | grep '[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}' | awk '{print $4}' | cut -d "/" -f1 | sort -k2 -n | sort -t " " -u)" | while read socket; do
echo $(echo $socket | cut -d " " -f1)
done)
path=$PATH
apps=()
#curl
if [ $(which curl > /dev/null 2>&1 ; echo $?) = "0" ]; then
curl_path=$(which curl | awk -F"/" '{$NF = ""; print}' | tr " " "/" | sed -e 's/.$//')
if [ $(echo "$path" | grep -c "$curl_path") = "1" ]; then
apps=("${apps[@]}" curl)
fi
else
echo "curl not found on the PATH system"
fi
curl_function(){
curl_output=$(for i in $(echo "$sockets"); do echo -e "[+]$i[+]\n\n$(timeout 2 curl -s --http0.9 $i --output - || echo -e -----Timeout $i-----)\n" & done 2>/dev/null)
echo "$curl_output"
}
#nc
if [ $(which nc > /dev/null 2>&1 ; echo $?) = "0" ]; then
nc_path=$(which nc | awk -F"/" '{$NF = ""; print}' | tr " " "/" | sed -e 's/.$//')
if [ $(echo "$path" | grep -c "$nc_path") = "1" ]; then
apps=("${apps[@]}" nc)
fi
else
echo "nc not found on the PATH system"
fi
nc_function(){
nc_output=$(for i in $(echo "$sockets") ; do echo -e "[+]$i[+]\n\n$(echo -e 'GET / HTTP/1.1\r\nHost: localhost\r\n\r\n' | timeout 2 nc $(echo $i | tr ":" " ") || echo -e -----Timeout $i-----)\n" & done 2>/dev/null)
echo "$nc_output"
}
#python
if [ $(which python > /dev/null 2>&1 ; echo $?) = "0" ]; then
python_path=$(which python | awk -F"/" '{$NF = ""; print}' | tr " " "/" | sed -e 's/.$//')
if [ $(echo "$path" | grep -c "$python_path") = "1" ]; then
apps=("${apps[@]}" python)
fi
else
echo "Python not found on the PATH system"
fi
python2_function(){
python2_output=$(for i in $(echo "$sockets") ; do export i=$i; echo -e "[+]$i[+]\n\n$(timeout 2 python -c 'import os; import requests; r = requests.get("http://" + os.environ["i"]); print r.text' || echo -e -----Timeout $i-----)\n" & done 2>/dev/null)
echo "$python2_output"
}
#python3
if [ $(which python3 > /dev/null 2>&1 ; echo $?) = "0" ]; then
python3_path=$(which python3 | awk -F"/" '{$NF = ""; print}' | tr " " "/" | sed -e 's/.$//')
if [ $(echo "$path" | grep -c "$python3_path") = "1" ]; then
apps=("${apps[@]}" python3)
fi
else
echo "Python3 not found on the PATH system"
fi
python3_function(){
python3_output=$(for i in $(echo "$sockets") ; do export i=$i; echo -e "[+]$i[+]\n\n$(timeout 2 python3 -c 'import os; import requests; r = requests.get("http://" + os.environ["i"]); print (r.text)' || echo -e -----Timeout $i-----)\n" & done 2>/dev/null)
echo "$python3_output"
}
####### Show installed apps to user and select one #########
cont=1
c=$(for i in "${apps[@]}"; do
echo "$cont)" $i
let cont=$cont+1
done)
echo "$c"
read -p "Choose a number: " app_number
app_name=$(echo "$c" | grep "$app_number)" | awk '{print $2}')
#### Select the app ####
case $app_name in
curl)
curl_function
;;
nc)
nc_function
;;
python)
python2_function
;;
python3)
python3_function
;;
*)
echo "Error"
exit 1
;;
esac
}
services_uids
checking_content
#Dot/@kx1z0
Final touches
As I was testing the program, I realized that when using Python, if the requests module is not installed, no error is displayed and the program continues to run without success, so I made a small fix. I added a small if condition, to check if the module was installed.
And now...
Another thing I added was color to the output, as it is a bit messy if it is all the same color. The result is as follows:
The tool only works in Bash. GitHub link: