HIBP Password Breach Bash Script

Another challenge – leverage the basic haveibeenpwned.com web API to see if passwords have been leaked in breaches.

Pretty simple criteria here so it’s not much of a problem. Openssl does what we want and bash script is posted below.

When I was writing this, it didn’t work at first because I was being an idiot and not accounting for the newline “\n” which was completely changing the submitted hash. Just a pointer for anyone else that is tempted to use echo in this sort of application. :)

#!/bin/bash
#
# HaveIBeenPwned Password/Hash Checker
# v1.1 - Mark M
# 
# Check password or hash against hibp.
#
# -p	Prompt for password to check
# -h	Use hash [hash] to check
#
# Simple func to leverage API
gethash(){
curl -A "hibp_checkverv1" -X GET https://api.pwnedpasswords.com/range/$TRUNC 2>/dev/null |\
 awk -F":" '{print "'$TRUNC'"$1" Count:"$2}'
}

USAGE="
`basename $0` [-f filename] [-h sha1-hash] [-p]
-h [sha1hash] checks given hash against HIBP
-p option will prompt for password"

optstring=h:p
while getopts $optstring opt
do
   case $opt in
      h)   MYHASH=$OPTARG;;
      p)   printf "Enter Password: "
           stty -echo
           read MYPASS
           stty echo ;;
      *)   echo "$USAGE.";exit 1;;
   esac
done

if [ "$MYPASS" ] && [ "$MYHASH" ]; then
   echo "File and Hash set. Only use one or the other."
   exit 1
elif [ ! "$MYPASS" ] && [ ! "$MYHASH" ]; then
   echo "No parameters."
   exit 1
fi


# Set required Vars
MYPASS=$(printf $MYPASS | tr -d '\n')

# Only hash with sha1 if password option was specified
if [ "$MYPASS" ]; then
   MYHASH=$(printf $MYPASS | openssl sha1 | awk '{print toupper($2)}')
fi

# Get first 5 chars into $TRUNC 
TRUNC=$(printf $MYHASH | cut -c 1-5)

# MYPASS no longer needed. Unset it.
unset MYPASS

# Run func and checks
printf "\nCheck HIBP for $TRUNC... Full hash is \033[33m$MYHASH\n\033[0m"
HASHLIST=$(gethash)
if [ "$HASHLIST" == "" ]; then
   printf "\nError retreiving Data from Web API\n"
   exit 1
fi

printf "Does it appear in list? "
HASHCHK=$(echo "$HASHLIST" | grep -o "$MYHASH")

if [ "$HASHCHK" == "$MYHASH" ]; then
   printf "\033[31;1m< YES >\n\033[0m"
else
   printf "\033[32;1m< NO >\n\033[0m"
fi

Bash multi-threading – parallel SNMP polls

Bit of a misleading title, that. It’s really not possible and there are issues trying to set vars from the output of background child processes. However, it seems it’s possible to fake it if you’re willing to fudge it a bit with temporary files.

I got annoyed with SNMP polls across a large number of targets being very slow so decided to write something to get around it. Actually, one of the biggest issues is the default retry value of snmp commands which is set at 5. See the man page for snmpcmd which shows this.

Here is a script for grabbing the first line of SNMP get output from the specified OID from a large number of devices. There is no maximum limit here unlike my previous batch script, so if it’s a huge list, run at your own risk. It’s good for checking for things like devices still set to public read string. Beware that it uses temporary files given bash limitations, so bear in mind your user file limits.

snmphosts.txt should contain an IP address or resolvable hostname on each line.

#!/bin/bash
#
# Parallel SNMP Query for BASH - Who needs multithreading? ;)
#
# Version: V1.0 - Mark M (sol@subnetzero.org)
# Date:    15/05/2019
#
# The intention of this script is to get around how slow it is to poll
# a large number of SNMP hosts sequentially. This is achieved by a loop
# which sends each poll to the background which writes its output to
# a unique file suffixed by .$i in folder $OUTDIR. It is not possible
# to populate variables with the results of background child processes 
# in BASH so this is one workaround.
#
# Once complete, awk is used to pick out the fields of the output files
# to avoid issues with blank responses/lack of newlines. Stderror is redirected 
# to the files so that we can see when a poll failed. We only pick out
# the first line of the result with head -1 in the poll to avoid
# loads of extra lines per host with say, sysDescr for example.
#
# Keeping retries low will speed this up even more.
#
# Caveats: Extremely large lists will generate enough files to hit quotas
# or user max file limits.
#
SNMPVER="2c"
SNMPRETRIES=1
SNMPCOMMUNITY=public
SNMPLIST=snmphosts.txt
OUTDIR=tmpoutdir

SNMPOID=".1.3.6.1.2.1.1.1.0"   # system.sysDescr.0
# Some additional useful SNMP OIDs below that should usually respond.
#SNMPOID=".1.3.6.1.2.1.1.3.0"   # system.sysUpTime.0
#SNMPOID=".1.3.6.1.2.1.1.5.0"	# system.sysName.0

# Create temporary output dir if required
if [ ! -d $OUTDIR ]; then
   mkdir $OUTDIR
   if [ $? -ne 0 ]; then
      echo "Problem creating temp dir. Quitting."
      exit 1
   fi
fi

# Delete any old temp files
rm -f $OUTDIR/snmpitem* 
if [ $? -ne 0 ]; then
   echo "Error deleting old temp files in $OUTDIR. Exiting."
fi

# Init i for loop
i=0

# Loop through each host, sending query to background.
# Assigning each host to an array element for future use.
# Ignore blank lines and commented lines in $SNMPLIST file.
for host in $(cat $SNMPLIST | egrep -iv "^$|^#");
do
   printf "Polling Item $i - $host\n"
   HOSTS[$i]=$host
   printf "$host:" > $OUTDIR/snmpitem.$i

   # This bit is tricky. We have to redirect stderr to stdout in both instances
   # here to ensure we see if we get no response or some other error.
   snmpget -Ov -v$SNMPVER -r $SNMPRETRIES -c $SNMPCOMMUNITY $host $SNMPOID 2>&1 | head -1 >> $OUTDIR/snmpitem.$i 2>&1 &
   i=$(( $i + 1 ))
done
printf "Queries launched. Waiting..."
# Use BASH builtin to wait for child processes to exit.
wait
printf "Done!\n"

# Count total number in array
SNMPCOUNT=$(echo ${#HOSTS[*]})
echo "Host Count: $SNMPCOUNT ($i)"

# Use Awk to pick out fields of all files which will avoid
# formatting errors for failures. This will be in same
# order as an ls statement
awk -F":" '{print $1":"$3}' $OUTDIR/snmpitem.*

# Delete temp files
rm -f $OUTDIR/snmpitem* 

Running tasks in parallel batches in Bash

I had a requirement to run quite a lot of tasks in parallel with varying parameters. Initial experimentation suggested I might end up with a lot of processes running and potentially cause system issues so I looked into creating a script to run things in parallel, albeit in controlled batches.

In this example, I’ve substituted the actual actions I was taking with a random sleep command so that processes will finish at different times. What would probably be best would be to have the actions in another script that will log its output somewhere either by writing to a file or by using logger so syslog deals with the flurry. Typically unix file writes are atomic up to 4KB so having several processes writing at the same time isn’t a huge issue.

Bash below:

#!/bin/bash
#
# Loop through items in word list to run actions
# and process in parallel batches to avoid having
# too many processes.
#
# sol@subnetzero.org v1.0 7/5/2019
#
USAGE="`basename $0` /path/to/wordlist {batch size}"
WORDLIST=$1
BATCHSIZE=$2
if [ ! $BATCHSIZE ]; then
    BATCHSIZE=10
fi

if [ ! $1 ] || [ ! $2 ]; then
    echo "$USAGE"
    exit 1
fi

echo "Using wordlist $WORDLIST in batches of $BATCHSIZE"
i=0
for word in `cat $WORDLIST`; do
    if [ $(( $i % $BATCHSIZE )) -eq 0 ] && [ $i -ne 0 ]; then
       echo "Batch of $BATCHSIZE done... waiting"
       wait
    fi

    # Take actions here and run as background processes
    SLEEPRND=`echo $(( $RANDOM % 9 + 1 ))`
    echo "Action: $word - Sleeping for $SLEEPRND"
    sleep $SLEEPRND &

    # Increment counter for tracking
    i=$(( $i + 1 ))
done
printf "Waiting..."
wait
printf "all jobs run.\n"

Output:

[me@server ~]$ ./parallel.sh wordlist localhost 5
Using wordlist wordlist against host localhost in batches of 5
Action: a - Sleeping for 2
Action: b - Sleeping for 3
Action: c - Sleeping for 2
Action: d - Sleeping for 2
Action: e - Sleeping for 5
Batch of 5 done... waiting
Action: f - Sleeping for 7
Action: g - Sleeping for 1
Action: h - Sleeping for 1
Action: i - Sleeping for 1
Action: j - Sleeping for 8
Batch of 5 done... waiting
Action: k - Sleeping for 2
Action: l - Sleeping for 1
Waiting...All jobs run.

Writing simple games in bash

I bought a book a while ago called “Shell Scripting” by Steve Parker (ISBN 1118024486). It has some pretty impressive ideas, including an example where the author has written a version of Space Invaders in bash. Video Here!

I thought I’d have a go at writing something myself, so chose to write a version of the old “Snake” game.

snake

You can reduce the game area to make it more of a challenge by tweaking the following variables:

LASTCOL=40                              # Last col of game area
LASTROW=20                              # Last row of game area

Code below:

#!/bin/bash
#
# Centipede game
#
# v2.0
#
# Author: sol@subnetzero.org
#
# Functions

drawborder() {
   # Draw top
   tput setf 6
   tput cup $FIRSTROW $FIRSTCOL
   x=$FIRSTCOL
   while [ "$x" -le "$LASTCOL" ];
   do
      printf %b "$WALLCHAR"
      x=$(( $x + 1 ));
   done

   # Draw sides
   x=$FIRSTROW
   while [ "$x" -le "$LASTROW" ];
   do
      tput cup $x $FIRSTCOL; printf %b "$WALLCHAR"
      tput cup $x $LASTCOL; printf %b "$WALLCHAR"
      x=$(( $x + 1 ));
   done

   # Draw bottom
   tput cup $LASTROW $FIRSTCOL
   x=$FIRSTCOL
   while [ "$x" -le "$LASTCOL" ];
   do
      printf %b "$WALLCHAR"
      x=$(( $x + 1 ));
   done
   tput setf 9
}

apple() {
   # Pick coordinates within the game area
   APPLEX=$[( $RANDOM % ( $[ $AREAMAXX - $AREAMINX ] + 1 ) ) + $AREAMINX ]
   APPLEY=$[( $RANDOM % ( $[ $AREAMAXY - $AREAMINY ] + 1 ) ) + $AREAMINY ]
}

drawapple() {
   # Check we haven't picked an occupied space
   LASTEL=$(( ${#LASTPOSX[@]} - 1 ))
   x=0
   apple
   while [ "$x" -le "$LASTEL" ];
   do
      if [ "$APPLEX" = "${LASTPOSX[$x]}" ] && [ "$APPLEY" = "${LASTPOSY[$x]}" ];
      then
         # Invalid coords... in use
         x=0
         apple
      else
         x=$(( $x + 1 ))
      fi
   done
   tput setf 4
   tput cup $APPLEY $APPLEX
   printf %b "$APPLECHAR"
   tput setf 9
}

growsnake() {
   # Pad out the arrays with oldest position 3 times to make snake bigger
   LASTPOSX=( ${LASTPOSX[0]} ${LASTPOSX[0]} ${LASTPOSX[0]} ${LASTPOSX[@]} )
   LASTPOSY=( ${LASTPOSY[0]} ${LASTPOSY[0]} ${LASTPOSY[0]} ${LASTPOSY[@]} )
   RET=1
   while [ "$RET" -eq "1" ];
   do
      apple
      RET=$?
   done
   drawapple
}

move() {
   case "$DIRECTION" in
      u) POSY=$(( $POSY - 1 ));;
      d) POSY=$(( $POSY + 1 ));;
      l) POSX=$(( $POSX - 1 ));;
      r) POSX=$(( $POSX + 1 ));;
   esac

   # Collision detection
   ( sleep $DELAY && kill -ALRM $$ ) &
   if [ "$POSX" -le "$FIRSTCOL" ] || [ "$POSX" -ge "$LASTCOL" ] ; then
      tput cup $(( $LASTROW + 1 )) 0
      stty echo
      echo " GAME OVER! You hit a wall!"
      gameover
   elif [ "$POSY" -le "$FIRSTROW" ] || [ "$POSY" -ge "$LASTROW" ] ; then
      tput cup $(( $LASTROW + 1 )) 0
      stty echo
      echo " GAME OVER! You hit a wall!"
      gameover
   fi

   # Get Last Element of Array ref
   LASTEL=$(( ${#LASTPOSX[@]} - 1 ))
   #tput cup $ROWS 0
   #printf "LASTEL: $LASTEL"

   x=1 # set starting element to 1 as pos 0 should be undrawn further down (end of tail)
   while [ "$x" -le "$LASTEL" ];
   do
      if [ "$POSX" = "${LASTPOSX[$x]}" ] && [ "$POSY" = "${LASTPOSY[$x]}" ];
      then
         tput cup $(( $LASTROW + 1 )) 0
         echo " GAME OVER! YOU ATE YOURSELF!"
         gameover
      fi
      x=$(( $x + 1 ))
   done

   # clear the oldest position on screen
   tput cup ${LASTPOSY[0]} ${LASTPOSX[0]}
   printf " "

   # truncate position history by 1 (get rid of oldest)
   LASTPOSX=( `echo "${LASTPOSX[@]}" | cut -d " " -f 2-` $POSX )
   LASTPOSY=( `echo "${LASTPOSY[@]}" | cut -d " " -f 2-` $POSY )
   tput cup 1 10
   #echo "LASTPOSX array ${LASTPOSX[@]} LASTPOSY array ${LASTPOSY[@]}"
   tput cup 2 10
   echo "SIZE=${#LASTPOSX[@]}"

   # update position history (add last to highest val)
   LASTPOSX[$LASTEL]=$POSX
   LASTPOSY[$LASTEL]=$POSY

   # plot new position
   tput setf 2
   tput cup $POSY $POSX
   printf %b "$SNAKECHAR"
   tput setf 9

   # Check if we hit an apple
   if [ "$POSX" -eq "$APPLEX" ] && [ "$POSY" -eq "$APPLEY" ]; then
      growsnake
      updatescore 10
   fi
}

updatescore() {
   SCORE=$(( $SCORE + $1 ))
   tput cup 2 30
   printf "SCORE: $SCORE"
}
randomchar() {
    [ $# -eq 0 ] && return 1
    n=$(( ($RANDOM % $#) + 1 ))
    eval DIRECTION=\${$n}
}

gameover() {
   tput cvvis
   stty echo
   sleep $DELAY
   trap exit ALRM
   tput cup $ROWS 0
   exit
}

###########################END OF FUNCS##########################

# Prettier characters but not supported
# by all termtypes/locales
#SNAKECHAR="\0256"                      # Character to use for snake
#WALLCHAR="\0244"                       # Character to use for wall
#APPLECHAR="\0362"                      # Character to use for apples
#
# Normal boring ASCII Chars
SNAKECHAR="@"                           # Character to use for snake
WALLCHAR="X"                            # Character to use for wall
APPLECHAR="o"                           # Character to use for apples
#
SNAKESIZE=3                             # Initial Size of array aka snake
DELAY=0.2                               # Timer delay for move function
FIRSTROW=3                              # First row of game area
FIRSTCOL=1                              # First col of game area
LASTCOL=40                              # Last col of game area
LASTROW=20                              # Last row of game area
AREAMAXX=$(( $LASTCOL - 1 ))            # Furthest right play area X
AREAMINX=$(( $FIRSTCOL + 1 ))           # Furthest left play area X
AREAMAXY=$(( $LASTROW - 1 ))            # Lowest play area Y
AREAMINY=$(( $FIRSTROW + 1))            # Highest play area Y
ROWS=`tput lines`                       # Rows in terminal
ORIGINX=$(( $LASTCOL / 2 ))             # Start point X - use bc as it will round
ORIGINY=$(( $LASTROW / 2 ))             # Start point Y - use bc as it will round
POSX=$ORIGINX                           # Set POSX to start pos
POSY=$ORIGINY                           # Set POSY to start pos

# Pad out arrays
ZEROES=`echo |awk '{printf("%0"'"$SNAKESIZE"'"d\n",$1)}' | sed 's/0/0 /g'`
LASTPOSX=( $ZEROES )                    # Pad with zeroes to start with
LASTPOSY=( $ZEROES )                    # Pad with zeroes to start with

SCORE=0                                 # Starting score

clear
echo "
Keys:

 W - UP
 S - DOWN
 A - LEFT
 D - RIGHT
 X - QUIT

If characters do not display properly, consider changing
SNAKECHAR, APPLECHAR and WALLCHAR variables in script.
Characters supported depend upon your terminal setup.

Press Return to continue
"

stty -echo
tput civis
read RTN
tput setb 0
tput bold
clear
drawborder
updatescore 0

# Draw the first apple on the screen
# (has collision detection to ensure we don't draw
# over snake)
drawapple
sleep 1
trap move ALRM

# Pick a random direction to start moving in
DIRECTIONS=( u d l r )
randomchar "${DIRECTIONS[@]}"

sleep 1
move
while :
do
   read -s -n 1 key
   case "$key" in
   w)   DIRECTION="u";;
   s)   DIRECTION="d";;
   a)   DIRECTION="l";;
   d)   DIRECTION="r";;
   x)   tput cup $COLS 0
        echo "Quitting..."
        tput cvvis
        stty echo
        tput reset
        printf "Bye Bye!\n"
        trap exit ALRM
        sleep $DELAY
        exit 0
        ;;
   esac
done