我正在编写一个使用 Tesseract 进行字符识别的 GUI 应用程序。/bin/sh -c我想允许用户指定在文本准备好时执行的自定义 shell 命令。问题是识别的文本实际上可以包含任何内容,例如&& rm -rf some_dir。
我的第一个想法是让它像许多其他程序一样,用户可以在文本条目中键入命令,然后printf()命令中的特殊字符串(例如 in )被适当的数据替换(在我的例子中,可能是%t)。然后整个字符串被传递到execvp(). 例如,这是 qBittorrent 的屏幕截图:

问题是,即使我在替换之前正确转义文本%t,也没有什么可以阻止用户在说明符周围添加额外的引号:
echo '%t' >> history.txt
Run Code Online (Sandbox Code Playgroud)
所以要执行的完整命令是:
echo ''&& rm -rf some_dir'' >> history.txt
Run Code Online (Sandbox Code Playgroud)
显然,这是一个坏主意。
第二个选项只让用户选择一个可执行文件(带有文件选择对话框),因此我可以手动将 Tesseract 中的文本argv[1]放入execvp(). 这个想法是,可执行文件可以是一个脚本,用户可以在其中放置他们想要的任何内容并使用"$1". 这样,命令注入是不可能的(我认为)。以下是用户可以创建的示例脚本:
#!/bin/sh
echo "$1" >> history.txt
Run Code Online (Sandbox Code Playgroud)
这种方法有什么陷阱吗?或者也许有更好的方法来安全地将任意文本作为参数传递给 shell 脚本中的程序?
不要这样做。请参阅下面的“带外”部分。
要使任意 C 字符串(不包含 NUL)在严格符合 POSIX 的 shell 中的不带引号的上下文中使用时计算其自身,可以使用以下步骤:
' (从所需的初始无引号上下文移动到单引号上下文)。'为字符串'"'"'。这些字符的工作方式如下:
'关闭初始单引号上下文。"输入双引号上下文。'在双引号上下文中,是字面意思。"关闭双引号上下文。'重新进入单引号上下文。' (返回到所需的初始单引号上下文)。这在符合 POSIX 标准的 shell 中可以正常工作,因为单引号上下文中唯一非文字的字符是'; 在该上下文中,甚至反斜杠也会被解析为文字。
然而,只有当 sigils 仅在未加引号的上下文中使用时(从而让用户有责任让事情正确)并且 shell 严格符合 POSIX 时,这才可以正常工作。此外,在最坏的情况下,此转换生成的字符串最多可以比原始字符串长 5 倍;因此,需要谨慎对待用于转换的内存的分配方式。
(有人可能会问为什么'"'"'建议而不是'\'';这是因为反斜杠改变了传统反引号命令替换语法中使用的含义,因此较长的形式更强大)。
数据只能在带外传递传递,这样它就根本不会通过解析器运行。调用 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);
Run Code Online (Sandbox Code Playgroud)
一些注意事项:
execve()使用较大命令行的 -family 调用失败。验证长度是否在合理的特定领域限制内是明智的。此版本需要显式 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);
Run Code Online (Sandbox Code Playgroud)