I have a situation where a Bash script runs and parses a user-supplied JSON file using jq
. Since it's supplied by the user, it's possible for them to include values in the JSON to perform an injection attack.
I'd like to know if there's a way to overcome this. Please note, the setup of: 'my script parsing a user-supplied JSON file' cannot be changed, as it's out of my control. Only thing I can control is the Bash script.
I've tried using jq
with and without the -r
flag, but in each case, I was successfully able to inject.
Here's what the Bash script looks like at the moment:
#!/bin/bash
set -e
eval "INCLUDES=($(cat user-supplied.json | jq '.Include[]'))"
CMD="echo Includes are: "
for e in "${INCLUDES[@]}"; do
CMD="$CMD\\\"$e\\\" "
done
eval "$CMD"
And here is an example of a sample user-supplied.json
file that demonstrates an injection attack:
{
"Include": [
"\\\";ls -al;echo\\\""
]
}
The above JSON file results in the output:
Includes are: ""
, followed by a directory listing (an actual attack would probably be something far more malicious).
What I'd like instead is something like the following to be outputted:
Includes are: "\\\";ls -al;echo\\\""
Edit 1
I used echo
as an example command in the script, which probably wasn’t the best example, as then the solution is simply not using eval
.
However the actual command that will be needed is dotnet test
, and each array item from Includes
needs to be passed as an option using /p:<Includes item>
. What I was hoping for was a way to globally neutralise injection regardless of the command, but perhaps that’s not possible, ie, the technique you go for relies heavily on the actual command.
答案 0 :(得分:2)
对于eval
,您也不需要使用dotnet test
。 POSIX sh中不存在的许多bash扩展特定存在,从而使eval
的使用成为不必要。如果您认为,您需要eval
,那么您应该提供足够的详细信息,以使我们解释为什么实际上不需要它。 :)
#!/usr/bin/env bash
# ^^^^- Syntax below is bash-only; the shell *must* be bash, not /bin/sh
include_args=( )
IFS=$'\n' read -r -d '' -a includes < <(jq -r '.Include[]' user-supplied.json && printf '\0')
for include in "${includes[@]}"; do
include_args+=( "/p:$include" )
done
dotnet test "${include_args[@]}"
对正在发生的事情说一些话:
IFS=$'\n' read -r -d '' -a arrayname
读取stdin中的下一个NUL字符(-d
指定一个字符停止;由于C字符串以NUL终止,因此空字符串中的第一个字符为NUL字节),在换行符上分割,然后将结果放入arrayname
。
在bash 4.0或更高版本中编写此命令的较短方法是readarray -t arrayname
,但这没有让您检测生成输入的程序是否失败的优点:因为我们有&& printf '\0'
附加到jq
代码上的read
期望的NUL终止符仅在jq
成功的情况下存在,从而导致read
的退出状态仅在{{ 1}}也报告了成功。
jq
正在从process substitution重定向标准输入,替换为文件名,当从文件名读取文件名时,该文件名返回运行代码< <(...)
的输出。...
并使其与include_args+=( "/p:$include" )
完全相同的原因是引号由shell本身读取,并用于确定在何处执行字符串拆分和globbing;它们不会不保留在生成的内容中(并随后传递给include_args+=( /p:"$include" )
)。其他一些有用的参考文献:
dotnet test
),并描述了更好的做法(将命令存储在函数中;将命令存储在数组中;等等)。eval
广为人知的原因。答案 1 :(得分:0)
您根本不需要eval
。
INCLUDES=( $(jq '.Include[]' user-supplied.json) )
echo "Includes are: "
for e in "${INCLUDES[@]}"; do
echo "$e"
done
可能发生的最糟糕的情况是,不带引号的命令替换可能会在您不希望的位置执行单词拆分或路径名扩展(这同样在您的原始版本中也是一个问题),但是不可能执行任意命令。