├── 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 |
3 |
4 | Bash Scanner is a fast and reliable way to scan your server for outdated software and potential exploits.
5 |
6 | 
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 |
44 |
45 | [](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 |
--------------------------------------------------------------------------------