安全文件传输
Linux 系统(以及可能所有 POSIX 系统)之间的文件传输在某些方面是一个被忽视的主题。常用的古老协议远非安全,而 SSH 替代方案又提供了过多的功能和复杂性。 存储高度敏感数据(例如信用卡号、社保号码、出生日期等)的服务器通常必须接受文件传输,但会严格限制远程可见性和管理,这对于知名的工具来说是很难做到的。
使用 RFC 1867 (https://www.ietf.org/rfc/rfc1867.txt) 进行文件传输可以提供优于大多数其他方法的许多好处:最高的安全性和可选的加密,所有这些都无需在 /etc/passwd 中输入条目或操作系统的其他凭据。
我在本文中介绍的用于实现此协议的工具是 sthttpd、一个上传 CGI 实用程序、stunnel 和 curl。 这里的示例是在 Oracle Linux 7.1 上开发的,但大多数代码都是可移植的,并且应该在其他平台上以最小的更改运行(systemd 配置除外)。
为什么不用 FTP?多年来,Linux 常用捆绑的 FTP 服务器软件在安全性和性能方面取得了显着进步 (https://security.appspot.com/vsftpd.html#security)。 仍然很容易为自动登录的批量活动配置 FTP 客户端
echo machine a_server.com login YourName password
↪a_Password >> ~/.netrc
chmod 600 ~/.netrc
echo -e 'ls -l \n quit' | ftp a_server.com
不幸的是,这是一个糟糕的主意,并且随着时间的推移会变得越来越糟
-
在正常配置中,登录名、密码和文件有效负载都以明文形式在线路上传输,并且有许多实用程序可以捕获它们,这些实用程序可能会在不受信任的网络上使用。
-
在端口 21 上侦听的经典 FTP 服务器必须以 root 身份运行。 如果攻击者发现并利用漏洞,您的操作系统将属于他们。
-
在“主动” FTP 中,客户端和服务器在运行
connect()
和listen()
系统调用时切换角色。 这会导致 TCP 连接在两个方向上打开,从而给防火墙带来问题。 -
除非 FTP 服务器支持 chroot() 并且它是为目标用户单独且专门配置的,否则该用户能够递归地获取系统上具有世界读取权限的所有可访问文件。
-
为少量文件创建的 FTP 帐户可以让几乎所有内容都可见。 大多数现代 FTP 客户端都允许这种递归传输。 FTP 用户需要在服务器上的 /etc/passwd 中创建一个条目,以创建一个操作系统帐户。 如果管理不当,这将允许远程用户登录到 shell 或以其他方式获得不必要的访问权限。
-
密码老化通常在高安全性环境中是强制性的,需要在客户端和服务器上同步更改密码(通常在隔夜批量运行失败后)。
FTP 协议的后续修订确实添加了 TLS/SSL 加密功能,但实施它们是不明智的
man vsftpd.conf | col -b | awk '/^[ ]*ssl_enable/,/^$/'
ssl_enable
If enabled, and vsftpd was compiled against OpenSSL,
vsftpd will support secure connections via SSL. This
applies to the control connection (including login)
and also data connections. You'll need a client with
SSL support too. NOTE!! Beware enabling this option.
Only enable it if you need it. vsftpd can make no
guarantees about the security of the OpenSSL libraries.
By enabling this option, you are declaring that you
trust the security of your installed OpenSSL library.
上述警告的原因是,由于 FTP 服务器以 root 身份运行,因此它将加密库暴露给具有最高系统权限的远程连接。 多年来,加密安全漏洞层出不穷,这种配置有些危险。
OpenSSH 通信实用程序套件包括“sftp”客户端和服务器,但这还需要操作系统上的帐户以及用于批量使用的特殊密钥安装。 推荐的最佳密钥处理实践需要密码和代理的使用
我们推荐的用于无人值守 SSH 操作的最佳安全方法是公钥身份验证,密钥存储在代理中……代理方法确实有一个缺点:系统在重启后无法继续无人值守。 当主机再次自动启动时,批量作业将无法获得其密钥,直到有人出现重启代理并提供密码来加载密钥。—SSH,安全外壳,第二版,Daniel J. Barrett、Richard E. Silverman 和 Robert G. Byrnes。
那些因安全压力而盲目从 FTP 转向 sftp 的人并不了解密钥生成、ssh-agent 和 ssh-add 的复杂性。 将如此复杂的实用程序强加给试图从 FTP 迁移出来的普通人群肯定会以糟糕的结果告终。
OpenSSH 还在默认配置中扩展了客户端运行 shell 的能力。 可以将用户限制为仅文件传输并配置更高安全性的 chroot(),但必须对服务器配置进行大量修改才能实现此目的。 SSH 的主要重点是安全的交互式登录——文件传输是副业。 缺少“匿名”sftp 或密钥文件投放突显了这种(缺乏)关注。
经典的 Berkeley R-Utilities 包括一个用于远程文件复制的 rcp 程序。 这确实消除了明文密码,但几乎没有其他改进。 在现代系统中强烈反对使用这些实用程序,并且默认情况下不安装和配置它们。
以上所有程序都不能很好地用于从不受信任的来源接收文件时的安全批量文件复制,出于这些原因,让我们转向 RFC 1867。
chroot() 中的 thttpdRFC 1867 是 Web 页面上“文件上传小工具”背后的规范。 实现该小工具的 HTML 相对简单
<form action="script.cgi" enctype="multipart/form-data"
method="post">
<input type="file" name="Whatever">
<input type="submit" value="Upload">
</form>
各种浏览器以略有不同的外观呈现该小工具,但功能是相同的(图 1-3)。

图 1. Google Chrome

图 2. Microsoft Internet Explorer

图 3. Mozilla Firefox
对于本文,我将使用“curl”非图形化命令行工具来使用此协议执行文件传输。 由于 RFC 1867 协议是在 HTTP 上实现的,因此需要 Web 服务器。 这里的服务器软件选择将是非传统的,因为我将需要对 chroot() 系统调用的本机支持,该调用将正在运行的进程隔离在文件系统树中。 这可以防止访问 /sbin 中的强大程序以及存储在受限位置的任何其他敏感数据。
最近,对 OpenBSD 新邮件系统的代码审核中,大量使用 chroot() 和权限分离使其免于灾难 (http://undeadly.org/cgi?action=article&sid=20151013161745)
首先,从积极的方面来看,权限分离、chrooting 和消息传递设计已被证明在保护我们免受彻底灾难方面相当有效。 [最坏的] 攻击导致 [非特权] 进程被破坏,特权进程保持不变,队列进程也以单独的用户身份运行,从而防止数据丢失……这是一个好消息,我们并不完美,并且错误会潜入,但我们知道这些防线有效,它们确实大大减少了我们因错误而遭受的痛苦,将错误变成麻烦而不是彻底的灾难。 据我们所知,在此审核期间没有 root 用户受到伤害。
Linux 上的常见 Web 服务器 Apache 和 Nginx 多次拒绝实施本机 chroot() 安全性 (http://www.openbsd.org/papers/httpd-asiabsdcon2015.pdf)
OpenBSD 多年来一直在 chroot 中运行其 Web 服务器; Apache 和 nginx 已被修补为默认以 chroot 方式运行。 这些补丁从未被上游接受,但它们提供了显着的好处。
尽管这种拒绝排除了在高度安全应用程序中使用 Apache 和 Nginx,但最近更新的 sthttpd Web 服务器 (http://opensource.dyc.edu/sthttpd) 确实提供了此功能。 thttpd 缺少许多现代功能(FastCGI、SPDY 和 SSL/TLS),但本机 chroot() 胜过这些缺点。 以下是下载和安装它的步骤
wget ftp://opensource.dyc.edu/pub/sthttpd/sthttpd-2.27.0.tar.gz
tar xvzf sthttpd-2.27.0.tar.gz
cd sthttpd-2.27.0/
./configure
make install exec_prefix=/home/jail
mkdir /home/jail/etc
mkdir /home/jail/logs
mkdir /home/jail/htdocs
mkdir /home/jail/upload
chown nobody:nobody /home/jail/logs /home/jail/upload
echo 'port=80
dir=/home/jail
chroot
data_dir=/htdocs
#data_dir=/home/jail/httpd/htdocs
user=nobody
cgipat=**.xyz
pidfile=/home/jail/logs/thttpd.pid
logfile=/home/jail/logs/thttpd.log' > /home/jail/etc/thttpd.conf
请注意上面的 cgipat=**.xyz
用于执行符合通用网关接口 (https://en.wikipedia.org/wiki/Common_Gateway_Interface) 的程序。 thttpd 文档提到使用传统的 .cgi 扩展名,但我建议您选择自己的随机扩展名并重命名您部署的任何 CGI 应用程序,以使攻击者更难找到和利用它们。
安装 thttpd Web 服务器后,可以使用以下命令启动副本
/home/jail/sbin/thttpd -C /home/jail/etc/thttpd.conf
如果您将 Web 浏览器指向您的机器(首先尝试 https://127.0.0.1—您的防火墙规则可能会阻止您使用远程浏览器),您应该会看到目录列表
Index of /
mode links bytes last-changed name
dr-x 2 6 Oct 22 22:08 ./
dr-x 6 51 Oct 22 22:08 ../
如果您愿意,可以通过下载 BusyBox 副本来探索您的新 chroot() 环境。 BusyBox 是“微型” UNIX/POSIX 实用程序的静态链接集合,其中包含多个特定于 Linux 的工具。 当 BusyBox 二进制文件以使其没有外部库链接的方式准备时,它们非常适合在 chroot() 中运行
cd /home/jail/sbin
wget http://busybox.net/downloads/binaries/busybox-x86_64
chmod 755 busybox-x86_64
ln -s busybox-x86_64 sh
cd ../htdocs
echo 'Keep out! This means you!' > index.html
echo '#!/sbin/sh
echo Content-type: text/plain
echo ""
/sbin/busybox-x86_64 env
echo "---"
/sbin/busybox-x86_64 id
echo "---"
/sbin/busybox-x86_64 ls -l /
echo "---"
/sbin/busybox-x86_64' > script.xyz
chmod 755 script.xyz
首先请注意,index.html 会阻止目录列表。 确保您的 CGI 应用程序以这种方式受到保护,因此除非您选择将它们作为 <FORM>
操作公开,否则它们不会被看到。 还要观察到从 /sbin/busybox-x86_64 到 /sbin/sh 创建了一个软链接。 使用链接调用 BusyBox 会更改程序的行为并将其变成 Bourne shell。 该程序检查 $argv[0]
,如果内容与已编译到其中的“小程序”匹配,则 BusyBox 直接执行该小程序。
如果您现在使用浏览器加载 https://127.0.0.1/script.xyz,shell 脚本将运行,您应该会看到
GATEWAY_INTERFACE=CGI/1.1
SHLVL=1
REMOTE_ADDR=::1
HTTP_USER_AGENT=Mozilla/5.0 (X11; Linux x86_64; rv:38.0)
↪Gecko/20100101 Firefox/38.0
CGI_PATTERN=**.xyz
HTTP_ACCEPT=text/html,application/xhtml+xml,application/
↪xml;q=0.9,*/*;q=0.8
HTTP_HOST=localhost
SERVER_SOFTWARE=thttpd/2.27.0 Oct 3, 2014
PATH=/usr/local/bin:/usr/ucb:/bin:/usr/bin
HTTP_ACCEPT_LANGUAGE=en-US,en;q=0.5
SERVER_PROTOCOL=HTTP/1.1
HTTP_ACCEPT_ENCODING=gzip, deflate
REQUEST_METHOD=GET
PWD=/htdocs
SERVER_PORT=80
SCRIPT_NAME=/script.xyz
SERVER_NAME=localhost.localdomain
---
uid=99 gid=99 groups=99
---
total 0
drwxr-xr-x 2 0 0 24 Oct 22 22:08 etc
drwxr-xr-x 2 0 0 40 Oct 24 15:03 htdocs
drwxr-xr-x 2 0 0 40 Oct 22 22:10 logs
drwxr-xr-x 2 0 0 97 Oct 24 15:02 sbin
---
BusyBox v1.24.0.git (2015-10-04 23:30:51 GMT) multi-call binary.
BusyBox is copyrighted by many authors between 1998-2015.
Licensed under GPLv2. See source distribution for detailed
copyright notices.
Usage: busybox [function [arguments]...]
or: busybox --list[-full]
or: busybox --install [-s] [DIR]
or: function [arguments]...
BusyBox is a multi-call binary that combines many common
Unix utilities into a single executable. Most people will
create a link to busybox for each function they wish to
use and BusyBox will act like whatever it was invoked as.
Currently defined functions:
[, [[, acpid, add-shell, addgroup, adduser, adjtimex, arp,
arping, ash, awk, base64, basename, beep, blkid, blockdev,
bootchartd, bunzip2, bzcat, bzip2, cal, cat, catv, chat,
chattr, chgrp, chmod, chown, chpasswd, chpst, chroot, chrt,
chvt, cksum, clear, cmp, comm, conspy, cp, cpio, crond,
crontab, cryptpw, cttyhack, cut, date, dc, dd, deallocvt,
delgroup, deluser, depmod, devmem, df, dhcprelay, diff,
dirname, dmesg, dnsd, dnsdomainname, dos2unix, du, dumpkmap,
dumpleases, echo, ed, egrep, eject, env, envdir, envuidgid,
ether-wake, expand, expr, fakeidentd, false, fatattr, fbset,
fbsplash, fdflush, fdformat, fdisk, fgconsole, fgrep, find,
findfs, flock, fold, free, freeramdisk, fsck, fsck.minix,
fstrim, fsync, ftpd, ftpget, ftpput, fuser, getopt, getty,
grep, groups, gunzip, gzip, halt, hd, hdparm, head, hexdump,
hostid, hostname, httpd, hush, hwclock, i2cdetect, i2cdump,
i2cget, i2cset, id, ifconfig, ifdown, ifenslave, ifup, inetd,
init, insmod, install, ionice, iostat, ip, ipaddr, ipcalc,
ipcrm, ipcs, iplink, iproute, iprule, iptunnel, kbd_mode,
kill, killall, killall5, klogd, less, linux32, linux64, linuxrc,
ln, loadfont, loadkmap, logger, login, logname, logread,
losetup, lpd, lpq, lpr, ls, lsattr, lsmod, lsof, lspci, lsusb,
lzcat, lzma, lzop, lzopcat, makedevs, makemime, man, md5sum,
mdev, mesg, microcom, mkdir, mkdosfs, mke2fs, mkfifo,
mkfs.ext2, mkfs.minix, mkfs.vfat, mknod, mkpasswd, mkswap,
mktemp, modinfo, modprobe, more, mount, mountpoint, mpstat,
mt, mv, nameif, nanddump, nandwrite, nbd-client, nc, netstat,
nice, nmeter, nohup, nslookup, ntpd, od, openvt, passwd, patch,
pgrep, pidof, ping, ping6, pipe_progress, pivot_root, pkill,
pmap, popmaildir, poweroff, powertop, printenv, printf, ps,
pscan, pstree, pwd, pwdx, raidautorun, rdate, rdev, readahead,
readlink, readprofile, realpath, reboot, reformime, remove-shell,
renice, reset, resize, rev, rm, rmdir, rmmod, route, rpm,
rpm2cpio, rtcwake, run-parts, runsv, runsvdir, rx, script,
scriptreplay, sed, sendmail, seq, setarch, setconsole, setfont,
setkeycodes, setlogcons, setserial, setsid, setuidgid, sh,
sha1sum, sha256sum, sha3sum, sha512sum, showkey, shuf, slattach,
sleep, smemcap, softlimit, sort, split, start-stop-daemon, stat,
strings, stty, su, sulogin, sum, sv, svlogd, swapoff, swapon,
switch_root, sync, sysctl, syslogd, tac, tail, tar, tcpsvd, tee,
telnet, telnetd, test, tftp, tftpd, time, timeout, top, touch,
tr, traceroute, traceroute6, true, truncate, tty, ttysize,
tunctl, ubiattach, ubidetach, ubimkvol, ubirmvol, ubirsvol,
ubiupdatevol, udhcpc, udhcpd, udpsvd, uevent, umount, uname,
unexpand, uniq, unix2dos, unlink, unlzma, unlzop, unxz, unzip,
uptime, usleep, uudecode, uuencode, vconfig, vi, vlock,
volname, watch, watchdog, wc, wget, which, whoami, whois, xargs,
xz, xzcat, yes, zcat, zcip
关于上面每个部分,有几件事需要指出
-
如果您从 GET 方法表单引用了第一个部分中的环境,则该环境将包含
QUERY_STRING
,也就是说,如果您将 ?abc=123 附加到 URL,您将看到QUERY_STRING=abc=123
作为标准 GET 方法参数。 -
上面的用户 99 实际上在测试系统的本地 /etc/passwd 中定义为 nobody。 因为 chroot() 中没有 /etc/passwd 文件,所以所有用户 ID 都将以数字形式表示。 如果您出于某种原因希望用户解析为名称,请在 jail 中的单独 passwd 文件副本中定义这些用户。
-
很明显,上面的根目录被限制在 jail 中。 这些文件也正在解析为数字所有权—如果在 passwd jail 文件中放置 root 的条目,则将出现命名所有者。
BusyBox 对于探索 chroot() 很有用,但不应将其留在生产服务器上,因为它引入了过多的功能。 这在 thttpd 网站上得到了证实,其中包含关于 jail 内容的警告 (http://www.acme.com/software/thttpd/notes.html)
另外:实际上有可能突破 chroot jail。 以 root 身份运行的进程,无论是通过 setuid 程序还是某些安全漏洞,都可以将其自身的 chroot 树更改为下一个更高级别的目录,并根据需要重复以到达文件系统的顶部。 因此,chroot 树必须仅仅被认为是多层纵深防御的一个方面。 如果您的 chroot 树中有足够的工具供破解者获得 root 访问权限,那么它就不好; 因此,您希望将内容保持在最低限度。 特别是,不要包含任何 setuid-root 可执行文件!
最近的“Towelroot”漏洞演示了一个普通的 C 程序,该程序编译成一个二进制可执行文件,没有任何特殊权限,但能够通过利用互斥锁错误在许多 Linux 系统上将权限提升为 root。 如果您的 jail 包含下载二进制镜像并将其标记为可执行文件的能力,则此类缺陷可能会允许攻击者突破 jail 并获得您系统的所有权。 请注意不要提供此类工具。
如果您想从主机操作系统复制实用程序以在 jail 中使用,可以使用 ldd
命令查找它们的共享对象依赖项。 例如,要将 GNU AWK 的功能副本移动到 jail 中,请检查依赖对象
# ldd /bin/gawk
linux-vdso.so.1 => (0x00007ffe9f488000)
libdl.so.2 => /lib64/libdl.so.2 (0x00007f7033e38000)
libm.so.6 => /lib64/libm.so.6 (0x00007f7033b36000)
libc.so.6 => /lib64/libc.so.6 (0x00007f7033776000)
/lib64/ld-linux-x86-64.so.2 (0x00007f7034053000)
这些对象目标通常是软链接,需要移动一系列文件和链接才能重新创建,如下面的库所示
# ll /lib64/libdl.so.2
lrwxrwxrwx. 1 root root 13 Mar 10 2015
↪/lib64/libdl.so.2 -> libdl-2.17.so
# ll /lib64/libdl-2.17.so
-rwxr-xr-x. 1 root root 19512 Mar 6 2015
↪/lib64/libdl-2.17.so
要在 Oracle Linux 7.1 上复制这些对象并重新创建它们的链接,请按照以下步骤操作
mkdir /home/jail/lib64
cd /home/jail/lib64
cp /lib64/libdl-2.17.so .
ln -s libdl-2.17.so libdl.so.2
cp /lib64/libm-2.17.so .
ln -s libm-2.17.so libm.so.6
cp /lib64/libc-2.17.so .
ln -s libc-2.17.so libc.so.6
cp /lib64/ld-2.17.so .
ln ld-2.17.so ld-linux-x86-64.so.2
然后,复制 gawk 二进制文件并创建一个测试脚本
cp /bin/gawk /home/jail/sbin
echo '#!/sbin/gawk -f
BEGIN {
print "Content-type: text/plain"
print ""
print "Hello, world!"
print ""
for(x in ENVIRON) print x,ENVIRON[x]
}' > /home/jail/htdocs/awk.xyz
chmod 755 /home/jail/htdocs/awk.xyz
如果您加载 https://127.0.0.1/awk.xyz,您将看到上面脚本的输出。 这意味着,有了添加的库,即使您删除了 BusyBox,您也可以随意使用 GNU AWK 编写 CGI 脚本
Hello, world!
HTTP_ACCEPT text/html,application/xhtml+xml,
↪application/xml;q=0.9,*/*;q=0.8
AWKPATH .:/usr/share/awk
REMOTE_ADDR ::1
HTTP_ACCEPT_ENCODING gzip, deflate
SERVER_PORT 80
SERVER_PROTOCOL HTTP/1.1
HTTP_ACCEPT_LANGUAGE en-US,en;q=0.5
CGI_PATTERN **.xyz
SCRIPT_NAME /awk.xyz
HTTP_HOST localhost
GATEWAY_INTERFACE CGI/1.1
SERVER_SOFTWARE thttpd/2.27.0 Oct 3, 2014
SERVER_NAME localhost.localdomain
PATH /usr/local/bin:/usr/ucb:/bin:/usr/bin
HTTP_USER_AGENT Mozilla/5.0 (X11; Linux x86_64;
↪rv:38.0) Gecko/20100101
Firefox/38.0
REQUEST_METHOD GET
GNU AWK 不是最好的例子,因为它确实提供网络连接。 Brian Kernighan 的“One True AWK”可能是一个更好的选择,因为它缺少扩展的网络功能。
让我们考虑其他启动参数,使用 systemd 来控制 thttpd 服务器。 如果您没有 systemd,请检查以下单元文件并使用您的 init 系统复制它。 首先,如果您仍在运行 thttpd,请将其杀死
kill $(</home/jail/logs/thttpd.pid)
然后,指示 systemd 启动它
echo "[Unit]
Description=thttpd web service
After=syslog.target
[Service]
ExecStart=/bin/ksh -c 'ulimit -H -f 48828; ulimit
↪-H -m 48828; /home/jail/sbin/thttpd -C
↪/home/jail/etc/thttpd.conf'
Type=forking
#Restart=always
[Install]
WantedBy=multi-user.target" > /etc/systemd/system/
↪thttpd.service
systemctl start thttpd.service
请注意上面 Korn shell 在启动 thttpd 之前执行的 ulimit
命令(您可能需要安装此 shell)
ulimit -H -f 48828
ulimit -H -m 48828
这些命令设置了 thttpd 及其所有子进程可以使用的最大内存和写入文件的最大限制。 这些以 1,024 字节的块为单位指定,相当于 50 兆字节的最大使用量。 这些是无法提高的硬限制。 在下一节中,设置这些限制的原因将变得清楚。
thttpd Web 服务器在能够记录系统日志时会记录活动,但在 chroot() 中运行时,/dev/log 套接字不存在,除非手动创建。 可以指示 rsyslog dæmon 侦听 /home/jail/dev/log 中的附加套接字,如下所示
echo '$ModLoad imuxsock
$AddUnixListenSocket /home/jail/dev/log
$umask 0000' > /etc/rsyslog.d/thttpd.conf
mkdir /home/jail/dev
chmod 755 /home/jail/dev
chcon -v --type=device_t /home/jail/dev
systemctl restart rsyslog.service
systemctl restart thttpd.service
重新启动后,您应该在 /var/log/messages 中看到 thttpd 条目。 如果您在运行使用 sysklogd 的旧版 Linux 系统,则以下选项将对您有利
-a socket:使用此参数,您可以指定 syslogd 必须侦听的附加套接字 [原文如此]。 如果您要让某些 dæmon 在 chroot() 环境中运行,则需要这样做。 您最多可以使用 19 个附加套接字。 如果您的环境需要更多,则必须增加 syslogd.c 源文件中的符号 MAXFUNIX。
您可能还会发现将 /home/jail/sbin/thttpd 二进制文件移动或复制到 chroot() 外部的位置很有用。 如果 jail 中保留了副本,则可以在启动时对其进行测试,并将其与受保护的副本进行比较。 如果文件不同,您的启动脚本可以发送警报,提示您的 jail 已被入侵。 thttpd.conf 文件可能也会受到类似的处理。
Upload.cgi2000 年,Jeroen C. Kessels 发布了 Upload-2.6.tar.gz,您可以轻松地使用主要的搜索引擎找到它。 尽管该软件相当古老,但它可能是 RFC 1867 最简洁的实现(从系统资源的角度来看)。
假设您有 Upload-2.6.tar.gz 的副本,请使用以下命令运行默认编译
tar xvzf Upload-2.6.tar.gz
cd Upload-2.6/sources/
make
ldd upload
请注意,不应在不受信任的软件上以 root 身份运行 ldd
命令,如手册页中所述(以常规的非 root 用户身份运行构建)。
上面的最终 ldd
将列出二进制文件的共享对象依赖项
linux-vdso.so.1 => (0x00007ffcbe5e1000)
libc.so.6 => /lib64/libc.so.6 (0x00007fbeffdad000)
/lib64/ld-linux-x86-64.so.2 (0x00007fbf00183000)
如果您之前为 GNU AWK 加载了库,您将拥有运行此程序在 chroot() 中所需的所有共享对象。 如果您没有选择将共享对象的副本放在 /home/jail/lib64 中,请使用静态链接重新编译该程序(假设您的编译器能够做到这一点—某些发行版缺少静态 libc.a)
gcc -static -O -o upload.static upload.c
将您选择的二进制文件复制到 /home/jail,并设置配置
cp upload /home/jail/upload.cgi
cp ../html/BadPage.html /home/jail/htdocs/test-fail.html
cp ../html/OkPage.html /home/jail/htdocs/test-good.html
sed 's/action=[^ ]*/action="test.xyz"/' ../html/index.html > \
/home/jail/htdocs/test.html
cd /home/jail/htdocs
ln -s ../upload.cgi test.xyz
echo 'Config = Default
Root = /upload
FileMask = *
IgnoreSubdirs = YES
Overwrite = YES
LogFile = /logs/upload.log
OkPage = /htdocs/test-good.html
BadPage = /htdocs/test-fail.html
Debug = 0' > test.cfg
如果您现在将浏览器指向 https://127.0.0.1/test.html,您将看到一个文件上传表单; 使用随机文件进行测试。 如果运气好的话,您应该会看到一个成功页面,并且您传输的文件应该出现在 /home/jail/upload 中。 您还应该在 /home/jail/logs/upload.log 中看到传输日志。
您可以使用 curl 二进制文件通过此机制进行批量传输—例如
curl -F file=@/etc/passwd https://127.0.0.1/test.xyz
Curl 应该将 HTML 返回到您的标准输出
<html>
<body>
<center>
<h1>Success!</h1>
<hr>
File uploaded: passwd<br>
Bytes uploaded: 2024
<p>
</center>
</body>
</html>
在这种情况下,上传的文件被配置为存储在 /home/jail/upload 中
# ll /home/jail/upload
total 1012
-rw-r--r--. 1 nobody nobody 1028368 Oct 25 10:26 foo.txt
-rw-r--r--. 1 nobody nobody 2024 Oct 25 10:29 passwd
这种配置非常强大,因为它消除了客户端浏览您的文件存储的能力(如果您选择这样做)。 在 FTP 或其后代中,PUT
到批量目录的能力也授予 GET
; 使用此机制,您可以将客户端限制为仅传输,而没有能力编目或检索任何内容。
此上传程序的一个潜在问题是内存消耗。 让我们检查 upload.c 的源代码
/* Allocate sufficient memory for the incoming data. */
Content = (char *)malloc(InCount + 1);
...
p1 = Content;
RealCount = 0;
/* For some reason fread() of Borland C 4.52 barfs if the
bytecount is bigger than 2.5Mb, so I have to do it
like this. */
while (fread(p1++,1,1,stdin) == 1) {
RealCount++;
if (RealCount >= InCount) break;
}
*p1 = '\0';
您可以在上面看到,整个文件是从网络(从标准输入)读取并存储在内存中的。 这可能是一个潜在的“拒绝服务”漏洞,因此在前一节末尾设置了 50mb ulimits。 调整这些 ulimits 以满足您的需求,但要防止滥用。 也可以使用 tmpfile()
函数假脱机到磁盘而不是内存,但这需要对 C 代码进行大量修改。
由于 upload.cgi 后面没有太多代码,因此相对容易扩展。 考虑 ShowOkPage()
函数的以下附加块
if (strnicmp(p1,"<insert sha256sum>",18) == 0) {
char scratch[BUFSIZ];
FILE *H;
*p1 = '\0';
sprintf(scratch,"%s%s",Root,LastFileName);
strcpy(s1, "/sbin/sha256sum '"); strcat(s1, scratch);
↪strcat(s1, "'");
if((H = popen(s1, "r")) != NULL && fgets(scratch, BUFSIZ,
↪H) != NULL)
{ sprintf(s1,"%s%s%s",Line,scratch,p1+18); strcpy(Line,s1);
↪fclose(H); }
}
if (strnicmp(p1,"<insert md5sum>",15) == 0) {
char scratch[BUFSIZ];
FILE *H;
*p1 = '\0';
sprintf(scratch,"%s%s",Root,LastFileName);
strcpy(s1, "/sbin/md5sum '"); strcat(s1, scratch);
↪strcat(s1, "'");
if((H = popen(s1, "r")) != NULL && fgets(scratch,
↪BUFSIZ, H) != NULL)
{ sprintf(s1,"%s%s%s",Line,scratch,p1+15);
↪strcpy(Line,s1); fclose(H); }
}
如果您在模板中指定,编译这些片段允许您选择性地报告从客户端接收的磁盘数据的 md5 和 sha256 签名,从而使客户端能够确认服务器的磁盘数据是否正确
$ curl -F file=@Upload-2.6.tar.gz
↪https://127.0.0.1/test.xyz
<html>
<body>
<center>
<h1>Success!</h1>
<hr>
File uploaded: Upload-2.6.tar.gz<br>
Bytes uploaded: 2039<br>
sha256sum: bed3540744b2486ff431226eba16c487dcdbd4e60
↪2268349fdf8b7f1fb25ad38
/upload/Upload-2.6.tar.gz
<br>
md5sum: d703c20032d76611e7e88ebf20c3687a
↪/upload/Upload-2.6.tar.gz
<p>
</center>
</body>
</html>
任何标准文件传输工具中都不提供此类数据验证功能,但由于该方法灵活,因此在此旧代码中很容易实现。 对于 FTP 来说,在文件接收时添加自定义处理将是一场噩梦,但对于 upload.cgi 来说,这相对简单。 Solaris ZFS 中也呼应了对此类功能的需求,它对所有磁盘写入执行积极的校验和—控制器固件会出错,并且对于某些应用程序来说,报告此类错误是强制性的。 另请注意上面 Jeroen C. Kessels 包的签名,并进一步警告 md5 签名容易受到篡改 (http://www.mathstat.dal.ca/~selinger/md5collision)—它们对于检测介质错误很有用,但它们不能保证数据免受恶意篡改。
对 upload.c 代码的其他有用更改包括始终添加到从 .CFG 文件读取的传入文件名的前缀(我在三行中添加了此功能),以及用来自 OpenBSD libc 的更安全的 strlcat()
/strlcpy()
替换 strcat()
/strcpy()
函数 (http://www.sudo.ws/todd/papers/strlcpy.html)。
在 C 编程语言中还有一个扩展的 CGI 处理库 (http://www.boutell.com/cgic),由 Tom Boutell(GD 图形库的作者)编写。 CGI-C 库提供更高效的 RFC 1867 处理(而不会过度消耗内存),但使用它构建应用程序超出了本文的讨论范围。
Stunnel TLS由于 thttpd 没有加密支持,因此居住在加密合法的地区 (http://www.cryptolaw.org) 的人可以使用 Michal Trojnara 的 stunnel “shim”网络加密 dæmon 在端口 443 上提供 https 服务。 首先,从标准 Oracle Linux 存储库安装该软件包
yum install stunnel
您也可以从源代码安装 stunnel。 yum 拉取的软件包实际上非常旧(4.56,比当前的 5.25 低一个主要版本),但它也包括 SELinux 的 stunnel 安全上下文,因此建议您安装该软件包,即使您计划构建较新的版本。
安装后,stunnel 将需要 TLS 的密钥对。 如果您愿意,密钥的公共部分可以由证书颁发机构 (CA) 签名,这将允许与大多数浏览器进行无错误操作。 您也可以使用“自签名”密钥,该密钥会显示浏览器错误,但会允许加密。
在您阅读本文时,应该可以从 Let's Encrypt 项目的网站 () 获得免费的签名 SSL 证书。 有关如何生成和维护被大多数浏览器视为有效的签名密钥的说明应很快出现。 Let's Encrypt 网站上的初步文档表明,这些工具将使用 .PEM 文件,stunnel 可能会使用这些文件。
如果您想为 stunnel 购买有效密钥,stunnel 网站上有一个关于让 CA 签署您的密钥的指南 (https://www.stunnel.org/howto.html)。
对于更非正式的用途,您可以使用以下命令生成自签名密钥
cd /etc/pki/tls/certs
make stunnel.pem
密钥生成过程将询问许多问题
Generating a 2048 bit RSA private key
........................................+++
.................................+++
writing new private key to '/tmp/openssl.hXP3gW'
-----
You are about to be asked to enter information that will
be incorporated into your certificate request.
What you are about to enter is what is called a
Distinguished Name or a DN. There are quite a few fields
but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [XX]:US
State or Province Name (full name) []:IL
Locality Name (eg, city) [Default City]:Chicago
Organization Name (eg, company)
↪[Default Company Ltd]:ACME Corporation
Organizational Unit Name (eg, section) []:Widget Division
Common Name (eg, your name or your server's hostname)
↪[]:darkstar
Email Address []:linus@posix.org
上面生成的密钥将被设置为在创建之日起 365 天后过期。 如果您想生成寿命更长的密钥,可以直接调用 openssl
openssl req -new -x509 -days 3650 -nodes -out
↪stunnel.pem -keyout stunnel.pem
密钥将如下所示(已缩写)
# cat /etc/pki/tls/certs/stunnel.pem
-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC23m+w0BLxI2zB
/p8/TiuFcEurTLbLCQwcO/FE+vNcJpddckuF6/VgpBAJk+d9i7NZNqrjMH711H18
3AYhewZTCbRUMQE3ndaYEIxSt4Qhbm8XbfUfx6Fmg4CnWh/XzE7B8Z7XbHpwRQ4d
kQOzICzb1nt96QKdWoAob73+hv7qdi3UjJ3/20z3Cx5LWfWoa32Y50//tvBjBtcQ
H7QpiE2tfLWHTQ5tztkqVY/MZJWVgoT5LnqQlZeZB/C4izSYNo9EGAnw4ThaFJ/y
NdvmyK6sYaO3Dq4eFO78O+zzqyfhPCtcfb8lMuRTZa8uiv7ziVf0A3eGSwKYonUf
...
-----END PRIVATE KEY-----
-----BEGIN CERTIFICATE-----
MIID/TCCAuWgAwIBAgIJALT/9skCvdR5MA0GCSqGSIb3DQEBCwUAMIGUMQswCQYD
VQQGEwJVUzELMAkGA1UECAwCSUwxEDAOBgNVBAcMB0NoaWNhZ28xGTAXBgNVBAoM
EEFDTUUgQ29ycG9yYXRpb24xGDAWBgNVBAsMD1dpZGdldCBEaXZpc2lvbjERMA8G
A1UEAwwIZGFya3N0YXIxHjAcBgkqhkiG9w0BCQEWD2xpbnVzQHBvc2l4Lm9yZzAe
Fw0xNTEwMzAwMzI2NTJaFw0yNTEwMjcwMzI2NTJaMIGUMQswCQYDVQQGEwJVUzEL
MAkGA1UECAwCSUwxEDAOBgNVBAcMB0NoaWNhZ28xGTAXBgNVBAoMEEFDTUUgQ29y
cG9yYXRpb24xGDAWBgNVBAsMD1dpZGdldCBEaXZpc2lvbjERMA8GA1UEAwwIZGFy
/JMRW5oa/+TFZIRcacTxgAw=
...
-----END CERTIFICATE-----
上面的 PRIVATE KEY 部分是文件中最敏感的部分; 确保不被您不信任的任何人看到或复制,并且备份介质上的任何记录都应加密。 BEGIN CERTIFICATE
部分在 TLS 客户端连接到 stunnel 时呈现给它们。
按照 stunnel 手册页的指导,为 Diffie-Hellman 密钥交换算法计算自定义素数也是明智之举
openssl dhparam 2048 >> stunnel.pem
前面的命令将在您的 stunnel.pem 文件中添加另一个部分,如下所示
-----BEGIN DH PARAMETERS-----
MIIBCAKCAQEAoHi5jzY5ZVwGCFFm1EhVsePXxNwCSs/eQbaC3rc+iXENL8xk21uq
6eSwYIQWUeDN/h6wBBDe6dpFoNDJQeqKCmUa8aojGHnkcqsJBdVUKVF5/7rWb1Yi
TzvbeZt8UvYnNUErJEpgBMiKPDYipE2BZ6k61WwkK6WV6svGAHpIc3o/9kU+72uf
dPFaNIygAb2HLaJYvXq9OYGvrMsmyZTh3fnpg2RiZSVJf+i4BfyeLiYkwnSZozAS
2rQ4hf2E5WY6jiAcNZBLKvqR8lUuIaXd9+VkiCSV0c2pXzb2ElxOk8sheAHliwip
SaKC694z9l63eNKQW2J4WI97wkil0qa4MwIBAg==
-----END DH PARAMETERS-----
一旦密钥位于 /etc/pki/tls/certs/stunnel.pem 中,就必须创建一个 stunnel 配置文件,如下所示
echo 'FIPS = no
options = NO_SSLv2
options = NO_SSLv3
options = CIPHER_SERVER_PREFERENCE
ciphers =
ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+
↪AES128:DH+AES:ECDH+3DES:DH+3DES:RSA+AESGCM:
↪RSA+AES:RSA+3DES:!aNULL:!MD5:!DSS
syslog = yes
#debug = 6 #/var/log/secure
chroot = /var/empty
setuid = nobody
setgid = nobody
cert = /etc/pki/tls/certs/stunnel.pem
connect = 127.0.0.1:80' > /etc/stunnel/https.conf
上面的密码设置来自 Hynek Schlawack 关于该主题的网站 (https://hynek.me/articles/hardening-your-web-servers-ssl-ciphers),它们代表了当前 TLS 加密的最佳实践。 最好不时访问此站点以获取有关 TLS 的新闻和建议,或者可以考虑关注 Hynek 的 Twitter feed。
上面的 FIPS
和 NO_SSL
选项是 stunnel 版本 5 的默认设置。 如果您正在运行与 Oracle Linux 捆绑的 4.56 版本软件包,则必须为最佳实践 TLS 提供它们。
上面的配置将 stunnel 设置为 inetd 样式的服务,该服务为每个连接启动。 每个进程都将在 /var/empty 中的 chroot() 中。 也可以将 stunnel 作为常驻 dæmon 运行,该 dæmon 为每个新客户端派生。 如果您这样做,请记住每次 OpenSSL 更新到达时都重新启动 dæmon,并且 chroot() 可能需要更仔细的准备。 如果您使用 inetd 方法,更新将在更新后立即应用于所有新连接,但您必须注意不要在高使用率下超过 NPROC。 以 inetd 样式运行会带来性能损失,但对于除重度使用以外的所有情况,安全管理的简易性可能都是值得的。
以下命令配置 stunnel 以在 systemd 下进行 inetd 样式套接字激活
echo '[Unit]
Description=https stunnel
[Socket]
ListenStream=443
Accept=yes
[Install]
WantedBy=sockets.target' > /etc/systemd/system/https.socket
echo '[Unit]
Description=https stunnel service
[Service]
ExecStart=-/usr/bin/stunnel /etc/stunnel/https.conf
StandardInput=socket' > /etc/systemd/system/https@.service
systemctl enable https.socket
systemctl start https.socket
此时,使用浏览器访问 https://127.0.0.1,您将看到您的索引页。 访问 https://127.0.0.1/test.html,您可以通过安全通道上传。 您也可以使用 curl
curl -k -F file=@/etc/group https://127.0.0.1/test.xyz
请注意上面的 -k
选项,该选项禁用客户端 CA 对服务器证书的验证。 如果您使用的是自签名密钥,或者您的 curl 二进制文件无法访问适当的 CA 存储库(由您的操作系统提供),则需要此选项
<html>
<body>
<center>
<h1>Success!</h1>
<hr>
File uploaded: group<br>
Bytes uploaded: 842<br>
sha256sum: 460917231dd5201d4c6cb0f959e1b49c101ea
↪9ead3ab91e904eac4d758ebad4a
/upload/group
<br>
md5sum: 31aa58285489369c8a340d47a9c8fc49
↪/upload/group
<p>
</center>
</body>
</html>
如果您使用的是使用 xinetd 的旧版 Linux 发行版,则此配置可能会很有用
service https
{
disable = no
socket_type = stream
wait = no
user = root
server = /usr/sbin/stunnel
server_args = /etc/stunnel/https.conf
}
如果您处于仍在使用 inetd 的环境中,则此行将启用 stunnel
https stream nobody nowait root /usr/sbin/stunnel
↪stunnel /etc/stunnel/https.conf
如果您在使用 stunnel 时遇到问题,请尝试使用 telnet 连接到端口 443—您可能会在那里看到有用的状态消息。 例如
# cd /etc/stunnel/
# mv https.conf https.conf.tmp
# busybox-x86_64 telnet localhost 443
Clients allowed=500
stunnel 4.56 on x86_64-redhat-linux-gnu platform
Compiled/running with OpenSSL 1.0.1e-fips 11 Feb 2013
Threading:PTHREAD Sockets:POLL,IPv6 SSL:ENGINE,OCSP,
↪FIPS Auth:LIBWRAP
Reading configuration from file
↪/etc/stunnel/https.conf
Cannot read configuration
Syntax:
stunnel [] ] -fd | -help | -version | -sockets
- use specified config file
-fd - read the config file from a file descriptor
-help - get config file help
-version - display version and defaults
-sockets - display default socket options
str_stats: 1 block(s), 24 data byte(s), 58 control byte(s)
Connection closed by foreign host
请注意,如果您引用的文件(密钥或配置文件)不在上面的标准路径中,则 Oracle Linux 7.1 上“强制执行”的 SELinux 可能会拒绝读取权限。 如果您在系统日志中看到此类错误,请尝试应用以下命令
chcon -v --type=stunnel_etc_t /alternate/path/to/https.conf
chcon -v --type=stunnel_etc_t /alternate/path/to/stunnel.pem
如果您的本地 Linux 防火墙已启用,则可以在 https 上打开 stunnel 的端口并允许远程浏览器连接。 如果您保持端口 80 关闭,则您正在为所有连接强制执行 TLS 加密通信
iptables -I INPUT -p tcp --dport 443 --syn -j ACCEPT
请注意,使用 stunnel 的 https 的一个缺点是上面 CGI 脚本中显示的 REMOTE_ADDR
环境变量将始终设置为 127.0.0.1。 如果您想从 thttpd 日志中确定特定连接或传输的来源,则必须将它们与 stunnel 连接日志交叉引用。 但是,此属性可能对 upload.cgi 有用—如果 getenv("REMOTE_ADDR") != "127.0.0.1"
,您应该调用 exit()
函数。 最终效果是,该网站可以在端口 80 上以明文形式可见,也可以通过端口 443 上的 TLS 以明文形式可见,但如果尝试以明文形式上传文件,则会失败。
最后,如果您的客户端必须确保服务器的身份,但您不想获得签名证书,则可以在客户端上运行远程 stunnel,该 stunnel 强制验证特定密钥。
从服务器的 stunnel .PEM 中提取并保存 CERTIFICATE
部分(缩写如下)
-----BEGIN CERTIFICATE-----
MIID/TCCAuWgAwIBAgIJALT/9skCvdR5MA0GCSqGSIb3DQEBCwUAMIGUMQswCQYD
VQQGEwJVUzELMAkGA1UECAwCSUwxEDAOBgNVBAcMB0NoaWNhZ28xGTAXBgNVBAoM
EEFDTUUgQ29ycG9yYXRpb24xGDAWBgNVBAsMD1dpZGdldCBEaXZpc2lvbjERMA8G
A1UEAwwIZGFya3N0YXIxHjAcBgkqhkiG9w0BCQEWD2xpbnVzQHBvc2l4Lm9yZzAe
VYckA2gQ+70yXXxpFSD4n2ecq3ebNtej07zR2wAtAkt/JtuGiUjbl1m4ZFTPoTwr
...
xDYMcezEgopMzYMihv6CQ0CEU+qL+92CYtEDsd1hzn74SlBK9HMKjMLrbBZPhbE4
/JMRW5oa/+TFZIRcacTxgAw=
-----END CERTIFICATE-----
将此文件传输到您的客户端,并设置客户端的 stunnel 配置文件
echo 'FIPS = no
client = yes
verify = 4
cafile = /path/to/publickey.pem
[client-https]
accept = 127.0.0.1:65432
connect = your.remote.server.com:443' >
↪stunnel-verify.conf
上面的配置将在 Windows 和各种其他平台上运行。 如果您在 UNIX 变体上运行,请考虑以与服务器上设置的类似方式添加 chroot() 选项。 但是,请注意,如果您打算使用 HUP 信号重新加载 stunnel 配置,则必须将您限制 stunnel 的 chroot() 内的所有必需文件复制到其中。 尽管这可能永远不会在 inetd 样式配置中完成,但这却是 chroot() 操作的几个缺点之一。
然后,客户端可以将他们的浏览器指向 https://127.0.0.1:65432,他们将通过 TLS 路由到远程 Web 服务器。 curl 实用程序也可以类似地在明文中使用本地 65432 端口,从而允许 stunnel 处理 TLS 会话。
当客户端连接启动时,客户端 stunnel 将打开与服务器端口 443 的连接,然后彻底执行服务器的密钥,以确保正确的身份并防止“中间人”拦截传输的数据。
curl 实用程序确实有许多选项可以导入证书存储,但它似乎无法像我刚才用 stunnel 演示的那样验证特定证书。
stunnel 的作者还指出,rsync 实用程序和协议可以用于匿名写入访问。 rsync 的标准网络配置是明文,但 rsync 也可以包装在 OpenSSH 或 stunnel 中。 有关通过 stunnel 使用 rsync 传输文件的过时指南,请在此处找到:http://www.netbits.us/docs/stunnel_rsync.html。 RFC 1867 的一个好处是,curl 是命令行传输所需的唯一实用程序; 将 rsync 二进制文件包装在 stunnel 二进制文件提供的服务中需要更复杂的配置。
致谢特别感谢 stunnel 的作者 Michal Trojnara,感谢他对本文的有用评论以及他在 stunnel 开发方面的更伟大的工作。 可以从他的组织获得 stunnel 的商业支持、许可和咨询。 请访问 http://www.stunnel.org/support.html 以获取他的最新版本。
赶紧降级经典的 UNIX 文件传输实用程序在确保身份、完整性和隐私方面非常不足。 FTP 在这些问题上完全失败,以至于唯一可以证明其继续使用的理由是通常熟悉命令集。 令人难以置信的是,现代 IT 长期以来一直受到如此限制。
我们需要一个文件传输协议,它具有临时投放、完整性检查、加密、权限分离(TLS 状态机和 HTTP 文件处理)、chroot() 安全性和用于到达时自定义处理的挂钩。 没有主流实用程序提供所有这些功能。
在这样的时间到来之前,RFC 1867 将允许您“滚动您自己的”传输。 虽然此协议有许多实现(Perl、PHP、Python 等),但很少有发现具有 chroot() 安全性的。 希望情况不会一直是这样。