Skip to the content.

Scripting for pentesting, offensive applications, and general efficiency


If there was one thing that OSCP drills into above all else, it is the importance of enumeration. This further translates into the importance of balancing thorough enumeration with efficiency as time is certainly a limiting factor when you have 24 hours to take over an entire active directory domain and 3 separate systems. This project was a way for me to gain some hands-on knowledge of using bash in a practical application while also furthering my enumeration efficiency in the course.

It is important to note that this script is tailored for the OSCP specifically and is extremely "loud" so may not be suitable for certain scenarios. With that said, these principles can be applied to craft a specialized script for your needs. I am also fairly new to bash scripting, having mostly used it within the context of the terminal in the past rather than full-blown scripts so experienced bash devs probably have a lot to teach me.

The main enumeration script can be found in the following repo: https://github.com/jeremylaratro/pentest_scripts/blob/main/start.sh

As an avid linux user, I strongly believe that learning about and taking advantage of bash is really useful. Some things, especially the more complex regex stuff can get really convoluted, but the increase in efficiency when you can use even simple commands like "cat * | grep 'Name:'" for file processing instead of having to open the files, is certainly worth the time and effort to learn it.

The script starts out with some basic argument checks and initializations. I broke the script into basic functions and instead of directly calling those functions via terminal switches, I made the choice to use an intermediate function so that I can have easier control of any messaging options and in general.

#!/bin/bash

#defaults
#ip=""
#domain=""
opts='i:d:x:nlbawshvf'
while getopts $opts arg; do
  case $arg in
    i ) ip=$OPTARG;;
    d ) domain=$OPTARG;;
    l ) lfi=1;;
    s ) sub=1;;
    w ) web=1;;
    n ) nmap=1;;
    f ) network=1;;
    a ) all=1;;
    b ) smb=1;;
    v ) exp=1;;
    r ) form=1;;
    h ) help=1;;
    * ) echo "unknown argument";;
  esac
done
argcheck() {
  if [ ! -z $ip ]; then
    export OPT=$ip
    echo "IP selected: $OPT"
  elif [ ! -z $domain ]; then
    export OPT=$domain
    echo "Domain selected: $OPT"
  fi
}

argcheck
echo "$OPT"

The section just above, argcheck is a recent addition. Basically, my focus was solely on OSCP labs where I am working mostly with IP's rather than domain names. That portion is my first step at trying to widen the scope of this script to integrate domain scanning as prior, it only really worked as intended if given IP and domain. The next step in this modification is to pull the IP address from the whatweb results.

RRED="\e[31m"
RED="31"
GREEN="32"
RGREEN="\e[32"
BOLDGREEN="\e[1;${GREEN}m"
BOLDRED="\e[1;${RED}m"
ITALICRED="\e[3;${RED}m"
ENDCOLOR="\e[0m"

help() {
echo -e "
-------------------------------------------------------------------------

| This is a simple script made to streamline inital discovery and        |
| enumeration for my OSCP course.                                        |
|   ----------------------------------------------------------------     |
| ${BOLDGREEN}Usage:${ENDCOLOR}                                                                 |
|  $(basename $BASH_SOURCE) -i 10.10.x.x -n -d example.com -v -b            		 |
|  $(basename $BASH_SOURCE) -i 10.10.x.x -a                                		 |
------------------------------------------------------------------------
"
  echo "
----------------------------
| Switches:                 |
|---------------------------|
| Arg required:             |
| -i ip_addr	-d domain   |
|---------------------------|
| Options (no args - on/off)|
|  -f full network scan     |
|  -b smb scan              |	
|  -s subdomain discovery   |	
|  -v exploit check         |	
|  -w directory enumeration |
|  -l lfi scan (requires -d |
| or -a switch first        |
|  -h help                  |
|  -a all                   |						
----------------------------	
"
  echo -e "${BOLDRED}Outputs available at output/scan_$ip ${ENDCOLOR}"
  echo "
Dependencies:
-rustscan	-nmap		-smbclient	-gobuster
-httpx		-gospider	-searchsploit  	-whatweb
-dnsrecon	-sublist3r	-enum4linux

"
}
# Shows the simple usage / syntax
usage() {
  for i in seq{1..1}; do
    echo -n "W"
    sleep 0.15s
    echo -n "E"
    sleep 0.15s
    echo -n "L"
    sleep 0.15s
    echo -n "C"
    sleep 0.15s
    echo -n "O"
    sleep 0.15s
    echo -n "M"
    sleep 0.15s
    echo -n "E"
    sleep 0.15s
  done
  echo "
-------------------------------------------------------------------------

| This is a simple script made to streamline inital discovery and        |
| enumeration for my OSCP course.                                        |
|   ----------------------------------------------------------------     |
| Usage:                                                                 |
|  $(basename $BASH_SOURCE) -i 10.10.x.x -n -d example.com -v -b         |
|  $(basename $BASH_SOURCE) -i 10.10.x.x -a                              |
------------------------------------------------------------------------
"
  echo "
----------------------------
| Switches:                 |
|---------------------------|
| Arg required:             |
| -i ip_addr	-d domain   |
|---------------------------|
| Options (no args - on/off)
|  -f full network scan	
|  -b smb scan              |	
|  -s subdomain discovery   |	
|  -v exploit check         |	
|  -w directory enumeration |
|  -l lfi scan              |
|  -h help                  |
|  -a all                   |						
----------------------------	
"	
  echo -e "${RED}Outputs available at output/scan_$ip ${ENDCOLOR}"				
  echo "
Write access is required within the directory from which the script is run and root privileges necessary for nmap syn scan.										
  "  

  echo " "
}
#touch output/inital_$ip.txt
dircreate() {
    if [ -d 'output/scan_$ip' ]
  then
    echo 'Directory already exists, moving on.'
  else
    echo 'Creating output directory'
    mkdir output/scan_$ip 
  fi
touch output/scan_$ip/web_scan_$ip.txt
touch output/scan_$ip/inital_$ip.txt
touch output/scan_$ip/open.txt
touch output/scan_$ip/rports_$ip.txt
#output/scan_$ip/vulnchk_$ip.xml
web_out=output/scan_$ip/web_scan_$ip.txt
LOGFILE=output/scan_$ip/inital_$ip.txt
LOG2=output/scan_$ip/vulnchk_$ip.xml
export rports=output/scan_$ip/rports_$ip.txt
export open=output/scan_$ip/open.txt
echo " "
echo -e "${RED}Outputs available at output/scan_$ip${ENDCOLOR}"
echo " "
}

The script then goes on to create directories where the output results will be stored. I've been trying to incorporate some colors and eventually would like to make the script format the data and output it into a report-ready state.

#what() {
#  echo "......................................"
#  echo "Grabbing banner with whatweb:"
# whatweb $ip
#  echo "......................................"
#}
rustall() {
	echo " Starting full port scan.."
	rustscan -a $ip --ulimit 5000 -g > $rports
	cat $rports | awk '/->/{print $3}' | tr -d '[]' | tr "," "\n" |tr -d "^ " > $open

	echo " ---------------------------"
	echo " "
}
nmap_run() {
  echo "......................................"
  echo -n "Starting nmap scan..."  
  #for i in seq{1..10}; do
  #  echo -n "."
  #sleep 0.15s
  #done
  
  echo " "
  echo "......................................"
  echo -e "${RGEEN}Nmap scan results:${ENDCOLOR}"
  ropen=$(cat $rports | awk '/->/{print $3}' | tr -d '[]') 
  echo 'The following ports are open: '$ropen
  sudo nmap -sS -sC -vv -sV $ip -p "$(echo $ropen)" -oX $LOG2 -oG $LOGFILE
  echo " "
  echo "......................................"
  echo " " 
  #nmap domain check
  #echo -n "Checking if domain exists..." 
  #for i in seq{1..10}; do
  #  echo -n "."
  #  sleep 0.15s
  #done
  #echo " "
  #sudo nmap $ip -p 53 -Pn -v
  echo "Nmap scan complete"
  echo " "
}

The first few functions are mostly centered around initial enumeration. I chose to use rustscan in this case because it can provide a fairly thorough report on port openings within seconds, whereas nmap may take up to 30 minutes in some cases.

I wanted to use rustscan but also retain the benefits of nmap's scripting engine and fingerprinting options, so I chose to pipe the rustscan output into a file, and then use grep, awk, and tr to parse that output and present a set of ports for nmap to perform targeted scanning on.

search_discover() {
  echo "Starting web discovery"
  #cat $LOGFILE | grep -o '[0-9]\{1,6\}/open/' | awk -F/ '{print$1}' > open.txt
  
  while read port; do
    echo -e "${BOLDGREEN}Checking for webserver on: $ip:$port ${ENDCOLOR}"
    httpx http://$ip:$port
    #whatweb http://$ip:$port
    #httpx https://$ip:$port
    if (httpx http://$ip:$port | grep -q -Eo '200 OK'); then
		echo $port >> output/scan_$ip/webserv.txt
		whatweb http://$ip:$port
	fi
  done < output/scan_$ip/open.txt
  echo ""
  echo " ----------------------------------------"
  echo "Starting web directory enumeration"
  echo ""
  while read wport; do
    echo "Starting GoSpider with:"
    echo "gospider -s http://'$ip:$wport"
    echo ' '
    gospider -s http://$ip:$wport
    echo "Starting GoBuster with:"
    echo "gobuster dir -u http://$ip:$wport --wordlist /usr/share/wordlists/SecLists-master/Discovery/Web-Content/raft-medium-directories.txt -x php,txt,js,html -o $web_out"
    echo ' '
    gobuster dir -u http://$ip:$wport --wordlist /usr/share/wordlists/SecLists-master/Discovery/Web-Content/raft-medium-directories.txt -x php,txt,js,html -o $web_out
    echo "Web server scanning complete" -v
    echo " "
  done < output/scan_$ip/webserv.txt
}


web_server_d() {
  echo ""
  echo " ----------------------------------------"
  echo "Starting web directory enumeration"
  echo ""
  #while read wport; do
  echo "Starting GoSpider with:"
  echo "gospider -s http://'$OPT:$wport"
  echo ' '
  gospider -s http://$OPT:$wport
  echo "Starting GoBuster with:"
  echo "gobuster dir -u http://$OPT:$wport --wordlist /usr/share/wordlists/SecLists-master/Discovery/Web-Content/raft-medium-directories.txt -x php,txt,js,html -o" $web_out
  echo ' '
  gobuster dir -u http://$OPT:$wport --wordlist /usr/share/dirb/wordlists/big.txt -x php,txt,js,html 
  echo "Web server scanning complete" -v
  echo " "
  #done < output/scan_$ip/webserv.txt
}

The next couple functions are focused on web discovery, and include directory bruteforcing, basic spidering, and domain discovery of the open ports.

searchspl() (
  echo -n "Checking for known exploits..."
  for i in seq{1..10}; do
    echo -n "."
    sleep 0.15s
  done
  echo " "
  #whatweb $ip > ww_$ip.txt
  #searchsploit --nmap vulnchk_$ip.xml 
  #cat ww_$ip.txt | grep -Po 'PHP/\d.\d.' | tr '/' ' ' | xargs -i searchsploit -t {} | grep php
  #cat ww_$ip.txt | grep -Po 'Apache/\d.\d.' | tr '/' ' ' | xargs -i searchsploit -t {} | grep apache
  #rm ww_$ip.txt


  ssh_() {
    if (cat $LOG2 | grep -q 'ssh'); then
      rm srch.txt
      touch srch.txt
      cat $LOG2 | grep 'ssh' | grep -o 'product=".*"' | cut -d \" -f2 | grep -o '^\S*'  > srch.txt
      cat $LOG2 | grep 'ssh' | grep -oE 'version="[0-9]?.[0-9]?.' | grep -Eo '[0-9]?.[0.9]..' >> srch.txt
      vv=$(cat srch.txt)
      if [ ! -z srch.txt ]; then
        echo 'Searching exploitdb for' $vv
        searchsploit $vv
        # echo 'Analyzing ssh vulnerabilities'
        # nmap $ip --script=ssh-\* -v
        echo ' '
        
      else
        echo ' '
      fi
    else
      echo ' '
    fi
  }
  ssh_
  
  ftp_() {
    if (cat $LOG2 | grep -q 'ftp'); then
  	rm srch.txt
  	touch srch.txt
  	cat $LOG2 | grep 'ftp' | grep -o 'product=".*"' | cut -d \" -f2 | grep -o '^\S*'  > srch.txt
  	cat $LOG2 | grep 'ftp' | grep -oE 'version="[0-9]?.[0-9]?.' | grep -Eo '[0-9]?.[0.9]..' >> srch.txt
  	vv=$(cat srch.txt)
  	if [ ! -z srch.txt ]; then
  		echo 'Searching exploitdb for' $vv
  		searchsploit $vv
  		echo ' '
  		
  	else
  		echo ' '
  	fi
    else
      echo ' '
    fi
  }
  ftp_
  
  smb_() {
    if (cat $LOG2 | grep -q 'smb'); then
  	rm srch.txt
  	touch srch.txt
  	cat $LOG2 | grep 'smb' | grep -o 'product=".*"' | cut -d \" -f2 | grep -o '^\S*'  > srch.txt
  	cat $LOG2 | grep 'smb' | grep -oE 'version="[0-9]?.[0-9]?.' | grep -Eo '[0-9]?.[0.9].' >> srch.txt
  	vv=$(cat srch.txt)
  	if [ ! -z srch.txt ]; then
  		echo 'Searching exploitdb for' $vv
  		searchsploit $vv
  		echo ' '
  		echo 'Analyzing smb vulnerabilities'
          nmap $ip --script=smb-vuln\* -v
          echo ' '
  		
  	else
  		echo ' '
  	fi
    else
      echo ' '
    fi
  }
  smb_
  
  web_() {
    if (cat $LOG2 | grep -q 'http'); then
  	rm srch.txt
  	touch srch.txt
  	cat $LOG2 | grep 'http' | grep -o 'product=".*"' | cut -d \" -f2 | cut -d " " -f1,2  > srch.txt
  	cat $LOG2 | grep 'http' | grep -oE 'version="[0-9]?.[0-9]?.' | grep -Eo '[0-9]?.[0.9].' >> srch.txt
  	vv=$(cat srch.txt)
  	if [ ! -z srch.txt ]; then
  		echo 'Searching exploitdb for' $vv
  		searchsploit $vv
  		echo ' '
  		echo 'Analyzing http vulnerabilities'
          nmap $ip --script=http-vuln\* -v
          echo ' '
  		
  	else
  		echo ' '
  	fi
    else
      echo ' '
    fi
  }
  web_
)

#TODO: print out the discovered versions and services above each searchsploit query

smb_scan_old() {
  cat $ropen | grep 139 | greprc=$?
  if [[ $greprc -eq 0 ]]; then
    echo "Found open SMB ports"
    echo " "
    enum4linux $ip
  fi
}

smb_scan() {
  echo "\n"
  echo "Checking for standard SMB service to scan with enum4linux"
  if grep -q -E '(^|, )139(,|$)|(^|, )445(,|$)' $rports; then
    echo "Found open SMB ports"
    echo " "
    enum4linux $ip
  else echo 'SMB not found'
  echo "\n"
     
  fi
}

# still developing this function
touch output/scan_$ip/lfi_output.txt
lfi_out=output/scan_$ip/lfi_output.txt
lfi_scan() {
  cat $web_out | grep php | sed -e's,^\(.* (\).*,\1,g' | cut -d ' ' -f1 > output/tmp.txt
  file=$(cat output/tmp.txt)
  for line in $file; do
    gobuster fuzz --url http://$ip/$line/?file=FUZZ -w /usr/share/wordlists/SecLists-master/Fuzzing/LFI/LFI-LFISuite-pathtotest-huge.txt -b 404,400 -o $lfi_out
  done
}

lfi_analysis() {
  echo "Full LFI output file available at lfi_output.txt"
  echo "Starting LFI output analysis for interesting results"
  echo " "
  nums="[0-9]+"
  for i in 200 201 202 302 301 304 305 307 308; do   
    stat=$(cat $lfi_out | grep -o Status=$i |   wc -l)
    echo "Status code ($i): " $stat
  done
  res=$(cat $lfi_out | grep -rohP Length=[0-9\.]+ | sort -u)
  for nums in $res; do
    occ=$(echo $nums && cat $lfi_out | grep $nums | wc -l)
    echo "Length frequencies (Length | Frequency): "$occ
    echo " "
  done
  echo "The results of this output are best analyzed manually; try searching for the least frequent results first."
  echo "For example: cat lfi_output.txt | grep Length=<output with least ocurrence>"
  echo " "
}

The next block of functions are my attempt at a targeted vulnerability discovery process. I'm still working on improving this section as the grep parsing is not always great. The intention is to grab service name and version and search it with searchsploit, however, sometimes this ends up with double service names instead of version, or is missing the version, so I will have to continue to refine this.

formatter() {
  #cat vulnchk_10.11.1.111.xml| grep -Po 'portid="*[0-9]+."' | tr -d 'portid="' | sed 's/$/,/' | tr -d '\n' >> report_$ip.txt
  echo 'Target: '$ip 
  echo 'Hostname: '
  echo 'TCP:\r' && cat $rports |  awk '/->/{print $3}' | tr -d '[]' 
}

subd() {
  if [ ! -z "$domain" ]
  then
    sublist3r -d $domain
    dig ANY $domain @$ip
    dig ANY $domain
    dnsrecon -d $domain -n $ip -t a
  else
	
    dig ANY $ip
  fi
}

# start function create the output file to which the scans will be written. 
start() {
  echo -n "Starting in.." 
  for i in {3..1}
  do
    echo -n "$i"
    sleep 0.5
    echo -n "."
    sleep 0.25
    echo -n "."
    sleep 0.25
    echo -n "."
    sleep 0.25
    echo -n "."
    sleep 0.25
  done
  echo " "

}

#main function runs the scan functions after usage info and initilization has completed. 
main() {
  # Check if either ip or domain is provided
  if [ -z "$ip" ] && [ -z "$domain" ]; then
    echo "Please provide either -i or -d option"
    exit 1
  fi
  
 
  if [ ! -z "$nmap" ]
  then
    echo "Starting inital nmap scan on $ip using top 1000 ports."
    echo " "
    dircreate 
    rustall
    nmap_run
  fi
    
  if [ ! -z "$network" ]
  then
    echo "Starting full network scan.."
    echo " " 
    dircreate
    #what
    search_discover
    rustall
    nmap_run    
  fi
  
  if [ ! -z "$exp" ]
  then
    dircreate
    echo "Searching for public exploits"
    searchspl
  fi
  if [ ! -z "$help" ]
  then
    help
  fi
  if [ ! -z "$sub" ]
  then
    dircreate
    echo "Starting subdomain discovery"
    subd
  fi
  
  if [ ! -z "$smb" ]
  then
    dircreate
    smb_scan
  fi
  
  if [ ! -z "$web" ] && [ ! -z "$ip" ] 
  then
    dircreate
    echo "Gathering web server information and starting directory enumeration on http://$OPT"
    #what
    search_discover
    #web_server
  fi
  
  if [ ! -z "$web" ]
  then
    web_server_d
  fi
  
  if [ ! -z "$lfi" ]
  then
    dircreate
    echo "Starting PHP/LFI scan"
    lfi_scan
    lfi_analysis
  fi
  
  if [ ! -z "$all" ]
  then
    dircreate
    echo -e "${BOLDGREEN}All commands enabled${ENDCOLOR}"
    #what
    rustall
    nmap_run
    search_discover
    searchspl
    web_server
    subd
    smb_scan
    lfi_scan
    lfi_analysis
  fi
#  if [ "${#}" -eq 0 ]; then
#    
#    >&2 echo "No arguments provided"
#    help
#    
#  fi

  formatter

}

main


The end of the script simply parses the switches used and then calls the relevant function. As mentioned earlier, I related general functions to the switches rather than the functions themselves so, as can be seen above, multiple functions can be called with the use of a single switch and to more easily add output messages without editing every function.