CVE-2023-52077: Incorrect Authorization (CWE-863)
value: Nexkey is a lightweight fork of Misskey v12 optimized for small to medium size servers. Prior to 12.23Q4.5, Nexkey allows external apps using tokens issued by administrators and moderators to call admin APIs.  This allows malicious third-party apps to perform operations such as updating server settings, as well as compromise object storage and email server credentials. This issue has been patched in 12.23Q4.5.
CWE description: The product performs an authorization check when an actor attempts to access a resource or perform an action, but it does not correctly perform the check. This allows attackers to bypass intended access restrictions.
CVSS v3 base score: 9.8
Published: 2023-12-27T19:15Z
Repository: https://github.com/mei23/misskey-v12
Language: TypeScript
Files changed: packages/backend/src/server/api/call.ts, packages/backend/src/server/api/endpoints/admin/accounts/create.ts
Commit date: 2023-12-27 01:30:47 +0900
Commit message: Appは管理者/モデレータ権限を使えないように v12 latest

Diff: diff --git a/packages/backend/src/server/api/call.ts b/packages/backend/src/server/api/call.ts
index aa130459a..ecb8a32d4 100644
--- a/packages/backend/src/server/api/call.ts
+++ b/packages/backend/src/server/api/call.ts
@@ -91,10 +91,18 @@ export default async (endpoint: string, user: CacheableLocalUser | null | undefi
 			code: 'PERMISSION_DENIED',
 			id: '1370e5b7-d4eb-4566-bb1d-7748ee6a1838',
 		});
 	}
 
+	if (token && ep.meta.requireAdmin) {
+		throw new ApiError(accessDenied, { reason: 'Apps cannot use admin privileges.' });
+	}
+
+	if (token && ep.meta.requireModerator) {
+		throw new ApiError(accessDenied, { reason: 'Apps cannot use moderator privileges.' });
+	}
+
 	// Cast non JSON input
 	if ((ep.meta.requireFile || ctx?.method === 'GET') && ep.params.properties) {
 		for (const k of Object.keys(ep.params.properties)) {
 			const param = ep.params.properties![k];
 			if (['boolean', 'number', 'integer'].includes(param.type ?? '') && typeof data[k] === 'string') {
diff --git a/packages/backend/src/server/api/endpoints/admin/accounts/create.ts b/packages/backend/src/server/api/endpoints/admin/accounts/create.ts
index 5f8921999..635accf9c 100644
--- a/packages/backend/src/server/api/endpoints/admin/accounts/create.ts
+++ b/packages/backend/src/server/api/endpoints/admin/accounts/create.ts
@@ -27,16 +27,17 @@ export const paramDef = {
 	},
 	required: ['username', 'password'],
 } as const;
 
 // eslint-disable-next-line import/no-default-export
-export default define(meta, paramDef, async (ps, _me) => {
+export default define(meta, paramDef, async (ps, _me, token) => {
 	const me = _me ? await Users.findOneByOrFail({ id: _me.id }) : null;
 	const noUsers = (await Users.countBy({
 		host: IsNull(),
 	})) === 0;
 	if (!noUsers && !me?.isAdmin) throw new Error('access denied');
+	if (token) throw new Error('access denied');
 
 	const { account, secret } = await signup({
 		username: ps.username,
 		password: ps.password,
 	});

Vulnerable code: export default define(meta, paramDef, async (ps, _me) => {
Fixed code: 	if (token && ep.meta.requireAdmin) {
		throw new ApiError(accessDenied, { reason: 'Apps cannot use admin privileges.' });
	}

	if (token && ep.meta.requireModerator) {
		throw new ApiError(accessDenied, { reason: 'Apps cannot use moderator privileges.' });
	}

export default define(meta, paramDef, async (ps, _me, token) => {
	if (token) throw new Error('access denied');