从蓝桥杯Web题实战解析Flask文件包含与加密逆向
2026/7/23 1:32:16 网站建设 项目流程

1. 项目概述:从一道蓝桥杯Web题切入CTF实战

如果你对网络安全感兴趣,或者听说过CTF(Capture The Flag,夺旗赛)但觉得它高深莫测,那么这篇文章就是为你准备的。我们不谈那些复杂的底层协议和汇编指令,就从一道非常经典的、来自蓝桥杯的Web题目“黑客密室逃脱”开始。这道题完美地融合了两个在CTF Web方向中极为常见的考点:Flask框架下的文件包含漏洞自定义加密算法的逆向分析。对于新手来说,它就像一座精心设计的“密室”,既有明确的线索(漏洞点),又有需要动脑破解的“锁”(加密逻辑)。通过拆解这道题,你不仅能亲手拿到Flag(比赛中的目标字符串),更能透彻理解这两个漏洞的原理、利用方法以及背后的安全思想。无论你是计算机专业的学生,还是希望转行安全领域的开发者,甚至是单纯对“黑客技术”感到好奇的爱好者,这篇指南都将带你跨出从理论到实战的关键一步。

2. 核心漏洞原理深度解析

在动手解题之前,我们必须先搞清楚要面对的是什么。题目基于Flask框架,这决定了我们的攻击面;漏洞点是文件包含,这是我们突破的入口;而自定义加密则是我们获取最终Flag前必须解开的谜题。

2.1 Flask框架与潜在的风险点

Flask是一个轻量级的Python Web框架,以其灵活和简洁著称。开发者通过定义路由(@app.route)和处理函数来构建应用。在本题中,风险往往源于开发者对用户输入过于信任。例如,一个常见的危险操作是使用render_template函数或send_from_directory函数时,直接拼接了用户可控的变量。

想象一下,如果代码中有一行类似send_from_directory(‘./templates‘, filename)的语句,而filename这个参数完全由用户通过URL参数(如?file=xxx)控制,那么危险就产生了。攻击者可以通过构造特殊的filename值(如../../etc/passwd),让服务器读取并返回本不应被访问的系统文件。这就是路径遍历攻击,是文件包含漏洞的一种常见形式。Flask本身并不“脆弱”,脆弱的是开发者不安全的使用方式。

2.2 文件包含漏洞(LFI)的攻防本质

文件包含漏洞,尤其是本地文件包含(Local File Inclusion, LFI),允许攻击者通过Web应用动态包含服务器本地的文件。其危害极大,因为攻击者可能读取到:

  • 配置文件:如/etc/passwd(Linux用户信息)、/proc/self/environ(进程环境变量,可能包含密钥)。
  • 源代码:通过包含.py文件或利用PHP等语言的特性(本题是Flask,但原理相通)来获取应用逻辑。
  • 日志文件:在特定条件下,向日志中写入恶意代码,再包含该日志文件以执行代码。

在CTF中,LFI的利用常常是为了获取源代码,从而发现更多的漏洞线索(比如本题中的加密逻辑)。防御的关键在于对用户输入进行严格的过滤和校验,比如限制包含的文件路径必须在某个安全目录内,或使用白名单机制。

2.3 自定义加密:CTF中的“锁匠”挑战

CTF题目很少使用标准的、强加密算法(如AES),因为那对于解题来说往往意味着暴力破解,缺乏技巧性。出题人偏爱自定义加密弱加密。这种加密可能只是简单的字符替换(如凯撒密码、Base64变种)、逻辑运算(XOR),或者是几种简单操作的组合。

解题者的任务就是扮演“锁匠”,通过分析有限的加密/解密样本(通常是题目给出的输入输出对),逆向推断出加密算法的逻辑。这需要一定的观察力、逻辑思维和编程能力。在本题中,我们最终需要逆向的,很可能就是一个用于验证身份或生成关键信息的自定义函数。

3. 靶场环境搭建与题目复现

“纸上得来终觉浅,绝知此事要躬行。” 要真正学会,最好的方法就是自己动手把题目跑起来。

3.1 使用Docker快速构建漏洞环境

对于CTF学习,我强烈推荐使用Docker。它能让你的实验环境与主机隔离,避免搞乱系统,并且一键部署,极其方便。

  1. 准备Dockerfile:如果题目方没有提供现成的镜像,我们需要根据题目描述(或逆向得到的源码)编写Dockerfile。一个典型的用于Flask题目的Dockerfile如下:

    FROM python:3.9-slim WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY . . CMD [“python”, “app.py”]

    这里基于Python 3.9精简镜像,将工作目录设为/app,复制依赖文件并安装,最后复制所有应用代码并启动。

  2. 编写requirements.txt:这个文件列出了项目依赖。

    flask==2.0.1
  3. 构建与运行

    # 在包含Dockerfile和app.py的目录下执行 docker build -t ctf-flask-lfi . docker run -p 8080:8080 --name my-ctf-challenge ctf-flask-lfi

    现在,访问http://localhost:8080就能看到题目界面了。

注意:在真实CTF或蓝桥杯比赛中,题目是远程在线提供的。本地搭建是为了方便我们反复调试、深入分析,尤其是查看服务端打印的调试信息,这在远程环境中是不可能的。

3.2 题目初步信息收集

启动环境后,第一步不是盲目测试,而是进行“侦察”。

  1. 前端观察:打开网页,查看所有可见的链接、表单、按钮和注释(Ctrl+U查看网页源代码)。寻找任何提示,比如注释里可能写着<!-- debug: source code in /source -->
  2. 目录扫描:使用工具如dirsearchgobuster进行简单的目录爆破,寻找常见的备份文件、源码文件或管理界面。
    dirsearch -u http://localhost:8080 -e php,py,txt,bak,zip,tar.gz
    可能会发现/app.py/www.zip/source等线索。
  3. 参数探测:观察URL。如果看到类似?page=about.html这样的参数,立即警觉,这很可能就是文件包含的点。尝试修改为?page=../../../../etc/passwd进行测试。

4. 漏洞利用:文件包含实战突破

假设通过信息收集,我们发现了URL中存在一个file参数用于加载模板:http://localhost:8080/show?file=welcome.html

4.1 基础路径遍历测试

我们的目标是读取应用的源代码app.py,因为加密逻辑一定写在里面。

  1. 尝试直接读取?file=app.py。如果直接返回了代码,那题目就太简单了。通常会有路径限制。
  2. 使用路径遍历?file=../../app.py。这里../表示上一级目录。我们需要猜测应用当前的相对路径。假设Flask应用从/app目录运行,模板在/app/templates,那么../../就能退回到根目录再进入/app
  3. 使用绝对路径:在某些配置下,可以直接使用绝对路径,如?file=/app/app.py
  4. 利用PHP包装器(非本题,但重要):如果是PHP环境,php://filter协议是神器,可以读取文件源码并进行Base64编码输出,避免代码被直接执行。例如:?file=php://filter/convert.base64-encode/resource=index.php但在纯Flask/Python环境下,这个技巧无效

4.2 进阶技巧:利用Flask特性获取源码

在无法直接穿越目录时,我们需要更巧妙的思路。Flask在调试模式下,如果触发服务器错误,会返回一个包含错误信息和局部变量值的交互式调试页面。虽然生产环境会关闭它,但CTF题有时会开启。

我们可以尝试触发一个错误,比如传入一个不存在的文件名,或者利用参数类型错误,让错误信息泄露部分代码栈信息。但这方法不稳定。

更可靠的方法是,如果文件包含点能读取到Python的临时文件被导入的模块文件吗?这通常很难。

在本题“黑客密室逃脱”的典型设定中,突破口往往是一个“源码泄露”。例如,通过?file=....//....//....//app.py这样的多重URL编码(../编码为%2e%2e%2f)绕过简单的过滤,或者题目本身就预留了一个路由/source/debug来直接显示源码。作为新手,一定要养成检查常见源码泄露路径的习惯。

假设我们通过?file=....//....//app.py成功读取到了app.py的源代码。接下来就是代码审计时间。

5. 代码审计与加密逻辑逆向

拿到源代码后,我们就像拿到了密室的建筑图纸。快速浏览代码,寻找几个关键部分:

  1. 路由定义:所有@app.route的地方,特别是处理我们刚才利用的file参数的路由。
  2. 加密/解密函数:寻找名为encryptdecryptcryptocheck_token等的函数,或者涉及hashlib、异或^运算、字符循环移位等操作的自定义函数。
  3. Flag生成或输出逻辑:寻找像flagFLAGsecret这样的变量,或者最终输出Flag的条件语句。

5.1 分析示例加密函数

假设我们在app.py中找到了如下函数:

def weird_encrypt(text): key = “SECRET_KEY_HERE“[:len(text)] result = ““ for i, c in enumerate(text): result += chr((ord(c) + ord(key[i]) - 2*ord(‘A‘)) % 26 + ord(‘A‘)) return result

这是一个典型的流加密(逐字符加密)。我们来逆向分析:

  • key被截取成和明文一样长。
  • 对明文的每个字符c和密钥对应字符key[i],进行运算:(ord(c) + ord(key[i]) - 2*ord(‘A‘)) % 26 + ord(‘A‘)
  • ord(‘A‘)是65。这个运算的本质是,将字符ckey[i]都视为0-25的数字(A=0, B=1, ... Z=25),然后相加后取模26,再转换回字符。
  • 这实际上是**维吉尼亚密码(Vigenère cipher)**的一种变体!解密过程就是逆运算:chr((ord(c_encrypted) - ord(key[i]) + 26) % 26 + ord(‘A‘))

5.2 寻找密钥与构造解密器

现在我们知道它是维吉尼亚密码,但密钥key是什么?继续审计代码。密钥可能硬编码在代码里,也可能来自某个文件(我们之前用LFI读到的/proc/self/environ或配置文件),或者通过某个路由计算出来。

假设我们在另一个路由/get_key中发现返回了密钥的Base64编码。或者,密钥就是SECRET_KEY_HERE。那么,我们就可以编写Python解密脚本了。

def weird_decrypt(enc_text, key): key = key[:len(enc_text)] result = ““ for i, c in enumerate(enc_text): # 逆向运算:加密是 (p + k) % 26, 解密就是 (c - k + 26) % 26 p = (ord(c) - ord(‘A‘) - (ord(key[i]) - ord(‘A‘)) + 26) % 26 result += chr(p + ord(‘A‘)) return result # 假设我们从题目响应中获取了密文和密钥 ciphertext = “LXFOPVEFRNHR“ # 示例密文 key = “SECRET“ plaintext = weird_decrypt(ciphertext, key) print(plaintext) # 输出解密结果

6. 整合利用链:获取最终Flag

通常,CTF题的Flag不会直接放在一个能被LFI读取的文本文件里。它需要我们将前几步串联起来,形成一个完整的利用链。

一个典型的流程可能是:

  1. 利用LFI读取/app/app.py, 获得源代码。
  2. 审计代码,发现访问/flag路由需要提供一个有效的token
  3. 分析token的生成算法。代码显示token = encrypt(username + “|“ + timestamp), 其中encrypt就是我们刚才分析的自定义函数。
  4. 寻找密钥。代码中可能写死了密钥,或者密钥来自环境变量。我们可以尝试用LFI读取/proc/self/environ(Linux下进程环境变量文件),里面可能包含SECRET_KEY=xxxxx
  5. 伪造token。知道了加密算法和密钥,我们就可以为任意用户名和时间戳生成合法的token。通常,题目要求管理员用户,比如username=admin
  6. 构造请求。带着伪造的token访问/flag路由,服务器验证通过,返回Flag。
import requests import time BASE_URL = “http://localhost:8080“ # 1. 读取源码(假设漏洞点) source_code = requests.get(f“{BASE_URL}/show?file=....//....//app.py“).text print(“[+] Source code obtained.“) # (此处省略代码解析和密钥提取过程,假设我们已得到密钥 MY_SECRET_KEY) KEY = “MY_SECRET_KEY“ # 2. 根据加密算法生成token def generate_token(username): timestamp = int(time.time()) plain = f“{username}|{timestamp}“ token = weird_encrypt(plain, KEY) # 使用前面分析出的加密函数 return token # 3. 以admin身份请求flag admin_token = generate_token(“admin“) flag_resp = requests.get(f“{BASE_URL}/flag“, cookies={“token“: admin_token}) # 或者可能是POST请求,token在参数里 # flag_resp = requests.post(f“{BASE_URL}/flag“, data={“token“: admin_token}) print(“[+] Flag response:“, flag_resp.text) # 在响应中寻找类似 flag{...} 格式的字符串

7. 常见问题与调试技巧实录

在实际操作中,你一定会遇到各种问题。下面是我踩过的一些坑和总结的技巧。

7.1 文件包含漏洞利用失败

  • 问题../被过滤或转义了。

  • 排查

    1. 尝试URL编码:..%2f(/的编码),%2e%2e%2f(../的编码)。
    2. 尝试双重编码:%252e%252e%252f(对%2e%再次编码)。
    3. 尝试绝对路径:/etc/passwd
    4. 尝试空字节截断(在较老版本PHP中有效,Python/Flask中通常无效):../../etc/passwd%00
    5. 查看服务器返回的错误信息,可能提示了过滤规则。
  • 心得:永远不要只试一种payload。准备一个payload字典,用Burp Suite的Intruder模块进行模糊测试,效率最高。

7.2 加密算法逆向困难

  • 问题:加密函数逻辑复杂,一眼看不出是什么。
  • 排查
    1. 黑盒测试:如果题目提供加密接口,输入有规律的数据(如‘A‘*10‘ABCDEFG‘),观察输出。这能帮你判断是分组加密还是流加密,是否有移位、替换等。
    2. 静态分析:在代码中,给加密函数插入print语句(在本地复现的环境里),打印中间变量值。这是最有效的方法。
    3. 搜索特征:代码中出现了ordchr^(XOR),&|<<>>%, 这些是典型的手工加密操作。hashlib.md5等是哈希,通常不可逆,但可能用于比较。
    4. 类比已知密码:维吉尼亚密码、栅栏密码、培根密码等是CTF常客。将你的输入输出对与这些经典密码的特征对比。

7.3 本地环境与远程题目差异

  • 问题:本地利用成功,但打远程服务器没反应或报错。
  • 排查
    1. 路径差异:Linux和Windows路径分隔符不同。你的../../app.py在Windows本地Docker里可能有效,但远程Linux服务器的工作目录结构可能不同。尝试更多层的../
    2. 过滤规则差异:远程服务器可能有额外的WAF(Web应用防火墙)规则。尝试更隐蔽的payload,或者使用其他等效的漏洞利用方式(比如本题可能还有其他入口点)。
    3. 网络问题:检查你的payload是否被浏览器或Burp Suite自动编码了。最好使用Burp Repeater模块手动发送原始HTTP请求。

7.4 工具使用心得

  • Burp Suite是你的主力:别只用浏览器。用Burp抓包、改包、重放(Repeater)、爆破(Intruder)。特别是Intruder,在测试文件包含路径、模糊参数时无比强大。
  • Python是你的瑞士军刀:快速编写解密脚本、生成payload、自动化请求。requests库必须熟练掌握。
  • Docker是你的沙盒:任何不确定的操作,先在本地Docker环境测试。别怕把容器搞崩,docker rm -f删掉重来就是。

8. 防御视角:如何避免此类漏洞

作为开发者,了解攻击手段是为了更好地防御。针对这道题体现的漏洞:

  1. 防御文件包含

    • 绝对路径+白名单:不要使用用户输入直接拼接路径。应该预先定义好允许访问的文件列表(白名单),用户只能选择列表内的项。
    • 使用安全的API:Flask的send_from_directory函数在正确使用时是安全的,但必须确保其第一个参数(目录)是固定的、不允许用户控制的。如果需要动态目录,必须进行严格的规范化(os.path.normpath)和前缀检查,确保路径不会跳出安全基目录。
    • 禁用危险函数:在PHP中,可以关闭allow_url_include。在Python中,避免使用eval()exec()或能动态执行代码的函数处理用户输入。
  2. 防御自定义加密漏洞

    • 不要自己造轮子:对于需要安全性的场景(如用户密码、会话令牌),务必使用业界标准、经过严格审计的加密库和算法,如Python的cryptography库,并使用安全的操作模式。
    • 密钥管理:密钥决不能硬编码在代码中。使用环境变量或专业的密钥管理服务(KMS),并确保其有足够的熵(随机性)。
    • 如果必须自定义(比如业务逻辑需要一种可逆的混淆),请明确告知这不是用于安全加密,并且最好让安全团队进行评审。

攻克这道“黑客密室逃脱”题目的过程,是一次完整的微型渗透测试演练:信息收集 -> 漏洞发现与利用 -> 权限提升/关键信息获取 -> 达成目标。它清晰地展示了Web安全中“漏洞链”的思想。Flask文件包含是你的敲门砖,而逆向自定义加密算法则是打开最终宝箱的钥匙。掌握了这两个核心技能,你就已经推开了CTF Web世界的大门。接下来,你可以去尝试更多包含文件上传、SQL注入、模板注入(SSTI)的题目,将这些技能组合运用,你会发现,那些看似复杂的系统,往往都是由一个个基础的不安全点构成的。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询