c - 如何安全地将任意文本作为参数传递给Shell脚本中的程序?

标签 c bash sh posix

我正在编写一个使用Tesseract的字符识别GUI应用程序。我想允许用户指定一个定制的shell命令,在文本准备好时用/bin/sh -c执行。
问题是识别的文本可以包含任何内容,例如&& rm -rf some_dir
我的第一个想法是让它像在许多其他程序中一样
用户可以在文本项中键入命令,然后命令中的特殊字符串(如printf())将替换为适当的数据(在我的示例中,可能是%t)。然后将整个字符串传递给execvp()。例如,以下是qBittorrent的截图:
enter image description here
问题是,即使在替换%t之前正确转义文本,也不会阻止用户在说明符周围添加额外的引号:

echo '%t' >> history.txt

所以要执行的完整命令是:
echo ''&& rm -rf some_dir'' >> history.txt

显然,这是个坏主意。
第二个选项是只允许用户选择一个可执行文件(带有一个文件选择对话框),因此我可以手动将Tesseract中的文本设置为argv[1]forexecvp()。其思想是,可执行文件可以是一个脚本,用户可以在其中放置任何他们想要的内容,并使用"$1"访问文本。这样,命令注入是不可能的(我认为)。以下是用户可以创建的示例脚本:
#!/bin/sh
echo "$1" >> history.txt

这种方法有什么缺陷吗?或者也许有更好的方法可以安全地将任意文本作为参数传递给shell脚本中的程序?

最佳答案

带内:在未引用的上下文中转义任意数据
别这样。请参阅下面的“带外”部分。
要使任意C字符串(不包含nul)在严格遵循POSIX的shell中的未引用上下文中使用时计算为自身,可以使用以下步骤:
前置a'(从所需的初始未引用上下文移动到单个引用上下文)。
用字符串'替换数据中的每个文本'"'"'。这些字符的工作方式如下:
'关闭初始的单引号上下文。
"输入双引号上下文。
'在双引号上下文中是文本。
"关闭双引号上下文。
'重新输入单引号上下文。
追加一个'(返回到所需的初始单引号上下文)。
这在符合POSIX的shell中正常工作,因为在一个引用的上下文中,唯一不是文本的字符是';甚至反斜杠在该上下文中也被解析为文本。
但是,只有当sigil只在未引用的上下文中使用(因此让用户有责任把事情弄对)并且shell严格遵守POSIX时,这才可以正确工作。此外,在最坏的情况下,可以使此转换生成的字符串比原始字符串长5倍;因此,需要注意转换使用的内存是如何分配的。
(有人可能会问为什么建议使用'"'"',而不是'\'';这是因为反斜杠改变了它们在旧的backtick命令替换语法中使用的含义,因此较长的形式更健壮)。
带外:环境变量或命令行参数
数据应该只从代码中带外传递,这样它就永远不会在解析器中运行。在调用shell时,有两种简单的方法(使用文件除外):环境变量和命令行参数。
在下面的两种机制中,只需要信任user_provided_shell_script(尽管这也要求信任它不要引入新的或额外的漏洞;调用eval或任何道德上的等价物会使所有保证无效,但这是用户的问题,而不是您的问题)。
使用环境变量
排除错误处理(如果setenv()返回非零结果,则应将其视为错误,并应使用perror()或类似方法向用户报告),这将如下所示:

setenv("torrent_name", torrent_name_str, 1);
setenv("torrent_category", torrent_category_str, 1);
setenv("save_path", path_str, 1);

# shell script should use "$torrent_name", etc
system(user_provided_shell_script);

几点注意事项
虽然值可以是任意的C字符串,但是变量名必须受到限制——或者如上所述的硬编码常量,或者以常量(小写7位ASCII)字符串作为前缀,并测试为只包含允许的外壳变量名字符。(建议使用小写前缀,因为符合POSIX的shell只对修改其自身行为的变量使用所有大写名称;请参见the POSIX spec on environment variables,特别是注意“包含小写字母的环境变量名称的名称空间是为应用程序保留的。应用程序可以使用此名称空间中的名称定义任何环境变量,而无需修改标准实用程序的行为)。
环境空间是一个有限的资源;在现代Linux上,环境变量和命令行参数的最大组合存储通常是128kb的规模;因此,设置大的环境变量会导致具有大命令行的execve()家庭调用失败。验证长度是否在合理的域特定限制内是明智的。
使用命令行参数:
这个版本需要一个显式的API,这样配置触发器命令的用户就知道哪个值将传入$1,哪个值将传入$2,等等。
/* You'll need to do the usual fork() before this, and the usual waitpid() after
 * if you want to let it complete before proceeding.
 * Lots of Q&A entries on the site already showing the context.
 */
execl("/bin/sh", "-c", user_provided_shell_script,
  "sh",                 /* this is $0 in the script */
  torrent_name_str,     /* this is $1 in the script */
  torrent_category_str, /* this is $2 in the script */
  path_str,             /* this is $3 in the script */
  NUL);

关于c - 如何安全地将任意文本作为参数传递给Shell脚本中的程序?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53364883/

相关文章:

c - Visual Studio 6使用的c编译器是什么?

c - 如何打印 uint32_t 和 uint16_t 变量的值?

bash - 如何扩展 bash shell?

bash - 有没有一种优雅的方式来存储和评估 bash 脚本中的返回值?

java - 从 java 运行一个 .sh 脚本

docker - 如何在Docker sh入口点修复 'Permission denied'

c - 使用 POSIX 消息队列的单进程线程安全

c - 如何左移大于 64 位的值?警告 : shift count >= width of type [-Wshift-count-overflow]

linux - 如何在不删除原始文件的情况下使用cat追加一个文件

bash - 在 docker 中运行时优雅地停止 Solr