CVE-2024-27927: Insufficient Information (NVD-CWE-noinfo)
value: RSSHub is an open source RSS feed generator. Prior to version 1.0.0-master.a429472, RSSHub allows remote attackers to use the server as a proxy to send HTTP GET requests to arbitrary targets and retrieve information in the internal network or conduct Denial-of-Service (DoS) attacks. The attacker can send malicious requests to a RSSHub server, to make the server send HTTP GET requests to arbitrary destinations and see partial responses. This may lead to leak the server IP address, which could be hidden behind a CDN; retrieving information in the internal network, e.g. which addresses/ports are accessible, the titles and meta descriptions of HTML pages; and denial of service amplification. The attacker could request the server to download some large files, or chain several SSRF requests in a single attacker request.
CWE description: There is insufficient information about the issue to classify it; details are unkown or unspecified.
Published: 2024-03-21T02:52Z
Repository: https://github.com/DIYgod/RSSHub
Language: TypeScript
Files changed: lib/routes/m4/index.ts, lib/routes/mastodon/acct.ts, lib/routes/mastodon/utils.ts
Commit date: 2024-03-05 17:27:51 +0800
Commit message: Merge pull request from GHSA-3p3p-cgj7-vgw3

* fix: SSRF in /m4/:id?/:category*

* fix: allow /mastodon/acct/ to any domain when MASTODON_API_HOST is set

* fix: add missing import for isValidHost
Diff: diff --git a/lib/routes/m4/index.ts b/lib/routes/m4/index.ts
index 8e48ebcc40..1230dc7119 100644
--- a/lib/routes/m4/index.ts
+++ b/lib/routes/m4/index.ts
@@ -2,18 +2,22 @@
 import { getCurrentPath } from '@/utils/helpers';
 const __dirname = getCurrentPath(import.meta.url);
 
 import cache from '@/utils/cache';
 import got from '@/utils/got';
+import { isValidHost } from '@/utils/valid-host';
 import { load } from 'cheerio';
 import timezone from '@/utils/timezone';
 import { parseDate } from '@/utils/parse-date';
 import { art } from '@/utils/render';
 import * as path from 'node:path';
 
 export default async (ctx) => {
     const { id = 'news', category = 'china' } = ctx.req.param();
+    if (!isValidHost(id)) {
+        throw new Error('Invalid id');
+    }
     const limit = ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit'), 10) : 30;
 
     const rootUrl = `http://${id}.m4.cn`;
     const currentUrl = new URL(category ? `/${category.replace(/\/$/, '')}/` : '/', rootUrl).href;
 
diff --git a/lib/routes/mastodon/acct.ts b/lib/routes/mastodon/acct.ts
index 1e0f36725d..57ac3a6a71 100644
--- a/lib/routes/mastodon/acct.ts
+++ b/lib/routes/mastodon/acct.ts
@@ -1,16 +1,10 @@
 const utils = require('./utils');
-import { config } from '@/config';
 
 export default async (ctx) => {
     const acct = ctx.req.param('acct');
     const only_media = ctx.req.param('only_media') ? 'true' : 'false';
-    const acctSite = acct.split('@').filter(Boolean)[1];
-
-    if (!config.feature.allow_user_supply_unsafe_domain && !utils.allowSiteList.includes(acctSite)) {
-        throw new Error(`This RSS is disabled unless 'ALLOW_USER_SUPPLY_UNSAFE_DOMAIN' is set to 'true'.`);
-    }
 
     const { site, account_id } = await utils.getAccountIdByAcct(acct);
 
     const { account_data, data } = await utils.getAccountStatuses(site, account_id, only_media);
 
diff --git a/lib/routes/mastodon/utils.ts b/lib/routes/mastodon/utils.ts
index 2d4d9193bd..9910a28b72 100644
--- a/lib/routes/mastodon/utils.ts
+++ b/lib/routes/mastodon/utils.ts
@@ -2,11 +2,11 @@
 import cache from '@/utils/cache';
 import got from '@/utils/got';
 import { parseDate } from '@/utils/parse-date';
 import { config } from '@/config';
 
-const allowSiteList = ['mastodon.social', 'pawoo.net', config.mastodon.apiHost];
+const allowSiteList = ['mastodon.social', 'pawoo.net', config.mastodon.apiHost].filter(Boolean);
 
 const apiHeaders = (site) => {
     const { accessToken, apiHost } = config.mastodon;
     // avoid sending API token to other sites
     return accessToken && site === apiHost ? { Authorization: `Bearer ${accessToken}` } : {};
@@ -94,10 +94,13 @@ async function getAccountIdByAcct(acct) {
     const site = mastodonConfig.apiHost || acctHost;
     const acctDomain = mastodonConfig.acctDomain || acctHost;
     if (!(site && acctDomain)) {
         throw new Error('Mastodon RSS is disabled due to the lack of <a href="https://docs.rsshub.app/en/install/#configuration-route-specific-configurations">relevant config</a>');
     }
+    if (!config.feature.allow_user_supply_unsafe_domain && !allowSiteList.includes(site)) {
+        throw new Error(`RSS for this domain is disabled unless 'ALLOW_USER_SUPPLY_UNSAFE_DOMAIN' is set to 'true' or 'MASTODON_API_HOST' is set.`);
+    }
 
     const search_url = `https://${site}/api/v2/search`;
     const cacheUid = `mastodon_acct_id/${site}/${acct}`;
 
     const account_id = await cache.tryGet(cacheUid, async () => {

Vulnerable code: import { config } from '@/config';
    const acctSite = acct.split('@').filter(Boolean)[1];

    if (!config.feature.allow_user_supply_unsafe_domain && !utils.allowSiteList.includes(acctSite)) {
        throw new Error(`This RSS is disabled unless 'ALLOW_USER_SUPPLY_UNSAFE_DOMAIN' is set to 'true'.`);
    }
const allowSiteList = ['mastodon.social', 'pawoo.net', config.mastodon.apiHost];
Fixed code: import { isValidHost } from '@/utils/valid-host';
    if (!isValidHost(id)) {
        throw new Error('Invalid id');
    }
const allowSiteList = ['mastodon.social', 'pawoo.net', config.mastodon.apiHost].filter(Boolean);
    if (!config.feature.allow_user_supply_unsafe_domain && !allowSiteList.includes(site)) {
        throw new Error(`RSS for this domain is disabled unless 'ALLOW_USER_SUPPLY_UNSAFE_DOMAIN' is set to 'true' or 'MASTODON_API_HOST' is set.`);
    }