当前位置: 首页 > news >正文

如何在 Bash 中不依赖 curl 或 wget 发出 HTTP 请求并实现文件传输——/dev/tcp的妙用

1. 前言

在 Bash 脚本编程中,发送 HTTP 请求通常依赖于像 curlwget 这样的外部工具。然而,Bash 本身隐藏着一个鲜为人知的功能:通过内置的 /dev/tcp/dev/udp 伪设备,可以直接与网络进行交互,而无需额外安装任何工具。这个特性最初由 KornShell (ksh) 引入,后来被 Bash 继承,其设计初衷是为了方便用户通过网络发送数据,例如生成报告或执行简单的网络操作。然而,这个功能也因其强大而灵活的特性,常常被黑客或渗透测试人员利用,比如创建反向 Shell。

本文将深入探讨这一功能的实现原理,展示如何利用它发送 HTTP 请求,并进一步扩展到使用 cat <cat > 实现文件的上传和下载。我们将从基础用法开始,逐步深入到实际应用场景,并提供丰富的示例代码和分析。


2. Bash 中的 /dev/tcp 功能解析

2.1 手册中的秘密

要了解这个功能的奥秘,我们可以查阅 Bash 的手册(运行 man bash),在 “REDIRECTION(重定向)” 部分会发现一些有趣的细节。手册中提到,Bash 在处理某些特殊文件名时会进行特殊操作,这些文件名包括:

/dev/fd/fd      # 复制文件描述符 fd
/dev/stdin      # 复制标准输入(文件描述符 0)
/dev/stdout     # 复制标准输出(文件描述符 1)
/dev/stderr     # 复制标准错误(文件描述符 2)
/dev/tcp/host/port  # 打开与指定主机和端口的 TCP 连接
/dev/udp/host/port  # 打开与指定主机和端口的 UDP 连接

其中,/dev/tcp/host/port 是本文的核心。如果 host 是一个有效的主机名或 IP 地址,而 port 是一个整数端口号或服务名称(如 80http),Bash 会尝试打开对应的 TCP 套接字。如果底层操作系统提供了这些特殊文件,Bash 会直接使用它们;否则,Bash 会通过内部机制模拟这些行为。

需要注意的是,/dev/tcp 并不是文件系统中的实际路径。尝试列出它(如 ls -l /dev/tcp)会返回“没有那个文件或目录”的错误。这是因为它完全是 Bash 的内置特性,而非 Linux 内核或文件系统的一部分。

2.2 基本用法

要使用 /dev/tcp,我们需要借助 exec 命令创建一个与目标主机和端口的连接。例如:

exec 3<>/dev/tcp/example.com/80

这条命令的作用是打开一个到 example.com 的 80 端口(HTTP 默认端口)的 TCP 连接,并将其绑定到文件描述符 3。<> 表示这是一个双向连接,既可以写入数据(发送请求),也可以读取数据(接收响应)。

运行后,表面上看似什么也没发生,但实际上 Bash 已经在后台建立了一个 TCP 套接字。我们可以通过 strace 工具验证这一过程:

strace -f -e trace=network bash -c 'exec 3<>/dev/tcp/baidu.com/80'

输出中会包含类似以下内容:

socket(AF_INET, SOCK_STREAM, IPPROTO_IP) = 3
connect(3, {sa_family=AF_INET, sin_port=htons(80), sin_addr=inet_addr("220.181.38.148")}, 16) = 0

这表明 Bash 创建了一个 TCP 套接字(文件描述符 3),并成功连接到了目标服务器。


3. 发送 HTTP 请求

3.1 GET 请求示例

让我们通过一个完整的脚本展示如何发送 HTTP GET 请求:

#!/bin/bash

# 打开 TCP 连接到 baidu.com 的 80 端口
exec 3<>/dev/tcp/baidu.com/80

# 发送 HTTP GET 请求
echo -ne "GET / HTTP/1.1\r\nHost: baidu.com\r\nConnection: close\r\n\r\n" >&3

# 读取并输出服务器响应
cat <&3

# 关闭文件描述符
exec 3<&-
逐步解析:
  1. exec 3<>/dev/tcp/baidu.com/80
    创建一个双向 TCP 连接,绑定到文件描述符 3。

  2. echo -ne "GET / HTTP/1.1\r\nHost: baidu.com\r\nConnection: close\r\n\r\n" >&3
    使用 echo 构造一个标准的 HTTP GET 请求,并通过 >&3 将其写入文件描述符 3。

    • -n 避免多余的换行符。
    • -e 启用转义字符(如 \r\n),以满足 HTTP 协议的换行要求。
    • Connection: close 确保服务器在响应后关闭连接。
  3. cat <&3
    从文件描述符 3 读取服务器的响应并输出到终端。

  4. exec 3<&-
    关闭文件描述符,释放资源。

运行后,你可能会看到类似以下的输出:

HTTP/1.1 200 OK
Date: Sun, 16 Mar 2025 10:00:00 GMT
Server: Apache
Content-Length: 81
Connection: close
Content-Type: text/html

<html>
<meta http-equiv="refresh" content="0;url=http://www.baidu.com/">
</html>

3.2 POST 请求示例

发送 POST 请求的步骤类似,只需调整 HTTP 请求头和添加请求体:

#!/bin/bash

# 打开 TCP 连接
exec 3<>/dev/tcp/baidu.com/80

# 发送 HTTP POST 请求
echo -ne "POST /submit HTTP/1.1\r\nHost: baidu.com\r\nContent-Type: application/x-www-form-urlencoded\r\nContent-Length: 27\r\nConnection: close\r\n\r\nparam1=value1&param2=value2" >&3

# 读取响应
cat <&3

# 关闭连接
exec 3<&-

输出可能是:

HTTP/1.1 302 Found
Date: Sun, 16 Mar 2025 10:05:00 GMT
Server: Apache
Location: http://www.baidu.com/search/error.html
Content-Length: 222
Connection: close
Content-Type: text/html

<!DOCTYPE HTML>
<html><head><title>302 Found</title></head><body><h1>Found</h1><p>The document has moved <a href="http://www.baidu.com/search/error.html">here</a>.</p></body></html>

这里的关键是正确设置 Content-Length,其值必须与请求体的字节数匹配。


4. 文件上传与下载的扩展

/dev/tcp 的强大之处不仅限于发送简单的 HTTP 请求,还可以通过文件描述符结合 cat 命令实现文件的上传和下载。以下是具体方法。

4.1 文件上传(使用 cat >)

假设我们有一个本地文件 upload.txt,内容为:

Hello, this is a test file for upload.

我们可以通过以下脚本将文件上传到支持文件上传的服务器(如一个简单的 HTTP 文件接收端点):

#!/bin/bash

# 定义目标服务器和端口
HOST="example.com"
PORT=80
ENDPOINT="/upload"

# 计算文件内容的长度
FILE="upload.txt"
CONTENT_LENGTH=$(wc -c < "$FILE")

# 打开 TCP 连接
exec 3<>/dev/tcp/$HOST/$PORT

# 构造并发送 POST 请求
{
    echo -ne "POST $ENDPOINT HTTP/1.1\r\n"
    echo -ne "Host: $HOST\r\n"
    echo -ne "Content-Type: text/plain\r\n"
    echo -ne "Content-Length: $CONTENT_LENGTH\r\n"
    echo -ne "Connection: close\r\n"
    echo -ne "\r\n"
    cat "$FILE"
} >&3

# 读取服务器响应
cat <&3

# 关闭连接
exec 3<&-
解析:
  • wc -c < "$FILE" 计算文件的字节数,用于设置 Content-Length
  • cat "$FILE" >&3 将文件内容直接写入文件描述符,实现上传。
  • 请求头和文件内容通过 {} 组合在一起,确保顺序发送。

如果服务器支持文件上传,响应可能是:

HTTP/1.1 200 OK
Date: Sun, 16 Mar 2025 10:10:00 GMT
Content-Type: text/plain
Content-Length: 20
Connection: close

File uploaded successfully
优化版本:支持大文件分块上传

对于大文件,可以使用分块上传的方式,避免一次性加载整个文件到内存:

#!/bin/bash

HOST="example.com"
PORT=80
ENDPOINT="/upload"
FILE="largefile.bin"
CHUNK_SIZE=1024  # 每次发送 1KB

# 获取文件总大小
TOTAL_SIZE=$(wc -c < "$FILE")

# 打开 TCP 连接
exec 3<>/dev/tcp/$HOST/$PORT

# 发送分块上传请求
{
    echo -ne "POST $ENDPOINT HTTP/1.1\r\n"
    echo -ne "Host: $HOST\r\n"
    echo -ne "Content-Type: application/octet-stream\r\n"
    echo -ne "Content-Length: $TOTAL_SIZE\r\n"
    echo -ne "Connection: close\r\n"
    echo -ne "\r\n"
    dd if="$FILE" bs=$CHUNK_SIZE  # 使用 dd 分块读取并发送
} >&3

# 读取响应
cat <&3

# 关闭连接
exec 3<&-

这里使用了 dd 工具按块读取文件并发送,适用于大文件场景。

4.2 文件下载(使用 cat <)

要从服务器下载文件,只需发送一个 GET 请求并将响应保存到本地文件。例如,下载一个远程文本文件:

#!/bin/bash

HOST="example.com"
PORT=80
ENDPOINT="/files/sample.txt"

# 打开 TCP 连接
exec 3<>/dev/tcp/$HOST/$PORT

# 发送 GET 请求
echo -ne "GET $ENDPOINT HTTP/1.1\r\nHost: $HOST\r\nConnection: close\r\n\r\n" >&3

# 将响应保存到文件(跳过 HTTP 头)
cat <&3 > downloaded.txt

# 关闭连接
exec 3<&-
注意事项:
  • 上面的脚本会将完整的 HTTP 响应(包括头信息)保存到 downloaded.txt。如果只需要文件内容,可以用工具(如 sedawk)过滤掉头信息:
#!/bin/bash

HOST="example.com"
PORT=80
ENDPOINT="/files/sample.txt"

# 打开 TCP 连接
exec 3<>/dev/tcp/$HOST/$PORT

# 发送 GET 请求
echo -ne "GET $ENDPOINT HTTP/1.1\r\nHost: $HOST\r\nConnection: close\r\n\r\n" >&3

# 跳过 HTTP 头,仅保存文件内容
sed '1,/^\r$/d' <&3 > downloaded.txt

# 关闭连接
exec 3<&-

这里 sed '1,/^\r$/d' 的作用是从开头删除到第一个空行(即 HTTP 头和正文之间的分隔符 \r\n)。

下载二进制文件

对于图片、视频等二进制文件,直接使用 cat 保存即可,但需要确保服务器返回的是正确的 Content-Type

#!/bin/bash

HOST="example.com"
PORT=80
ENDPOINT="/images/sample.jpg"

# 打开 TCP 连接
exec 3<>/dev/tcp/$HOST/$PORT

# 发送 GET 请求
echo -ne "GET $ENDPOINT HTTP/1.1\r\nHost: $HOST\r\nConnection: close\r\n\r\n" >&3

# 保存二进制文件(包含头信息)
cat <&3 > sample.jpg

# 关闭连接
exec 3<&-

如果需要纯二进制内容,可以结合 taildd 跳过头信息:

#!/bin/bash

HOST="example.com"
PORT=80
ENDPOINT="/images/sample.jpg"

# 打开 TCP 连接
exec 3<>/dev/tcp/$HOST/$PORT

# 发送 GET 请求
echo -ne "GET $ENDPOINT HTTP/1.1\r\nHost: $HOST\r\nConnection: close\r\n\r\n" >&3

# 跳过头信息保存文件
tail -n +5 <&3 > sample.jpg  # 假设头信息占前 4 行,具体行数需根据实际响应调整

# 关闭连接
exec 3<&-

5. 进阶应用与优化

5.1 处理 HTTPS

/dev/tcp 本身不支持 SSL/TLS 加密,因此无法直接处理 HTTPS 请求。不过,可以通过代理或外部工具(如 openssl s_client)间接实现:

#!/bin/bash

HOST="example.com"
PORT=443

# 使用 openssl 建立 SSL 连接并绑定到文件描述符
exec 3<>/dev/tcp/$HOST/$PORT
openssl s_client -connect $HOST:$PORT -quiet <&3 >&3 2>/dev/null &

# 发送 HTTPS 请求
echo -ne "GET / HTTP/1.1\r\nHost: $HOST\r\nConnection: close\r\n\r\n" >&3

# 读取响应
cat <&3

# 关闭连接
exec 3<&-

这种方法依赖 openssl,不算完全“无外部工具”,但展示了 /dev/tcp 的灵活性。

5.2 多线程下载

通过结合 Bash 的后台进程,可以实现简单的多线程下载。例如,分段下载一个大文件:

#!/bin/bash

HOST="example.com"
PORT=80
ENDPOINT="/largefile.bin"
TOTAL_SIZE=10485760  # 假设文件大小 10MB
CHUNK_SIZE=5242880   # 每个分段 5MB

# 分段下载函数
download_chunk() {
    local OFFSET=$1
    local LENGTH=$2
    local OUTPUT="part_$OFFSET.bin"
    exec 3<>/dev/tcp/$HOST/$PORT
    echo -ne "GET $ENDPOINT HTTP/1.1\r\nHost: $HOST\r\nRange: bytes=$OFFSET-$(($OFFSET+$LENGTH-1))\r\nConnection: close\r\n\r\n" >&3
    sed '1,/^\r$/d' <&3 > "$OUTPUT"
    exec 3<&-
}

# 启动两个分段下载
download_chunk 0 $CHUNK_SIZE &
download_chunk $CHUNK_SIZE $CHUNK_SIZE &

# 等待所有分段完成
wait

# 合并文件
cat part_0.bin part_$CHUNK_SIZE.bin > largefile.bin

这里使用了 HTTP 的 Range 头实现分段下载,适用于支持范围请求的服务器。


6. 总结

Bash 的 /dev/tcp 功能提供了一种轻量、原生的方式来发送 HTTP 请求和实现文件传输。通过文件描述符和 cat <cat >,我们可以在不依赖 curlwget 的情况下完成 GET、POST 请求,甚至上传和下载文件。这一特性特别适合快速测试、脚本自动化或资源受限的环境。

通过掌握这一隐藏功能,你可以在 Bash 中解锁更多可能性,无论是网络调试还是自动化任务,都能游刃有余。

扩展阅读

  • 查看文件描述符:ls -l /proc/self/fd/
  • 调试网络连接:stracetcpdump
  • 学习 HTTP 协议:RFC 2616(HTTP/1.1)

相关文章:

  • illustrate:一款蛋白/核酸结构快速渲染为“卡通风格”的小工具
  • Ciura序列
  • 弱网测试:全链路实战、高阶策略与自动化落地
  • 多线程14(哈希表与文件操作IO)
  • CPU架构和微架构
  • 中颖SH366000介绍和使用全解
  • Web安全策略CSP详解与实践
  • HTTP请求过程详解
  • 构建自定义MCP天气服务器:集成Claude for Desktop与实时天气数据
  • /2要求:定义一个方法,根据id查找对应的用户信息 //如果存在,返回id //如果不存在,返回-1
  • 蓝桥杯 小球反弹
  • 278.缀点成线
  • uniapp 和 webview 之间的通信
  • 【1】Java 零基础入门学习(小白专用)
  • 新配置了一台服务器+域名共178:整个安装步骤,恢复服务
  • Docker逃逸
  • 基于SSM框架的汽车租赁平台(源码+lw+部署文档+讲解),源码可白嫖!
  • React Native进阶(六十一): WebView 替代方案 react-native-webview 应用详解
  • Redis内存碎片详解
  • 1998-2022年各地级市第三产业占GDP比重/地级市第三产业占比数据(市辖区)
  • 是否有中国公民受印巴冲突影响?外交部:建议中国公民避免前往冲突涉及地点
  • 央视315晚会曝光“保水虾仁”后,湛江4家涉事企业被罚超800万元
  • 碧桂园服务:拟向杨惠妍全资持有的公司提供10亿元贷款,借款将转借给碧桂园用作保交楼
  • 丁薛祥在学习《习近平经济文选》第一卷专题研讨班上强调:深入学习贯彻习近平经济思想,加强党中央对经济工作的集中统一领导
  • 山东滕州一车辆撞向公交站台致多人倒地,肇事者被控制,案件已移交刑警
  • 贵州召开全省安全生产电视电话会议:以最严要求最实举措守牢安全底线