📚 网络安全Kali学习系列 · 模块0-1 Linux系统深度
01 Kali环境搭建与文件权限
02 SUID/SGID特殊权限与ACL
03 进程管理与服务管理
04 网络配置与防火墙基础
05 Shell脚本编写(当前篇)
06 包管理实战
Shell脚本编写:Bash自动化与条件循环
难度:基础 | 网络安全Kali学习系列 第05篇
读完本篇你将能:编写 Bash 脚本实现命令自动化,掌握变量、条件判断、循环和函数四大核心语法,编写一个串联前四篇知识的系统信息收集脚本,并理解脚本安全的命令注入防范原则。
📑 本文目录
01Shell脚本基础:从命令到脚本
02条件判断:if与case
03循环结构:for与while
04函数与脚本组织
05实战:自动化系统信息收集脚本
06安全视角:脚本安全
07动手练习
01 Shell脚本基础:从命令到脚本
前四篇你学会了文件权限管理、进程服务控制、网络配置与防火墙——这些命令每次手动输入,效率有限。如果把它们比作单个工具,Shell脚本就是把这些工具组装成一条自动化流水线:原料进去,成品出来,中间无需人工干预。本节从最基本的脚本结构开始。
Shebang与脚本执行
每个 Bash 脚本第一行是 Shebang(#!),告诉系统用哪个解释器执行。Bash 脚本固定写 #!/bin/bash。
Bash
#!/bin/bash
# 这是第一个脚本 hello.sh
echo "Hello, Kali!"
两种执行方式:
Bash
# 方式1:直接用bash执行(无需执行权限)
bash hello.sh
# 方式2:赋予执行权限后直接运行
chmod +x hello.sh
./hello.sh
变量定义与引用
Bash 变量无需声明类型,赋值时等号两边不能有空格——这是初学者最常踩的坑。
Bash
# 变量赋值(等号两边不能有空格!)
target="192.168.1.1"
port=80
# 引用变量:$var 或 ${var}
echo "扫描目标: ${target}"
echo "端口: $port"
⚠️ 常见错误
target = "192.168.1.1" — 等号两边有空格时,Bash 把 target 当作命令执行,报 command not found
✓ 正确:target="192.168.1.1" 等号紧贴变量名和值,不留空格
字符串操作
Bash 内置丰富的字符串操作语法,在处理文件名、IP地址、命令输出时非常实用。
Bash
file="report_20260809.txt"
# 取字符串长度
echo ${#file} # 输出: 20
# 截取子串(从位置7开始取8个字符)
echo ${file:7:8} # 输出: 20260809
# 替换字符串(替换第一个匹配)
echo ${file/txt/md} # 输出: report_20260809.md
# 删除后缀(提取文件名不带扩展名)
echo ${file%%.*} # 输出: report_20260809
特殊变量
Bash 预定义了一组特殊变量,在脚本中直接使用,无需赋值:
| 变量 |
含义 |
安全场景示例 |
$0 |
脚本文件名 |
日志中标识脚本来源 |
$1 $2... |
位置参数 |
传入目标IP、端口等参数 |
$# |
参数个数 |
校验用户是否传了必需参数 |
$@ |
所有参数列表 |
循环遍历多个目标IP |
$? |
上一条命令退出码 |
判断扫描是否成功 |
$$ |
当前脚本PID |
创建唯一临时文件名 |
02 条件判断:if与case
你已经会定义变量了。但脚本不能只是从头到尾顺序执行——它需要根据不同情况走不同路径。条件判断让脚本具备"做决策"的能力:端口开放就记录、权限不对就告警、参数缺失就提示用法。
if-then-elif-else结构
Bash
if [[ $# -lt 1 ]]; then
echo "用法: $0 <目标IP>"
exit 1
elif ping -c 1 -W 2 $1 > /dev/null; then
echo "$1 在线"
else
echo "$1 离线"
fi
条件测试:[ ] vs [[ ]]
[[ ]] 是 Bash 增强版测试语法,支持正则匹配 =~ 和逻辑运算 &&/||,比 POSIX [ ] 更安全(变量为空时不会语法错误)。脚本中优先用 [[ ]]。
Bash
# 文件测试
if [[ -f /etc/passwd ]]; then echo "文件存在"; fi
if [[ -d /tmp/logs ]]; then echo "目录存在"; fi
# 字符串测试
if [[ -z $ip ]]; then echo "IP为空"; fi
# -z 空字符串 -n 非空字符串
# 数值比较
# -eq等于 -ne不等 -lt小于 -gt大于 -le小于等于 -ge大于等于
# 正则匹配([[ ]] 独有功能)
if [[ $ip =~ ^[0-9]+(\.[0-9]+){3}$ ]]; then
echo "合法IP格式"
fi
case语句:模式匹配
当条件分支超过3个时,case 比 if-elif 更清晰。它支持通配符模式匹配,适合处理用户输入或命令返回值。
Bash
|
1
2
3
4
5
6
7
8
9
10
11
12
|
read -p "选择扫描类型(quick/full/stealth): " mode
case $mode in
quick) nmap -F $1 ;;
full) nmap -p- $1 ;;
stealth) nmap -sS $1 ;;
*) echo "未知模式: $mode"
exit 1 ;;
esac
# *) 匹配所有其他值(类似default)
# ;; 类似break,结束当前分支
# case...in...esac 是完整结构
|
03 循环结构:for与while
条件判断让脚本走不同路径,循环让脚本批量处理重复任务——扫描整个网段、检查多个端口、处理目录下所有文件。渗透测试中,循环是自动化扫描脚本的核心骨架。
for循环
Bash
# 遍历列表
for port in 22 80 443 3306; do
echo "扫描端口 $port"
nmap -p $port $1
done
Bash
# C风格for循环(适合数值递增)
for ((i=1; i<=254; i++)); do
ping -c 1 -W 1 "192.168.1.$i" > /dev/null
if [[ $? -eq 0 ]]; then
echo "192.168.1.$i 在线"
fi
done
while循环与循环控制
Bash
# 逐行读取文件
while read line; do
echo "处理: $line"
done < /etc/hosts
# break跳出循环 continue跳过本次
for ip in $@; do
if [[ $ip == "127.0.0.1" ]]; then continue; fi
nmap $ip
done
💡 小贴士
逐行读取文件时用 while read 而非 for。for 按空格分割,遇到含空格的行会拆成多段;while read 按行读取,完整保留每行内容。
04 函数与脚本组织
当脚本超过 50 行时,把重复代码块封装成函数是保持可读性的关键。函数让脚本从"一坨命令"升级为"有结构的程序",也为后面的实战脚本做好模块化准备。
函数定义与调用
Bash
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
# 定义函数:检查端口是否开放
check_port() {
local host=$1
local port=$2
if nc -z -w2 $host $port 2>/dev/null; then
echo "端口 $port 开放"
else
echo "端口 $port 关闭"
fi
}
# 调用函数
check_port "192.168.1.1" 22
check_port "192.168.1.1" 80
|
关键点:local 声明局部变量,避免污染全局作用域;函数内 $1 接收第一个参数,与脚本级的 $1 独立。函数返回值用 return(退出码 0-255)或 echo + $() 捕获输出。
05 实战:自动化系统信息收集脚本
把前四篇学的命令和本篇的语法全部串起来——这个脚本自动收集主机的网络配置、进程状态、监听端口、磁盘和用户信息,生成一份安全基线报告。在渗透测试中,拿到 shell 后第一步就是信息收集;在防御中,这是安全审计的自动化工具。
Bash · sysinfo.sh
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
|
#!/bin/bash
# sysinfo.sh - 系统信息收集脚本
# 函数:打印分隔标题
section() {
echo "=== $1 ==="
}
# 1. 网络信息(串联第04篇)
section "网络配置"
ip -br addr show
ip route show | head -3
# 2. 监听端口(串联第04篇)
section "监听端口"
ss -tlnp 2>/dev/null
# 3. 活跃连接
section "活跃连接"
ss -tnp state established
# 4. 服务状态(串联第03篇)
section "运行中服务"
systemctl list-units \
--type=service --state=running
# 5. 高权限进程(串联第02/03篇)
section "root进程"
ps aux | awk '$1=="root"' \
| head -10
# 6. SUID文件(串联第02篇)
section "SUID文件"
find /usr/bin -perm -4000 2>/dev/null
# 7. 磁盘使用
section "磁盘使用"
df -h | grep -v tmpfs
# 8. 登录用户
section "当前登录用户"
who
echo "=== 收集完毕 ==="
|
这个脚本串联了前四篇所有核心命令:ip/ss(第04篇网络配置)、systemctl/ps(第03篇进程管理)、find -perm -4000(第02篇SUID审计)。用函数 section() 统一格式化输出,每个模块独立清晰。
06 安全视角:脚本安全
脚本能自动化安全工作,但脚本本身也可能成为安全漏洞。一个未引用变量的脚本,可能被攻击者利用进行命令注入——在参数中嵌入恶意命令,让脚本在 root 权限下执行。本节讲解脚本安全的三个核心防线。
命令注入:未引用变量的危险
Bash
# 危险写法:变量未加引号
cat $filename
# 如果 filename="; rm -rf /"
# 实际执行: cat ; rm -rf /
# 分号被解释为命令分隔符!
# 安全写法:变量必须加双引号
cat "$filename"
# 引号内的内容作为整体参数传递
⚠️ 常见错误
ping $user_input — 用户输入 8.8.8.8; cat /etc/shadow 时,分号后的命令会被执行
✓ 正确:ping "$user_input" 所有引用变量都加双引号,杜绝命令注入
set安全选项
在每个脚本开头加上 set -euo pipefail,这三行是 Bash 脚本的安全带:
Bash
#!/bin/bash
set -e # 任何命令失败立即退出
set -u # 引用未定义变量报错
set -o pipefail # 管道中任一命令失败即报错
# 或简写为一行
set -euo pipefail
💡 小贴士
eval 命令会把字符串当作命令执行,是脚本中最危险的函数。eval "$cmd" 中如果 $cmd 含恶意命令,引号也拦不住。安全原则:能用变量和函数解决的,绝不使用 eval。
📖 知识回顾
Shebang与脚本执行
变量定义与引用
字符串操作
特殊变量$0$#$@$?
if/case条件判断
[[]]条件测试
for/while循环
函数与local变量
命令注入防范
set -euo pipefail
✏️ 动手练习
🟢 基础验证
编写一个脚本 portcheck.sh,接收两个参数(IP和端口),用 nc -z -w2 检测端口是否开放并输出结果。如果参数不足则提示用法并退出。
参考:用 if [[ $# -ne 2 ]] 校验参数数量,nc -z -w2 "$1" "$2" 检测端口,$? 判断结果。
🟡 组合应用
编写一个 netscan.sh 脚本:接收一个网段参数(如 192.168.1),用 for 循环 ping 该网段所有 254 个 IP,只输出在线主机的 IP 地址。要求脚本开头加 set -euo pipefail,所有变量引用都加双引号。
提示:C风格for循环 + ping -c1 -W1 + $?判断 + 变量双引号引用。
🔴 开放挑战
在第五节 sysinfo.sh 的基础上扩展:①增加一个函数 check_suid(),扫描指定目录下的 SUID 文件并与白名单对比,输出非白名单的异常 SUID 文件;②增加 iptables 规则审计模块,检查是否有 ACCEPT all 的危险规则;③将报告输出到带时间戳的文件(report_$(date +%Y%m%d).txt)。
下篇预告
06 包管理实战:apt/dpkg、pip、npm
将学习 apt 软件源配置与包安装、dpkg 底层操作、pip 管理 Python 安全工具、npm 安装 Node.js 工具,以及从安全视角识别后门包和软件版本漏洞