From 7c66cd1f85663ceb7b4f215dfcec1d09c480c40f Mon Sep 17 00:00:00 2001 From: Wonchan Lee Date: Fri, 29 May 2026 16:29:57 +0900 Subject: [PATCH] =?UTF-8?q?[fix]=20prototype=20pollution=EC=9D=84=20?= =?UTF-8?q?=ED=86=B5=ED=95=9C=20Stored=20XSS=20=EC=B7=A8=EC=95=BD=EC=A0=90?= =?UTF-8?q?=20=EC=B0=A8=EB=8B=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Entry.Scope의 예약어 필터가 문자열 "__proto__"만 차단하여, 배열 ["__proto__"]를 객체 키로 사용할 때 String() 변환으로 우회되는 prototype pollution → Stored XSS 취약점을 수정한다. - scope.js: filterReservedKeywords에서 객체/배열 param을 정규화 후 검사 - block_KKMOO.js: kkmoo 모션 블록 3종의 motnum 인덱스 범위 검증 추가 Co-Authored-By: Claude Opus 4.8 (1M context) --- src/playground/blocks/hardware/block_KKMOO.js | 27 ++++++++++++++++--- src/playground/scope.js | 6 ++++- 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/src/playground/blocks/hardware/block_KKMOO.js b/src/playground/blocks/hardware/block_KKMOO.js index 972b5a4af2..44dd69ec74 100644 --- a/src/playground/blocks/hardware/block_KKMOO.js +++ b/src/playground/blocks/hardware/block_KKMOO.js @@ -651,9 +651,16 @@ Entry.kkmoo.getBlocks = function () { class: 'Make_Motion', isNotFor: ['kkmoo'], func: function (sprite, script) { - const motnum = script.getField('MOTNUM', script); + const motnum = Number(script.getField('MOTNUM', script)); const angle = script.getValue('ANGLE', script); Entry.hw.update(); + if ( + !Number.isInteger(motnum) || + motnum < 0 || + motnum >= Entry.kkmoo.motData.length + ) { + return script; + } if (script.isStart != true) { script.isStart = true; if (angle >= -90 && angle <= 90) { @@ -853,8 +860,15 @@ Entry.kkmoo.getBlocks = function () { class: 'Save_Motion', isNotFor: ['kkmoo'], func: function (sprite, script) { - const motnum = script.getField('FRAME', script); + const motnum = Number(script.getField('FRAME', script)); Entry.hw.update(); + if ( + !Number.isInteger(motnum) || + motnum < 0 || + motnum >= Entry.kkmoo.motionFrame.length + ) { + return script; + } if (script.isStart != true) { script.isStart = true; var data = Entry.kkmoo.copyObj(Entry.kkmoo.motData); @@ -972,9 +986,16 @@ Entry.kkmoo.getBlocks = function () { class: 'Save_Motion', isNotFor: ['kkmoo'], func: function (sprite, script) { - const motnum = script.getField('FRAME', script); + const motnum = Number(script.getField('FRAME', script)); const time = script.getValue('TIME', script); Entry.hw.update(); + if ( + !Number.isInteger(motnum) || + motnum < 0 || + motnum >= Entry.kkmoo.motionFrame.length + ) { + return script; + } if (script.isStart != true) { script.isStart = true; Entry.kkmoo.motionFrame[motnum].time = time; diff --git a/src/playground/scope.js b/src/playground/scope.js index bcf714db76..b3b0b8b388 100644 --- a/src/playground/scope.js +++ b/src/playground/scope.js @@ -27,7 +27,11 @@ class Scope { static _reservedKeywords = new Set(['__proto__']); filterReservedKeywords(param) { - return Scope._reservedKeywords.has(param) ? '' : param; + // 배열/객체를 객체 키로 사용할 때 toString 변환(예: ["__proto__"] → "__proto__")으로 + // 예약어를 우회하는 것을 차단한다. + const normalized = + typeof param === 'object' && param !== null ? String(param) : param; + return Scope._reservedKeywords.has(normalized) ? '' : param; } getParams() {