├── compiled.sign ├── compile.sh ├── env.sh ├── scanners ├── dpkg.sh ├── phpmyadmin.sh ├── joomla.sh ├── npm.sh ├── composer.sh ├── magento.sh ├── drupal.sh └── wordpress.sh ├── README.md ├── patrolserver ├── args.sh ├── json.sh ├── api.sh ├── index.sh └── compiled.sh /compiled.sign: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PatrolServer/bashscanner/HEAD/compiled.sign -------------------------------------------------------------------------------- /compile.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | . env.sh 4 | 5 | SetEnv 6 | 7 | COMPILED=$(cat index.sh) 8 | INCLUDES=$(grep "^\. " < index.sh) 9 | 10 | for INCLUDE in $INCLUDES; do 11 | FILE=$(echo "$INCLUDE" | cut -d ' ' -f2) 12 | FILE_CONTENTS=$(cat "$FILE") 13 | COMPILED="${COMPILED/$INCLUDE/$FILE_CONTENTS}" 14 | done 15 | 16 | echo "$COMPILED" > compiled.sh 17 | openssl dgst -sha256 -sign private.key -out compiled.sign compiled.sh 18 | 19 | ResetEnv -------------------------------------------------------------------------------- /env.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | function SetEnv { 4 | # IFS for return function contents in newlines. 5 | OLDIFS=$IFS 6 | IFS=$'\n' 7 | 8 | # Set 77 error code as exit any subshell level. 9 | set -E 10 | trap '[ "$?" -ne 77 ] || exit 77' ERR 11 | } 12 | 13 | function ResetEnv { 14 | IFS=$OLDIFS 15 | } 16 | 17 | function Exit { 18 | exit 77; 19 | } 20 | 21 | function Random { 22 | tr -dc 'a-zA-Z0-9' < /dev/urandom | fold -w "${1:-32}" | head -n 1 23 | } -------------------------------------------------------------------------------- /scanners/dpkg.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | function DpkgSoftware { 4 | if `command -v dpkg >/dev/null 2>&1` 5 | then 6 | local SUBSOFTWARE=`dpkg -l 2> /dev/null | grep '^i' | grep -v "lib" | tr -s ' ' | sed 's/ /\t/g'| cut -f2,3` 7 | 8 | for LINE in $SUBSOFTWARE; do 9 | NAME=`echo $LINE | cut -f1` 10 | VERSION=`echo $LINE | cut -f2` 11 | 12 | NAME=`Jsonspecialchars $NAME` 13 | VERSION=`Jsonspecialchars $VERSION` 14 | 15 | echo -e "/\t\t$NAME\t$VERSION" >> $SOFTWARE 16 | done 17 | fi 18 | } -------------------------------------------------------------------------------- /scanners/phpmyadmin.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | function PhpmyadminSoftware { 4 | FILES=`locate --database=$LOCATE "phpmyadmin.css.php" 2> /dev/null` 5 | for FILE in $FILES; do 6 | 7 | # Get root path 8 | local DIR=`dirname $FILE` 9 | local VERSION_FILE="${DIR}/libraries/Config.php" 10 | 11 | local VERSION="" 12 | if [ -f $VERSION_FILE ] 13 | then 14 | local VERSION=`cat "$VERSION_FILE" | grep "\\$this->set('PMA_VERSION', '[0-9.]*')" | grep -o "[0-9.]*" 2> /dev/null` 15 | fi 16 | 17 | if [ "$VERSION" == "" ] 18 | then 19 | local VERSION_FILE="${DIR}/libraries/Config.class.php" 20 | if [ -f $VERSION_FILE ] 21 | then 22 | local VERSION=`cat "$VERSION_FILE" | grep "\\$this->set('PMA_VERSION', '[0-9.]*')" | grep -o "[0-9.]*" 2> /dev/null` 23 | fi 24 | fi 25 | 26 | echo -e "$DIR\t\tphpmyadmin\t$VERSION" >> $SOFTWARE 27 | 28 | done 29 | } 30 | -------------------------------------------------------------------------------- /scanners/joomla.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | function JoomlaSoftware { 4 | FILES=`locate --database=$LOCATE "authentication/joomla/joomla.xml" | sort | uniq 2> /dev/null` 5 | for FILE in $FILES; do 6 | 7 | # Get root path 8 | local DIR=`dirname $FILE` 9 | local DIR=`cd "$DIR/../../../"; pwd` 10 | local VERSION_FILE="${DIR}/libraries/cms/version/version.php" 11 | 12 | local VERSION="" 13 | if [ -f $VERSION_FILE ] 14 | then 15 | 16 | local VERSION_BRANCH=`cat "$VERSION_FILE" | grep "RELEASE = '[0-9.]*';" | grep -o "[0-9.]*" 2> /dev/null` 17 | local VERSION_SECURITY=`cat "$VERSION_FILE" | grep "DEV_LEVEL = '[0-9]*';" | grep -o "[0-9.]*" 2> /dev/null` 18 | 19 | if [[ "$VERSION_BRANCH" != "" && "$VERSION_SECURITY" != "" ]] 20 | then 21 | local VERSION="$VERSION_BRANCH.$VERSION_SECURITY" 22 | fi 23 | fi 24 | 25 | echo -e "$DIR\t\tjoomla\t$VERSION" >> $SOFTWARE 26 | 27 | done 28 | } 29 | -------------------------------------------------------------------------------- /scanners/npm.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | function NpmSoftware { 4 | 5 | POTENTIAL=`locate --database=$LOCATE --regex "node_modules$" | grep -v "node_modules.*node_modules" | grep -v "\.npm" 2> /dev/null` 6 | for DIR in $POTENTIAL; do 7 | 8 | if command -v npm >/dev/null 2>&1 9 | then 10 | VERSION=`npm -v` 11 | echo -e "$DIR\t\tnpm\t$VERSION" >> $SOFTWARE 12 | fi 13 | 14 | for MODULE in $DIR/*/package.json; do 15 | if [[ "$MODULE" != "$DIR/*/package.json" ]] 16 | then 17 | JSON=`cat $MODULE | json` 18 | NAME=`echo "$JSON" | grep '\[\"name\"\]' | cut -f2- | sed -e 's/^"//' -e 's/"$//'` 19 | VERSION=`echo "$JSON" | grep '\[\"version\"\]' | cut -f2- | sed -e 's/^"//' -e 's/"$//'` 20 | 21 | echo -e "$DIR\tnpm\tnpm/$NAME\t$VERSION" >> $SOFTWARE 22 | fi 23 | done 24 | done 25 | 26 | } -------------------------------------------------------------------------------- /scanners/composer.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | function ComposerSoftware { 4 | local FILES=`locate --database=$LOCATE "composer.lock" 2> /dev/null` 5 | for FILE in $FILES; do 6 | local DIR=`dirname $FILE` 7 | local JSON="$DIR/composer.json" 8 | 9 | local PARENT="Undefined" 10 | if [ -f $JSON ] 11 | then 12 | PARENT=`cat $JSON | json | grep '^\["name"\]' | cut -f2- | sed -e 's/^"//' -e 's/"$//'` 13 | fi 14 | 15 | echo -e "$FILE\t\t$PARENT\t" >> $SOFTWARE 16 | 17 | local JSON_DATA=`cat $FILE | json -bn` 18 | local NAMES=`echo "$JSON_DATA" | grep -E '^\["packages",[0-9]{1,},"name"\]' | cut -f2- | sed -e 's/^"//' -e 's/"$//'` 19 | local VERSIONS=`echo "$JSON_DATA" | grep -E '^\["packages",[0-9]{1,},"version"\]' | cut -f2- | sed -e 's/^"//' -e 's/"$//'` 20 | local COMPSERSOFTWARE=`paste <(echo "$NAMES") <(echo "$VERSIONS")` 21 | for LINE in $COMPSERSOFTWARE; do 22 | NAME=`echo $LINE | cut -f1` 23 | VERSION=`echo $LINE | cut -f2` 24 | 25 | NAME=`Jsonspecialchars $NAME` 26 | VERSION=`Jsonspecialchars $VERSION` 27 | 28 | echo -e "$FILE\t$PARENT\t$NAME\t$VERSION" >> $SOFTWARE 29 | done 30 | done 31 | } -------------------------------------------------------------------------------- /scanners/magento.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | function MagentoSoftware { 4 | FILES=`locate --database=$LOCATE "app/Mage.php" 2> /dev/null` 5 | for FILE in $FILES; do 6 | 7 | # Get root path 8 | local DIR=`dirname $FILE` 9 | local DIR=`cd "$DIR/../"; pwd` 10 | local VERSION_FILE="${DIR}/app/Mage.php" 11 | 12 | local VERSION="" 13 | if [ -f $VERSION_FILE ] 14 | then 15 | 16 | local VERSION_MAJOR=`cat "$VERSION_FILE" | grep "'major' *=> '[0-9.]*'" | grep -o "[0-9.]*" 2> /dev/null` 17 | local VERSION_MINOR=`cat "$VERSION_FILE" | grep "'minor' *=> '[0-9]*'" | grep -o "[0-9.]*" 2> /dev/null` 18 | local VERSION_REVISION=`cat "$VERSION_FILE" | grep "'revision' *=> '[0-9]*'" | grep -o "[0-9.]*" 2> /dev/null` 19 | local VERSION_PATCH=`cat "$VERSION_FILE" | grep "'patch' *=> '[0-9]*'" | grep -o "[0-9.]*" 2> /dev/null` 20 | 21 | echo $VERSION_MAJOR 22 | 23 | if [[ "$VERSION_MAJOR" != "" && "$VERSION_MINOR" != "" && "$VERSION_REVISION" != "" && "$VERSION_PATCH" != "" ]] 24 | then 25 | local VERSION="$VERSION_MAJOR.$VERSION_MINOR.$VERSION_REVISION.$VERSION_PATCH" 26 | fi 27 | fi 28 | 29 | echo -e "$DIR\t\tmagentoCommerce\t$VERSION" >> $SOFTWARE 30 | 31 | done 32 | } 33 | -------------------------------------------------------------------------------- /scanners/drupal.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | function DrupalSoftware { 4 | ALL_MODULES=`locate --database=$LOCATE "*.info" 2> /dev/null` 5 | FILES=`locate --database=$LOCATE "drupal.js" 2> /dev/null` 6 | for FILE in $FILES; do 7 | 8 | # Get root path 9 | local DIR=`dirname $FILE` 10 | local DIR=`cd "$DIR/../"; pwd` 11 | local VERSION_FILE="${DIR}/includes/bootstrap.inc" 12 | 13 | local VERSION="" 14 | if [ -f $VERSION_FILE ] 15 | then 16 | local VERSION=`cat "$VERSION_FILE" | grep "define('VERSION', '[0-9]*\.[0-9]*')" | grep -o "[0-9]*\.[0-9]*" 2> /dev/null` 17 | fi 18 | 19 | if [ "$VERSION" == "" ] 20 | then 21 | local VERSION_FILE="${DIR}/modules/php/php.info" 22 | if [ -f $VERSION_FILE ] 23 | then 24 | local VERSION=`cat "$VERSION_FILE" | grep "version = \"[0-9]*\.[0-9]*\"" | grep -o "[0-9]*\.[0-9]*"` 25 | fi 26 | fi 27 | 28 | echo -e "$DIR\t\tdrupal\t$VERSION" >> $SOFTWARE 29 | 30 | # Get modules 31 | MODULES=`echo "$ALL_MODULES" | grep "^$DIR/sites/all"` 32 | for MODULE in $MODULES; do 33 | local VERSION=`grep "version = ['\"]*[0-9a-z\.\-]*['\"]*" "$MODULE" | grep -o "[0-9][0-9a-z\.\-]*"` 34 | local NAME=`basename $MODULE` 35 | local NAME=${NAME%.*} 36 | 37 | echo -e "$DIR\tdrupal\tdrupal/$NAME\t$VERSION" >> $SOFTWARE 38 | done 39 | 40 | done 41 | } -------------------------------------------------------------------------------- /scanners/wordpress.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | function WordpressSoftware { 4 | FILES=`locate --database=$LOCATE "wp-settings.php" 2> /dev/null` 5 | for FILE in $FILES; do 6 | 7 | # Get root path 8 | DIR=$(dirname $FILE) 9 | VERSION_FILE="${DIR}/wp-includes/version.php" 10 | 11 | VERSION="" 12 | if [ -f $VERSION_FILE ] 13 | then 14 | VERSION=$(cat "$VERSION_FILE" | grep "\$wp_version = '[0-9]*\.[0-9]*\.[0-9]*'" | grep -o "[0-9]*\.[0-9]*\.[0-9]*" 2> /dev/null) 15 | fi 16 | 17 | echo -e "$DIR\t\twordpress\t$VERSION" >> $SOFTWARE 18 | 19 | # Get modules 20 | MODULES=$(find "$DIR/wp-content/plugins" -mindepth 2 -maxdepth 2 -type f | grep "\.php$" | xargs grep "Plugin Name:" -l) 21 | for MODULE in $MODULES; do 22 | VERSION=$(grep "Version: ['\"]*[0-9a-z\.\-]*['\"]*" "$MODULE" 2> /dev/null | grep -o "[0-9][0-9a-z\.\-]*") 23 | NAME=$(dirname $MODULE | xargs basename) 24 | echo -e "$DIR\twordpress\twordpress:$NAME\t$VERSION" >> $SOFTWARE 25 | done 26 | 27 | MODULES=$(find "$DIR/wp-content/plugins" -maxdepth 1 -type f | grep "\.php$" | xargs grep "Plugin Name:" -l) 28 | for MODULE in $MODULES; do 29 | VERSION=$(grep "Version: ['\"]*[0-9a-z\.\-]*['\"]*" "$MODULE" 2> /dev/null | grep -o "[0-9][0-9a-z\.\-]*") 30 | NAME=$(basename $MODULE) 31 | NAME="${NAME%.*}" 32 | echo -e "$DIR\twordpress\twordpress:$NAME\t$VERSION" >> $SOFTWARE 33 | done 34 | done 35 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Bash Scanner 2 | GPL v3 3 | 4 | Bash Scanner is a fast and reliable way to scan your server for outdated software and potential exploits. 5 | 6 | ![PatrolServer Bash Scanner](http://i.imgur.com/O4fu9Nk.png) 7 | 8 | ## Getting started 9 | ### Install 10 | The easiest way to install the Bash Scanner tool is by using `wget` to get the runnable shell script. This file is signed with a SHA 256 key and allows you to safely install the security monitor by following several simple steps. 11 | ``` 12 | wget https://raw.githubusercontent.com/PatrolServer/bash-scanner/master/patrolserver 13 | ``` 14 | In order to run the monitor tool, use the `bash` command to execute the shell script downloaded before. 15 | ``` 16 | bash patrolserver 17 | ``` 18 | 19 | ### Extended reports 20 | After an initial scan, you will be asked to create an account on the PatrolServer dashboard (which is totally optional, you are free to use the tool without an account). The benefit of creating a sustainable account is detailed reporting, together with documentation on how to secure your server. 21 | 22 | ### Continuous scanning 23 | The script will ask you if it should set a cronjob, this simply means your server software will be in sync for **daily scans**. And you will be reported by email when your current software becomes outdated. 24 | 25 | ## Supported software 26 | The Bash Scanner currently detects the following software for updates (keep in mind, this list is an ongoing process and more software packages will be added in the future): 27 | * Debian* + dotdeb 28 | * Ubuntu* 29 | * OpenSSL* 30 | * OpenSSH* 31 | * cPanel 32 | * Nginx* 33 | * Laravel 34 | * Apache* 35 | * PHP* 36 | * BIND* 37 | * Drupal + modules 38 | * Composer modules 39 | * Wordpress + plugins 40 | 41 | *: This software also returns the exploits information. 42 | 43 | PatrolServer 44 | 45 | [![Analytics](https://ga-beacon.appspot.com/UA-65036233-1/PatrolServer/bash-scanner?pixel)](https://github.com/igrigorik/ga-beacon) 46 | -------------------------------------------------------------------------------- /patrolserver: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # PatrolServer Bash Scanner 4 | # 5 | # This is an intermediate file for calling the PatrolServer source code. 6 | # This is used for 2 reasons 7 | # 1. Automatic updates, the newest version of the code is run each time 8 | # 2. Security, we sign our source code and here we explicitly check the signature. 9 | # In that case, no rogue individu in GitHub can let you execute something they want. 10 | # Because the private key is saved on an offline computer. 11 | # 12 | # All code is found on our GitHub account and can be reviewed by anybody. 13 | # It is advised you check the code! 14 | # 15 | # This scanner will only get the version numbers of your installed software 16 | # and send them to us to check if they are the newest version. 17 | # This will never ever install, update something or change config or files. 18 | # Warn us immediatly if this is not the case. 19 | 20 | # Get the runner 21 | RUNNER=`mktemp` 22 | wget -O $RUNNER "https://raw.githubusercontent.com/PatrolServer/bashScanner/master/compiled.sh" --no-check-certificate 2> /dev/null 23 | chmod +x $RUNNER 24 | 25 | # Get the signature 26 | SIGNATURE=`mktemp` 27 | wget -O $SIGNATURE "https://raw.githubusercontent.com/PatrolServer/bashScanner/master/compiled.sign" --no-check-certificate 2> /dev/null 28 | 29 | # Public key 30 | PUBLIC_KEY=`mktemp` 31 | echo "-----BEGIN PUBLIC KEY-----" > $PUBLIC_KEY 32 | echo "MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA6thRDBr1JJRrWQkIzRdF" >> $PUBLIC_KEY 33 | echo "XxuPBH6ZkuAbDNxYT75pqQXMXMO3C/N2LiWwXVJTLIsuOEtegySzNc0T6JylwUXN" >> $PUBLIC_KEY 34 | echo "ljNHltqa5KBdAmpSGaZJ8JYwd1iNarrf1GQfEVpnvNF85EtcwKo0L5U4aelLdpaG" >> $PUBLIC_KEY 35 | echo "aEigyJwnk5I5Ji+kIzcMkHTiF5RzSpJcoSvbKem++x4bvIrfwfdnvEctcX8/m/PD" >> $PUBLIC_KEY 36 | echo "8c/hQL1OW1gjvmNiO3AlAnr41y3QBnpcchcXv05yX3VAfZhjMZdD8JS5wvke3GT7" >> $PUBLIC_KEY 37 | echo "Vji5ToLPfUzyvlH9tjHx4zefxIvSTIMVI2gg+bXw5VlNIWp/ST9xQGjFG1wEa+uy" >> $PUBLIC_KEY 38 | echo "PU3T69j0ylEA0SaTX/ZDo8qZSn8XTNdhtc0lOK7GFM+U/iZvWe+CRA41DafsUsPa" >> $PUBLIC_KEY 39 | echo "GkxVS84eZ/xkIMh5EBghaxfmkYN8yubK0yILr95kU/gpFjPxRimHFvIBxIAhE8Xv" >> $PUBLIC_KEY 40 | echo "3+QSGEt8h11WHd8I27U3egDwVsDCDtgbPedOTiW7MGoHcxtTdcl1Fpp6cLaNeJJ/" >> $PUBLIC_KEY 41 | echo "UPzlIqow4S6I0O7hboiB6wSDwXfKcjg4F/7JJH3TOevNK7DLZOGOxTEX7z5JRaJZ" >> $PUBLIC_KEY 42 | echo "m8pa1pizQ598dwailRhkbPJGzcSCawmXq6HQTyh7F5PeyolSKVKMA4mOKSJ4KLdv" >> $PUBLIC_KEY 43 | echo "ceos+VHmkBUcE8QrAZNFNq0CAwEAAQ==" >> $PUBLIC_KEY 44 | echo "-----END PUBLIC KEY-----" >> $PUBLIC_KEY 45 | 46 | # Verify the runner 47 | VERIFIED=`openssl dgst -sha256 -verify $PUBLIC_KEY -signature $SIGNATURE $RUNNER` 48 | 49 | if [ "$VERIFIED" != "Verified OK" ] 50 | then 51 | echo "Verification of PatrolServer code failed, somebody tried to edit our code without proper signing" 52 | exit 2; 53 | fi 54 | 55 | # Run the runner 56 | bash $RUNNER "$@" 57 | 58 | -------------------------------------------------------------------------------- /args.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | function EnvFile { 4 | if [ -f ~/.patrolserver/env ]; 5 | then 6 | source ~/.patrolserver/env 7 | LOCATE="$HOME/.patrolserver/locate.db" 8 | fi 9 | } 10 | 11 | function Args { 12 | 13 | optspec=":e:p:n:k:s:ci:b:hv-:" 14 | while getopts "$optspec" optchar; do 15 | case "${optchar}" in 16 | -) 17 | case "${OPTARG}" in 18 | version) 19 | echo "PatrolServer BashScanner $VERSION" >&2 20 | exit 21 | ;; 22 | email) 23 | EMAIL="${!OPTIND}"; OPTIND=$(( $OPTIND + 1 )) 24 | ;; 25 | email=*) 26 | EMAIL=${OPTARG#*=} 27 | ;; 28 | password) 29 | PASSWORD="${!OPTIND}"; OPTIND=$(( $OPTIND + 1 )) 30 | ;; 31 | password=*) 32 | PASSWORD=${OPTARG#*=} 33 | ;; 34 | hostname) 35 | HOSTNAME="${!OPTIND}"; OPTIND=$(( $OPTIND + 1 )) 36 | ;; 37 | hostname=*) 38 | HOSTNAME=${OPTARG#*=} 39 | ;; 40 | key) 41 | KEY="${!OPTIND}"; OPTIND=$(( $OPTIND + 1 )) 42 | ;; 43 | key=*) 44 | KEY=${OPTARG#*=} 45 | ;; 46 | secret) 47 | SECRET="${!OPTIND}"; OPTIND=$(( $OPTIND + 1 )) 48 | ;; 49 | secret=*) 50 | SECRET=${OPTARG#*=} 51 | ;; 52 | cmd) 53 | CMD="true" 54 | ;; 55 | cmd=*) 56 | CMD=${OPTARG#*=} 57 | ;; 58 | server_id) 59 | SERVER_ID="${!OPTIND}"; OPTIND=$(( $OPTIND + 1 )) 60 | ;; 61 | server_id=*) 62 | SERVER_ID=${OPTARG#*=} 63 | ;; 64 | bucket) 65 | BUCKET="${!OPTIND}"; OPTIND=$(( $OPTIND + 1 )) 66 | ;; 67 | bucket=*) 68 | BUCKET=${OPTARG#*=} 69 | ;; 70 | cron) 71 | CRON="true" 72 | ;; 73 | cron=*) 74 | CRON=${OPTARG#*=} 75 | ;; 76 | target) 77 | MY_HOME="${!OPTIND}"; OPTIND=$(( $OPTIND + 1 )) 78 | ;; 79 | target=*) 80 | MY_HOME=${OPTARG#*=} 81 | ;; 82 | *) 83 | if [ "$OPTERR" = 1 ] && [ "${optspec:0:1}" != ":" ]; then 84 | echo "Unknown option --${OPTARG}" >&2 85 | fi 86 | ;; 87 | esac;; 88 | h) 89 | echo "usage: $0 [-v] [--key=] [--secret=] [--hostname=] [--cmd]" >&2 90 | exit 2 91 | ;; 92 | v) 93 | echo "PatrolServer BashScanner $VERSION" >&2 94 | exit 95 | ;; 96 | e) 97 | EMAIL=${OPTARG} 98 | ;; 99 | p) 100 | PASSWORD=${OPTARG} 101 | ;; 102 | n) 103 | HOSTNAME=${OPTARG} 104 | ;; 105 | k) 106 | KEY=${OPTARG} 107 | ;; 108 | s) 109 | SECRET=${OPTARG} 110 | ;; 111 | c) 112 | CMD="true" 113 | ;; 114 | i) 115 | SERVER_ID=${OPTARG} 116 | ;; 117 | b) 118 | BUCKET=${OPTARG} 119 | ;; 120 | t) 121 | MY_HOME=${OPTARG} 122 | ;; 123 | *) 124 | if [ "$OPTERR" != 1 ] || [ "${optspec:0:1}" = ":" ]; then 125 | echo "Non-option argument: '-${OPTARG}'" >&2 126 | fi 127 | ;; 128 | esac 129 | done 130 | } 131 | -------------------------------------------------------------------------------- /json.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | json() { 4 | 5 | ResetEnv 6 | 7 | throw () { 8 | echo "$*" >&2 9 | exit 1 10 | } 11 | 12 | BRIEF=0 13 | LEAFONLY=0 14 | PRUNE=0 15 | NORMALIZE_SOLIDUS=0 16 | 17 | usage() { 18 | echo 19 | echo "Usage: JSON.sh [-b] [-l] [-p] [-s] [-h]" 20 | echo 21 | echo "-p - Prune empty. Exclude fields with empty values." 22 | echo "-l - Leaf only. Only show leaf nodes, which stops data duplication." 23 | echo "-b - Brief. Combines 'Leaf only' and 'Prune empty' options." 24 | echo "-s - Remove escaping of the solidus symbol (stright slash)." 25 | echo "-h - This help text." 26 | echo 27 | } 28 | 29 | parse_options() { 30 | set -- "$@" 31 | local ARGN=$# 32 | while [ "$ARGN" -ne 0 ] 33 | do 34 | case $1 in 35 | -h) usage 36 | exit 0 37 | ;; 38 | -b) BRIEF=1 39 | LEAFONLY=1 40 | PRUNE=1 41 | ;; 42 | -l) LEAFONLY=1 43 | ;; 44 | -p) PRUNE=1 45 | ;; 46 | -s) NORMALIZE_SOLIDUS=1 47 | ;; 48 | ?*) echo "ERROR: Unknown option." 49 | usage 50 | exit 0 51 | ;; 52 | esac 53 | shift 1 54 | ARGN=$((ARGN-1)) 55 | done 56 | } 57 | 58 | awk_egrep () { 59 | local pattern_string=$1 60 | 61 | gawk '{ 62 | while ($0) { 63 | start=match($0, pattern); 64 | token=substr($0, start, RLENGTH); 65 | print token; 66 | $0=substr($0, start+RLENGTH); 67 | } 68 | }' pattern="$pattern_string" 69 | } 70 | 71 | tokenize () { 72 | local GREP 73 | local ESCAPE 74 | local CHAR 75 | 76 | if echo "test string" | egrep -ao --color=never "test" &>/dev/null 77 | then 78 | GREP='egrep -ao --color=never' 79 | else 80 | GREP='egrep -ao' 81 | fi 82 | 83 | if echo "test string" | egrep -o "test" &>/dev/null 84 | then 85 | ESCAPE='(\\[^u[:cntrl:]]|\\u[0-9a-fA-F]{4})' 86 | CHAR='[^[:cntrl:]"\\]' 87 | else 88 | GREP=awk_egrep 89 | ESCAPE='(\\\\[^u[:cntrl:]]|\\u[0-9a-fA-F]{4})' 90 | CHAR='[^[:cntrl:]"\\\\]' 91 | fi 92 | 93 | local STRING="\"$CHAR*($ESCAPE$CHAR*)*\"" 94 | local NUMBER='-?(0|[1-9][0-9]*)([.][0-9]*)?([eE][+-]?[0-9]*)?' 95 | local KEYWORD='null|false|true' 96 | local SPACE='[[:space:]]+' 97 | 98 | $GREP "$STRING|$NUMBER|$KEYWORD|$SPACE|." | egrep -v "^$SPACE$" 99 | } 100 | 101 | parse_array () { 102 | local index=0 103 | local ary='' 104 | read -r token 105 | case "$token" in 106 | ']') ;; 107 | *) 108 | while : 109 | do 110 | parse_value "$1" "$index" 111 | index=$((index+1)) 112 | ary="$ary""$value" 113 | read -r token 114 | case "$token" in 115 | ']') break ;; 116 | ',') ary="$ary," ;; 117 | *) throw "EXPECTED , or ] GOT ${token:-EOF}" ;; 118 | esac 119 | read -r token 120 | done 121 | ;; 122 | esac 123 | [ "$BRIEF" -eq 0 ] && value=$(printf '[%s]' "$ary") || value= 124 | : 125 | } 126 | 127 | parse_object () { 128 | local key 129 | local obj='' 130 | read -r token 131 | case "$token" in 132 | '}') ;; 133 | *) 134 | while : 135 | do 136 | case "$token" in 137 | '"'*'"') key=$token ;; 138 | *) throw "EXPECTED string GOT ${token:-EOF}" ;; 139 | esac 140 | read -r token 141 | case "$token" in 142 | ':') ;; 143 | *) throw "EXPECTED : GOT ${token:-EOF}" ;; 144 | esac 145 | read -r token 146 | parse_value "$1" "$key" 147 | obj="$obj$key:$value" 148 | read -r token 149 | case "$token" in 150 | '}') break ;; 151 | ',') obj="$obj," ;; 152 | *) throw "EXPECTED , or } GOT ${token:-EOF}" ;; 153 | esac 154 | read -r token 155 | done 156 | ;; 157 | esac 158 | [ "$BRIEF" -eq 0 ] && value=$(printf '{%s}' "$obj") || value= 159 | : 160 | } 161 | 162 | parse_value () { 163 | local jpath="${1:+$1,}$2" isleaf=0 isempty=0 print=0 164 | case "$token" in 165 | '{') parse_object "$jpath" ;; 166 | '[') parse_array "$jpath" ;; 167 | # At this point, the only valid single-character tokens are digits. 168 | ''|[!0-9]) throw "EXPECTED value GOT ${token:-EOF}" ;; 169 | *) value=$token 170 | # if asked, replace solidus ("\/") in json strings with normalized value: "/" 171 | [ "$NORMALIZE_SOLIDUS" -eq 1 ] && value=${value//\\\//\/} 172 | isleaf=1 173 | [ "$value" = '""' ] && isempty=1 174 | ;; 175 | esac 176 | [ "$value" = '' ] && return 177 | [ "$LEAFONLY" -eq 0 ] && [ "$PRUNE" -eq 0 ] && print=1 178 | [ "$LEAFONLY" -eq 1 ] && [ "$isleaf" -eq 1 ] && [ $PRUNE -eq 0 ] && print=1 179 | [ "$LEAFONLY" -eq 0 ] && [ "$PRUNE" -eq 1 ] && [ "$isempty" -eq 0 ] && print=1 180 | [ "$LEAFONLY" -eq 1 ] && [ "$isleaf" -eq 1 ] && \ 181 | [ $PRUNE -eq 1 ] && [ $isempty -eq 0 ] && print=1 182 | [ "$print" -eq 1 ] && printf "[%s]\t%s\n" "$jpath" "$value" 183 | : 184 | } 185 | 186 | parse () { 187 | read -r token 188 | parse_value 189 | read -r token 190 | case "$token" in 191 | '') ;; 192 | *) throw "EXPECTED EOF GOT $token" ;; 193 | esac 194 | } 195 | 196 | parse_options "$@" 197 | tokenize | parse 198 | 199 | SetEnv 200 | } -------------------------------------------------------------------------------- /api.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | COOKIES=$(mktemp) 4 | POSTFILE=$(mktemp) 5 | 6 | function ApiUserRegister { 7 | local EMAIL 8 | local PASSWORD 9 | local OUTPUT 10 | local ERROR 11 | local USER 12 | local KEY 13 | local SECRET 14 | 15 | EMAIL=$(Urlencode "$1") 16 | PASSWORD=$(Urlencode "$2") 17 | 18 | OUTPUT=$(wget -t2 -T6 --header="X-PS-Bash: 1" -qO- "${MY_HOME}/extern/api/users" --post-data "email=$EMAIL&password=$PASSWORD&password_confirmation=$PASSWORD") 19 | 20 | if [ "$OUTPUT" == "" ] 21 | then 22 | echo "> patrolserver.com is not reachable, contact us (1)" >&2 23 | exit 77 24 | fi 25 | 26 | ERROR=$(echo "$OUTPUT" | json | grep '^\["error"\]' | cut -f2-) 27 | USER=$(echo "$OUTPUT" | json | grep '^\["data","user"\]' | cut -f2-) 28 | KEY=$(echo "$OUTPUT" | json | grep '^\["data","api_credentials","key"\]' | cut -f2- | sed -e 's/^"//' -e 's/"$//') 29 | SECRET=$(echo "$OUTPUT" | json | grep '^\["data","api_credentials","secret"\]' | cut -f2- | sed -e 's/^"//' -e 's/"$//') 30 | 31 | echo "${ERROR:-false}" 32 | echo "${USER:-false}" 33 | echo "${KEY:-false}" 34 | echo "${SECRET:-false}" 35 | } 36 | 37 | function ApiUserLogin { 38 | local EMAIL 39 | local PASSWORD 40 | local OUTPUT 41 | local ERROR 42 | local USER 43 | local KEY 44 | local SECRET 45 | 46 | EMAIL=$(Urlencode "$1") 47 | PASSWORD=$(Urlencode "$2") 48 | 49 | OUTPUT=$(wget -t2 -T6 --header="X-PS-Bash: 1" -qO- "${MY_HOME}/api/user/request_api_credentials" --post-data "email=$EMAIL&password=$PASSWORD") 50 | 51 | if [ "$OUTPUT" == "" ] 52 | then 53 | echo "> patrolserver.com is not reachable, contact us (2)" >&2 54 | exit 77 55 | fi 56 | 57 | ERROR=$(echo "$OUTPUT" | json | grep '^\["error","code"\]' | cut -f2- | sed -e 's/^"//' -e 's/"$//') 58 | USER=$(echo "$OUTPUT" | json | grep '^\["user"\]' | cut -f2-) 59 | KEY=$(echo "$OUTPUT" | json | grep '^\["api_credentials","key"\]' | cut -f2- | sed -e 's/^"//' -e 's/"$//') 60 | SECRET=$(echo "$OUTPUT" | json | grep '^\["api_credentials","secret"\]' | cut -f2- | sed -e 's/^"//' -e 's/"$//') 61 | 62 | echo "${ERROR:-false}" 63 | echo "${USER:-false}" 64 | echo "${KEY:-false}" 65 | echo "${SECRET:-false}" 66 | } 67 | 68 | function ApiServerExists { 69 | local HOST 70 | local OUTPUT 71 | local ERROR 72 | local EXISTS 73 | 74 | HOST=$(Urlencode "$1") 75 | 76 | OUTPUT=$(wget -t2 -T6 --header="X-PS-Bash: 1" -qO- "${MY_HOME}/extern/api/serverExists?host=$HOST") 77 | 78 | if [ "$OUTPUT" == "" ] 79 | then 80 | echo "> patrolserver.com is not reachable, contact us (3)" >&2 81 | exit 77 82 | fi 83 | 84 | ERROR=$(echo "$OUTPUT" | json | grep '^\["error","code"\]' | cut -f2- | sed -e 's/^"//' -e 's/"$//') 85 | EXISTS=$(echo "$OUTPUT" | json | grep '^\["exists"\]' | cut -f2-) 86 | 87 | echo "${ERROR:-false}" 88 | echo "${EXISTS:-false}" 89 | } 90 | 91 | function ApiServerCreate { 92 | local KEY 93 | local SECRET 94 | local HOSTNAME 95 | local OUTPUT 96 | local ID 97 | local ERROR 98 | 99 | KEY=$(Urlencode "$1") 100 | SECRET=$(Urlencode "$2") 101 | HOSTNAME=$(Urlencode "$3") 102 | 103 | OUTPUT=$(wget -t2 -T6 --header="X-PS-Bash: 1" -qO- "${MY_HOME}/extern/api/servers?key=$KEY&secret=$SECRET" --post-data "domain=$HOSTNAME") 104 | 105 | if [ "$OUTPUT" == "" ] 106 | then 107 | echo "> patrolserver.com is not reachable, contact us (4)" >&2 108 | exit 77 109 | fi 110 | 111 | ID=$(echo "$OUTPUT" | json | grep '^\["data","id"\]' | cut -f2-) 112 | ERROR=$(echo "$OUTPUT" | json | grep '^\["error"\]' | cut -f2-) 113 | 114 | echo "${ERROR:-false}" 115 | echo "${ID:-false}" 116 | } 117 | 118 | function ApiServerToken { 119 | local HOSTNAME 120 | local OUTPUT 121 | local TOKEN 122 | local ERROR 123 | 124 | HOSTNAME=$(Urlencode "$1") 125 | 126 | OUTPUT=$(wget -t2 -T6 --header="X-PS-Bash: 1" -qO- "${MY_HOME}/extern/api/request_verification_token?domain=$HOSTNAME") 127 | 128 | if [ "$OUTPUT" == "" ] 129 | then 130 | echo "> patrolserver.com is not reachable, contact us (5)" >&2 131 | exit 77 132 | fi 133 | 134 | TOKEN=$(echo "$OUTPUT" | json | grep '^\["data","token"\]' | cut -f2- | sed -e 's/^"//' -e 's/"$//') 135 | ERROR=$(echo "$OUTPUT" | json | grep '^\["error"\]' | cut -f2-) 136 | 137 | echo "${ERROR:-false}" 138 | echo "${TOKEN:-false}" 139 | } 140 | 141 | function ApiVerifyServer { 142 | local KEY 143 | local SECRET 144 | local SERVER_ID 145 | local TOKEN 146 | local OUTPUT 147 | local ERROR 148 | 149 | KEY=$(Urlencode "$1") 150 | SECRET=$(Urlencode "$2") 151 | SERVER_ID=$(Urlencode "$3") 152 | TOKEN=$(Urlencode "$4") 153 | 154 | OUTPUT=$(wget -t2 -T6 --header="X-PS-Bash: 1" -qO- "${MY_HOME}/extern/api/servers/${SERVER_ID}/verify?key=$KEY&secret=$SECRET" --post-data "token=$TOKEN") 155 | 156 | if [ "$OUTPUT" == "" ] 157 | then 158 | echo "> patrolserver.com is not reachable, contact us (6)" >&2 159 | exit 77 160 | fi 161 | 162 | ERROR=$(echo "$OUTPUT" | json | grep '^\["error"\]' | cut -f2-) 163 | 164 | echo "${ERROR:-false}" 165 | } 166 | 167 | function ApiServerPush { 168 | local KEY 169 | local SECRET 170 | local SERVER_ID 171 | local BUCKET 172 | local EXPIRE 173 | local OUTPUT 174 | local ERROR 175 | 176 | KEY=$(Urlencode "$1") 177 | SECRET=$(Urlencode "$2") 178 | SERVER_ID=$(Urlencode "$3") 179 | BUCKET=$(Urlencode "$4") 180 | EXPIRE="129600" 181 | 182 | echo -n "expire=$EXPIRE&software=" > "$POSTFILE" 183 | 184 | SOFTWARE=$(sort < "$SOFTWARE" | uniq | awk 'BEGIN { RS="\n"; FS="\t"; print "["; prevLocation="---"; prevName="---"; prevVersion="---"; prevParent="---";} 185 | { 186 | if($1 == prevLocation){ $1=""; } else { prevLocation = $1; $1 = "\"l\":\""$1"\"," }; 187 | if($2 == prevParent){ $2=""; } else { prevParent = $2; $2 = "\"p\":\""$2"\"," }; 188 | if($3 == prevName){ $3=""; } else { prevName = $3; $3 = "\"n\":\""$3"\"," }; 189 | if($4 == prevVersion){ $4=""; } else { prevVersion = $4; $4 = "\"v\":\""$4"\"," }; 190 | line = $1$2$3$4; 191 | print "{"line"},"; 192 | } 193 | END { print "{}]"; }' | sed 's/,},/},/' | tr -d '\n') 194 | SOFTWARE=$(Urlencode "$SOFTWARE") 195 | 196 | echo "$SOFTWARE" >> "$POSTFILE" 197 | 198 | OUTPUT=$(wget -t2 -T6 --header="X-PS-Bash: 1" -qO- "${MY_HOME}/extern/api/servers/${SERVER_ID}/buckets/$BUCKET?key=$KEY&secret=$SECRET&scope=silent" --post-file $POSTFILE) 199 | 200 | if [ "$OUTPUT" == "" ] 201 | then 202 | echo "> patrolserver.com is not reachable, contact us (7)" >&2 203 | exit 77 204 | fi 205 | 206 | ERROR=$(echo "$OUTPUT" | json | grep '^\["error"\]' | cut -f2-) 207 | 208 | echo "${ERROR:-false}" 209 | } 210 | 211 | function ApiServers { 212 | local KEY 213 | local SECRET 214 | local OUTPUT 215 | local SERVERS 216 | local ERROR 217 | 218 | KEY=$(Urlencode "$1") 219 | SECRET=$(Urlencode "$2") 220 | 221 | OUTPUT=$(wget -t2 -T6 --header="X-PS-Bash: 1" -qO- "${MY_HOME}/extern/api/servers?key=$KEY&secret=$SECRET") 222 | 223 | if [ "$OUTPUT" == "" ] 224 | then 225 | echo "> patrolserver.com is not reachable, contact us (10)" >&2 226 | exit 77 227 | fi 228 | 229 | SERVERS=$(echo "$OUTPUT" | json | grep '^\["data"\]' | cut -f2-) 230 | ERROR=$(echo "$OUTPUT" | json | grep '^\["error"\]' | cut -f2-) 231 | 232 | echo "${ERROR:-false}" 233 | echo "${SERVERS:-false}" 234 | } 235 | 236 | function ApiSoftware { 237 | local KEY 238 | local SECRET 239 | local SERVER_ID 240 | local OUTPUT 241 | local SOFTWARE 242 | local ERROR 243 | 244 | KEY=$(Urlencode "$1") 245 | SECRET=$(Urlencode "$2") 246 | SERVER_ID=$(Urlencode "$3") 247 | 248 | OUTPUT=$(wget -t2 -T6 --header="X-PS-Bash: 1" -qO- "${MY_HOME}/extern/api/servers/$SERVER_ID/software?key=$KEY&secret=$SECRET&scope=exploits") 249 | 250 | if [ "$OUTPUT" == "" ] 251 | then 252 | echo "> patrolserver.com is not reachable, contact us (11)" >&2 253 | exit 77 254 | fi 255 | 256 | SOFTWARE=$(echo "$OUTPUT" | json | grep '^\["data"\]' | cut -f2-) 257 | ERROR=$(echo "$OUTPUT" | json | grep '^\["error"\]' | cut -f2-) 258 | 259 | echo "${ERROR:-false}" 260 | echo "${SOFTWARE:-false}" 261 | } 262 | 263 | function ApiServerScan { 264 | local KEY 265 | local SECRET 266 | local SERVER_ID 267 | local OUTPUT 268 | local ERROR 269 | 270 | KEY=$(Urlencode "$1") 271 | SECRET=$(Urlencode "$2") 272 | SERVER_ID=$(Urlencode "$3") 273 | 274 | OUTPUT=$(wget -t2 -T6 -qO- "${MY_HOME}/extern/api/servers/$SERVER_ID/scan?key=$KEY&secret=$SECRET" --post-data "not=used") 275 | 276 | if [ "$OUTPUT" == "" ] 277 | then 278 | echo "> patrolserver.com is not reachable, contact us (12)" >&2 279 | exit 77 280 | fi 281 | 282 | ERROR=$(echo "$OUTPUT" | json | grep '^\["error"\]' | cut -f2-) 283 | 284 | echo "${ERROR:-false}" 285 | } 286 | 287 | function ApiServerIsScanning { 288 | local KEY 289 | local SECRET 290 | local SERVER_ID 291 | local OUTPUT 292 | local ERROR 293 | local SCANNING 294 | 295 | KEY=$(Urlencode "$1") 296 | SECRET=$(Urlencode "$2") 297 | SERVER_ID=$(Urlencode "$3") 298 | 299 | OUTPUT=$(wget -t2 -T6 --header="X-PS-Bash: 1" -qO- "${MY_HOME}/extern/api/servers/$SERVER_ID/isScanning?key=$KEY&secret=$SECRET") 300 | 301 | if [ "$OUTPUT" == "" ] 302 | then 303 | echo "> patrolserver.com is not reachable, contact us (13)" >&2 304 | exit 77 305 | fi 306 | 307 | ERROR=$(echo "$OUTPUT" | json | grep '^\["error"\]' | cut -f2-) 308 | SCANNING=$(echo "$OUTPUT" | json | grep '^\["data"\]' | cut -f2-) 309 | 310 | echo "${ERROR:-false}" 311 | echo "${SCANNING:-false}" 312 | } 313 | 314 | function ApiUserChange { 315 | local KEY 316 | local SECRET 317 | local EMAIL 318 | local OUTPUT 319 | local ERROR 320 | local USER 321 | 322 | KEY=$(Urlencode "$1") 323 | SECRET=$(Urlencode "$2") 324 | EMAIL=$(Urlencode "$3") 325 | 326 | OUTPUT=$(wget -t2 -T6 --header="X-PS-Bash: 1" -qO- "${MY_HOME}/extern/api/user?key=$KEY&secret=$SECRET" --post-data "email=$EMAIL") 327 | 328 | if [ "$OUTPUT" == "" ] 329 | then 330 | echo "> patrolserver.com is not reachable, contact us (14)" >&2 331 | exit 77 332 | fi 333 | 334 | ERROR=$(echo "$OUTPUT" | json | grep '^\["error","code"\]' | cut -f2- | sed -e 's/^"//' -e 's/"$//') 335 | USER=$(echo "$OUTPUT" | json | grep '^\["data"\]' | cut -f2-) 336 | 337 | echo "${ERROR:-false}" 338 | echo "${USER:-false}" 339 | } 340 | 341 | function ApiUserRemove { 342 | local KEY 343 | local SECRET 344 | local OUTPUT 345 | 346 | KEY=$(Urlencode "$1") 347 | SECRET=$(Urlencode "$2") 348 | 349 | OUTPUT=$(wget -t2 -T6 --header="X-PS-Bash: 1" -qO- "${MY_HOME}/extern/api/user/delete?key=$KEY&secret=$SECRET" --post-data "not=used") 350 | 351 | if [ "$OUTPUT" == "" ] 352 | then 353 | echo "> patrolserver.com is not reachable, contact us (15)" >&2 354 | exit 77 355 | fi 356 | 357 | #ERRORS=$(echo "$OUTPUT" | json | grep '^\["errors"\]' | cut -f2-) 358 | #SUCCESS=$(echo "$OUTPUT" | json | grep '^\["success"\]' | cut -f2-) 359 | 360 | #echo "${ERRORS:-false}" 361 | #echo "${SUCCESS:-false}" 362 | } 363 | 364 | URLENCODE_SED=$(mktemp) 365 | cat > $URLENCODE_SED <<- EOF 366 | s:%:%25:g 367 | s: :%20:g 368 | s:<:%3C:g 369 | s:>:%3E:g 370 | s:#:%23:g 371 | s:{:%7B:g 372 | s:}:%7D:g 373 | s:|:%7C:g 374 | s:\^:%5E:g 375 | s:~:%7E:g 376 | s:\[:%5B:g 377 | s:\]:%5D:g 378 | s:\`:%60:g 379 | s:;:%3B:g 380 | s:/:%2F:g 381 | s:?:%3F:g 382 | s^:^%3A^g 383 | s:@:%40:g 384 | s:=:%3D:g 385 | s:&:%26:g 386 | s:\!:%21:g 387 | s:\*:%2A:g 388 | s:\+:%2B:g 389 | EOF 390 | 391 | function Urlencode { 392 | local STRING 393 | local ENCODED 394 | 395 | STRING="${1}" 396 | ENCODED=$(echo "$STRING" | sed -f $URLENCODE_SED) 397 | 398 | echo "$ENCODED" 399 | } 400 | 401 | function Jsonspecialchars { 402 | echo "$1" | sed "s/'/\\\\\'/g" 403 | } 404 | -------------------------------------------------------------------------------- /index.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | MY_HOME="https://demo.patrolserver.com" 4 | 5 | . env.sh 6 | . json.sh 7 | . api.sh 8 | . args.sh 9 | . scanners/composer.sh 10 | . scanners/dpkg.sh 11 | . scanners/drupal.sh 12 | . scanners/npm.sh 13 | . scanners/wordpress.sh 14 | . scanners/phpmyadmin.sh 15 | . scanners/joomla.sh 16 | . scanners/magento.sh 17 | 18 | VERSION="1.0.0" 19 | EMAIL="" 20 | PASSWORD="" 21 | HOSTNAME="" 22 | KEY="" 23 | SECRET="" 24 | CMD="false" 25 | SERVER_ID="" 26 | BUCKET="BashScanner" 27 | LOCATE=$(mktemp) 28 | CRON="ask" 29 | 30 | function Start { 31 | SetEnv 32 | EnvFile 33 | Args "$@" 34 | 35 | if [ "$CMD" == "false" ] 36 | then 37 | echo "> Hi $USER," 38 | echo "> PatrolServer.com at your service. " 39 | echo "> I'm starting..." 40 | echo "" 41 | fi 42 | 43 | DetermineHostname 44 | if [ "$KEY" == "" ] || [ "$SECRET" == "" ] 45 | then 46 | Account 47 | fi 48 | 49 | DetermineServer 50 | Scan 51 | 52 | if [ "$CMD" == "false" ] 53 | then 54 | if [[ "$CRON" == "ask" ]] 55 | then 56 | Output 57 | fi 58 | 59 | Cronjob 60 | fi 61 | 62 | echo "> Have a nice day!" 63 | } 64 | 65 | function Login { 66 | if [ "$CMD" == "false" ] && [ "$EMAIL" == "" ] && [ "$PASSWORD" == "" ] 67 | then 68 | for I in 1 2 3 69 | do 70 | echo -en "\tYour email: " 71 | read -r EMAIL 72 | echo -en "\tYour password: " 73 | read -rs PASSWORD 74 | echo ""; 75 | 76 | LOGIN_RET=($(ApiUserLogin "$EMAIL" "$PASSWORD")) 77 | 78 | local LOGIN_ERROR=${LOGIN_RET[0]} 79 | local LOGIN_USER=${LOGIN_RET[1]} 80 | local LOGIN_KEY=${LOGIN_RET[2]} 81 | local LOGIN_SECRET=${LOGIN_RET[3]} 82 | 83 | if [ "$LOGIN_ERROR" == "false" ] 84 | then 85 | StoreKeySecret "$LOGIN_KEY" "$LOGIN_SECRET" 86 | return 87 | elif [ "$LOGIN_ERROR" == "too_many_failed_attempts" ] 88 | then 89 | echo "> Your login was blocked for security issues, please try again in 10 min." >&2 90 | exit 77 91 | elif [ "$LOGIN_ERROR" == "different_country" ] 92 | then 93 | echo "> Your login is temporarily blocked for security measurements. Check your email for further instructions." >&2 94 | exit 77 95 | else 96 | echo "> Wrong email and/or password! Try again." >&2 97 | fi 98 | done 99 | echo "> Invalid login"; 100 | 101 | else 102 | if [ "$EMAIL" == "" ] 103 | then 104 | echo "Specify login credentials." >&2 105 | exit 77 106 | fi 107 | 108 | if [ "$PASSWORD" == "" ] 109 | then 110 | echo "Specify login credentials." >&2 111 | exit 77 112 | fi 113 | 114 | LOGIN_RET=($(ApiUserLogin "$EMAIL" "$PASSWORD")) 115 | 116 | local LOGIN_ERROR=${LOGIN_RET[0]} 117 | local LOGIN_USER=${LOGIN_RET[1]} 118 | local LOGIN_KEY=${LOGIN_RET[2]} 119 | local LOGIN_SECRET=${LOGIN_RET[3]} 120 | 121 | if [ "$LOGIN_ERROR" == "false" ] 122 | then 123 | StoreKeySecret "$LOGIN_KEY" "$LOGIN_SECRET" 124 | return 125 | elif [ "$LOGIN_ERROR" == "too_many_failed_attempts" ] 126 | then 127 | echo "> Your login was blocked for security issues, please try again in 10 min." >&2 128 | exit 77 129 | elif [ "$LOGIN_ERROR" == "different_country" ] 130 | then 131 | echo "> Your login is temporarily blocked for security measurements. Check your email for further instructions." >&2 132 | exit 77 133 | else 134 | echo "> Invalid login credentials." >&2 135 | exit 77 136 | fi 137 | fi 138 | 139 | exit 77; 140 | } 141 | 142 | function StoreKeySecret { 143 | KEY="$1" 144 | SECRET="$2" 145 | 146 | if [ "$KEY" == "false" ] 147 | then 148 | echo "> Internal error, could not get key/secret combo." >&2 149 | exit 77 150 | fi 151 | 152 | if [ "$SECRET" == "false" ] 153 | then 154 | echo "> Internal error, could not get key/secret combo." >&2 155 | exit 77 156 | fi 157 | } 158 | 159 | function Register { 160 | REGISTER_RET=($(ApiUserRegister "$EMAIL" "$PASSWORD")) 161 | 162 | local REGISTER_ERROR=${REGISTER_RET[0]} 163 | local REGISTER_USER=${REGISTER_RET[1]} 164 | local REGISTER_KEY=${REGISTER_RET[2]} 165 | local REGISTER_SECRET=${REGISTER_RET[3]} 166 | 167 | if [ "$REGISTER_ERROR" == "false" ] 168 | then 169 | echo "success" 170 | echo "${REGISTER_KEY:-false}" 171 | echo "${REGISTER_SECRET:-false}" 172 | return 173 | else 174 | if [[ "$REGISTER_ERROR" =~ "The email has already been taken" ]] 175 | then 176 | echo "email" 177 | return 178 | fi 179 | 180 | echo "> Unexpected error occured." >&2 181 | exit 77; 182 | fi 183 | } 184 | 185 | function TestHostname { 186 | OPEN_PORT_53=$(echo "quit" | timeout 1 telnet 8.8.8.8 53 2> /dev/null | grep "Escape character is") 187 | if [[ "$OPEN_PORT_53" != "" ]] && command -v dig >/dev/null 2>&1 188 | then 189 | EXTERNAL_IP=$(dig +time=1 +tries=1 +retry=1 +short myip.opendns.com @resolver1.opendns.com | tail -n1) 190 | IP=$(dig @8.8.8.8 +short $HOSTNAME | tail -n1) 191 | else 192 | 193 | if ! command -v host >/dev/null 2>&1 && command -v yum >/dev/null 2>&1 194 | then 195 | echo "This script needs the bind utils package, please install: yum install bind-utils" 196 | exit 77 197 | fi 198 | 199 | EXTERNAL_IP=$(wget -qO- ipv4.icanhazip.com) 200 | IP=$(host "$HOSTNAME" | grep -v 'alias' | grep -v 'mail' | cut -d' ' -f4 | head -n1) 201 | fi 202 | } 203 | 204 | function Hostname { 205 | 206 | if [[ "$HOSTNAME" == "" ]] 207 | then 208 | HOSTNAME=$(hostname -f 2> /dev/null) 209 | fi 210 | 211 | if [[ "$HOSTNAME" != "" ]] 212 | then 213 | TestHostname 214 | fi 215 | 216 | if [[ "$CMD" != "false" ]] 217 | then 218 | if [ "$IP" == "" ] 219 | then 220 | echo "Hostname not found. (Please enter with command)" >&2 221 | exit 77; 222 | elif [[ "$IP" != "$EXTERNAL_IP" ]] 223 | then 224 | echo "Hostname doesn't resolve to external IP of this server." >&2 225 | exit 77; 226 | fi 227 | fi 228 | 229 | for I in 1 2 3 230 | do 231 | 232 | if [ "$IP" == "" ] 233 | then 234 | echo "> Could not determine your hostname." 235 | echo -en "\tPlease enter the hostname of this server: " 236 | read -r HOSTNAME 237 | echo ""; 238 | elif [[ "$IP" != "$EXTERNAL_IP" ]] 239 | then 240 | echo "> Your hostname ($HOSTNAME) doesn't resolve to this IP." 241 | echo -en "\tPlease enter the hostname that resolved to this ip: " 242 | read -r HOSTNAME 243 | echo ""; 244 | fi 245 | 246 | TestHostname 247 | 248 | if [[ "$IP" != "" ]] && [ "$IP" == "$EXTERNAL_IP" ] 249 | then 250 | return; 251 | fi 252 | done 253 | 254 | echo "> Could not determine hostname." >&2 255 | exit 77; 256 | } 257 | 258 | function DetermineHostname { 259 | 260 | Hostname 261 | 262 | if [ "$EMAIL" == "" ] && [ "$PASSWORD" == "" ] && [ "$KEY" == "" ] && [ "$SECRET" == "" ] 263 | then 264 | # Check if the host is already in our DB 265 | # Please note! You can remove this check, but our policy doesn't change. 266 | # Only one free server per domain is allowed. 267 | # We actively check for these criteria 268 | SERVER_EXISTS_RET=($(ApiServerExists "$HOSTNAME")) 269 | 270 | SERVER_EXISTS_ERROR=${SERVER_EXISTS_RET[0]} 271 | SERVER_EXISTS=${SERVER_EXISTS_RET[1]} 272 | 273 | if [ "$SERVER_EXISTS_ERROR" == "false" ] 274 | then 275 | return 276 | 277 | # There is already an host from this user. 278 | elif [ "$SERVER_EXISTS_ERROR" == "15" ] 279 | then 280 | echo "> An account was already created for this host or a subdomain of this host. Please login into your patrolserver.com account or add the key/secret when calling this command." >&2 281 | Login 282 | 283 | # Hostname could not be found. 284 | elif [ "$SERVER_EXISTS_ERROR" == "83" ] 285 | then 286 | echo "> Your hostname ($HOSTNAME) doesn't resolve to this IP ($IP)." >&2 287 | exit 77; 288 | # Undefined error occured. 289 | else 290 | echo "> Unexpected error occured." >&2 291 | exit 77; 292 | fi 293 | fi 294 | } 295 | 296 | function Account { 297 | 298 | if [[ "$EMAIL" != "" ]] && [[ "$PASSWORD" != "" ]] 299 | then 300 | Login 301 | return; 302 | fi 303 | 304 | if [[ "$CMD" != "false" ]] 305 | then 306 | YN="n" 307 | else 308 | echo "> You can use this tool 5 times without account." 309 | 310 | YN="..." 311 | while [[ "$YN" != "n" ]] && [[ $YN != "y" ]]; do 312 | read -rp "> Do you want to create an account (y/n)? " YN 313 | done 314 | fi 315 | 316 | if [ "$YN" == "n" ] 317 | then 318 | # Create account when no account exists. 319 | EMAIL="tmp-$(Random)@$HOSTNAME" 320 | PASSWORD=$(Random) 321 | 322 | REGISTER_RET=($(Register "$EMAIL" "$PASSWORD")) 323 | 324 | REGISTER_RET_STATUS=${REGISTER_RET[0]} 325 | 326 | if [ "$REGISTER_RET_STATUS" != "success" ] 327 | then 328 | echo "> Internal error, could not create temporary account" 329 | exit 77 330 | else 331 | REGISTER_RET_KEY=${REGISTER_RET[1]} 332 | REGISTER_RET_SECRET=${REGISTER_RET[2]} 333 | StoreKeySecret "$REGISTER_RET_KEY" "$REGISTER_RET_SECRET" 334 | fi 335 | 336 | else 337 | for I in 1 2 3 338 | do 339 | # Ask what account should be created. 340 | echo -en "\tYour email: " 341 | read -r EMAIL 342 | echo -en "\tNew password: " 343 | read -rs PASSWORD 344 | echo "" 345 | echo -en "\tRetype your password: " 346 | read -rs PASSWORD2 347 | echo ""; 348 | 349 | REGISTER_RET_STATUS="" 350 | if [ "$PASSWORD" == "$PASSWORD2" ] && [ ${#PASSWORD} -ge 7 ] 351 | then 352 | REGISTER_RET=($(Register "$EMAIL" "$PASSWORD")) 353 | 354 | REGISTER_RET_STATUS=${REGISTER_RET[0]} 355 | 356 | if [ "$REGISTER_RET_STATUS" == "success" ] 357 | then 358 | REGISTER_RET_KEY=${REGISTER_RET[1]} 359 | REGISTER_RET_SECRET=${REGISTER_RET[2]} 360 | StoreKeySecret "$REGISTER_RET_KEY" "$REGISTER_RET_SECRET" 361 | return 362 | fi 363 | fi 364 | 365 | if [ ${#PASSWORD} -le 6 ] 366 | then 367 | echo "> Password should minimal contain 6 characters" >&2 368 | fi 369 | 370 | if [ "$PASSWORD" != "$PASSWORD2" ] 371 | then 372 | echo "> The password confirmation does not match" >&2 373 | fi 374 | 375 | if [ "$REGISTER_RET" == "email" ] 376 | then 377 | echo "> Account already exists with this account. Use command with email and password parameters." >&2 378 | exit 77 379 | fi 380 | done 381 | 382 | echo "> Account could not be created." >&2 383 | exit 77 384 | fi 385 | } 386 | 387 | function DetermineServer { 388 | SERVERS_RET=($(ApiServers "$KEY" "$SECRET")) 389 | SERVERS_ERRORS=${SERVERS_RET[0]} 390 | SERVERS=${SERVERS_RET[1]} 391 | 392 | HAS_SERVER=$(echo "$SERVERS" | json | grep -P '^\[[0-9]*,"name"\]\t"'"$HOSTNAME"'"') 393 | 394 | # Check if the server has already been created 395 | if [ "$HAS_SERVER" == "" ] 396 | then 397 | 398 | SERVER_CREATE_RET=($(ApiServerCreate "$KEY" "$SECRET" "$HOSTNAME")) 399 | SERVER_CREATE_ERRORS=${SERVER_CREATE_RET[0]} 400 | 401 | if [[ "$SERVER_CREATE_ERRORS" =~ "You exceeded the maximum allowed server slots" ]] 402 | then 403 | echo "> You exceeded the maximum allowed servers on your account, please login onto http://patrolserver.com and upgrade your account"; 404 | exit 77; 405 | fi 406 | 407 | SERVERS_RET=($(ApiServers "$KEY" "$SECRET")) 408 | SERVERS_ERRORS=${SERVERS_RET[0]} 409 | SERVERS=${SERVERS_RET[1]} 410 | 411 | HAS_SERVER=$(echo "$SERVERS" | json | grep -P '^\[[0-9]*,"name"\]\t"'"$HOSTNAME"'"' -o) 412 | fi 413 | 414 | if [ "$HAS_SERVER" == "" ] 415 | then 416 | echo "> Internal error, could not create the server online" >&2 417 | exit 77 418 | fi 419 | 420 | SERVER_ARRAY_ID=$(echo "$HAS_SERVER" | grep -P '^\[[0-9]+' -o | grep -P '[0-9]+' -o) 421 | SERVER_ID=$(echo "$SERVERS" | json | grep "^\[$SERVER_ARRAY_ID,\"id\"\]" | cut -f2-) 422 | SERVER_VERIFIED=$(echo "$SERVERS" | json | grep "^\[$SERVER_ARRAY_ID,\"verified\"\]" | cut -f2-) 423 | 424 | # Check if the server has already been verified 425 | if [ "$SERVER_VERIFIED" == "false" ] 426 | then 427 | SERVER_TOKEN_RET=($(ApiServerToken "$HOSTNAME")) 428 | SERVER_TOKEN_ERRORS=${SERVER_TOKEN_RET[0]} 429 | SERVER_TOKEN=${SERVER_TOKEN_RET[1]} 430 | 431 | SERVER_VERIFY_RET=($(ApiVerifyServer "$KEY" "$SECRET" "$SERVER_ID" "$SERVER_TOKEN")) 432 | SERVER_TOKEN_ERRORS=${SERVER_VERIFY_RET[0]} 433 | fi 434 | } 435 | 436 | function Scan { 437 | if [[ "$CMD" == "false" ]] 438 | then 439 | echo "> Searching for packages, can take some time..." 440 | fi 441 | 442 | SOFTWARE=$(mktemp) 443 | 444 | # Update db 445 | if ! command -v updatedb >/dev/null 2>&1 && command -v yum >/dev/null 2>&1 446 | then 447 | echo "This script needs the mlocate package, please install: yum install mlocate" 448 | exit 77 449 | fi 450 | updatedb -o "$LOCATE" -U / --require-visibility 0 2> /dev/null 451 | 452 | # Do all scanners 453 | ComposerSoftware 454 | DpkgSoftware 455 | DrupalSoftware 456 | NpmSoftware 457 | WordpressSoftware 458 | PhpmyadminSoftware 459 | JoomlaSoftware 460 | MagentoSoftware 461 | 462 | if [[ "$CMD" == "false" ]] 463 | then 464 | echo "> Scanning for newest releases and exploits, can take serveral minutes..." 465 | echo "> Take a coffee break ;) " 466 | fi 467 | 468 | SOFTWARE_PUST_RET=($(ApiServerPush "$KEY" "$SECRET" "$SERVER_ID" "$BUCKET" "$SOFTWARE")) 469 | SOFTWARE_PUST_ERRORS=${SOFTWARE_PUST_RET[0]} 470 | 471 | if [ "$SOFTWARE_PUST_ERRORS" != "false" ] 472 | then 473 | echo "> Could not upload software data, please give our support team a call with the following details" >&2 474 | echo "$SOFTWARE_PUST_ERRORS" >&2 475 | exit 77 476 | fi 477 | 478 | SERVER_SCAN_RET=($(ApiServerScan "$KEY" "$SECRET" "$SERVER_ID")) 479 | SERVER_SCAN_ERRORS=${SERVER_SCAN_RET[0]} 480 | 481 | if [ "$SERVER_SCAN_ERRORS" != "false" ] 482 | then 483 | echo "> Could not send scan command, please give our support team a call with the following details" >&2 484 | echo "$SERVER_SCAN_ERRORS" >&2 485 | exit 77 486 | fi 487 | 488 | SERVER_SCANNING="true" 489 | while [ "$SERVER_SCANNING" == "true" ] ; do 490 | SERVER_SCANNING_RET=($(ApiServerIsScanning "$KEY" "$SECRET" "$SERVER_ID")) 491 | SERVER_SCANNING_ERRORS=${SERVER_SCANNING_RET[0]} 492 | SERVER_SCANNING=${SERVER_SCANNING_RET[1]} 493 | 494 | if [[ "$CMD" == "false" ]] 495 | then 496 | echo -n "." 497 | fi 498 | 499 | sleep 5 500 | done 501 | 502 | if [[ "$CMD" == "false" ]] 503 | then 504 | echo ""; 505 | fi 506 | } 507 | 508 | function Output { 509 | if [[ "$CMD" == "false" ]] 510 | then 511 | echo "> Software versions has been retrieved (Solutions for the exploits can be seen on our web interface):" 512 | fi 513 | 514 | SOFTWARE_RET=($(ApiSoftware "$KEY" "$SECRET" "$SERVER_ID")) 515 | SOFTWARE_ERRORS=${SOFTWARE_RET[0]} 516 | SOFTWARE_JSON=${SOFTWARE_RET[1]} 517 | 518 | SOFTWARE=$(echo "$SOFTWARE_JSON" | json | grep -P "^\[[0-9]{1,}\]" | cut -f2-) 519 | if [ "$SOFTWARE" == "" ] 520 | then 521 | echo -e "\tStrangely, No packages were found..." 522 | fi 523 | 524 | CORE_SOFTWARE=$(echo "$SOFTWARE" | grep '"parent":null' | grep '"location":"\\\/"') 525 | OutputBlock "$SOFTWARE" "$CORE_SOFTWARE" 526 | 527 | CORE_SOFTWARE=$(echo "$SOFTWARE" | grep "\"parent\":null" | grep -v '"location":"\\\/"') 528 | OutputBlock "$SOFTWARE" "$CORE_SOFTWARE" 529 | } 530 | 531 | function OutputBlock { 532 | SOFTWARE="$1" 533 | BLOCK_SOFTWARE="$2" 534 | 535 | PREV_LOCATION="---" 536 | for LINE in $BLOCK_SOFTWARE; do 537 | 538 | LINE=$(echo "$LINE" | json) 539 | CANONICAL_NAME=$(echo "$LINE" | grep '^\["canonical_name"\]' | cut -f2- | sed -e 's/^"//' -e 's/"$//') 540 | CANONICAL_NAME_GREP=$(echo "$CANONICAL_NAME" | sed -e 's/[]\/$*.^|[]/\\&/g') 541 | LOCATION=$(echo "$LINE" | grep '^\["location"\]' | cut -f2- | sed -e 's/^"//' -e 's/"$//') 542 | LOCATION_GREP=$(echo "$LOCATION" | sed -e 's/[]\/$*.^|[]/\\&/g') 543 | 544 | # Print out the location when it has changed 545 | if [[ "$PREV_LOCATION" != "$LOCATION" ]] && [[ "$LOCATION" != '\/' ]] 546 | then 547 | echo ""; 548 | echo -ne "\e[0;90m" 549 | echo -n "$LOCATION" 550 | echo -e "\e[0m" 551 | echo ""; 552 | 553 | PREV_LOCATION="$LOCATION" 554 | fi 555 | 556 | OutputLine "$LINE" 557 | 558 | # Print submodules 559 | for LINE in $(echo "$SOFTWARE" | grep '"parent":"'"$CANONICAL_NAME_GREP"'"' | grep "location\":\"$LOCATION_GREP"); do 560 | LINE=$(echo "$LINE" | json) 561 | 562 | echo -en "\t" 563 | OutputLine "$LINE" 564 | done 565 | done 566 | } 567 | 568 | function OutputLine { 569 | LINE="$1" 570 | 571 | NAME=$(echo "$LINE" | grep '^\["name"\]' | cut -f2- | sed -e 's/^"//' -e 's/"$//') 572 | VERSION=$(echo "$LINE" | grep '^\["version"\]' | cut -f2- | sed -e 's/^"//' -e 's/"$//') 573 | VERSIONS=$(echo "$LINE" | grep '^\["versions"\]' | cut -f2-) 574 | NEW_VERSION=$(echo "$LINE" | grep '^\["newest_bugfix_release"\]' | cut -f2- | sed -e 's/^"//' -e 's/"$//') 575 | SUPPORTED=$(echo "$LINE" | grep '^\["supported"\]' | cut -f2- | sed -e 's/^"//' -e 's/"$//') 576 | EXPLOITS=$(echo "$LINE" | grep '^\["exploits"\]' | cut -f2- | json | grep '^\[[0-9]*,"risk"\]' | cut -f2-) 577 | LOCATION=$(echo "$LINE" | grep '^\["location"\]' | cut -f2- | sed -e 's/^"//' -e 's/"$//') 578 | 579 | if [ "$VERSIONS" != "" ] 580 | then 581 | VERSION=$(echo "$VERSIONS" | json | grep '^\[0]' | cut -f2- | sed -e 's/^"//' -e 's/"$//') 582 | VERSION="<=$VERSION" 583 | fi 584 | 585 | echo -ne "\t$NAME: " 586 | 587 | # Print current version 588 | if [ "$VERSION" == "" ] 589 | then 590 | echo -n "version not detected" 591 | elif [ "$SUPPORTED" == "yes" ] 592 | then 593 | echo -ne "\e[0;32m" 594 | echo -n "$VERSION" 595 | echo -ne "\e[0m" 596 | elif [[ "$NEW_VERSION" != "" ]] 597 | then 598 | echo -ne "\e[0;33m" 599 | echo -n "$VERSION" 600 | echo -ne "\e[0m" 601 | echo -n ", update to " 602 | echo -ne "\e[0;32m" 603 | echo -n "$NEW_VERSION" 604 | echo -ne "\e[0m" 605 | else 606 | echo -ne "\e[0;31m" 607 | echo -n "$VERSION" 608 | echo -ne "\e[0m" 609 | echo -n ", not supported anymore" 610 | fi 611 | 612 | # Check exploits 613 | COUNT_EXPLOITS=0 614 | for EXPLOIT in $EXPLOITS; do 615 | IS_BIGGER=$(echo "$EXPLOIT" | grep "^[5-9]") 616 | if [ "$IS_BIGGER" == "1" ] 617 | then 618 | COUNT_EXPLOITS=$((COUNT_EXPLOITS+1)) 619 | fi 620 | done 621 | 622 | if [ "$COUNT_EXPLOITS" != "0" ] 623 | then 624 | echo -ne "\e[0;31m" 625 | echo -n " ($COUNT_EXPLOITS exploits)" 626 | echo -ne "\e[0m" 627 | fi 628 | 629 | echo "" 630 | } 631 | 632 | function Cronjob { 633 | 634 | # User has already a cronjob defined 635 | HAS_CRONTAB=$(crontab -l 2> /dev/null | grep "patrolserver") 636 | if [ "$HAS_CRONTAB" != "" ] 637 | then 638 | return 639 | fi 640 | 641 | if [[ "$CMD" != "false" ]] 642 | then 643 | return 644 | fi 645 | 646 | if [[ "$CRON" != "ask" ]] && [[ "$CRON" != "true" ]] 647 | then 648 | return 649 | fi 650 | 651 | # Check if user want a cronjob 652 | YN="..." 653 | if [[ "$CRON" == "true" ]] 654 | then 655 | YN="y" 656 | fi 657 | while [[ "$YN" != "n" ]] && [[ $YN != "y" ]]; do 658 | read -rp "> It is advisable to check your server daily, should we set a cronjob (y/n)? " YN 659 | done 660 | 661 | if [ "$YN" == "n" ] 662 | then 663 | if [[ "$EMAIL" == tmp\-* ]] 664 | then 665 | ApiUserRemove "$KEY" "$SECRET" 666 | fi 667 | 668 | else 669 | 670 | if [[ "$EMAIL" == tmp\-* ]] 671 | then 672 | echo -n "> What is your email address to send reports to? " 673 | read -r REAL_EMAIL 674 | echo "" 675 | 676 | CHANGE_EMAIL_RET=($(ApiUserChange "$KEY" "$SECRET" "$REAL_EMAIL")) 677 | CHANGE_EMAIL_ERROR=${CHANGE_EMAIL_RET[0]} 678 | CHANGE_EMAIL_USER=${CHANGE_EMAIL_RET[1]} 679 | 680 | if [ "$CHANGE_EMAIL_ERROR" != "false" ] 681 | then 682 | if [[ "$CHANGE_EMAIL_ERROR" == "82" ]] 683 | then 684 | echo "> There is already an account with this email address, use this tool with email and password parameters." >&2 685 | exit 77 686 | fi 687 | 688 | echo "> Internal error when changing username" >&2 689 | exit 77 690 | fi 691 | fi 692 | 693 | mkdir ~/.patrolserver 2> /dev/null 694 | echo -e "HOSTNAME=$HOSTNAME\nKEY=$KEY\nSECRET=$SECRET" > ~/.patrolserver/env 695 | cat "$LOCATE" > ~/.patrolserver/locate.db 696 | wget -O ~/.patrolserver/patrolserver "https://raw.githubusercontent.com/PatrolServer/bashScanner/master/patrolserver" 2&>1 /dev/null 697 | chmod +x ~/.patrolserver/patrolserver 698 | 699 | # Set cronjob 700 | CRON_TMP=$(mktemp) 701 | crontab -l 2> /dev/null > "$CRON_TMP" 702 | CRON_HOUR=$((RANDOM % 24)) 703 | CRON_MINUTE=$((RANDOM % 60)) 704 | echo "$CRON_MINUTE $CRON_HOUR * * * /bin/bash $HOME/.patrolserver/patrolserver --cmd --key=\"$KEY\" --secret=\"$SECRET\" --hostname=\"$HOSTNAME\" > /dev/null" >> $CRON_TMP 705 | crontab "$CRON_TMP" 706 | 707 | echo "> cronjob was set." 708 | 709 | if [[ "$EMAIL" == tmp\-* ]] 710 | then 711 | echo "> Your login on patrolserver.com:" 712 | echo -e "\tlogin: $REAL_EMAIL" 713 | echo -e "\tpassword: $PASSWORD" 714 | fi 715 | 716 | echo "> A environment file was created on ~/.patrolserver, so you don't have to call it anymore with the key and secret" 717 | fi 718 | } 719 | 720 | Start "$@" 721 | -------------------------------------------------------------------------------- /compiled.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | MY_HOME="https://demo.patrolserver.com" 4 | 5 | #!/usr/bin/env bash 6 | 7 | function SetEnv { 8 | # IFS for return function contents in newlines. 9 | OLDIFS=$IFS 10 | IFS=$'\n' 11 | 12 | # Set 77 error code as exit any subshell level. 13 | set -E 14 | trap '[ "$?" -ne 77 ] || exit 77' ERR 15 | } 16 | 17 | function ResetEnv { 18 | IFS=$OLDIFS 19 | } 20 | 21 | function Exit { 22 | exit 77; 23 | } 24 | 25 | function Random { 26 | tr -dc 'a-zA-Z0-9' < /dev/urandom | fold -w "${1:-32}" | head -n 1 27 | } 28 | #!/usr/bin/env bash 29 | 30 | json() { 31 | 32 | ResetEnv 33 | 34 | throw () { 35 | echo "$*" >&2 36 | exit 1 37 | } 38 | 39 | BRIEF=0 40 | LEAFONLY=0 41 | PRUNE=0 42 | NORMALIZE_SOLIDUS=0 43 | 44 | usage() { 45 | echo 46 | echo "Usage: JSON.sh [-b] [-l] [-p] [-s] [-h]" 47 | echo 48 | echo "-p - Prune empty. Exclude fields with empty values." 49 | echo "-l - Leaf only. Only show leaf nodes, which stops data duplication." 50 | echo "-b - Brief. Combines 'Leaf only' and 'Prune empty' options." 51 | echo "-s - Remove escaping of the solidus symbol (stright slash)." 52 | echo "-h - This help text." 53 | echo 54 | } 55 | 56 | parse_options() { 57 | set -- "$@" 58 | local ARGN=$# 59 | while [ "$ARGN" -ne 0 ] 60 | do 61 | case $1 in 62 | -h) usage 63 | exit 0 64 | ;; 65 | -b) BRIEF=1 66 | LEAFONLY=1 67 | PRUNE=1 68 | ;; 69 | -l) LEAFONLY=1 70 | ;; 71 | -p) PRUNE=1 72 | ;; 73 | -s) NORMALIZE_SOLIDUS=1 74 | ;; 75 | ?*) echo "ERROR: Unknown option." 76 | usage 77 | exit 0 78 | ;; 79 | esac 80 | shift 1 81 | ARGN=$((ARGN-1)) 82 | done 83 | } 84 | 85 | awk_egrep () { 86 | local pattern_string=$1 87 | 88 | gawk '{ 89 | while ($0) { 90 | start=match($0, pattern); 91 | token=substr($0, start, RLENGTH); 92 | print token; 93 | $0=substr($0, start+RLENGTH); 94 | } 95 | }' pattern="$pattern_string" 96 | } 97 | 98 | tokenize () { 99 | local GREP 100 | local ESCAPE 101 | local CHAR 102 | 103 | if echo "test string" | egrep -ao --color=never "test" &>/dev/null 104 | then 105 | GREP='egrep -ao --color=never' 106 | else 107 | GREP='egrep -ao' 108 | fi 109 | 110 | if echo "test string" | egrep -o "test" &>/dev/null 111 | then 112 | ESCAPE='(\\[^u[:cntrl:]]|\\u[0-9a-fA-F]{4})' 113 | CHAR='[^[:cntrl:]"\\]' 114 | else 115 | GREP=awk_egrep 116 | ESCAPE='(\\\\[^u[:cntrl:]]|\\u[0-9a-fA-F]{4})' 117 | CHAR='[^[:cntrl:]"\\\\]' 118 | fi 119 | 120 | local STRING="\"$CHAR*($ESCAPE$CHAR*)*\"" 121 | local NUMBER='-?(0|[1-9][0-9]*)([.][0-9]*)?([eE][+-]?[0-9]*)?' 122 | local KEYWORD='null|false|true' 123 | local SPACE='[[:space:]]+' 124 | 125 | $GREP "$STRING|$NUMBER|$KEYWORD|$SPACE|." | egrep -v "^$SPACE$" 126 | } 127 | 128 | parse_array () { 129 | local index=0 130 | local ary='' 131 | read -r token 132 | case "$token" in 133 | ']') ;; 134 | *) 135 | while : 136 | do 137 | parse_value "$1" "$index" 138 | index=$((index+1)) 139 | ary="$ary""$value" 140 | read -r token 141 | case "$token" in 142 | ']') break ;; 143 | ',') ary="$ary," ;; 144 | *) throw "EXPECTED , or ] GOT ${token:-EOF}" ;; 145 | esac 146 | read -r token 147 | done 148 | ;; 149 | esac 150 | [ "$BRIEF" -eq 0 ] && value=$(printf '[%s]' "$ary") || value= 151 | : 152 | } 153 | 154 | parse_object () { 155 | local key 156 | local obj='' 157 | read -r token 158 | case "$token" in 159 | '}') ;; 160 | *) 161 | while : 162 | do 163 | case "$token" in 164 | '"'*'"') key=$token ;; 165 | *) throw "EXPECTED string GOT ${token:-EOF}" ;; 166 | esac 167 | read -r token 168 | case "$token" in 169 | ':') ;; 170 | *) throw "EXPECTED : GOT ${token:-EOF}" ;; 171 | esac 172 | read -r token 173 | parse_value "$1" "$key" 174 | obj="$obj$key:$value" 175 | read -r token 176 | case "$token" in 177 | '}') break ;; 178 | ',') obj="$obj," ;; 179 | *) throw "EXPECTED , or } GOT ${token:-EOF}" ;; 180 | esac 181 | read -r token 182 | done 183 | ;; 184 | esac 185 | [ "$BRIEF" -eq 0 ] && value=$(printf '{%s}' "$obj") || value= 186 | : 187 | } 188 | 189 | parse_value () { 190 | local jpath="${1:+$1,}$2" isleaf=0 isempty=0 print=0 191 | case "$token" in 192 | '{') parse_object "$jpath" ;; 193 | '[') parse_array "$jpath" ;; 194 | # At this point, the only valid single-character tokens are digits. 195 | ''|[!0-9]) throw "EXPECTED value GOT ${token:-EOF}" ;; 196 | *) value=$token 197 | # if asked, replace solidus ("\/") in json strings with normalized value: "/" 198 | [ "$NORMALIZE_SOLIDUS" -eq 1 ] && value=${value//\\\//\/} 199 | isleaf=1 200 | [ "$value" = '""' ] && isempty=1 201 | ;; 202 | esac 203 | [ "$value" = '' ] && return 204 | [ "$LEAFONLY" -eq 0 ] && [ "$PRUNE" -eq 0 ] && print=1 205 | [ "$LEAFONLY" -eq 1 ] && [ "$isleaf" -eq 1 ] && [ $PRUNE -eq 0 ] && print=1 206 | [ "$LEAFONLY" -eq 0 ] && [ "$PRUNE" -eq 1 ] && [ "$isempty" -eq 0 ] && print=1 207 | [ "$LEAFONLY" -eq 1 ] && [ "$isleaf" -eq 1 ] && \ 208 | [ $PRUNE -eq 1 ] && [ $isempty -eq 0 ] && print=1 209 | [ "$print" -eq 1 ] && printf "[%s]\t%s\n" "$jpath" "$value" 210 | : 211 | } 212 | 213 | parse () { 214 | read -r token 215 | parse_value 216 | read -r token 217 | case "$token" in 218 | '') ;; 219 | *) throw "EXPECTED EOF GOT $token" ;; 220 | esac 221 | } 222 | 223 | parse_options "$@" 224 | tokenize | parse 225 | 226 | SetEnv 227 | } 228 | #!/usr/bin/env bash 229 | 230 | COOKIES=$(mktemp) 231 | POSTFILE=$(mktemp) 232 | 233 | function ApiUserRegister { 234 | local EMAIL 235 | local PASSWORD 236 | local OUTPUT 237 | local ERROR 238 | local USER 239 | local KEY 240 | local SECRET 241 | 242 | EMAIL=$(Urlencode "$1") 243 | PASSWORD=$(Urlencode "$2") 244 | 245 | OUTPUT=$(wget -t2 -T6 --header="X-PS-Bash: 1" -qO- "${MY_HOME}/extern/api/users" --post-data "email=$EMAIL&password=$PASSWORD&password_confirmation=$PASSWORD") 246 | 247 | if [ "$OUTPUT" == "" ] 248 | then 249 | echo "> patrolserver.com is not reachable, contact us (1)" >&2 250 | exit 77 251 | fi 252 | 253 | ERROR=$(echo "$OUTPUT" | json | grep '^\["error"\]' | cut -f2-) 254 | USER=$(echo "$OUTPUT" | json | grep '^\["data","user"\]' | cut -f2-) 255 | KEY=$(echo "$OUTPUT" | json | grep '^\["data","api_credentials","key"\]' | cut -f2- | sed -e 's/^"//' -e 's/"$//') 256 | SECRET=$(echo "$OUTPUT" | json | grep '^\["data","api_credentials","secret"\]' | cut -f2- | sed -e 's/^"//' -e 's/"$//') 257 | 258 | echo "${ERROR:-false}" 259 | echo "${USER:-false}" 260 | echo "${KEY:-false}" 261 | echo "${SECRET:-false}" 262 | } 263 | 264 | function ApiUserLogin { 265 | local EMAIL 266 | local PASSWORD 267 | local OUTPUT 268 | local ERROR 269 | local USER 270 | local KEY 271 | local SECRET 272 | 273 | EMAIL=$(Urlencode "$1") 274 | PASSWORD=$(Urlencode "$2") 275 | 276 | OUTPUT=$(wget -t2 -T6 --header="X-PS-Bash: 1" -qO- "${MY_HOME}/api/user/request_api_credentials" --post-data "email=$EMAIL&password=$PASSWORD") 277 | 278 | if [ "$OUTPUT" == "" ] 279 | then 280 | echo "> patrolserver.com is not reachable, contact us (2)" >&2 281 | exit 77 282 | fi 283 | 284 | ERROR=$(echo "$OUTPUT" | json | grep '^\["error","code"\]' | cut -f2- | sed -e 's/^"//' -e 's/"$//') 285 | USER=$(echo "$OUTPUT" | json | grep '^\["user"\]' | cut -f2-) 286 | KEY=$(echo "$OUTPUT" | json | grep '^\["api_credentials","key"\]' | cut -f2- | sed -e 's/^"//' -e 's/"$//') 287 | SECRET=$(echo "$OUTPUT" | json | grep '^\["api_credentials","secret"\]' | cut -f2- | sed -e 's/^"//' -e 's/"$//') 288 | 289 | echo "${ERROR:-false}" 290 | echo "${USER:-false}" 291 | echo "${KEY:-false}" 292 | echo "${SECRET:-false}" 293 | } 294 | 295 | function ApiServerExists { 296 | local HOST 297 | local OUTPUT 298 | local ERROR 299 | local EXISTS 300 | 301 | HOST=$(Urlencode "$1") 302 | 303 | OUTPUT=$(wget -t2 -T6 --header="X-PS-Bash: 1" -qO- "${MY_HOME}/extern/api/serverExists?host=$HOST") 304 | 305 | if [ "$OUTPUT" == "" ] 306 | then 307 | echo "> patrolserver.com is not reachable, contact us (3)" >&2 308 | exit 77 309 | fi 310 | 311 | ERROR=$(echo "$OUTPUT" | json | grep '^\["error","code"\]' | cut -f2- | sed -e 's/^"//' -e 's/"$//') 312 | EXISTS=$(echo "$OUTPUT" | json | grep '^\["exists"\]' | cut -f2-) 313 | 314 | echo "${ERROR:-false}" 315 | echo "${EXISTS:-false}" 316 | } 317 | 318 | function ApiServerCreate { 319 | local KEY 320 | local SECRET 321 | local HOSTNAME 322 | local OUTPUT 323 | local ID 324 | local ERROR 325 | 326 | KEY=$(Urlencode "$1") 327 | SECRET=$(Urlencode "$2") 328 | HOSTNAME=$(Urlencode "$3") 329 | 330 | OUTPUT=$(wget -t2 -T6 --header="X-PS-Bash: 1" -qO- "${MY_HOME}/extern/api/servers?key=$KEY&secret=$SECRET" --post-data "domain=$HOSTNAME") 331 | 332 | if [ "$OUTPUT" == "" ] 333 | then 334 | echo "> patrolserver.com is not reachable, contact us (4)" >&2 335 | exit 77 336 | fi 337 | 338 | ID=$(echo "$OUTPUT" | json | grep '^\["data","id"\]' | cut -f2-) 339 | ERROR=$(echo "$OUTPUT" | json | grep '^\["error"\]' | cut -f2-) 340 | 341 | echo "${ERROR:-false}" 342 | echo "${ID:-false}" 343 | } 344 | 345 | function ApiServerToken { 346 | local HOSTNAME 347 | local OUTPUT 348 | local TOKEN 349 | local ERROR 350 | 351 | HOSTNAME=$(Urlencode "$1") 352 | 353 | OUTPUT=$(wget -t2 -T6 --header="X-PS-Bash: 1" -qO- "${MY_HOME}/extern/api/request_verification_token?domain=$HOSTNAME") 354 | 355 | if [ "$OUTPUT" == "" ] 356 | then 357 | echo "> patrolserver.com is not reachable, contact us (5)" >&2 358 | exit 77 359 | fi 360 | 361 | TOKEN=$(echo "$OUTPUT" | json | grep '^\["data","token"\]' | cut -f2- | sed -e 's/^"//' -e 's/"$//') 362 | ERROR=$(echo "$OUTPUT" | json | grep '^\["error"\]' | cut -f2-) 363 | 364 | echo "${ERROR:-false}" 365 | echo "${TOKEN:-false}" 366 | } 367 | 368 | function ApiVerifyServer { 369 | local KEY 370 | local SECRET 371 | local SERVER_ID 372 | local TOKEN 373 | local OUTPUT 374 | local ERROR 375 | 376 | KEY=$(Urlencode "$1") 377 | SECRET=$(Urlencode "$2") 378 | SERVER_ID=$(Urlencode "$3") 379 | TOKEN=$(Urlencode "$4") 380 | 381 | OUTPUT=$(wget -t2 -T6 --header="X-PS-Bash: 1" -qO- "${MY_HOME}/extern/api/servers/${SERVER_ID}/verify?key=$KEY&secret=$SECRET" --post-data "token=$TOKEN") 382 | 383 | if [ "$OUTPUT" == "" ] 384 | then 385 | echo "> patrolserver.com is not reachable, contact us (6)" >&2 386 | exit 77 387 | fi 388 | 389 | ERROR=$(echo "$OUTPUT" | json | grep '^\["error"\]' | cut -f2-) 390 | 391 | echo "${ERROR:-false}" 392 | } 393 | 394 | function ApiServerPush { 395 | local KEY 396 | local SECRET 397 | local SERVER_ID 398 | local BUCKET 399 | local EXPIRE 400 | local OUTPUT 401 | local ERROR 402 | 403 | KEY=$(Urlencode "$1") 404 | SECRET=$(Urlencode "$2") 405 | SERVER_ID=$(Urlencode "$3") 406 | BUCKET=$(Urlencode "$4") 407 | EXPIRE="129600" 408 | 409 | echo -n "expire=$EXPIRE&software=" > "$POSTFILE" 410 | 411 | SOFTWARE=$(sort < "$SOFTWARE" | uniq | awk 'BEGIN { RS="\n"; FS="\t"; print "["; prevLocation="---"; prevName="---"; prevVersion="---"; prevParent="---";} 412 | { 413 | if($1 == prevLocation){ $1=""; } else { prevLocation = $1; $1 = "\"l\":\""$1"\"," }; 414 | if($2 == prevParent){ $2=""; } else { prevParent = $2; $2 = "\"p\":\""$2"\"," }; 415 | if($3 == prevName){ $3=""; } else { prevName = $3; $3 = "\"n\":\""$3"\"," }; 416 | if($4 == prevVersion){ $4=""; } else { prevVersion = $4; $4 = "\"v\":\""$4"\"," }; 417 | line = $1$2$3$4; 418 | print "{"line"},"; 419 | } 420 | END { print "{}]"; }' | sed 's/,},/},/' | tr -d '\n') 421 | SOFTWARE=$(Urlencode "$SOFTWARE") 422 | 423 | echo "$SOFTWARE" >> "$POSTFILE" 424 | 425 | OUTPUT=$(wget -t2 -T6 --header="X-PS-Bash: 1" -qO- "${MY_HOME}/extern/api/servers/${SERVER_ID}/buckets/$BUCKET?key=$KEY&secret=$SECRET&scope=silent" --post-file $POSTFILE) 426 | 427 | if [ "$OUTPUT" == "" ] 428 | then 429 | echo "> patrolserver.com is not reachable, contact us (7)" >&2 430 | exit 77 431 | fi 432 | 433 | ERROR=$(echo "$OUTPUT" | json | grep '^\["error"\]' | cut -f2-) 434 | 435 | echo "${ERROR:-false}" 436 | } 437 | 438 | function ApiServers { 439 | local KEY 440 | local SECRET 441 | local OUTPUT 442 | local SERVERS 443 | local ERROR 444 | 445 | KEY=$(Urlencode "$1") 446 | SECRET=$(Urlencode "$2") 447 | 448 | OUTPUT=$(wget -t2 -T6 --header="X-PS-Bash: 1" -qO- "${MY_HOME}/extern/api/servers?key=$KEY&secret=$SECRET") 449 | 450 | if [ "$OUTPUT" == "" ] 451 | then 452 | echo "> patrolserver.com is not reachable, contact us (10)" >&2 453 | exit 77 454 | fi 455 | 456 | SERVERS=$(echo "$OUTPUT" | json | grep '^\["data"\]' | cut -f2-) 457 | ERROR=$(echo "$OUTPUT" | json | grep '^\["error"\]' | cut -f2-) 458 | 459 | echo "${ERROR:-false}" 460 | echo "${SERVERS:-false}" 461 | } 462 | 463 | function ApiSoftware { 464 | local KEY 465 | local SECRET 466 | local SERVER_ID 467 | local OUTPUT 468 | local SOFTWARE 469 | local ERROR 470 | 471 | KEY=$(Urlencode "$1") 472 | SECRET=$(Urlencode "$2") 473 | SERVER_ID=$(Urlencode "$3") 474 | 475 | OUTPUT=$(wget -t2 -T6 --header="X-PS-Bash: 1" -qO- "${MY_HOME}/extern/api/servers/$SERVER_ID/software?key=$KEY&secret=$SECRET&scope=exploits") 476 | 477 | if [ "$OUTPUT" == "" ] 478 | then 479 | echo "> patrolserver.com is not reachable, contact us (11)" >&2 480 | exit 77 481 | fi 482 | 483 | SOFTWARE=$(echo "$OUTPUT" | json | grep '^\["data"\]' | cut -f2-) 484 | ERROR=$(echo "$OUTPUT" | json | grep '^\["error"\]' | cut -f2-) 485 | 486 | echo "${ERROR:-false}" 487 | echo "${SOFTWARE:-false}" 488 | } 489 | 490 | function ApiServerScan { 491 | local KEY 492 | local SECRET 493 | local SERVER_ID 494 | local OUTPUT 495 | local ERROR 496 | 497 | KEY=$(Urlencode "$1") 498 | SECRET=$(Urlencode "$2") 499 | SERVER_ID=$(Urlencode "$3") 500 | 501 | OUTPUT=$(wget -t2 -T6 -qO- "${MY_HOME}/extern/api/servers/$SERVER_ID/scan?key=$KEY&secret=$SECRET" --post-data "not=used") 502 | 503 | if [ "$OUTPUT" == "" ] 504 | then 505 | echo "> patrolserver.com is not reachable, contact us (12)" >&2 506 | exit 77 507 | fi 508 | 509 | ERROR=$(echo "$OUTPUT" | json | grep '^\["error"\]' | cut -f2-) 510 | 511 | echo "${ERROR:-false}" 512 | } 513 | 514 | function ApiServerIsScanning { 515 | local KEY 516 | local SECRET 517 | local SERVER_ID 518 | local OUTPUT 519 | local ERROR 520 | local SCANNING 521 | 522 | KEY=$(Urlencode "$1") 523 | SECRET=$(Urlencode "$2") 524 | SERVER_ID=$(Urlencode "$3") 525 | 526 | OUTPUT=$(wget -t2 -T6 --header="X-PS-Bash: 1" -qO- "${MY_HOME}/extern/api/servers/$SERVER_ID/isScanning?key=$KEY&secret=$SECRET") 527 | 528 | if [ "$OUTPUT" == "" ] 529 | then 530 | echo "> patrolserver.com is not reachable, contact us (13)" >&2 531 | exit 77 532 | fi 533 | 534 | ERROR=$(echo "$OUTPUT" | json | grep '^\["error"\]' | cut -f2-) 535 | SCANNING=$(echo "$OUTPUT" | json | grep '^\["data"\]' | cut -f2-) 536 | 537 | echo "${ERROR:-false}" 538 | echo "${SCANNING:-false}" 539 | } 540 | 541 | function ApiUserChange { 542 | local KEY 543 | local SECRET 544 | local EMAIL 545 | local OUTPUT 546 | local ERROR 547 | local USER 548 | 549 | KEY=$(Urlencode "$1") 550 | SECRET=$(Urlencode "$2") 551 | EMAIL=$(Urlencode "$3") 552 | 553 | OUTPUT=$(wget -t2 -T6 --header="X-PS-Bash: 1" -qO- "${MY_HOME}/extern/api/user?key=$KEY&secret=$SECRET" --post-data "email=$EMAIL") 554 | 555 | if [ "$OUTPUT" == "" ] 556 | then 557 | echo "> patrolserver.com is not reachable, contact us (14)" >&2 558 | exit 77 559 | fi 560 | 561 | ERROR=$(echo "$OUTPUT" | json | grep '^\["error","code"\]' | cut -f2- | sed -e 's/^"//' -e 's/"$//') 562 | USER=$(echo "$OUTPUT" | json | grep '^\["data"\]' | cut -f2-) 563 | 564 | echo "${ERROR:-false}" 565 | echo "${USER:-false}" 566 | } 567 | 568 | function ApiUserRemove { 569 | local KEY 570 | local SECRET 571 | local OUTPUT 572 | 573 | KEY=$(Urlencode "$1") 574 | SECRET=$(Urlencode "$2") 575 | 576 | OUTPUT=$(wget -t2 -T6 --header="X-PS-Bash: 1" -qO- "${MY_HOME}/extern/api/user/delete?key=$KEY&secret=$SECRET" --post-data "not=used") 577 | 578 | if [ "$OUTPUT" == "" ] 579 | then 580 | echo "> patrolserver.com is not reachable, contact us (15)" >&2 581 | exit 77 582 | fi 583 | 584 | #ERRORS=$(echo "$OUTPUT" | json | grep '^\["errors"\]' | cut -f2-) 585 | #SUCCESS=$(echo "$OUTPUT" | json | grep '^\["success"\]' | cut -f2-) 586 | 587 | #echo "${ERRORS:-false}" 588 | #echo "${SUCCESS:-false}" 589 | } 590 | 591 | URLENCODE_SED=$(mktemp) 592 | cat > $URLENCODE_SED <<- EOF 593 | s:%:%25:g 594 | s: :%20:g 595 | s:<:%3C:g 596 | s:>:%3E:g 597 | s:#:%23:g 598 | s:{:%7B:g 599 | s:}:%7D:g 600 | s:|:%7C:g 601 | s:\^:%5E:g 602 | s:~:%7E:g 603 | s:\[:%5B:g 604 | s:\]:%5D:g 605 | s:\`:%60:g 606 | s:;:%3B:g 607 | s:/:%2F:g 608 | s:?:%3F:g 609 | s^:^%3A^g 610 | s:@:%40:g 611 | s:=:%3D:g 612 | s:&:%26:g 613 | s:\!:%21:g 614 | s:\*:%2A:g 615 | s:\+:%2B:g 616 | EOF 617 | 618 | function Urlencode { 619 | local STRING 620 | local ENCODED 621 | 622 | STRING="${1}" 623 | ENCODED=$(echo "$STRING" | sed -f $URLENCODE_SED) 624 | 625 | echo "$ENCODED" 626 | } 627 | 628 | function Jsonspecialchars { 629 | echo "$1" | sed "s/'/\\\\\'/g" 630 | } 631 | #!/usr/bin/env bash 632 | 633 | function EnvFile { 634 | if [ -f ~/.patrolserver/env ]; 635 | then 636 | source ~/.patrolserver/env 637 | LOCATE="$HOME/.patrolserver/locate.db" 638 | fi 639 | } 640 | 641 | function Args { 642 | 643 | optspec=":e:p:n:k:s:ci:b:hv-:" 644 | while getopts "$optspec" optchar; do 645 | case "${optchar}" in 646 | -) 647 | case "${OPTARG}" in 648 | version) 649 | echo "PatrolServer BashScanner $VERSION" >&2 650 | exit 651 | ;; 652 | email) 653 | EMAIL="${!OPTIND}"; OPTIND=$(( $OPTIND + 1 )) 654 | ;; 655 | email=*) 656 | EMAIL=${OPTARG#*=} 657 | ;; 658 | password) 659 | PASSWORD="${!OPTIND}"; OPTIND=$(( $OPTIND + 1 )) 660 | ;; 661 | password=*) 662 | PASSWORD=${OPTARG#*=} 663 | ;; 664 | hostname) 665 | HOSTNAME="${!OPTIND}"; OPTIND=$(( $OPTIND + 1 )) 666 | ;; 667 | hostname=*) 668 | HOSTNAME=${OPTARG#*=} 669 | ;; 670 | key) 671 | KEY="${!OPTIND}"; OPTIND=$(( $OPTIND + 1 )) 672 | ;; 673 | key=*) 674 | KEY=${OPTARG#*=} 675 | ;; 676 | secret) 677 | SECRET="${!OPTIND}"; OPTIND=$(( $OPTIND + 1 )) 678 | ;; 679 | secret=*) 680 | SECRET=${OPTARG#*=} 681 | ;; 682 | cmd) 683 | CMD="true" 684 | ;; 685 | cmd=*) 686 | CMD=${OPTARG#*=} 687 | ;; 688 | server_id) 689 | SERVER_ID="${!OPTIND}"; OPTIND=$(( $OPTIND + 1 )) 690 | ;; 691 | server_id=*) 692 | SERVER_ID=${OPTARG#*=} 693 | ;; 694 | bucket) 695 | BUCKET="${!OPTIND}"; OPTIND=$(( $OPTIND + 1 )) 696 | ;; 697 | bucket=*) 698 | BUCKET=${OPTARG#*=} 699 | ;; 700 | cron) 701 | CRON="true" 702 | ;; 703 | cron=*) 704 | CRON=${OPTARG#*=} 705 | ;; 706 | target) 707 | MY_HOME="${!OPTIND}"; OPTIND=$(( $OPTIND + 1 )) 708 | ;; 709 | target=*) 710 | MY_HOME=${OPTARG#*=} 711 | ;; 712 | *) 713 | if [ "$OPTERR" = 1 ] && [ "${optspec:0:1}" != ":" ]; then 714 | echo "Unknown option --${OPTARG}" >&2 715 | fi 716 | ;; 717 | esac;; 718 | h) 719 | echo "usage: $0 [-v] [--key=] [--secret=] [--hostname=] [--cmd]" >&2 720 | exit 2 721 | ;; 722 | v) 723 | echo "PatrolServer BashScanner $VERSION" >&2 724 | exit 725 | ;; 726 | e) 727 | EMAIL=${OPTARG} 728 | ;; 729 | p) 730 | PASSWORD=${OPTARG} 731 | ;; 732 | n) 733 | HOSTNAME=${OPTARG} 734 | ;; 735 | k) 736 | KEY=${OPTARG} 737 | ;; 738 | s) 739 | SECRET=${OPTARG} 740 | ;; 741 | c) 742 | CMD="true" 743 | ;; 744 | i) 745 | SERVER_ID=${OPTARG} 746 | ;; 747 | b) 748 | BUCKET=${OPTARG} 749 | ;; 750 | t) 751 | MY_HOME=${OPTARG} 752 | ;; 753 | *) 754 | if [ "$OPTERR" != 1 ] || [ "${optspec:0:1}" = ":" ]; then 755 | echo "Non-option argument: '-${OPTARG}'" >&2 756 | fi 757 | ;; 758 | esac 759 | done 760 | } 761 | #!/usr/bin/env bash 762 | 763 | function ComposerSoftware { 764 | local FILES=`locate --database=$LOCATE "composer.lock" 2> /dev/null` 765 | for FILE in $FILES; do 766 | local DIR=`dirname $FILE` 767 | local JSON="$DIR/composer.json" 768 | 769 | local PARENT="Undefined" 770 | if [ -f $JSON ] 771 | then 772 | PARENT=`cat $JSON | json | grep '^\["name"\]' | cut -f2- | sed -e 's/^"//' -e 's/"$//'` 773 | fi 774 | 775 | echo -e "$FILE\t\t$PARENT\t" >> $SOFTWARE 776 | 777 | local JSON_DATA=`cat $FILE | json -bn` 778 | local NAMES=`echo "$JSON_DATA" | grep -E '^\["packages",[0-9]{1,},"name"\]' | cut -f2- | sed -e 's/^"//' -e 's/"$//'` 779 | local VERSIONS=`echo "$JSON_DATA" | grep -E '^\["packages",[0-9]{1,},"version"\]' | cut -f2- | sed -e 's/^"//' -e 's/"$//'` 780 | local COMPSERSOFTWARE=`paste <(echo "$NAMES") <(echo "$VERSIONS")` 781 | for LINE in $COMPSERSOFTWARE; do 782 | NAME=`echo $LINE | cut -f1` 783 | VERSION=`echo $LINE | cut -f2` 784 | 785 | NAME=`Jsonspecialchars $NAME` 786 | VERSION=`Jsonspecialchars $VERSION` 787 | 788 | echo -e "$FILE\t$PARENT\t$NAME\t$VERSION" >> $SOFTWARE 789 | done 790 | done 791 | } 792 | #!/usr/bin/env bash 793 | 794 | function DpkgSoftware { 795 | if `command -v dpkg >/dev/null 2>&1` 796 | then 797 | local SUBSOFTWARE=`dpkg -l 2> /dev/null | grep '^i' | grep -v "lib" | tr -s ' ' | sed 's/ /\t/g'| cut -f2,3` 798 | 799 | for LINE in $SUBSOFTWARE; do 800 | NAME=`echo $LINE | cut -f1` 801 | VERSION=`echo $LINE | cut -f2` 802 | 803 | NAME=`Jsonspecialchars $NAME` 804 | VERSION=`Jsonspecialchars $VERSION` 805 | 806 | echo -e "/\t\t$NAME\t$VERSION" >> $SOFTWARE 807 | done 808 | fi 809 | } 810 | #!/usr/bin/env bash 811 | 812 | function DrupalSoftware { 813 | ALL_MODULES=`locate --database=$LOCATE "*.info" 2> /dev/null` 814 | FILES=`locate --database=$LOCATE "drupal.js" 2> /dev/null` 815 | for FILE in $FILES; do 816 | 817 | # Get root path 818 | local DIR=`dirname $FILE` 819 | local DIR=`cd "$DIR/../"; pwd` 820 | local VERSION_FILE="${DIR}/includes/bootstrap.inc" 821 | 822 | local VERSION="" 823 | if [ -f $VERSION_FILE ] 824 | then 825 | local VERSION=`cat "$VERSION_FILE" | grep "define('VERSION', '[0-9]*\.[0-9]*')" | grep -o "[0-9]*\.[0-9]*" 2> /dev/null` 826 | fi 827 | 828 | if [ "$VERSION" == "" ] 829 | then 830 | local VERSION_FILE="${DIR}/modules/php/php.info" 831 | if [ -f $VERSION_FILE ] 832 | then 833 | local VERSION=`cat "$VERSION_FILE" | grep "version = \"[0-9]*\.[0-9]*\"" | grep -o "[0-9]*\.[0-9]*"` 834 | fi 835 | fi 836 | 837 | echo -e "$DIR\t\tdrupal\t$VERSION" >> $SOFTWARE 838 | 839 | # Get modules 840 | MODULES=`echo "$ALL_MODULES" | grep "^$DIR/sites/all"` 841 | for MODULE in $MODULES; do 842 | local VERSION=`grep "version = ['\"]*[0-9a-z\.\-]*['\"]*" "$MODULE" | grep -o "[0-9][0-9a-z\.\-]*"` 843 | local NAME=`basename $MODULE` 844 | local NAME=${NAME%.*} 845 | 846 | echo -e "$DIR\tdrupal\tdrupal/$NAME\t$VERSION" >> $SOFTWARE 847 | done 848 | 849 | done 850 | } 851 | #!/usr/bin/env bash 852 | 853 | function NpmSoftware { 854 | 855 | POTENTIAL=`locate --database=$LOCATE --regex "node_modules$" | grep -v "node_modules.*node_modules" | grep -v "\.npm" 2> /dev/null` 856 | for DIR in $POTENTIAL; do 857 | 858 | if command -v npm >/dev/null 2>&1 859 | then 860 | VERSION=`npm -v` 861 | echo -e "$DIR\t\tnpm\t$VERSION" >> $SOFTWARE 862 | fi 863 | 864 | for MODULE in $DIR/*/package.json; do 865 | if [[ "$MODULE" != "$DIR/*/package.json" ]] 866 | then 867 | JSON=`cat $MODULE | json` 868 | NAME=`echo "$JSON" | grep '\[\"name\"\]' | cut -f2- | sed -e 's/^"//' -e 's/"$//'` 869 | VERSION=`echo "$JSON" | grep '\[\"version\"\]' | cut -f2- | sed -e 's/^"//' -e 's/"$//'` 870 | 871 | echo -e "$DIR\tnpm\tnpm/$NAME\t$VERSION" >> $SOFTWARE 872 | fi 873 | done 874 | done 875 | 876 | } 877 | #!/usr/bin/env bash 878 | 879 | function WordpressSoftware { 880 | FILES=`locate --database=$LOCATE "wp-settings.php" 2> /dev/null` 881 | for FILE in $FILES; do 882 | 883 | # Get root path 884 | DIR=$(dirname $FILE) 885 | VERSION_FILE="${DIR}/wp-includes/version.php" 886 | 887 | VERSION="" 888 | if [ -f $VERSION_FILE ] 889 | then 890 | VERSION=$(cat "$VERSION_FILE" | grep "\$wp_version = '[0-9]*\.[0-9]*\.[0-9]*'" | grep -o "[0-9]*\.[0-9]*\.[0-9]*" 2> /dev/null) 891 | fi 892 | 893 | echo -e "$DIR\t\twordpress\t$VERSION" >> $SOFTWARE 894 | 895 | # Get modules 896 | MODULES=$(find "$DIR/wp-content/plugins" -mindepth 2 -maxdepth 2 -type f | grep "\.php$" | xargs grep "Plugin Name:" -l) 897 | for MODULE in $MODULES; do 898 | VERSION=$(grep "Version: ['\"]*[0-9a-z\.\-]*['\"]*" "$MODULE" 2> /dev/null | grep -o "[0-9][0-9a-z\.\-]*") 899 | NAME=$(dirname $MODULE | xargs basename) 900 | echo -e "$DIR\twordpress\twordpress:$NAME\t$VERSION" >> $SOFTWARE 901 | done 902 | 903 | MODULES=$(find "$DIR/wp-content/plugins" -maxdepth 1 -type f | grep "\.php$" | xargs grep "Plugin Name:" -l) 904 | for MODULE in $MODULES; do 905 | VERSION=$(grep "Version: ['\"]*[0-9a-z\.\-]*['\"]*" "$MODULE" 2> /dev/null | grep -o "[0-9][0-9a-z\.\-]*") 906 | NAME=$(basename $MODULE) 907 | NAME="${NAME%.*}" 908 | echo -e "$DIR\twordpress\twordpress:$NAME\t$VERSION" >> $SOFTWARE 909 | done 910 | done 911 | } 912 | #!/usr/bin/env bash 913 | 914 | function PhpmyadminSoftware { 915 | FILES=`locate --database=$LOCATE "phpmyadmin.css.php" 2> /dev/null` 916 | for FILE in $FILES; do 917 | 918 | # Get root path 919 | local DIR=`dirname $FILE` 920 | local VERSION_FILE="${DIR}/libraries/Config.php" 921 | 922 | local VERSION="" 923 | if [ -f $VERSION_FILE ] 924 | then 925 | local VERSION=`cat "$VERSION_FILE" | grep "\\$this->set('PMA_VERSION', '[0-9.]*')" | grep -o "[0-9.]*" 2> /dev/null` 926 | fi 927 | 928 | if [ "$VERSION" == "" ] 929 | then 930 | local VERSION_FILE="${DIR}/libraries/Config.class.php" 931 | if [ -f $VERSION_FILE ] 932 | then 933 | local VERSION=`cat "$VERSION_FILE" | grep "\\$this->set('PMA_VERSION', '[0-9.]*')" | grep -o "[0-9.]*" 2> /dev/null` 934 | fi 935 | fi 936 | 937 | echo -e "$DIR\t\tphpmyadmin\t$VERSION" >> $SOFTWARE 938 | 939 | done 940 | } 941 | #!/usr/bin/env bash 942 | 943 | function JoomlaSoftware { 944 | FILES=`locate --database=$LOCATE "authentication/joomla/joomla.xml" | sort | uniq 2> /dev/null` 945 | for FILE in $FILES; do 946 | 947 | # Get root path 948 | local DIR=`dirname $FILE` 949 | local DIR=`cd "$DIR/../../../"; pwd` 950 | local VERSION_FILE="${DIR}/libraries/cms/version/version.php" 951 | 952 | local VERSION="" 953 | if [ -f $VERSION_FILE ] 954 | then 955 | 956 | local VERSION_BRANCH=`cat "$VERSION_FILE" | grep "RELEASE = '[0-9.]*';" | grep -o "[0-9.]*" 2> /dev/null` 957 | local VERSION_SECURITY=`cat "$VERSION_FILE" | grep "DEV_LEVEL = '[0-9]*';" | grep -o "[0-9.]*" 2> /dev/null` 958 | 959 | if [[ "$VERSION_BRANCH" != "" && "$VERSION_SECURITY" != "" ]] 960 | then 961 | local VERSION="$VERSION_BRANCH.$VERSION_SECURITY" 962 | fi 963 | fi 964 | 965 | echo -e "$DIR\t\tjoomla\t$VERSION" >> $SOFTWARE 966 | 967 | done 968 | } 969 | #!/usr/bin/env bash 970 | 971 | function MagentoSoftware { 972 | FILES=`locate --database=$LOCATE "app/Mage.php" 2> /dev/null` 973 | for FILE in $FILES; do 974 | 975 | # Get root path 976 | local DIR=`dirname $FILE` 977 | local DIR=`cd "$DIR/../"; pwd` 978 | local VERSION_FILE="${DIR}/app/Mage.php" 979 | 980 | local VERSION="" 981 | if [ -f $VERSION_FILE ] 982 | then 983 | 984 | local VERSION_MAJOR=`cat "$VERSION_FILE" | grep "'major' *=> '[0-9.]*'" | grep -o "[0-9.]*" 2> /dev/null` 985 | local VERSION_MINOR=`cat "$VERSION_FILE" | grep "'minor' *=> '[0-9]*'" | grep -o "[0-9.]*" 2> /dev/null` 986 | local VERSION_REVISION=`cat "$VERSION_FILE" | grep "'revision' *=> '[0-9]*'" | grep -o "[0-9.]*" 2> /dev/null` 987 | local VERSION_PATCH=`cat "$VERSION_FILE" | grep "'patch' *=> '[0-9]*'" | grep -o "[0-9.]*" 2> /dev/null` 988 | 989 | echo $VERSION_MAJOR 990 | 991 | if [[ "$VERSION_MAJOR" != "" && "$VERSION_MINOR" != "" && "$VERSION_REVISION" != "" && "$VERSION_PATCH" != "" ]] 992 | then 993 | local VERSION="$VERSION_MAJOR.$VERSION_MINOR.$VERSION_REVISION.$VERSION_PATCH" 994 | fi 995 | fi 996 | 997 | echo -e "$DIR\t\tmagentoCommerce\t$VERSION" >> $SOFTWARE 998 | 999 | done 1000 | } 1001 | 1002 | VERSION="1.0.0" 1003 | EMAIL="" 1004 | PASSWORD="" 1005 | HOSTNAME="" 1006 | KEY="" 1007 | SECRET="" 1008 | CMD="false" 1009 | SERVER_ID="" 1010 | BUCKET="BashScanner" 1011 | LOCATE=$(mktemp) 1012 | CRON="ask" 1013 | 1014 | function Start { 1015 | SetEnv 1016 | EnvFile 1017 | Args "$@" 1018 | 1019 | if [ "$CMD" == "false" ] 1020 | then 1021 | echo "> Hi $USER," 1022 | echo "> PatrolServer.com at your service. " 1023 | echo "> I'm starting..." 1024 | echo "" 1025 | fi 1026 | 1027 | DetermineHostname 1028 | if [ "$KEY" == "" ] || [ "$SECRET" == "" ] 1029 | then 1030 | Account 1031 | fi 1032 | 1033 | DetermineServer 1034 | Scan 1035 | 1036 | if [ "$CMD" == "false" ] 1037 | then 1038 | if [[ "$CRON" == "ask" ]] 1039 | then 1040 | Output 1041 | fi 1042 | 1043 | Cronjob 1044 | fi 1045 | 1046 | echo "> Have a nice day!" 1047 | } 1048 | 1049 | function Login { 1050 | if [ "$CMD" == "false" ] && [ "$EMAIL" == "" ] && [ "$PASSWORD" == "" ] 1051 | then 1052 | for I in 1 2 3 1053 | do 1054 | echo -en "\tYour email: " 1055 | read -r EMAIL 1056 | echo -en "\tYour password: " 1057 | read -rs PASSWORD 1058 | echo ""; 1059 | 1060 | LOGIN_RET=($(ApiUserLogin "$EMAIL" "$PASSWORD")) 1061 | 1062 | local LOGIN_ERROR=${LOGIN_RET[0]} 1063 | local LOGIN_USER=${LOGIN_RET[1]} 1064 | local LOGIN_KEY=${LOGIN_RET[2]} 1065 | local LOGIN_SECRET=${LOGIN_RET[3]} 1066 | 1067 | if [ "$LOGIN_ERROR" == "false" ] 1068 | then 1069 | StoreKeySecret "$LOGIN_KEY" "$LOGIN_SECRET" 1070 | return 1071 | elif [ "$LOGIN_ERROR" == "too_many_failed_attempts" ] 1072 | then 1073 | echo "> Your login was blocked for security issues, please try again in 10 min." >&2 1074 | exit 77 1075 | elif [ "$LOGIN_ERROR" == "different_country" ] 1076 | then 1077 | echo "> Your login is temporarily blocked for security measurements. Check your email for further instructions." >&2 1078 | exit 77 1079 | else 1080 | echo "> Wrong email and/or password! Try again." >&2 1081 | fi 1082 | done 1083 | echo "> Invalid login"; 1084 | 1085 | else 1086 | if [ "$EMAIL" == "" ] 1087 | then 1088 | echo "Specify login credentials." >&2 1089 | exit 77 1090 | fi 1091 | 1092 | if [ "$PASSWORD" == "" ] 1093 | then 1094 | echo "Specify login credentials." >&2 1095 | exit 77 1096 | fi 1097 | 1098 | LOGIN_RET=($(ApiUserLogin "$EMAIL" "$PASSWORD")) 1099 | 1100 | local LOGIN_ERROR=${LOGIN_RET[0]} 1101 | local LOGIN_USER=${LOGIN_RET[1]} 1102 | local LOGIN_KEY=${LOGIN_RET[2]} 1103 | local LOGIN_SECRET=${LOGIN_RET[3]} 1104 | 1105 | if [ "$LOGIN_ERROR" == "false" ] 1106 | then 1107 | StoreKeySecret "$LOGIN_KEY" "$LOGIN_SECRET" 1108 | return 1109 | elif [ "$LOGIN_ERROR" == "too_many_failed_attempts" ] 1110 | then 1111 | echo "> Your login was blocked for security issues, please try again in 10 min." >&2 1112 | exit 77 1113 | elif [ "$LOGIN_ERROR" == "different_country" ] 1114 | then 1115 | echo "> Your login is temporarily blocked for security measurements. Check your email for further instructions." >&2 1116 | exit 77 1117 | else 1118 | echo "> Invalid login credentials." >&2 1119 | exit 77 1120 | fi 1121 | fi 1122 | 1123 | exit 77; 1124 | } 1125 | 1126 | function StoreKeySecret { 1127 | KEY="$1" 1128 | SECRET="$2" 1129 | 1130 | if [ "$KEY" == "false" ] 1131 | then 1132 | echo "> Internal error, could not get key/secret combo." >&2 1133 | exit 77 1134 | fi 1135 | 1136 | if [ "$SECRET" == "false" ] 1137 | then 1138 | echo "> Internal error, could not get key/secret combo." >&2 1139 | exit 77 1140 | fi 1141 | } 1142 | 1143 | function Register { 1144 | REGISTER_RET=($(ApiUserRegister "$EMAIL" "$PASSWORD")) 1145 | 1146 | local REGISTER_ERROR=${REGISTER_RET[0]} 1147 | local REGISTER_USER=${REGISTER_RET[1]} 1148 | local REGISTER_KEY=${REGISTER_RET[2]} 1149 | local REGISTER_SECRET=${REGISTER_RET[3]} 1150 | 1151 | if [ "$REGISTER_ERROR" == "false" ] 1152 | then 1153 | echo "success" 1154 | echo "${REGISTER_KEY:-false}" 1155 | echo "${REGISTER_SECRET:-false}" 1156 | return 1157 | else 1158 | if [[ "$REGISTER_ERROR" =~ "The email has already been taken" ]] 1159 | then 1160 | echo "email" 1161 | return 1162 | fi 1163 | 1164 | echo "> Unexpected error occured." >&2 1165 | exit 77; 1166 | fi 1167 | } 1168 | 1169 | function TestHostname { 1170 | OPEN_PORT_53=$(echo "quit" | timeout 1 telnet 8.8.8.8 53 2> /dev/null | grep "Escape character is") 1171 | if [[ "$OPEN_PORT_53" != "" ]] && command -v dig >/dev/null 2>&1 1172 | then 1173 | EXTERNAL_IP=$(dig +time=1 +tries=1 +retry=1 +short myip.opendns.com @resolver1.opendns.com | tail -n1) 1174 | IP=$(dig @8.8.8.8 +short $HOSTNAME | tail -n1) 1175 | else 1176 | 1177 | if ! command -v host >/dev/null 2>&1 && command -v yum >/dev/null 2>&1 1178 | then 1179 | echo "This script needs the bind utils package, please install: yum install bind-utils" 1180 | exit 77 1181 | fi 1182 | 1183 | EXTERNAL_IP=$(wget -qO- ipv4.icanhazip.com) 1184 | IP=$(host "$HOSTNAME" | grep -v 'alias' | grep -v 'mail' | cut -d' ' -f4 | head -n1) 1185 | fi 1186 | } 1187 | 1188 | function Hostname { 1189 | 1190 | if [[ "$HOSTNAME" == "" ]] 1191 | then 1192 | HOSTNAME=$(hostname -f 2> /dev/null) 1193 | fi 1194 | 1195 | if [[ "$HOSTNAME" != "" ]] 1196 | then 1197 | TestHostname 1198 | fi 1199 | 1200 | if [[ "$CMD" != "false" ]] 1201 | then 1202 | if [ "$IP" == "" ] 1203 | then 1204 | echo "Hostname not found. (Please enter with command)" >&2 1205 | exit 77; 1206 | elif [[ "$IP" != "$EXTERNAL_IP" ]] 1207 | then 1208 | echo "Hostname doesn't resolve to external IP of this server." >&2 1209 | exit 77; 1210 | fi 1211 | fi 1212 | 1213 | for I in 1 2 3 1214 | do 1215 | 1216 | if [ "$IP" == "" ] 1217 | then 1218 | echo "> Could not determine your hostname." 1219 | echo -en "\tPlease enter the hostname of this server: " 1220 | read -r HOSTNAME 1221 | echo ""; 1222 | elif [[ "$IP" != "$EXTERNAL_IP" ]] 1223 | then 1224 | echo "> Your hostname ($HOSTNAME) doesn't resolve to this IP." 1225 | echo -en "\tPlease enter the hostname that resolved to this ip: " 1226 | read -r HOSTNAME 1227 | echo ""; 1228 | fi 1229 | 1230 | TestHostname 1231 | 1232 | if [[ "$IP" != "" ]] && [ "$IP" == "$EXTERNAL_IP" ] 1233 | then 1234 | return; 1235 | fi 1236 | done 1237 | 1238 | echo "> Could not determine hostname." >&2 1239 | exit 77; 1240 | } 1241 | 1242 | function DetermineHostname { 1243 | 1244 | Hostname 1245 | 1246 | if [ "$EMAIL" == "" ] && [ "$PASSWORD" == "" ] && [ "$KEY" == "" ] && [ "$SECRET" == "" ] 1247 | then 1248 | # Check if the host is already in our DB 1249 | # Please note! You can remove this check, but our policy doesn't change. 1250 | # Only one free server per domain is allowed. 1251 | # We actively check for these criteria 1252 | SERVER_EXISTS_RET=($(ApiServerExists "$HOSTNAME")) 1253 | 1254 | SERVER_EXISTS_ERROR=${SERVER_EXISTS_RET[0]} 1255 | SERVER_EXISTS=${SERVER_EXISTS_RET[1]} 1256 | 1257 | if [ "$SERVER_EXISTS_ERROR" == "false" ] 1258 | then 1259 | return 1260 | 1261 | # There is already an host from this user. 1262 | elif [ "$SERVER_EXISTS_ERROR" == "15" ] 1263 | then 1264 | echo "> An account was already created for this host or a subdomain of this host. Please login into your patrolserver.com account or add the key/secret when calling this command." >&2 1265 | Login 1266 | 1267 | # Hostname could not be found. 1268 | elif [ "$SERVER_EXISTS_ERROR" == "83" ] 1269 | then 1270 | echo "> Your hostname ($HOSTNAME) doesn't resolve to this IP ($IP)." >&2 1271 | exit 77; 1272 | # Undefined error occured. 1273 | else 1274 | echo "> Unexpected error occured." >&2 1275 | exit 77; 1276 | fi 1277 | fi 1278 | } 1279 | 1280 | function Account { 1281 | 1282 | if [[ "$EMAIL" != "" ]] && [[ "$PASSWORD" != "" ]] 1283 | then 1284 | Login 1285 | return; 1286 | fi 1287 | 1288 | if [[ "$CMD" != "false" ]] 1289 | then 1290 | YN="n" 1291 | else 1292 | echo "> You can use this tool 5 times without account." 1293 | 1294 | YN="..." 1295 | while [[ "$YN" != "n" ]] && [[ $YN != "y" ]]; do 1296 | read -rp "> Do you want to create an account (y/n)? " YN 1297 | done 1298 | fi 1299 | 1300 | if [ "$YN" == "n" ] 1301 | then 1302 | # Create account when no account exists. 1303 | EMAIL="tmp-$(Random)@$HOSTNAME" 1304 | PASSWORD=$(Random) 1305 | 1306 | REGISTER_RET=($(Register "$EMAIL" "$PASSWORD")) 1307 | 1308 | REGISTER_RET_STATUS=${REGISTER_RET[0]} 1309 | 1310 | if [ "$REGISTER_RET_STATUS" != "success" ] 1311 | then 1312 | echo "> Internal error, could not create temporary account" 1313 | exit 77 1314 | else 1315 | REGISTER_RET_KEY=${REGISTER_RET[1]} 1316 | REGISTER_RET_SECRET=${REGISTER_RET[2]} 1317 | StoreKeySecret "$REGISTER_RET_KEY" "$REGISTER_RET_SECRET" 1318 | fi 1319 | 1320 | else 1321 | for I in 1 2 3 1322 | do 1323 | # Ask what account should be created. 1324 | echo -en "\tYour email: " 1325 | read -r EMAIL 1326 | echo -en "\tNew password: " 1327 | read -rs PASSWORD 1328 | echo "" 1329 | echo -en "\tRetype your password: " 1330 | read -rs PASSWORD2 1331 | echo ""; 1332 | 1333 | REGISTER_RET_STATUS="" 1334 | if [ "$PASSWORD" == "$PASSWORD2" ] && [ ${#PASSWORD} -ge 7 ] 1335 | then 1336 | REGISTER_RET=($(Register "$EMAIL" "$PASSWORD")) 1337 | 1338 | REGISTER_RET_STATUS=${REGISTER_RET[0]} 1339 | 1340 | if [ "$REGISTER_RET_STATUS" == "success" ] 1341 | then 1342 | REGISTER_RET_KEY=${REGISTER_RET[1]} 1343 | REGISTER_RET_SECRET=${REGISTER_RET[2]} 1344 | StoreKeySecret "$REGISTER_RET_KEY" "$REGISTER_RET_SECRET" 1345 | return 1346 | fi 1347 | fi 1348 | 1349 | if [ ${#PASSWORD} -le 6 ] 1350 | then 1351 | echo "> Password should minimal contain 6 characters" >&2 1352 | fi 1353 | 1354 | if [ "$PASSWORD" != "$PASSWORD2" ] 1355 | then 1356 | echo "> The password confirmation does not match" >&2 1357 | fi 1358 | 1359 | if [ "$REGISTER_RET" == "email" ] 1360 | then 1361 | echo "> Account already exists with this account. Use command with email and password parameters." >&2 1362 | exit 77 1363 | fi 1364 | done 1365 | 1366 | echo "> Account could not be created." >&2 1367 | exit 77 1368 | fi 1369 | } 1370 | 1371 | function DetermineServer { 1372 | SERVERS_RET=($(ApiServers "$KEY" "$SECRET")) 1373 | SERVERS_ERRORS=${SERVERS_RET[0]} 1374 | SERVERS=${SERVERS_RET[1]} 1375 | 1376 | HAS_SERVER=$(echo "$SERVERS" | json | grep -P '^\[[0-9]*,"name"\]\t"'"$HOSTNAME"'"') 1377 | 1378 | # Check if the server has already been created 1379 | if [ "$HAS_SERVER" == "" ] 1380 | then 1381 | 1382 | SERVER_CREATE_RET=($(ApiServerCreate "$KEY" "$SECRET" "$HOSTNAME")) 1383 | SERVER_CREATE_ERRORS=${SERVER_CREATE_RET[0]} 1384 | 1385 | if [[ "$SERVER_CREATE_ERRORS" =~ "You exceeded the maximum allowed server slots" ]] 1386 | then 1387 | echo "> You exceeded the maximum allowed servers on your account, please login onto http://patrolserver.com and upgrade your account"; 1388 | exit 77; 1389 | fi 1390 | 1391 | SERVERS_RET=($(ApiServers "$KEY" "$SECRET")) 1392 | SERVERS_ERRORS=${SERVERS_RET[0]} 1393 | SERVERS=${SERVERS_RET[1]} 1394 | 1395 | HAS_SERVER=$(echo "$SERVERS" | json | grep -P '^\[[0-9]*,"name"\]\t"'"$HOSTNAME"'"' -o) 1396 | fi 1397 | 1398 | if [ "$HAS_SERVER" == "" ] 1399 | then 1400 | echo "> Internal error, could not create the server online" >&2 1401 | exit 77 1402 | fi 1403 | 1404 | SERVER_ARRAY_ID=$(echo "$HAS_SERVER" | grep -P '^\[[0-9]+' -o | grep -P '[0-9]+' -o) 1405 | SERVER_ID=$(echo "$SERVERS" | json | grep "^\[$SERVER_ARRAY_ID,\"id\"\]" | cut -f2-) 1406 | SERVER_VERIFIED=$(echo "$SERVERS" | json | grep "^\[$SERVER_ARRAY_ID,\"verified\"\]" | cut -f2-) 1407 | 1408 | # Check if the server has already been verified 1409 | if [ "$SERVER_VERIFIED" == "false" ] 1410 | then 1411 | SERVER_TOKEN_RET=($(ApiServerToken "$HOSTNAME")) 1412 | SERVER_TOKEN_ERRORS=${SERVER_TOKEN_RET[0]} 1413 | SERVER_TOKEN=${SERVER_TOKEN_RET[1]} 1414 | 1415 | SERVER_VERIFY_RET=($(ApiVerifyServer "$KEY" "$SECRET" "$SERVER_ID" "$SERVER_TOKEN")) 1416 | SERVER_TOKEN_ERRORS=${SERVER_VERIFY_RET[0]} 1417 | fi 1418 | } 1419 | 1420 | function Scan { 1421 | if [[ "$CMD" == "false" ]] 1422 | then 1423 | echo "> Searching for packages, can take some time..." 1424 | fi 1425 | 1426 | SOFTWARE=$(mktemp) 1427 | 1428 | # Update db 1429 | if ! command -v updatedb >/dev/null 2>&1 && command -v yum >/dev/null 2>&1 1430 | then 1431 | echo "This script needs the mlocate package, please install: yum install mlocate" 1432 | exit 77 1433 | fi 1434 | updatedb -o "$LOCATE" -U / --require-visibility 0 2> /dev/null 1435 | 1436 | # Do all scanners 1437 | ComposerSoftware 1438 | DpkgSoftware 1439 | DrupalSoftware 1440 | NpmSoftware 1441 | WordpressSoftware 1442 | PhpmyadminSoftware 1443 | JoomlaSoftware 1444 | MagentoSoftware 1445 | 1446 | if [[ "$CMD" == "false" ]] 1447 | then 1448 | echo "> Scanning for newest releases and exploits, can take serveral minutes..." 1449 | echo "> Take a coffee break ;) " 1450 | fi 1451 | 1452 | SOFTWARE_PUST_RET=($(ApiServerPush "$KEY" "$SECRET" "$SERVER_ID" "$BUCKET" "$SOFTWARE")) 1453 | SOFTWARE_PUST_ERRORS=${SOFTWARE_PUST_RET[0]} 1454 | 1455 | if [ "$SOFTWARE_PUST_ERRORS" != "false" ] 1456 | then 1457 | echo "> Could not upload software data, please give our support team a call with the following details" >&2 1458 | echo "$SOFTWARE_PUST_ERRORS" >&2 1459 | exit 77 1460 | fi 1461 | 1462 | SERVER_SCAN_RET=($(ApiServerScan "$KEY" "$SECRET" "$SERVER_ID")) 1463 | SERVER_SCAN_ERRORS=${SERVER_SCAN_RET[0]} 1464 | 1465 | if [ "$SERVER_SCAN_ERRORS" != "false" ] 1466 | then 1467 | echo "> Could not send scan command, please give our support team a call with the following details" >&2 1468 | echo "$SERVER_SCAN_ERRORS" >&2 1469 | exit 77 1470 | fi 1471 | 1472 | SERVER_SCANNING="true" 1473 | while [ "$SERVER_SCANNING" == "true" ] ; do 1474 | SERVER_SCANNING_RET=($(ApiServerIsScanning "$KEY" "$SECRET" "$SERVER_ID")) 1475 | SERVER_SCANNING_ERRORS=${SERVER_SCANNING_RET[0]} 1476 | SERVER_SCANNING=${SERVER_SCANNING_RET[1]} 1477 | 1478 | if [[ "$CMD" == "false" ]] 1479 | then 1480 | echo -n "." 1481 | fi 1482 | 1483 | sleep 5 1484 | done 1485 | 1486 | if [[ "$CMD" == "false" ]] 1487 | then 1488 | echo ""; 1489 | fi 1490 | } 1491 | 1492 | function Output { 1493 | if [[ "$CMD" == "false" ]] 1494 | then 1495 | echo "> Software versions has been retrieved (Solutions for the exploits can be seen on our web interface):" 1496 | fi 1497 | 1498 | SOFTWARE_RET=($(ApiSoftware "$KEY" "$SECRET" "$SERVER_ID")) 1499 | SOFTWARE_ERRORS=${SOFTWARE_RET[0]} 1500 | SOFTWARE_JSON=${SOFTWARE_RET[1]} 1501 | 1502 | SOFTWARE=$(echo "$SOFTWARE_JSON" | json | grep -P "^\[[0-9]{1,}\]" | cut -f2-) 1503 | if [ "$SOFTWARE" == "" ] 1504 | then 1505 | echo -e "\tStrangely, No packages were found..." 1506 | fi 1507 | 1508 | CORE_SOFTWARE=$(echo "$SOFTWARE" | grep '"parent":null' | grep '"location":"\\\/"') 1509 | OutputBlock "$SOFTWARE" "$CORE_SOFTWARE" 1510 | 1511 | CORE_SOFTWARE=$(echo "$SOFTWARE" | grep "\"parent\":null" | grep -v '"location":"\\\/"') 1512 | OutputBlock "$SOFTWARE" "$CORE_SOFTWARE" 1513 | } 1514 | 1515 | function OutputBlock { 1516 | SOFTWARE="$1" 1517 | BLOCK_SOFTWARE="$2" 1518 | 1519 | PREV_LOCATION="---" 1520 | for LINE in $BLOCK_SOFTWARE; do 1521 | 1522 | LINE=$(echo "$LINE" | json) 1523 | CANONICAL_NAME=$(echo "$LINE" | grep '^\["canonical_name"\]' | cut -f2- | sed -e 's/^"//' -e 's/"$//') 1524 | CANONICAL_NAME_GREP=$(echo "$CANONICAL_NAME" | sed -e 's/[]\/$*.^|[]/\\&/g') 1525 | LOCATION=$(echo "$LINE" | grep '^\["location"\]' | cut -f2- | sed -e 's/^"//' -e 's/"$//') 1526 | LOCATION_GREP=$(echo "$LOCATION" | sed -e 's/[]\/$*.^|[]/\\&/g') 1527 | 1528 | # Print out the location when it has changed 1529 | if [[ "$PREV_LOCATION" != "$LOCATION" ]] && [[ "$LOCATION" != '\/' ]] 1530 | then 1531 | echo ""; 1532 | echo -ne "\e[0;90m" 1533 | echo -n "$LOCATION" 1534 | echo -e "\e[0m" 1535 | echo ""; 1536 | 1537 | PREV_LOCATION="$LOCATION" 1538 | fi 1539 | 1540 | OutputLine "$LINE" 1541 | 1542 | # Print submodules 1543 | for LINE in $(echo "$SOFTWARE" | grep '"parent":"'"$CANONICAL_NAME_GREP"'"' | grep "location\":\"$LOCATION_GREP"); do 1544 | LINE=$(echo "$LINE" | json) 1545 | 1546 | echo -en "\t" 1547 | OutputLine "$LINE" 1548 | done 1549 | done 1550 | } 1551 | 1552 | function OutputLine { 1553 | LINE="$1" 1554 | 1555 | NAME=$(echo "$LINE" | grep '^\["name"\]' | cut -f2- | sed -e 's/^"//' -e 's/"$//') 1556 | VERSION=$(echo "$LINE" | grep '^\["version"\]' | cut -f2- | sed -e 's/^"//' -e 's/"$//') 1557 | VERSIONS=$(echo "$LINE" | grep '^\["versions"\]' | cut -f2-) 1558 | NEW_VERSION=$(echo "$LINE" | grep '^\["newest_bugfix_release"\]' | cut -f2- | sed -e 's/^"//' -e 's/"$//') 1559 | SUPPORTED=$(echo "$LINE" | grep '^\["supported"\]' | cut -f2- | sed -e 's/^"//' -e 's/"$//') 1560 | EXPLOITS=$(echo "$LINE" | grep '^\["exploits"\]' | cut -f2- | json | grep '^\[[0-9]*,"risk"\]' | cut -f2-) 1561 | LOCATION=$(echo "$LINE" | grep '^\["location"\]' | cut -f2- | sed -e 's/^"//' -e 's/"$//') 1562 | 1563 | if [ "$VERSIONS" != "" ] 1564 | then 1565 | VERSION=$(echo "$VERSIONS" | json | grep '^\[0]' | cut -f2- | sed -e 's/^"//' -e 's/"$//') 1566 | VERSION="<=$VERSION" 1567 | fi 1568 | 1569 | echo -ne "\t$NAME: " 1570 | 1571 | # Print current version 1572 | if [ "$VERSION" == "" ] 1573 | then 1574 | echo -n "version not detected" 1575 | elif [ "$SUPPORTED" == "yes" ] 1576 | then 1577 | echo -ne "\e[0;32m" 1578 | echo -n "$VERSION" 1579 | echo -ne "\e[0m" 1580 | elif [[ "$NEW_VERSION" != "" ]] 1581 | then 1582 | echo -ne "\e[0;33m" 1583 | echo -n "$VERSION" 1584 | echo -ne "\e[0m" 1585 | echo -n ", update to " 1586 | echo -ne "\e[0;32m" 1587 | echo -n "$NEW_VERSION" 1588 | echo -ne "\e[0m" 1589 | else 1590 | echo -ne "\e[0;31m" 1591 | echo -n "$VERSION" 1592 | echo -ne "\e[0m" 1593 | echo -n ", not supported anymore" 1594 | fi 1595 | 1596 | # Check exploits 1597 | COUNT_EXPLOITS=0 1598 | for EXPLOIT in $EXPLOITS; do 1599 | IS_BIGGER=$(echo "$EXPLOIT" | grep "^[5-9]") 1600 | if [ "$IS_BIGGER" == "1" ] 1601 | then 1602 | COUNT_EXPLOITS=$((COUNT_EXPLOITS+1)) 1603 | fi 1604 | done 1605 | 1606 | if [ "$COUNT_EXPLOITS" != "0" ] 1607 | then 1608 | echo -ne "\e[0;31m" 1609 | echo -n " ($COUNT_EXPLOITS exploits)" 1610 | echo -ne "\e[0m" 1611 | fi 1612 | 1613 | echo "" 1614 | } 1615 | 1616 | function Cronjob { 1617 | 1618 | # User has already a cronjob defined 1619 | HAS_CRONTAB=$(crontab -l 2> /dev/null | grep "patrolserver") 1620 | if [ "$HAS_CRONTAB" != "" ] 1621 | then 1622 | return 1623 | fi 1624 | 1625 | if [[ "$CMD" != "false" ]] 1626 | then 1627 | return 1628 | fi 1629 | 1630 | if [[ "$CRON" != "ask" ]] && [[ "$CRON" != "true" ]] 1631 | then 1632 | return 1633 | fi 1634 | 1635 | # Check if user want a cronjob 1636 | YN="..." 1637 | if [[ "$CRON" == "true" ]] 1638 | then 1639 | YN="y" 1640 | fi 1641 | while [[ "$YN" != "n" ]] && [[ $YN != "y" ]]; do 1642 | read -rp "> It is advisable to check your server daily, should we set a cronjob (y/n)? " YN 1643 | done 1644 | 1645 | if [ "$YN" == "n" ] 1646 | then 1647 | if [[ "$EMAIL" == tmp\-* ]] 1648 | then 1649 | ApiUserRemove "$KEY" "$SECRET" 1650 | fi 1651 | 1652 | else 1653 | 1654 | if [[ "$EMAIL" == tmp\-* ]] 1655 | then 1656 | echo -n "> What is your email address to send reports to? " 1657 | read -r REAL_EMAIL 1658 | echo "" 1659 | 1660 | CHANGE_EMAIL_RET=($(ApiUserChange "$KEY" "$SECRET" "$REAL_EMAIL")) 1661 | CHANGE_EMAIL_ERROR=${CHANGE_EMAIL_RET[0]} 1662 | CHANGE_EMAIL_USER=${CHANGE_EMAIL_RET[1]} 1663 | 1664 | if [ "$CHANGE_EMAIL_ERROR" != "false" ] 1665 | then 1666 | if [[ "$CHANGE_EMAIL_ERROR" == "82" ]] 1667 | then 1668 | echo "> There is already an account with this email address, use this tool with email and password parameters." >&2 1669 | exit 77 1670 | fi 1671 | 1672 | echo "> Internal error when changing username" >&2 1673 | exit 77 1674 | fi 1675 | fi 1676 | 1677 | mkdir ~/.patrolserver 2> /dev/null 1678 | echo -e "HOSTNAME=$HOSTNAME\nKEY=$KEY\nSECRET=$SECRET" > ~/.patrolserver/env 1679 | cat "$LOCATE" > ~/.patrolserver/locate.db 1680 | wget -O ~/.patrolserver/patrolserver "https://raw.githubusercontent.com/PatrolServer/bashScanner/master/patrolserver" 2&>1 /dev/null 1681 | chmod +x ~/.patrolserver/patrolserver 1682 | 1683 | # Set cronjob 1684 | CRON_TMP=$(mktemp) 1685 | crontab -l 2> /dev/null > "$CRON_TMP" 1686 | CRON_HOUR=$((RANDOM % 24)) 1687 | CRON_MINUTE=$((RANDOM % 60)) 1688 | echo "$CRON_MINUTE $CRON_HOUR * * * /bin/bash $HOME/.patrolserver/patrolserver --cmd --key=\"$KEY\" --secret=\"$SECRET\" --hostname=\"$HOSTNAME\" > /dev/null" >> $CRON_TMP 1689 | crontab "$CRON_TMP" 1690 | 1691 | echo "> cronjob was set." 1692 | 1693 | if [[ "$EMAIL" == tmp\-* ]] 1694 | then 1695 | echo "> Your login on patrolserver.com:" 1696 | echo -e "\tlogin: $REAL_EMAIL" 1697 | echo -e "\tpassword: $PASSWORD" 1698 | fi 1699 | 1700 | echo "> A environment file was created on ~/.patrolserver, so you don't have to call it anymore with the key and secret" 1701 | fi 1702 | } 1703 | 1704 | Start "$@" 1705 | --------------------------------------------------------------------------------