《bash网络安全运维》 第8章

实时日志监控

监控文本日志

实时监控日志的最基本的方法是使用 tail 命令的 -f 选项,该选项不对地读取文件,并在添加新行时将它们输出到 stdout。
使用 tail 监控 Apache 访问日志:

tail -f access.log

命令可以组合起来提供更高级的功能。 tail 的输出可以通过管道输入到 grep 中,从而只输出与特定条件匹配的条目。
监控 Apache 访问日志并输出匹配特定 IP 地址的条目:

tail -f access.log | grep '10.0.0.152'

也可以使用正则表达式。
只显示返回 404 页面未找到状态代码的条目:

tail -f access.log | egrep -i 'HTTP/.*" 404'

要清理输出,可以通过管道将其输入到 cut 命令中,以删除无关信息。
监控请求的访问日志,生成 404 状态代码,然后使用 cut 只显示请求的日期/时间和页面:

tail -f access.log | egrep --line-buffered 'HTTP/.*" 404' | cut -d' ' -f4-7 

egrep 命令的 --line-buffered 选项强制 egrep 在每次发生换行时将数据输出到 stdout,而不是进入缓冲区。

基于日志的入侵检测

我们可以使用 tail 和 egrep 的强大功能监控日志,并输出与已知可疑或恶意行为模式相匹配的任何列表,这些行为模式通常被称为折中指标(IOC)。
例:
ioc.txt

\.\./
etc/passwd
etc/shadow
cmd\.exe
/bin/sh
/bin/bash
  1. 模式(../) 是目录遍历攻击的指示器:攻击者试图从当前工作目录中逃脱并访问他们没有权限的文件。
  2. Linux etc/passwd 和 etc/shadow 文件用于系统身份验证,不应该通过 Web 服务器提供。
  3. 服务器 cmd.exe、/bin/sh、/bin/bash 文件是 Web 服务器返回反向 shell 的指示符,一个反向的 shell 通常是成功利用尝试的指标。

接下来,ioc.txt 可以与 egrep -f 选项一起使用。此选项告诉 egrep 读取正则表达式模式以从指定文件中搜索。这允许你使用 tail 来监控日志文件,并在添加每个条目时,将它与 IOC 文件中的所有模式进行比较,输出匹配的任何条目。
示例:

tail -f access.log | egrep -i -f ioc.txt

此外,tee 命令可以用于同时向屏幕显示警报并将其保存到自己的文件中以供以后处理:

tail -f access.log | egrep --line-buffered -i -f ioc.txt | tee -a interesting.txt

监控 Windows 日志

wintail.sh

  
#!/bin/bash -
#
# Cybersecurity Ops with bash
# wintail.sh
#
# Description: 
# Perform a tail-like function on a Windows log
#
# Usage: ./wintail.sh 
#

WINLOG="Application"  #<1>

LASTLOG=$(wevtutil qe "$WINLOG" //c:1 //rd:true //f:text)  #<2>

while true
do
	CURRENTLOG=$(wevtutil qe "$WINLOG" //c:1 //rd:true //f:text)  #<3>
	if [[ "$CURRENTLOG" != "$LASTLOG" ]]
	then		
		echo "$CURRENTLOG"
		echo "----------------------------------"
		LASTLOG="$CURRENTLOG"
	fi
done
  1. 此变量表示要监控的 Windows 日志。可以使用 wevtutil el 获取系统上当前可用的日志列表。
  2. 这将执行 wevtutil 命令来查询指定的日志文件。参数 c:1 只返回一个日志条目。rd:true 参数使命令读取最近的日志条目。最后,f:text 以纯文本的形式而不是 XML 的形式返回结果,这使得从屏幕上读取结果很容易。
  3. 接下来的几行再次执行 wevtutil 命令,并将最新的日志条目与打印到屏幕的最后一个日志条目进行比较。如果两者不同,意味着日志中添加了新条目,则会将此条目打印到屏幕。如果它们是相同的,则没有任何反应,他会循环返回并再次检查。

生成直方图

looper.sh

#!/bin/bash -
#
# Cybersecurity Ops with bash
# looper.sh
#
# Description: 
# Count the lines in a file being tailed -f
# Report the count interval on every SIGUSR1
#
# Usage: ./looper.sh [filename]
#   filename of file to be tailed, default: log.file
# 

function interval ()					# <1>
{
    echo $(date '+%y%m%d %H%M%S') $cnt			# <2>
    cnt=0
}

declare -i cnt=0
trap interval SIGUSR1					# <3>

shopt -s lastpipe					# <4>

tail -f --pid=$$ ${1:-log.file} | while read aline	# <5>
do
    let cnt++
done
  1. 我们需要在接收到每个信号时调整函数 interval。
  2. 调用 date 命令为我们输出的计数值提供一个时间戳。打印计数之后,将其值重置为 0,以开始下一个间隔的计数。
  3. 现在已经定义了 interval 函数,此处告诉 bash 在进程接收到 SIGUSR1 信号时调用该函数。
  4. 这是至关重要的一步。通常,当存在命令管道(例如 ls -l | grep rwx | wc)时,管道的各组成部分(每个命令)在子 shell 中运行,并且每个命令都以自己的进程 ID 结束。这对此脚本来说是个需要解决的问题,因为 while 循环将位于子 shell 中,具有不同的进程 ID 。无论什么进程启动,looper.sh 脚本都不会知道 while 循环的进程 ID 来想向其发送信号。此外,更改子 shell 中的 cnt 变量的值不会更改主进程中 cnt 的值,因此主进程的信号每次都会导致值为 0。解决办法是 shopt 命令,他设置 shell 选项 lastpipe。该选项告诉 shell 不要为管道中的最后一个命令创建子 shell,而是与脚本本身相同的进程中运行该命令。在我们的例子中,这意味着 tail 将在子 shell 中运行,但 while 循环将是主脚本进程的一部分。
  5. 选项 --pid 指定一个进程 ID 来告诉 tail 在该进程终止时退出。这里指定了 $$,即当前 shell 脚本的进程的 ID,作为要观察的 ID。

looper.sh 脚本显示执行计数。

tailcount.sh

#!/bin/bash -
#
# Cybersecurity Ops with bash
# tailcount.sh
#
# Description: 
# Count lines every n seconds
#
# Usage: ./tailcount.sh [filename]
#   filename: passed to looper.sh
#

# cleanup - the other processes on exit
function cleanup ()
{
  [[ -n $LOPID ]] && kill $LOPID		# <1>
}

trap cleanup EXIT 				# <2>

bash looper.sh $1 &				# <3>
LOPID=$!					# <4>
# give it a chance to start up
sleep 3

while true
do
    kill -SIGUSR1 $LOPID
    sleep 5
done >&2					# <5>

脚本 tailcount.sh 用于启动和停止计数——这是一个具有“秒表”功能的脚本,对这些间隔进行计数。

  1. 由于此脚本将启动其他进程,因此应具有自行清理功能。如果进程 ID 已存储在 LOPID 中,则该变量将为非空,此函数能够通过 kill 命令向该进程发送信号。当不为 kill 命令指定特定信号时,则会发送默认信号,即 SIGTERM。
  2. 不是信号,EXIT 是 trap 语句的一种特殊情况,它告诉 shell 在运行此脚本的 shell 即将退出时调用此函数。
  3. 此行开始是脚本真正的主体部分。此行调用 looper.sh 脚本但是放在“后台中”;当脚本继续运行时,它与键盘输入分离以自行运行。
  4. 保存我们刚刚放在后台的脚本的进程 ID。
  5. 这种重定向只是一种预防措施。通过将 stdout 重定向到 stderr,来自 while 循环或 kill 或 sleep 语句的所有输出将被发送到 stderr 并且不会混入来自 looper.sh 的任何输出,虽然它在后台,仍然写入 stdout。

使用一个计数脚本和一个实现“秒表”的脚本,我们可以用它们的输出作为脚本的输入,该脚本打印出类似直方图的条形来表示计数。它被调用如下:

bash tailcount.sh | bash livebar.sh

livebar.sh

#!/bin/bash -
#
# Cybersecurity Ops with bash
# livebar.sh
#
# Description: 
# Creates a rolling horizontal bar chart of live data
#
# Usage:
# <output from other script or program> | bash livebar.sh
#

function pr_bar ()					# <1>
{
    local raw maxraw scaled
    raw=$1
    maxraw=$2
    ((scaled=(maxbar*raw)/maxraw))
    ((scaled == 0)) && scaled=1		# min size guarantee
    for((i=0; i<scaled; i++)) ; do printf '#' ; done
    printf '\n'
    
} # pr_bar


maxbar=60   # largest no. of chars in a bar		# <2>
MAX=60
while read dayst timst qty
do
    if (( qty > MAX ))					# <3>
    then
	let MAX=$qty+$qty/4	# allow some room
	echo "              **** rescaling: MAX=$MAX"
    fi
    printf '%6.6s %6.6s %4d:' $dayst $timst $qty	# <4>
    pr_bar $qty $MAX
done
  1. pr_bar 函数根据我们提供的参数打印缩放到最大尺寸的主题标签栏。
  2. 这是我们将在一行上允许的最长的标签。
  3. 需要显示的值事先不知道有多大,但脚本将跟踪最大值。如果超过最大值,它将重新调整,并且当前和未来的线将缩放到新的最大值。
  4. printf 指定打印的前两个字段的最小和最大宽度。它们是日期和时间戳,如果它们超过那些宽度将被截断。我们不希望计数被截断,因此将其指定为四位数宽,但无论如何都会打印整个值。

livebar.sh 脚本从 stdin 读取并将输出打印到 stdout,每行输入输出一行。

内容来源

《bash网络安全运维》

posted @ 2021-01-24 15:23  PwnKi  阅读(18)  评论(0编辑  收藏