Files
s-hy2/scripts/secure-download.sh
T
sindricn 43818ba7fe 更新
2025-09-24 15:57:45 +08:00

277 lines
6.3 KiB
Bash

#!/bin/bash
# 安全下载和文件验证模块
# 防止恶意文件下载和远程代码执行
# 严格错误处理
set -euo pipefail
# 安全下载配置 (防止重复定义)
if [[ -z "${DOWNLOAD_TIMEOUT:-}" ]]; then
readonly DOWNLOAD_TIMEOUT=30
readonly MAX_FILE_SIZE=$((10 * 1024 * 1024)) # 10MB
readonly ALLOWED_PROTOCOLS=("https")
readonly USER_AGENT="s-hy2-secure-downloader/1.0"
fi
# 临时目录
if [[ -z "${SECURE_TEMP_DIR:-}" ]]; then
readonly SECURE_TEMP_DIR="/tmp/s-hy2-secure-$$"
fi
# 清理函数
cleanup_temp_files() {
if [[ -d "$SECURE_TEMP_DIR" ]]; then
rm -rf "$SECURE_TEMP_DIR"
fi
}
# 设置清理陷阱
trap cleanup_temp_files EXIT
# 创建安全临时目录
create_secure_temp_dir() {
mkdir -p "$SECURE_TEMP_DIR"
chmod 700 "$SECURE_TEMP_DIR"
}
# 验证URL安全性
validate_url_secure() {
local url="$1"
# 检查协议
local protocol="${url%%://*}"
local is_allowed=false
for allowed in "${ALLOWED_PROTOCOLS[@]}"; do
if [[ "$protocol" == "$allowed" ]]; then
is_allowed=true
break
fi
done
if [[ "$is_allowed" != "true" ]]; then
echo "不安全的协议: $protocol" >&2
return 1
fi
# 检查URL格式
if [[ ! "$url" =~ ^https://[a-zA-Z0-9.-]+(/.*)?$ ]]; then
echo "URL格式不正确" >&2
return 1
fi
# 防止本地文件访问
if [[ "$url" =~ file://|localhost|127\.0\.0\.1|0\.0\.0\.0 ]]; then
echo "禁止访问本地资源" >&2
return 1
fi
return 0
}
# 安全下载文件
download_file_secure() {
local url="$1"
local output_file="$2"
local expected_hash="${3:-}"
# 验证URL
if ! validate_url_secure "$url"; then
return 1
fi
# 创建安全临时目录
create_secure_temp_dir
# 临时文件路径
local temp_file="$SECURE_TEMP_DIR/download.tmp"
echo "正在安全下载: $url"
# 使用curl安全下载
if ! curl \
--silent \
--show-error \
--fail \
--location \
--max-time "$DOWNLOAD_TIMEOUT" \
--max-filesize "$MAX_FILE_SIZE" \
--user-agent "$USER_AGENT" \
--proto "=https" \
--tlsv1.2 \
--cert-status \
--output "$temp_file" \
"$url"; then
echo "下载失败: $url" >&2
return 1
fi
# 验证文件大小
local file_size
file_size=$(stat -c%s "$temp_file" 2>/dev/null || echo 0)
if [[ $file_size -eq 0 ]]; then
echo "下载的文件为空" >&2
return 1
fi
if [[ $file_size -gt $MAX_FILE_SIZE ]]; then
echo "文件大小超过限制 ($MAX_FILE_SIZE bytes)" >&2
return 1
fi
# 验证文件哈希(如果提供)
if [[ -n "$expected_hash" ]]; then
local actual_hash
actual_hash=$(sha256sum "$temp_file" | cut -d' ' -f1)
if [[ "$actual_hash" != "$expected_hash" ]]; then
echo "文件哈希验证失败" >&2
echo "期望: $expected_hash" >&2
echo "实际: $actual_hash" >&2
return 1
fi
echo "文件哈希验证通过: $actual_hash"
fi
# 移动到目标位置
if ! mv "$temp_file" "$output_file"; then
echo "无法移动文件到目标位置: $output_file" >&2
return 1
fi
# 设置安全权限
chmod 644 "$output_file"
echo "文件安全下载完成: $output_file"
return 0
}
# 下载并验证shell脚本
download_script_secure() {
local url="$1"
local output_file="$2"
local expected_hash="${3:-}"
# 下载文件
if ! download_file_secure "$url" "$output_file" "$expected_hash"; then
return 1
fi
# 验证shell脚本语法
if ! bash -n "$output_file"; then
echo "脚本语法检查失败: $output_file" >&2
rm -f "$output_file"
return 1
fi
# 检查危险命令
local dangerous_patterns=(
"rm -rf /"
"dd if="
"mkfs"
"fdisk"
"format"
"> /dev/"
"curl.*|.*sh"
"wget.*|.*sh"
"eval.*\$"
"exec.*\$"
)
for pattern in "${dangerous_patterns[@]}"; do
if grep -q "$pattern" "$output_file"; then
echo "脚本包含危险命令: $pattern" >&2
rm -f "$output_file"
return 1
fi
done
# 设置执行权限
chmod 755 "$output_file"
echo "脚本安全验证通过: $output_file"
return 0
}
# 安全执行下载的脚本
execute_downloaded_script_secure() {
local script_file="$1"
shift
local args=("$@")
# 验证脚本存在
if [[ ! -f "$script_file" ]]; then
echo "脚本文件不存在: $script_file" >&2
return 1
fi
# 验证脚本权限
if [[ ! -x "$script_file" ]]; then
echo "脚本没有执行权限: $script_file" >&2
return 1
fi
# 记录执行日志
echo "[$(date '+%Y-%m-%d %H:%M:%S')] 安全执行脚本: $script_file ${args[*]}" >&2
# 在受限环境中执行
timeout 300 bash "$script_file" "${args[@]}"
}
# 获取文件的SHA256哈希
get_file_hash() {
local file="$1"
if [[ -f "$file" ]]; then
sha256sum "$file" | cut -d' ' -f1
else
echo "文件不存在: $file" >&2
return 1
fi
}
# 验证已知的官方文件哈希
verify_official_hash() {
local file="$1"
local file_type="$2"
# 官方文件哈希数据库(示例)
case "$file_type" in
"hysteria2-install")
# 这里应该是官方安装脚本的已知哈希值
# 实际使用时需要从官方获取并定期更新
local known_hashes=(
"placeholder_hash_1"
"placeholder_hash_2"
)
;;
*)
echo "未知文件类型: $file_type" >&2
return 1
;;
esac
local file_hash
file_hash=$(get_file_hash "$file")
for known_hash in "${known_hashes[@]}"; do
if [[ "$file_hash" == "$known_hash" ]]; then
echo "文件哈希验证通过: $file_hash"
return 0
fi
done
echo "文件哈希不在已知列表中: $file_hash" >&2
return 1
}
# 导出函数
export -f validate_url_secure
export -f download_file_secure
export -f download_script_secure
export -f execute_downloaded_script_secure
export -f get_file_hash
export -f verify_official_hash