强制 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 foobash 中找到变量,但有趣的是在zsh(我选择的shell)中什么也没找到。 (没有exportps ewwp $$ 找不到bashzsh 中的变量,但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(&lt;( command ) 在子shell 中运行command 以填充file descriptor 以作为“文件”传递给父命令,在本例中为curl)。进程替换包含here-string(cat &lt;&lt;&lt; textecho 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 的命令行程序无法自己做我想做的事情,但我没有“接受”这个答案,因为它没有帮助解决问题。

【讨论】:

&lt;(cat &lt;&lt;&lt;"$PASSWORD") 可以安全地替换为 &lt;(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 和此处的字符串:将&lt;(echo "string") 替换为&lt;(cat &lt;&lt;&lt; "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 &lt;file&gt; 选项,&lt;file&gt; 设置为-,可用于在 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 使用用户名和密码登录 Gitlab

使用 CURL 从 URL 获取 XML 在 PHP 更新后停止工作

Curl:如何传递包含百分号的密码

使用 curl 从 mailchimp 获取数据我在哪里将 api 密钥放在 curl 调用中

如何从php中的paypal curl响应获取付款参考号

从 curl_exec 获取响应代码、标头和内容