twikoo-func 1.7.22 → 1.7.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.js CHANGED
@@ -35,6 +35,7 @@ const {
35
35
  getConfig,
36
36
  getConfigForAdmin,
37
37
  validate,
38
+ validateClientFields,
38
39
  checkCommentOwnership,
39
40
  isValidEmail
40
41
  } = require('./utils')
@@ -87,6 +88,8 @@ exports.main = async (event, context) => {
87
88
  let res = {}
88
89
  try {
89
90
  protect()
91
+ // 统一校验客户端字段类型,防止查询操作符对象注入数据库查询条件
92
+ validateClientFields(event)
90
93
  await readConfig()
91
94
  switch (event.event) {
92
95
  case 'GET_FUNC_VERSION':
@@ -284,7 +287,7 @@ async function commentGet (event) {
284
287
  url: _.in(getUrlQuery(event.url)),
285
288
  rid: _.in(['', null])
286
289
  }
287
- // 查询非垃圾评论 + 自己的评论
290
+ // 按当前用户和配置查询可见评论
288
291
  query = getCommentQuery({ condition, uid, isAdminUser })
289
292
  // 读取总条数
290
293
  const count = await db
@@ -325,10 +328,7 @@ async function commentGet (event) {
325
328
  let top = []
326
329
  if (!config.TOP_DISABLED && !event.before) {
327
330
  // 查询置顶评论
328
- query = {
329
- ...condition,
330
- top: true
331
- }
331
+ query = getCommentQuery({ condition: { ...condition, top: true }, uid, isAdminUser })
332
332
  top = await db
333
333
  .collection('comment')
334
334
  .where(query)
@@ -434,6 +434,9 @@ async function commentSearch (event) {
434
434
  }
435
435
 
436
436
  function getCommentQuery ({ condition, uid, isAdminUser }) {
437
+ if (config.HIDE_SPAM === 'true') {
438
+ return { ...condition, isSpam: _.neq(true) }
439
+ }
437
440
  return _.or(
438
441
  { ...condition, isSpam: _.neq(isAdminUser ? 'imaegoo' : true) },
439
442
  { ...condition, uid }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "twikoo-func",
3
- "version": "1.7.22",
3
+ "version": "1.7.24",
4
4
  "description": "A simple comment system.",
5
5
  "author": "imaegoo <hello@imaegoo.com> (https://github.com/imaegoo)",
6
6
  "license": "MIT",
package/utils/index.js CHANGED
@@ -288,7 +288,7 @@ const fn = {
288
288
  }
289
289
  },
290
290
  // 预垃圾评论检测
291
- preCheckSpam ({ comment, nick }, config) {
291
+ preCheckSpam ({ comment, nick, link, mail }, config) {
292
292
  // 长度限制
293
293
  let limitLength = parseInt(config.LIMIT_LENGTH)
294
294
  if (Number.isNaN(limitLength)) limitLength = 500
@@ -313,9 +313,11 @@ const fn = {
313
313
  // 违禁词检测
314
314
  const commentLowerCase = comment.toLowerCase()
315
315
  const nickLowerCase = nick.toLowerCase()
316
+ const linkLowerCase = (link || '').toLowerCase()
317
+ const mailLowerCase = (mail || '').toLowerCase()
316
318
  for (const forbiddenWord of config.FORBIDDEN_WORDS.replace(/,+$/, '').split(',')) {
317
319
  const forbiddenWordLowerCase = forbiddenWord.trim().toLowerCase()
318
- if (commentLowerCase.indexOf(forbiddenWordLowerCase) !== -1 || nickLowerCase.indexOf(forbiddenWordLowerCase) !== -1) {
320
+ if (commentLowerCase.indexOf(forbiddenWordLowerCase) !== -1 || nickLowerCase.indexOf(forbiddenWordLowerCase) !== -1 || linkLowerCase.indexOf(forbiddenWordLowerCase) !== -1 || mailLowerCase.indexOf(forbiddenWordLowerCase) !== -1) {
319
321
  logger.warn('包含违禁词,直接标记为垃圾评论~')
320
322
  return true
321
323
  }
@@ -467,9 +469,33 @@ const fn = {
467
469
  }
468
470
  }
469
471
  },
472
+ // 客户端字段类型校验,防止 NoSQL 查询条件注入。
473
+ // 以下字段会直接参与数据库查询或作为评论归属标识,
474
+ // 如果传入对象,会被数据库解释为查询操作符(如 $ne、$gt),
475
+ // 导致越权删除评论、绕过评论可见性限制等问题。
476
+ // 允许字段缺省或为 null(兼容首次匿名请求),有值时必须是字符串。
477
+ validateClientFields (event = {}) {
478
+ const stringFields = ['accessToken', 'id', 'url', 'pid', 'rid']
479
+ for (const field of stringFields) {
480
+ const value = event[field]
481
+ if (value !== undefined && value !== null && typeof value !== 'string') {
482
+ throw new Error(`参数"${field}"必须是字符串`)
483
+ }
484
+ }
485
+ if (event.urls !== undefined && event.urls !== null) {
486
+ if (!Array.isArray(event.urls) || event.urls.some((url) => typeof url !== 'string')) {
487
+ throw new Error('参数"urls"必须是字符串数组')
488
+ }
489
+ }
490
+ },
470
491
  // 校验评论归属:确认评论存在且属于当前用户
471
492
  async checkCommentOwnership (id, uid, getComment) {
472
493
  fn.validate({ id }, ['id'])
494
+ // 兜底校验:id 必须是字符串,防止查询操作符对象注入 _id 条件,
495
+ // 绕过归属校验后批量删除评论
496
+ if (typeof id !== 'string') {
497
+ throw new Error('参数"id"必须是字符串')
498
+ }
473
499
  const comment = await getComment(id)
474
500
  if (!comment) {
475
501
  throw new Error('评论不存在')