Pure Shell HTTP Server - V2EX
2025-1-15 17:13:11 Author: v2ex.com(查看原文) 阅读量:14 收藏

V2EX = way to explore

V2EX 是一个关于分享和探索的地方

  1. 这是一个纯 shell 实现的 HTTP 服务器,能够使用 GET/POST 请求读写 Working directory 下的文件。
  2. 可以通过 PORT 和 KEY 环境变量设置监听的端口和写入操作的 API Key 。如果没有指定,那么会自动生成一个 UUID 作为 API Key ,并且在控制台打印出来。写入操作应该设置 Authorization 头为: APIKey 你的 API 密钥
  3. 对于中文或者没有正确 URL Encode 的请求支持应该不太好。
  4. 如果想要结束服务器,请在服务器目录下运行 kill -9 $(cat server.pid)
  5. 虽然做了一些安全措施,但是 shell 写的东西总归有些危险,不建议在有个人数据且暴露公网的设备上长期运行。
  6. 当然,这个服务器很简陋,也不支持多进程、多连接、可能有 race condition 问题,但是 just for fun ,更多的是作为一个「 shell 也可以写 HTTP 服务器」的概念验证。如果对代码有什么建议也欢迎提出。
#!/bin/bash
function server {
	read il
	echo "recv_il: $il" >&2
	method=$( echo "$il" | cut -d" " -f1)
	path=$(   echo "$il" | cut -d" " -f2)
	proto=$(  echo "$il" | cut -d" " -f3)
	echo "method: $method" >&2
	echo "path:   $path"   >&2
	echo "proto:  $proto"  >&2

	declare -A hdr
	while read line; do
		sline=`echo $line | tr -d '[\r\n]'`
		[ -z "$sline" ] && break
		echo "recv_hdr: $line" >&2
		hdr_k=$(echo $line | cut -d":" -f1)
		hdr_v=$(echo $line | cut -d":" -f2- | cut -c2- | tr -d '[\r\n]')
		hdr[$hdr_k]="$hdr_v"
	done
	echo "recv_hdr_end" >&2

	relpath=$(realpath "$(pwd)$path")
	if [[ "$relpath" != "$(pwd)"* ]]; then
		echo "possible path traversal attack: $relpath" >&2
		echo "pwd:     $(pwd)"   >&2
		echo "relpath: $relpath" >&2
		echo -ne "HTTP/1.1 403 Forbidden\r\n"
		echo -ne "\r\n"
		echo -ne "possible path traversal attack: $relpath"
		exit 0
	fi
	echo "relpath: $relpath" >&2
	if [ $method = "GET" ]; then
		if [ ! -f "$relpath" ]; then
			echo -ne "HTTP/1.1 404 Not Found\r\n"
			echo -ne "\r\n"
			echo -ne "not found: $relpath"
			exit 0
		fi
		echo -ne "HTTP/1.1 200\r\n"
		echo -ne "\r\n"
		cat  "$relpath"
		exit 0
	fi

	if [ $method != "POST" ]; then
		echo -ne "HTTP/1.1 405 Method Not Allowed\r\n"
		echo -ne "\r\n"
		exit 0
	fi

	if [ "${hdr[Authorization]}" != "APIKey $KEY" ]; then
		echo -ne "HTTP/1.1 401 Unauthorized\r\n"
		echo -ne "\r\n"
		exit 0
	fi

	body_file=$(mktemp)
	body_len=0
	if [ ! -z "${hdr[Content-Length]}" ]; then
		echo "hdr_cl > body_len"   >&2
		body_len="${hdr[Content-Length]}"
	fi
	echo "body_len:  $body_len"    >&2
	echo "body_file: $body_file"   >&2
	dd of=$body_file bs=1 count=$body_len

	mkdir -p $(dirname "$relpath") >&2
	cp -v $body_file "$relpath"    >&2

	echo -ne "HTTP/1.1 201 Created\r\n"
	echo -ne "\r\n"
	echo -ne "created: $relpath\r\n"
	rm -rvf $body_file             >&2
}

if [ -z "$PORT" ]; then
	PORT=3000
fi

if [ -z "$KEY" ]; then
	KEY=$(uuidgen)
fi

if [ "$EXEC" = "server" ]; then
	server
	exit 0
fi

SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &> /dev/null && pwd)
PID=$$
PIDFILE="$SCRIPT_DIR/server.pid"

if [[ -f "$PIDFILE" && -d "/proc/$(cat $PIDFILE)" ]]; then
	echo "one instance is running, refuse to start another"
	exit 1
fi

if [ -f "server.sh" ]; then
	echo "DO NOT START SERVER WHEN CURRENT WORKING DIRECTORY IS SAME AS SCRIPT DIRECTORY"
	echo "THIS MAY CAUSE UNEXPECTED OVERWRITING SERVER AND RCE"
	echo "EXITING"
	exit 1
fi

echo "PID=$PID"
echo $PID > $PIDFILE
echo "PIDFILE=$PIDFILE"
echo "KEY=$KEY"

while true; do
	nc -vlp $PORT -c "EXEC=server KEY=$KEY $0"
done
trepwq

1

trepwq      2 小时 29 分钟前 via iPhone   ❤️ 1

nc 换成 socat ,可以端口复用

sagaxu

3

sagaxu      2 小时 3 分钟前

python -m http.server $PORT
php -S localhost:$PORT
jwebserver -p $PORT
ruby -run -e httpd -p $PORT

sagaxu

5

sagaxu      1 小时 16 分钟前

@baobao1270 大部分 Linux 发行版依赖 Python ,不用另外安装。以前写 web 服务的时候,直接用 linux 自带的 inetd 监听端口,收到请求时调用 CGI 调用处理程序,CGI 可以是任何语言写的,只要这个语言能读 stdio 和环境变量以及写入 stdout 。像 Ubuntu 标准版自带的 busybox 也自带了一个 httpd ,可以提供 CGI 转发。

w568w

8

w568w      7 分钟前

cool ,这才是真正的 shell

另有一些语法风格上的建议:

1. function 关键字是兼容一些远古 shell 给出的。既然指定了 bash ,用 server() {} 就好了;

2. 函数内的变量最好用 local 声明,否则作用域会泄漏到函数外;

3. 可以用 shellcheck 过一遍,可能有其他忽略的点


文章来源: https://v2ex.com/t/1105382#reply8
如有侵权请联系:admin#unsafe.sh