#!/usr/bin/env bash # no_buster - a tool to check if someone uses a 'default' wordlist to gain access to web directorys that are not visible by default # author: Mike Wollmann aka. jak1 # requires: root access # iptables # # TODO/WIP: # add: checks if apache has this path, so we don't catch real paths in our routin and accidentaly ban someone for using the webservices the right way X_X! # --> configuration section max_rows=2; # when get 5 (default) hits in a row the ip gets blocked dir_files="./wordlists/dirb/common.txt ./wordlists/dirbuster/apache-user-enum-1.0.txt"; # dont add more then 10 files! every file has 10 words to check for! 10*10 = 100 words per log entry! every 60 secounds per default. check_first_x_lines=10; wl_file_opt="head -n${check_first_x_lines}"; check_every_sec=60; # time between checks check_log_lines=10; # 10 by default, can be increased, but will cause some cpu usage the higher it is. log_file_opt="tail -n${check_log_lines}"; # using tail access_log="/var/log/apache2/access.log"; access_log="./access.log"; # remove this line later! no_buster_log_dir="/var/log/no_buster/"; no_buster_log_file="no_buster"; ip_tables="/sbin/iptables"; ip_tables_chain="no_buster"; # <-- configuration section if [[ `whoami` != "root" ]]; then echo "you need to be root for:"; echo " - reading ${access_log}"; echo " - reading and writing to iptables"; echo " - writing no_buster.log to ${no_buster_log_dir}"; exit 1 fi n=`which iptables` if [[ $? != 0 ]]; then echo "iptables is required!"; fi unset $n function cac_chain(){ # check and create chain check=`${ip_tables} -L ${ip_tables_chain} -v -n`; if [[ $? != 0 ]]; then `${ip_tables} -N ${ip_tables_chain}`; `${ip_tables} -A OUTPUT -j ${ip_tables_chain}`; `${ip_tables} -A ${ip_tables_chain} -p tcp --dport 80 -j ACCEPT`; `${ip_tables} -A ${ip_tables_chain} -p tcp --dport 443 -j ACCEPT`; echo "chain '${ip_tables_chain}' created"; fi } function logging(){ if [[ ! -d "${no_buster_log_dir}" ]]; then mkdir -p "${no_buster_log_dir}"; fi timestamp=$(date +'%d.%m.%Y - %r'); filedate=$(date +'%m-%Y'); echo -e "${timestamp}: ${1}" >> "${no_buster_log_dir}${no_buster_log_file}-${filedate}.log"; } function block_ip(){ # dont block loopback! if [[ "${1}" != "127.0.0.1" ]]; then check=`${ip_tables} -L ${ip_tables_chain} -v -n | grep "${1}"`; if [[ ${?} != 0 ]]; then # http:// (should not even get used anymore) e1=`${ip_tables} -A ${ip_tables_chain} -p tcp --dport 80 -s "${1}" -j DROP`; # https:// e2=`${ip_tables} -A ${ip_tables_chain} -p tcp --dport 443 -s "${1}" -j DROP`; logging "${1} got blocked for ports: 80 & 443\n ${2}"; return 1 else # because of log spam ^^' cause of tail -n{N} it well get removed in the next {N} hits via p80/p443 #logging "WARN: $1 got allready blocked" #logging "${check}" return 0 fi fi return 0 } function wl_check(){ n_ip=""; last_lines=`${log_file_opt} ${access_log}`; IFS=$'\n' read -rd '' -a y <<<"$last_lines"; o_hit_paths=""; for ((i = 1; i < ${#y[@]}; i++)); do o_ip=`echo ${y[$i]} | awk '{print $1}'`; o_hit_path=`echo ${y[$i]} | awk '{print $7}'`; if [[ "${n_ip}" == "${o_ip}" || ${n_ip} == "" ]]; then for dir_file in ${dir_files}; do if [[ -f "${dir_file}" ]]; then wl_file=`${wl_file_opt} ${dir_file}`; for x in ${wl_file}; do if [[ "${o_hit_path}" == *"${x}"* ]]; then hits_per_row=$((hits_per_row+1)); o_hit_paths="${o_hit_paths}\n\t\t\t\t${o_hit_path}"; if [[ ${hits_per_row} -ge ${max_rows} ]]; then block_ip ${o_ip} "reason:\n\t\t\t\tfound '${hits_per_row}' hits in a row. \n\t\t\tpaths:${o_hit_paths}"; fi break; else o_hit_paths=""; hits_per_row=0; fi done fi done fi n_ip=${o_ip}; done } running=1; cac_chain; while [[ ${running} != 0 ]]; do wl_check; sleep ${check_every_sec}; done