强制 cURL 从环境中获取密码
Posted
技术标签:
【中文标题】强制 cURL 从环境中获取密码【英文标题】:Forcing cURL to get a password from the environment 【发布时间】:2016-02-21 01:28:22 【问题描述】:这个question about using cURL with a username and password 对我的回答并不理想:
curl -u "user:pw" https://example.com
将 pw 放入进程列表中
curl "https://user:pw@example.com"
将密码放入进程列表中
curl -u "user:$(cat ~/.passwd)" https://example.com
将密码放入进程列表中
curl -u user https://example.com
提示输入密码
curl --netrc-file ~/.netrc https://example.com
需要文件
#4 是安全的,但我可能每天运行这个命令数百次,所以很乏味。 #5 接近安全,但该文件可以被具有 root 访问权限的人读取。
cURL man page 说(注意粗体字):
-u/--user <user:password>
指定用于服务器身份验证的用户名和密码。 覆盖
-n/--netrc
和--netrc-optional
。如果你只给出用户名(不输入冒号)curl 会 提示输入密码。
如果您使用支持 SSPI 的 curl 二进制文件并进行 NTLM 身份验证,您 可以强制 curl 从您的环境中获取用户名和密码 只需使用此选项指定一个冒号即可:
-u :
。
我尝试在环境中设置$USER
和$PASSWORD
(以及$CURLOPT_PASSWORD
和其他),但是当以curl -u : https://example.com
调用时,cURL 不会选择其中任何一个(如果没有-u :
)。
我没有做 NTLM,所以这不起作用。除非我错过了什么。
有没有办法仅通过环境将凭据传递给curl
?
(解决方法移至答案)
【问题讨论】:
您不需要 perl,因为您也可以在那里使用 shell sn-p 但请注意,root
的进程环境与文件一样多(尽管对于可能的时间范围更短)。
SSPI 和 NTLM 都是 Windows 技术。大概curl
手册页不是在谈论从 POSIX 风格的环境中检索密码。
我认为 declare -p USER | sed 's/^[^=]*="//;s/"[^"]*$//;s/\\"/"/'
满足但肯定不比 perl 等解决方案更好。 heredocs/herestrings 是否显示在命令中? (我不记得了。)如果不是这些也可以。
我花了 29 秒的时间将 curl
替换为一个包装脚本,该脚本对原始二进制文件进行了 strace 转储,绕过了包括 #4 在内的所有这些技术。按照设计,您不能从本地根目录中隐藏。
可能是--netrc-file <(cat <<<"machine $SRV login $USER password $PASSWORD")
之类的东西?但是,一旦您遇到了子外壳/等。避免sed
并不值得。
【参考方案1】:
有没有办法仅通过环境将凭据传递给
curl
?
不,我认为没有。
CURLOPT_USERPWD 文档我认为描述了您需要的内容,但这是一个可以使用 curl 库以其他语言提供的选项。 php、Perl、C 等。
从 shell 运行的 curl 二进制文件只是该库的另一个前端,但是通过 curl 二进制文件将 CURLOPT_USERPWD 等内容传递到库的方式是使用二进制文件上的命令行选项。
理论上,您可以编写自己的二进制文件作为 curl 库的前端,并支持环境变量。
您可以交替地破解环境支持,因为您希望将其添加到现有的 curl 二进制文件中,并使用本地函数编译您自己的。
请注意,即使是环境变量也可能会被您的 shell 泄露到进程表中。 (运行ps ewwp $$
时看到了什么?)
也许使用受限权限的 .netrc 文件是最安全的方法。也许您需要生成一个临时 .netrc 文件以供 --netrc-file
curl 选项使用。
我认为你要么必须为你的环境选择风险最小的解决方案,要么用真正的安全语言编写一些东西。
【讨论】:
foo=bar; export foo; ps ewwp $$ |grep -i foo
在bash
中找到变量,但有趣的是在zsh
(我选择的shell)中什么也没找到。 (没有export
,ps ewwp $$
找不到bash
或zsh
中的变量,但perl
也找不到它,使其无用。)bash/zsh 进程替换(@987654334 @) 在我的解决方法中似乎很好地限制了对该临时“文件”的访问(尽管我确信它仍然存在于内存中)。
我已将解决方法移至 an answer 并减少了对 export
的需求,这使得变量对 ps ewwp
不可见【参考方案2】:
这个bash
解决方案似乎最适合我的需求。它非常安全、便携且速度快。
#!/bin/bash
SRV="example.com"
URL="https://$SRV/path"
curl --netrc-file <(cat <<<"machine $SRV login $USER password $PASSWORD") "$URL"
这使用process substitution(<( command )
在子shell 中运行command
以填充file descriptor 以作为“文件”传递给父命令,在本例中为curl
)。进程替换包含here-string(cat <<< text
,echo text
的变体,不会将任何内容放入进程列表),为netrc file 创建文件描述符,以便将凭据传递给远程 Web 服务器.
进程替换提供的安全性实际上非常合理:它的文件描述符不是临时文件,即使是同一 shell 实例中的其他调用也不可用,所以在这种情况下appears secure ;对手必须挖掘内存或发起复杂的攻击才能找到其内容。由于$PASSWORD
环境变量也在内存中,这不应该增加attack surface。
只要您没有使用过export PASSWORD
,像ps ewwp $$
这样的技巧就不会泄露密码(如this comment 中所述)。使用一些不太明显的变量名也是明智的。
这是上述代码的简化不安全版本,可能有助于解释其工作原理:
#!/bin/sh
# INSECURE VERSION, DO NOT USE
SRV=example.com
URL="https://$SRV/path"
TMP=$(mktemp)
printf "machine %s login %s password %s\n" "$SRV" "$USER" "$PASSWORD" > "$TMP"
curl --netrc-file "$TMP" "$URL"
rm -f "$TMP"
这个不安全的版本有很多缺陷,在之前的版本中都解决了:
它将密码存储在一个文件中(尽管该文件只有您可以读取) 在命令行中非常简短地包含密码 临时文件一直保留到之后curl
退出
Ctrl+c 将退出而不删除临时文件
其中一些可以通过以下方式解决:
#!/bin/sh
SRV=example.com
URL="https://$SRV/path"
TMP=$(mktemp /dev/shm/.XXXXX) # assumes /dev/shm is a ramdisk
trap "rm -f $TMP" 0 18
cat << EOF > "$TMP"
machine $SRV login $USER password $PASSWORD
EOF
(sleep 0.1; rm -f "$TMP") & # queue removing temp file in 0.1 seconds
curl --netrc-file "$TMP" "$URL"
我认为这个版本是混乱的、次优的,并且可能不太安全(尽管它更便携)。它还需要理解小数的sleep
版本(如果系统负载过重,0.1 秒可能太快了)。
我最初发布了一个解决方法,其中在我的问题中包含了 perl
单行,然后(使用 help from Etan Reisner)我通过了一些更好的方法,然后才确定了这个这里的字符串方法,这两者都更轻 -重量(更快)和更便携。
在这一点上,它已经足够优雅了,我将其视为“答案”而不是“丑陋的解决方法”,因此我已将其迁移为官方答案。我给@ghoti 给了his answer 的+1,它正确地指出cURL 的命令行程序无法自己做我想做的事情,但我没有“接受”这个答案,因为它没有帮助解决问题。
【讨论】:
<(cat <<<"$PASSWORD")
可以安全地替换为 <(echo "$PASSWORD")
如果您的 shell 的 echo
命令是内置命令。 $PASSWORD 不会出现在进程表中。在 bash 中,可以使用type -t echo
来测试 echo 是否是内置的。【参考方案3】:
用户“Tom, Bom”在这里为此提供了一个不错的解决方案:https://coderwall.com/p/dsfmwa/securely-use-basic-auth-with-curl
curl --config - https://example.com <<< 'user = "username:password"'
这可以防止密码出现在进程列表中,尽管这并没有专门解决 OP 的原始问题:
有没有办法让 curl 仅通过环境传递凭据?
我仍然给 @ghoti 打分,因为它给出了更全面和信息丰富的答案。
【讨论】:
【参考方案4】:以前的答案是正确的,最好的选择是使用 -n 进行 curl(假设你在 linux 上):
-
创建一个文件(使用您自己喜欢的编辑器)
vi ~YOUR_USER_NAME/.netrc
-
添加以下内容
machine example.com
login YOUR_USER_NAME
password THE_ACTUAL_PASSWORD
-
运行
curl -n https://example.com/some_end_point
【讨论】:
这使用文件系统而不是环境,这不是这个问题要问的。 这是建议将密码存储在文件中的唯一答案。我的问题是关于如何在环境中使用密码,特别是这样它就不会碰到文件系统。这里的其他答案都不涉及实际文件;他们只是使用一个被像文件一样对待的shell结构。--netrc-file
指的是要读取的“文件”来代替~/.netrc
。但是,我的回答提供了一个process substitution 来填充一个file descriptor,这不是一个文件,从不存在于任何文件系统上,并且对任何其他用户(包括root)都不可用,甚至对来自另一个 shell 进程的调用用户。
您推荐的解决方案实际上比存储在文件中要糟糕得多,因为任何执行 ps -ef|grep curl 的人都会看到假设 curl 命令在此期间执行的密码......这是你几乎不应该在 linux 中这样做
我对此进行了测试(尽管您可以自己实际测试并分享任何相互矛盾的发现)。正如我在回答中明确指出的那样,here-string 内容不会显示在进程列表中(ps -ef|grep curl
将向您显示类似curl --netrc-file /proc/self/fd/18 http://…
的内容)。这就是它起作用的全部原因。请完整阅读我的答案,因为所有这些都在那里进行了详细讨论。如果您阅读我的问题、我的回答和 ghoti 的回答中的 cmets,还有更多内容。【参考方案5】:
Curl 在解析 netrc 文件中的标记时使用 SPACE 和 TAB 作为分隔符:
https://github.com/curl/curl/blob/bc5a0b3e9f16a431523ae54822adc38c3a396a26/lib/netrc.c#L122
--netrc-file
方法因此无法处理密码中的空格或制表符。
密码空格测试
SRV="httpbin.org"
URL="https://$SRV/basic-auth/username/pass%20word"
USERNAME=username
PASSWORD='pass word'
curl -v --netrc-file <(echo "machine $SRV login $USERNAME password $PASSWORD") "$URL"
结果:❌失败
警告:如果您的 shell 的
echo
命令不是内置命令,则上述 curl 调用将立即将 $PASSWORD 泄漏到进程表中。在bash
中,echo
是否是内置的可以用type -t echo
测试。解决方法:使用cat
和此处的字符串:将<(echo "string")
替换为<(cat <<< "string")
。此警告适用于此答案中的所有示例。
密码TAB测试
SRV="httpbin.org"
URL="https://$SRV/basic-auth/username/pass%09word"
USERNAME=username
PASSWORD=$'pass\tword'
curl -v --netrc-file <(echo "machine $SRV login $USERNAME password $PASSWORD") "$URL"
结果:❌失败
密码中双引号"
的测试
SRV="httpbin.org"
URL="https://$SRV/basic-auth/username/pass%22word"
USERNAME=username
PASSWORD='pass\"word'
curl -v --netrc-file <(echo "machine $SRV login $USERNAME password $PASSWORD") "$URL"
结果:❌失败
更稳健的方式
@sfgeorge 正确指出-K, --config <file>
选项,<file>
设置为-
,可用于在 STDIN 上提供密码。但是,将 STDIN 用于此目的会妨碍将 STDIN 用于其他目的,例如使用 --data @-
发布数据。
幸运的是,我们可以使用process substitution 代替 STDIN。进程替换扩展为文件名,可用于需要文件名的地方。
密码空格测试
USERNAME=username
PASSWORD='pass word'
curl -v \
-K <(echo "user: \"$USERNAME:$PASSWORD\"") \
"https://httpbin.org/basic-auth/username/pass%20word"
结果:✅成功
密码TAB测试
USERNAME=username
PASSWORD=$'pass\tword'
curl -v \
-K <(echo "user: \"$USERNAME:$PASSWORD\"") \
"https://httpbin.org/basic-auth/username/pass%09word"
结果:✅成功
而且,为了更加稳健,让我们让它也处理密码中的双引号。
密码中双引号"
的测试
USERNAME=username
PASSWORD=$'p?a s\ts"wo$rd'
# Build 'user' option
USER_OPT="$USERNAME:$PASSWORD"
USER_OPT=$USER_OPT//\\/\\\\ # Escape `\`
USER_OPT=$USER_OPT//\"/\\\" # Escape `"`
USER_OPT="user: \"$USER_OPT\""
curl -v \
-K <(echo "$USER_OPT") \
"https://httpbin.org/basic-auth/username/p?a%20s%09s%22wo%24rd"
结果:✅成功
我添加了一个表情符号。
【讨论】:
我想这不是最佳/接受的答案,因为它在技术上不是 ENV,但它是一个很好而彻底的答案。我需要处理带有$
的密码,这很棒。我可以保存一个creds.curl
文件并运行curl -K creds.url (whatever)
。以上是关于强制 cURL 从环境中获取密码的主要内容,如果未能解决你的问题,请参考以下文章
使用 CURL 从 URL 获取 XML 在 PHP 更新后停止工作