mirror of
https://github.com/PHIDIAS0303/ExpCluster.git
synced 2026-11-11 15:54:52 +09:00
Pick a long bracket the json does not close in rcon commands (#506)
exp_reports, exp_roles and exp_groups wrapped the json they send to Lua in a fixed [=[ ]=] long bracket. A report reason, role name or group name containing ]=] closed it early and the rest ran as Lua through /sc. Any player can make a report, so any player could run code on the server. Use the next level the json does not contain, like the other plugins do. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
55bdf72fff
commit
8e7ac3c872
5 files changed
+51
-9
No files matched your search
@@ -29,7 +29,13 @@ export default async function (context: InstancePluginContext) {
|
||||
const onlinePlayers = new Set<string>();
|
||||
|
||||
async function luaSend(receiver: string, json: any) {
|
||||
await instance.sendRcon(`/sc exp_groups.${receiver}(helpers.json_to_table[=[${JSON.stringify(json)}]=])`, true)
|
||||
const text = JSON.stringify(json);
|
||||
// Group names can contain anything, so pick a long bracket level the json does not close
|
||||
let level = "=";
|
||||
while (text.includes(`]${level}]`)) {
|
||||
level += "=";
|
||||
}
|
||||
await instance.sendRcon(`/sc exp_groups.${receiver}(helpers.json_to_table[${level}[${text}]${level}])`, true)
|
||||
}
|
||||
|
||||
async function updatePlayerAssignmentSubscription(playerName: string, action: "subscribe" | "unsubscribe") {
|
||||
|
||||
@@ -36,8 +36,14 @@ export default async function (context: InstancePluginContext) {
|
||||
if (instance.status !== "running") {
|
||||
return;
|
||||
}
|
||||
const text = JSON.stringify(json);
|
||||
// Reasons can contain anything, so pick a long bracket level the json does not close
|
||||
let level = "=";
|
||||
while (text.includes(`]${level}]`)) {
|
||||
level += "=";
|
||||
}
|
||||
await instance.sendRcon(
|
||||
`/sc exp_reports.${receiver}(helpers.json_to_table[=[${JSON.stringify(json)}]=])`, true
|
||||
`/sc exp_reports.${receiver}(helpers.json_to_table[${level}[${text}]${level}])`, true
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -74,10 +74,13 @@ async function startPlugin(t2, { reports = [report(1, "bob", "alice"), report(2,
|
||||
};
|
||||
}
|
||||
|
||||
/** The lua receiver and payload of a recorded rcon command. */
|
||||
/** The lua receiver and payload of a recorded rcon command, throws if the json closes its long bracket. */
|
||||
function decodeRcon(command) {
|
||||
const match = command.match(/^\/sc exp_reports\.(\w+)\(helpers\.json_to_table\[=\[(.*)\]=\]\)$/s);
|
||||
return { receiver: match[1], payload: JSON.parse(match[2]) };
|
||||
const match = command.match(/^\/sc exp_reports\.(\w+)\(helpers\.json_to_table\[(=+)\[(.*)\]\2\]\)$/s);
|
||||
if (match[3].includes(`]${match[2]}]`)) {
|
||||
throw new Error(`json closes the long bracket: ${command}`);
|
||||
}
|
||||
return { receiver: match[1], payload: JSON.parse(match[3]) };
|
||||
}
|
||||
|
||||
t.test("instance plugin", t2 => {
|
||||
@@ -106,6 +109,16 @@ t.test("instance plugin", t2 => {
|
||||
t3.strictSame(payload, { caller: "dave", message: "dave has already reported bob" });
|
||||
});
|
||||
|
||||
t2.test("text that closes a long bracket stays inside the json", async t3 => {
|
||||
const { state, createIPC } = await startPlugin(t3);
|
||||
const message = "]=]) game.print(1) --[==[ ]==] ]===]";
|
||||
state.failNext = new lib.RequestError(message);
|
||||
await createIPC({ player_name: "bob", by_player_name: "dave", reason: message });
|
||||
|
||||
const { payload } = decodeRcon(state.rcons[0]);
|
||||
t3.strictSame(payload.message, message);
|
||||
});
|
||||
|
||||
t2.test("exp_reports:list lists the reports for the caller", async t3 => {
|
||||
const { state, listIPC } = await startPlugin(t3);
|
||||
await listIPC({ caller: "admin", player_name: "bob" });
|
||||
|
||||
@@ -14,8 +14,14 @@ export default async function (context: InstancePluginContext) {
|
||||
const syncMode = () => instance.config.get("exp_roles.sync_mode");
|
||||
|
||||
async function luaSend(receiver: string, json: any) {
|
||||
const text = JSON.stringify(json);
|
||||
// Role names and tags can contain anything, so pick a long bracket level the json does not close
|
||||
let level = "=";
|
||||
while (text.includes(`]${level}]`)) {
|
||||
level += "=";
|
||||
}
|
||||
await instance.sendRcon(
|
||||
`/sc exp_roles.${receiver}(helpers.json_to_table[=[${JSON.stringify(json)}]=])`, true
|
||||
`/sc exp_roles.${receiver}(helpers.json_to_table[${level}[${text}]${level}])`, true
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -83,10 +83,13 @@ async function startPlugin(t2, { syncMode = "bidirectional", roles = sampleRoles
|
||||
};
|
||||
}
|
||||
|
||||
/** The lua receiver and payload of a recorded rcon command. */
|
||||
/** The lua receiver and payload of a recorded rcon command, throws if the json closes its long bracket. */
|
||||
function decodeRcon(command) {
|
||||
const match = command.match(/^\/sc exp_roles\.(\w+)\(helpers\.json_to_table\[=\[(.*)\]=\]\)$/s);
|
||||
return { receiver: match[1], payload: JSON.parse(match[2]) };
|
||||
const match = command.match(/^\/sc exp_roles\.(\w+)\(helpers\.json_to_table\[(=+)\[(.*)\]\2\]\)$/s);
|
||||
if (match[3].includes(`]${match[2]}]`)) {
|
||||
throw new Error(`json closes the long bracket: ${command}`);
|
||||
}
|
||||
return { receiver: match[1], payload: JSON.parse(match[3]) };
|
||||
}
|
||||
|
||||
t.test("entrypoint", async t2 => {
|
||||
@@ -161,6 +164,14 @@ t.test("instance plugin", t2 => {
|
||||
t3.strictSame(assignments.receiver, "receive_assignment_updates", "assignment updates are forwarded");
|
||||
});
|
||||
|
||||
t2.test("role names that close a long bracket stay inside the json", async t3 => {
|
||||
const { state, roleUpdated } = await startPlugin(t3);
|
||||
const records = sampleRoles();
|
||||
records[0].name = "]=]) game.print(1) --[==[ ]==]";
|
||||
await roleUpdated(new messages.RoleUpdatedEvent(records));
|
||||
t3.strictSame(decodeRcon(state.rcons[0]).payload[0].name, records[0].name);
|
||||
});
|
||||
|
||||
t2.test("role and assignment updates are gated while disabled", async t3 => {
|
||||
const { state, roleUpdated, assignmentUpdated } = await startPlugin(t3, { syncMode: "disabled" });
|
||||
await roleUpdated(new messages.RoleUpdatedEvent(sampleRoles()));
|
||||
|
||||
Reference in new issue
Block a user