CVE-2023-22493 (JavaScript, published 2023-01-13T15:15Z)
Description: value: RSSHub is an open source RSS feed generator. RSSHub is vulnerable to Server-Side Request Forgery (SSRF) attacks. This vulnerability allows an attacker to send arbitrary HTTP requests from the server to other servers or resources on the network. An attacker can exploit this vulnerability by sending a request to the affected routes with a malicious URL. An attacker could also use this vulnerability to send requests to internal or any other servers or resources on the network, potentially gain access to sensitive information that would not normally be accessible and amplifying the impact of the attack. The patch for this issue can be found in commit a66cbcf.
Severity: nan (CVSS v3 base score: 7.5)
Weakness: CWE-918 \u2014 Server-Side Request Forgery (SSRF)
The web server receives a URL or similar request from an upstream component and retrieves the contents of this URL, but it does not sufficiently ensure that the request is being sent to the expected destination.
Repository: https://github.com/DIYgod/RSSHub
Files changed:
docs/en/install/README.md
docs/install/README.md
lib/config.js
lib/routes/bandisoft/index.js
lib/routes/biobio/others.js
lib/routes/blogs/hedwig.js
lib/routes/blogs/wordpress.js
lib/routes/booth-pm/shop.js
lib/routes/caixin/blog.js
lib/routes/caixin/category.js
lib/routes/craigslist/search.js
lib/routes/engadget/home.js
lib/routes/fanbox/main.js
lib/routes/fashionnetwork/headline.js
lib/routes/fashionnetwork/news.js
lib/routes/gitlab/common.js
lib/routes/gitlab/explore.js
lib/routes/gitlab/release.js
lib/routes/gitlab/tag.js
lib/routes/hexo/fluid.js
lib/routes/hexo/next.js
lib/routes/hexo/yilia.js
lib/routes/mastodon/account_id.js
lib/routes/mastodon/timeline_local.js
lib/routes/mastodon/timeline_remote.js
lib/routes/mastodon/utils.js
lib/routes/pornhub/category_url.js
lib/routes/pornhub/model.js
lib/routes/pornhub/pornstar.js
lib/routes/pornhub/users.js
lib/routes/touhougarakuta/index.js
lib/routes/weforum/report.js
lib/routes/yahoo-news/index.js
lib/routes/ziroom/room.js
lib/utils/valid-host.js
lib/v2/19lou/index.js
lib/v2/91porn/author.js
lib/v2/91porn/index.js
lib/v2/91porn/utils.js
lib/v2/bendibao/news.js
lib/v2/biquge/index.js
lib/v2/btzj/index.js
lib/v2/cnjxol/index.js
lib/v2/dut/index.js
lib/v2/eagle/blog.js
lib/v2/eprice/rss.js
lib/v2/ff14/ff14_global.js
lib/v2/gamme/category.js
lib/v2/gamme/tag.js
lib/v2/gumroad/index.js
lib/v2/huanqiu/index.js
lib/v2/itch/devlog.js
lib/v2/javbus/index.js
lib/v2/javdb/utils.js
lib/v2/lofter/user.js
lib/v2/mirror/index.js
lib/v2/myfigurecollection/activity.js
lib/v2/myfigurecollection/index.js
lib/v2/nikkei-cn/index.js
lib/v2/people/index.js
lib/v2/scitation/journal.js
lib/v2/scitation/section.js
lib/v2/solidot/main.js
lib/v2/zcool/user.js
lib/v2/zhubai/index.js
test/utils/valid-host.js
Commit message: feat(config)!: unsafe domain toggle (#11588)



Vulnerable code:
Configs in this sections are in beta stage, and are turn off by default. Please read corresponded description and turn on if necessary.
这个板块控制的是一些新特性的选项，默认他们都是关闭的。如果有需要请阅读对应说明后按需开启


    ctx.params.lang = ctx.params.lang || 'www';
    ctx.params.platform = ctx.params.platform || '';
    ctx.params.year = ctx.params.year || '';

    const rootUrl = `https://${ctx.params.lang === 'en' ? 'www' : ctx.params.lang}.weforum.org`;
    const currentUrl = `${rootUrl}/reports?platform=${ctx.params.platform}&year=${ctx.params.year}`;
    let domain = ctx.query.domain ?? 'btbtt20.com';

    if (cateList.indexOf(cate) === -1) {
    const rootUrl = `https://www.${ctx.query.domain ?? 'javbus.com'}`;
    const westernUrl = `https://www.${ctx.query.western_domain ?? 'javbus.org'}`;


Fixed code:
Configs in this sections are in beta stage, and **are turn off by default**. Please read corresponded description and turn on if necessary.
`ALLOW_USER_SUPPLY_UNSAFE_DOMAIN`: allow users to provide a domain as a parameter to routes that are not in their allow list, respectively. Public instances are suggested to leave this value default, as it may lead to [Server-Side Request Forgery (SSRF)](https://owasp.org/www-community/attacks/Server_Side_Request_Forgery)

这个板块控制的是一些新特性的选项，他们都是**默认关闭**的。如果有需要请阅读对应说明后按需开启
`ALLOW_USER_SUPPLY_UNSAFE_DOMAIN`: 允许用户为路由提供域名作为参数。建议公共实例不要调整此选项，开启后可能会导致 [服务端请求伪造（SSRF）](https://owasp.org/www-community/attacks/Server_Side_Request_Forgery)

            allow_user_supply_unsafe_domain: envs.ALLOW_USER_SUPPLY_UNSAFE_DOMAIN === 'true',
const { isValidHost } = require('@/utils/valid-host');
    if (!isValidHost(lang)) {
        throw Error('Invalid language code');
    }
const { isValidHost } = require('@/utils/valid-host');
    if (!isValidHost(ctx.params.column)) {
        throw Error('Invalid column');
    }

const { isValidHost } = require('@/utils/valid-host');
    if (!isValidHost(type)) {
        throw Error('Invalid type');
    }
const allowDomain = ['lawrence.code.blog'];
    if (!config.feature.allow_user_supply_unsafe_domain && !allowDomain.includes(ctx.params.domain)) {
        ctx.throw(403, `This RSS is disabled unless 'ALLOW_USER_SUPPLY_UNSAFE_DOMAIN' is set to 'true'.`);
    }

const { isValidHost } = require('@/utils/valid-host');
    if (!isValidHost(subdomain)) {
        throw Error('Invalid subdomain');
    }
const { isValidHost } = require('@/utils/valid-host');
    if (!isValidHost(column)) {
        throw Error('Invalid column');
    }
const { isValidHost } = require('@/utils/valid-host');
    if (!isValidHost(column)) {
        throw Error('Invalid column');
    }
const { isValidHost } = require('@/utils/valid-host');
    if (!isValidHost(ctx.params.location)) {
        throw Error('Invalid location');
    }

const allowLang = ['chinese', 'cn', 'us', 'japanese', 'www'];
    if (!allowLang.includes(lang)) {
        throw Error('Invalid lang');
    }
const { isValidHost } = require('@/utils/valid-host');
    if (!isValidHost(user)) {
        throw Error('Invalid user');
    }
const { isValidHost } = require('@/utils/valid-host');
    if (!isValidHost(country)) {
        throw Error('Invalid country');
    }
const { isValidHost } = require('@/utils/valid-host');
    if (!isValidHost(country)) {
        throw Error('Invalid country');
    }

const allowHost = ['gitlab.com'];

module.exports = {
    allowHost,
};
const config = require('@/config').value;
const { allowHost } = require('./common');
    if (!config.feature.allow_user_supply_unsafe_domain && !allowHost.includes(new URL(host).hostname)) {
        ctx.throw(403, `This RSS is disabled unless 'ALLOW_USER_SUPPLY_UNSAFE_DOMAIN' is set to 'true'.`);
    }
const config = require('@/config').value;
const { allowHost } = require('./common');
    if (!config.feature.allow_user_supply_unsafe_domain && !allowHost.includes(host)) {
        ctx.throw(403, `This RSS is disabled unless 'ALLOW_USER_SUPPLY_UNSAFE_DOMAIN' is set to 'true'.`);
    }
const config = require('@/config').value;
const { allowHost } = require('./common');
    if (!config.feature.allow_user_supply_unsafe_domain && !allowHost.includes(host)) {
        ctx.throw(403, `This RSS is disabled unless 'ALLOW_USER_SUPPLY_UNSAFE_DOMAIN' is set to 'true'.`);
    }
const config = require('@/config').value;
    if (!config.feature.allow_user_supply_unsafe_domain) {
        ctx.throw(403, `This RSS is disabled unless 'ALLOW_USER_SUPPLY_UNSAFE_DOMAIN' is set to 'true'.`);
    }
const config = require('@/config').value;
    if (!config.feature.allow_user_supply_unsafe_domain) {
        ctx.throw(403, `This RSS is disabled unless 'ALLOW_USER_SUPPLY_UNSAFE_DOMAIN' is set to 'true'.`);
    }
const config = require('@/config').value;
    if (!config.feature.allow_user_supply_unsafe_domain) {
        ctx.throw(403, `This RSS is disabled unless 'ALLOW_USER_SUPPLY_UNSAFE_DOMAIN' is set to 'true'.`);
    }
const config = require('@/config').value;
    if (!config.feature.allow_user_supply_unsafe_domain && !utils.allowSiteList.includes(site)) {
        ctx.throw(403, `This RSS is disabled unless 'ALLOW_USER_SUPPLY_UNSAFE_DOMAIN' is set to 'true'.`);
    }
const config = require('@/config').value;
    if (!config.feature.allow_user_supply_unsafe_domain && !utils.allowSiteList.includes(site)) {
        ctx.throw(403, `This RSS is disabled unless 'ALLOW_USER_SUPPLY_UNSAFE_DOMAIN' is set to 'true'.`);
    }
const config = require('@/config').value;
    if (!config.feature.allow_user_supply_unsafe_domain && !utils.allowSiteList.includes(site)) {
        ctx.throw(403, `This RSS is disabled unless 'ALLOW_USER_SUPPLY_UNSAFE_DOMAIN' is set to 'true'.`);
    }
const allowSiteList = ['mastodon.social', 'pawoo.net'];

    allowSiteList,
const { isValidHost } = require('@/utils/valid-host');
    if (!isValidHost(language)) {
        throw Error('Invalid language');
    }
const { isValidHost } = require('@/utils/valid-host');
    if (!isValidHost(language)) {
        throw Error('Invalid language');
    }
const { isValidHost } = require('@/utils/valid-host');
    if (!isValidHost(language)) {
        throw Error('Invalid language');
    }
const { isValidHost } = require('@/utils/valid-host');
    if (!isValidHost(language)) {
        throw Error('Invalid language');
    }
    if (!Object.keys(languageCodes).includes(language)) {
        throw Error('Invalid language');
    }
const { isValidHost } = require('@/utils/valid-host');
    const lang = ctx.params.lang || 'www';
    const platform = ctx.params.platform || '';
    const year = ctx.params.year || '';
    if (!isValidHost(lang)) {
        throw Error('Invalid lang');
    }

    const rootUrl = `https://${lang === 'en' ? 'www' : lang}.weforum.org`;
    const currentUrl = `${rootUrl}/reports?platform=${platform}&year=${year}`;
const { isValidHost } = require('@/utils/valid-host');
    if (!isValidHost(region)) {
        throw Error('Invalid region');
    }
const { isValidHost } = require('@/utils/valid-host');
    if (!isValidHost(city)) {
        throw Error('Invalid city');
    }

/**
 * Check if a sub-domain is valid
 * @param {String} hostname sub-domain
 * @returns {Boolean} true if valid
 */
const isValidHost = (hostname) => {
    if (typeof hostname !== 'string') {
        return false;
    }
    const regex = /^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?$/;
    return regex.test(hostname);
};

module.exports = {
    isValidHost,
};
const { isValidHost } = require('@/utils/valid-host');
    if (!isValidHost(city)) {
        throw Error('Invalid city');
    }
const { domainValidation } = require('./utils');
    domainValidation(domain, ctx);
const { domainValidation } = require('./utils');
    domainValidation(domain, ctx);
const config = require('@/config').value;
const allowDomain = ['91porn.com', 'www.91porn.com', '0122.91p30.com', 'www.91zuixindizhi.com', 'w1218.91p46.com'];

const domainValidation = (domain, ctx) => {
    if (!config.feature.allow_user_supply_unsafe_domain && !allowDomain.includes(domain)) {
        ctx.throw(403, `This RSS is disabled unless 'ALLOW_USER_SUPPLY_UNSAFE_DOMAIN' is set to 'true'.`);
    }
};

module.exports = {
    domainValidation,
};
const { isValidHost } = require('@/utils/valid-host');
    if (!isValidHost(city)) {
        throw Error('Invalid city');
    }
const config = require('@/config').value;
const allowHost = [
    'www.xbiquwx.la',
    'www.biqu5200.net',
    'www.xbiquge.so',
    'www.biqugeu.net',
    'www.b520.cc',
    'www.ahfgb.com',
    'www.ibiquge.la',
    'www.biquge.tv',
    'www.bswtan.com',
    'www.biquge.co',
    'www.bqzhh.com',
    'www.biqugse.com',
    'www.ibiquge.info',
    'www.ishuquge.com',
    'www.mayiwxw.com',
];
    if (!config.feature.allow_user_supply_unsafe_domain && !allowHost.includes(new URL(rootUrl).hostname)) {
        ctx.throw(403, `This RSS is disabled unless 'ALLOW_USER_SUPPLY_UNSAFE_DOMAIN' is set to 'true'.`);
    }
const config = require('@/config').value;
const allowDomain = ['btbtt15.com'];
    let domain = ctx.query.domain ?? 'btbtt15.com';
    if (!config.feature.allow_user_supply_unsafe_domain && !allowDomain.includes(new URL(domain).hostname)) {
        ctx.throw(403, `This RSS is disabled unless 'ALLOW_USER_SUPPLY_UNSAFE_DOMAIN' is set to 'true'.`);
    }
    if (!Object.keys(categories).includes(category)) {
        throw Error('Invalid category');
    }
const { isValidHost } = require('@/utils/valid-host');
    if (!isValidHost(site)) {
        throw Error('Invalid site');
    }
const { isValidHost } = require('@/utils/valid-host');
    if (!isValidHost(cate) || !isValidHost(language)) {
        throw Error('Invalid host');
    }
    if (!cateList.includes(cate)) {

const allowRegion = ['tw', 'hk'];
    if (!allowRegion.includes(region)) {
        throw Error('Invalid region');
    }
const { isValidHost } = require('@/utils/valid-host');
    if (!isValidHost(lang)) {
        throw Error('Invalid lang');
    }

const { isValidHost } = require('@/utils/valid-host');
    if (!isValidHost(domain)) {
        throw Error('Invalid domain');
    }
const { isValidHost } = require('@/utils/valid-host');
    if (!isValidHost(domain)) {
        throw Error('Invalid domain');
    }
const { isValidHost } = require('@/utils/valid-host');
    if (!isValidHost(username)) {
        throw Error('Invalid username');
    }
const { isValidHost } = require('@/utils/valid-host');
    if (!isValidHost(category)) {
        throw Error('Invalid category');
    }

const { isValidHost } = require('@/utils/valid-host');
    if (!isValidHost(user)) {
        throw Error('Invalid user');
    }
const config = require('@/config').value;
const allowDomain = ['javbus.com', 'javbus.org', 'javsee.icu', 'javsee.one'];

    const domain = ctx.query.domain ?? 'javbus.com';
    const westernDomain = ctx.query.western_domain ?? 'javbus.org';

    const rootUrl = `https://www.${domain}`;
    const westernUrl = `https://www.${westernDomain}`;

    if (!config.feature.allow_user_supply_unsafe_domain && (!allowDomain.includes(new URL(domain).hostname) || !allowDomain.includes(new URL(westernDomain).hostname))) {
        ctx.throw(403, `This RSS is disabled unless 'ALLOW_USER_SUPPLY_UNSAFE_DOMAIN' is set to 'true'.`);
    }
const config = require('@/config').value;
const allowDomain = ['javdb.com', 'javdb36.com', 'javdb007.com'];
        if (!config.feature.allow_user_supply_unsafe_domain && !allowDomain.includes(new URL(domain).hostname)) {
            ctx.throw(403, `This RSS is disabled unless 'ALLOW_USER_SUPPLY_UNSAFE_DOMAIN' is set to 'true'.`);
        }

const { isValidHost } = require('@/utils/valid-host');
    if (!isValidHost(name)) {
        throw Error('Invalid name');
    }
const { isValidHost } = require('@/utils/valid-host');
    if (!id.endsWith('.eth') && !isValidHost(id)) {
        throw Error('Invalid id');
    }
const { isValidHost } = require('@/utils/valid-host');
    if (language && !isValidHost(language)) {
        throw Error('Invalid language');
    }

const { isValidHost } = require('@/utils/valid-host');
    if (language && !isValidHost(language)) {
        throw Error('Invalid language');
    }
const { isValidHost } = require('@/utils/valid-host');
    if (!isValidHost(language)) {
        throw Error('Invalid language');
    }
const { isValidHost } = require('@/utils/valid-host');
    if (!isValidHost(site)) {
        throw Error('Invalid site');
    }
const { isValidHost } = require('@/utils/valid-host');
    if (!isValidHost(pub)) {
        throw Error('Invalid pub');
    }
const { isValidHost } = require('@/utils/valid-host');
    if (!isValidHost(pub)) {
        throw Error('Invalid pub');
    }
const { isValidHost } = require('@/utils/valid-host');
    if (!isValidHost(type)) {
        throw Error('Invalid type');
    }

const { isValidHost } = require('@/utils/valid-host');
        if (!isValidHost(uid)) {
            throw Error('Invalid uid');
        }
const { isValidHost } = require('@/utils/valid-host');
    if (!isValidHost(id)) {
        throw Error('Invalid id');
    }
const { isValidHost } = require('../../lib/utils/valid-host');

describe('valid-host', () => {
    it('validate hostname', () => {
        expect(isValidHost()).toBe(false);
        expect(isValidHost(123)).toBe(false);
        expect(isValidHost('')).toBe(false);
        expect(isValidHost('subd0main')).toBe(true);
        expect(isValidHost('-subd0main')).toBe(false);
        expect(isValidHost('sub-d0main')).toBe(true);
        expect(isValidHost('subd0main-')).toBe(false);
        expect(isValidHost('sub.d0main')).toBe(false);
        expect(isValidHost('sub-.d0main')).toBe(false);
        expect(isValidHost('s')).toBe(true);
        expect(isValidHost('-')).toBe(false);
        expect(isValidHost('0')).toBe(true);
        expect(isValidHost('s-')).toBe(false);
        expect(isValidHost('s-u')).toBe(true);
        expect(isValidHost('su')).toBe(true);
    });
});