CVE-2021-21278 (Markdown, published 2021-01-26T21:15Z)
Description: value: RSSHub is an open source, easy to use, and extensible RSS feed generator. In RSSHub before version 7f1c430 (non-semantic versioning) there is a risk of code injection. Some routes use `eval` or `Function constructor`, which may be injected by the target site with unsafe code, causing server-side security issues The fix in version 7f1c430 is to temporarily remove the problematic route and added a `no-new-func` rule to eslint.
Severity: HIGH (CVSS v3 base score: 9.8)
Weakness: CWE-74 \u2014 Improper Neutralization of Special Elements in Output Used by a Downstream Component ('Injection')
The product constructs all or part of a command, data structure, or record using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify how it is parsed or interpreted when it is sent to a downstream component.
Repository: https://github.com/DIYgod/RSSHub
Files changed:
.eslintrc
docs/game.md
docs/live.md
docs/new-media.md
docs/program-update.md
docs/programming.md
lib/router.js
lib/routes/huxiu/utils.js
Commit message: feat: remove some routes for security reasons, !!! no-eval no-new-func !!!


Vulnerable code:
        "no-prototype-builtins": 0
## 篝火营地

### 游戏资讯

<Route author="sintak" example="/gouhuo/news/switch" path="/gouhuo/news/:category" :paramsDesc="['资讯类型']">

| 精选       | 海外     | 原创    | PS4 | Xboxone | PC | Switch | 掌机     | 手游       | 新闻 | 评测   | 文化    | 视频  | 音频  | 折扣     |
| ---------- | -------- | ------- | --- | ------- | -- | ------ | -------- | ---------- | ---- | ------ | ------- | ----- | ----- | -------- |
| choiceness | overseas | orignal | ps4 | xboxone | pc | switch | handheld | mobilegame | news | review | culture | video | audio | discount |

</Route>

### 游戏攻略

<Route author="sintak" example="/gouhuo/strategy" path="/gouhuo/strategy"/>

## 黑白直播

### 直播间开播

<Route author="laampui" example="/heibaizhibo/room/195976" path="/heibaizhibo/room/:id" :paramsDesc="['直播间 id, 可在主播直播间页 URL 中找到']"/>

### 数读

<Route author="laampui" example="/netease/news/data" path="/netease/news/data"/>

## 腾讯柠檬 Lab

### 柠檬精选 Mac Apps

<Route author="HenryQW" example="/tencent/lemon" path="/tencent/lemon"/>

## 前端技术文章

<Route author="laampui" example="/front-end-rss" path="/front-end-rss" :paramsDesc="['分类']">
</Route>

// 篝火营地
router.get('/gouhuo/news/:category', require('./routes/gouhuo'));
router.get('/gouhuo/strategy', require('./routes/gouhuo/strategy'));

// 前端
router.get('/front-end-rss', require('./routes/frontend/index'));

// 腾讯柠檬精选
router.get('/tencent/lemon', require('./routes/tencent/lemon/index'));

router.get('/netease/news/data', require('./routes/netease/news/data'));
// 黑白直播
router.get('/heibaizhibo/room/:id', require('./routes/heibaizhibo/room'));

const got = require('@/utils/got');
const cheerio = require('cheerio');

module.exports = async (ctx) => {
    const response = await got.get(`https://front-end-rss.now.sh/`);
    const $ = cheerio.load(response.data);
    const fn = Function($($('script')[1]).html() + 'return Array.isArray(LINKS_DATA) ? LINKS_DATA : []');
    const items = fn().reduce(
        (acc, category) => [
            ...acc,
            ...category.items.map((item) => ({
                link: item.link,
                pubDate: item.date,
                title: item.title,
                author: category.title,
                description: category.title,
            })),
        ],
        []
    );

    ctx.state.data = {
        title: '前端技术文章',
        link: 'https://front-end-rss.now.sh/',
        item: items,
    };
};
const got = require('@/utils/got');
const cheerio = require('cheerio');

const self = {
    _getProfile: async (ctx) => {
        const key = 'gouhuo-profile';
        let profile = await ctx.cache.get(key);
        if (!profile) {
            const response = await got({
                method: 'get',
                url: `https://gouhuo.qq.com/`,
                headers: {
                    Referer: `https://gouhuo.qq.com/`,
                },
            });
            const body = response.data;
            const $ = cheerio.load(body);

            // eslint-disable-next-line no-eval
            const obj = eval(body.match(/(?<=<script>\s*window\.__NUXT__=)[\s\S]*?(?=<\/script>)/g)[0]);
            const tabNameMapping = {};
            for (const item of obj.data[0].tabList) {
                tabNameMapping[item.tab_id] = item.tab_name;
            }
            const title = $('title').text().split('-')[0].trim();
            const description = $('title').text().split('-')[1].trim();

            profile = JSON.stringify({
                title: title,
                description: description,
                tabNameMapping: tabNameMapping,
            });

            ctx.cache.set(key, profile);
        }
        return JSON.parse(profile);
    },
    getStaffs: async (ctx, tabId) => {
        const profile = await self._getProfile(ctx);
        return [profile.title, profile.description, profile.tabNameMapping[tabId]];
    },
};

module.exports = self;
const got = require('@/utils/got');
const cache = require('./cache');

module.exports = async (ctx) => {
    const category = ctx.params.category;

    const tabIdMapping = {
        choiceness: '1_110',
        overseas: '1_108',
        orignal: '1_109',
        ps4: '2_5',
        xboxone: '2_13',
        pc: '2_1',
        switch: '2_30',
        handheld: '3_1',
        mobilegame: '3_2',
        news: '1_101',
        review: '1_104',
        culture: '1_103',
        video: '1_102',
        audio: '1_107',
        discount: '1_120',
    };
    const tabId = (() => {
        if (tabIdMapping[category]) {
            return tabIdMapping[category];
        } else {
            throw Error('Unknow route.');
        }
    })();

    const response = await got({
        method: 'get',
        url: `https://gouhuo.qq.com/content/getContentByTab?tabId=${tabId}`,
        headers: {
            Referer: `https://gouhuo.qq.com/`,
        },
    });
    const list = response.data.data.list;

    const staffs = await cache.getStaffs(ctx, tabId);
    const title = staffs[0];
    const description = staffs[1];
    const tabName = staffs[2];

    ctx.state.data = {
        title: `${title} ${tabName}`,
        link: `https://gouhuo.qq.com/`,
        description: description,
        item:
            list &&
            list.map((item) => {
                const single = {
                    title: item.title,
                    description: `${item.title}<br><img src="${item.img_url}">`, // Get fulltext manually if needs be. Snippet like: $('.widget-article').first().html();
                    pubDate: new Date(item.publish_ts * 1000).toUTCString(),
                    link: item.link,
                };
                return single;
            }),
    };
};
const got = require('@/utils/got');
const cheerio = require('cheerio');

module.exports = async (ctx) => {
    const baseUrl = 'https://gouhuo.qq.com';
    const url = `${baseUrl}/guide`;
    const response = await got({
        method: 'get',
        url: url,
        headers: {
            Referer: `https://gouhuo.qq.com/`,
        },
    });
    const body = response.data;
    const $ = cheerio.load(body);

    // eslint-disable-next-line no-eval
    const obj = eval(body.match(/(?<=<script>\s*window\.__NUXT__=)[\s\S]*?(?=<\/script>)/g)[0]);
    const items = obj.data[0].newGuides.map((item) => {
        const img_url = item.img_url;
        const single = {
            title: item.title,
            description: `${item.title}<br><img src="${img_url}">`,
            pubDate: new Date(item.publish_ts * 1000).toUTCString(),
            link: `${baseUrl}${item.link}`,
        };
        return single;
    });

    ctx.state.data = {
        title: $('title').text().split('_')[0],
        link: url,
        item: items,
    };
};
const got = require('@/utils/got');

module.exports = async (ctx) => {
    const id = ctx.params.id;
    const response = await got.get(`https://www.heibaizhibo.com/anchorLive/${id}`);

    const liveInfo = Function('const window={};' + response.data.match(/window\.__NUXT__=.*;/gm)[0] + 'return window.__NUXT__')();
    const gtvId = liveInfo.data[0].gtvId;
    const res = await got.get(`https://sig.heibaizhibo.com/signal-front/live/matchLiveInfo?gtvId=${gtvId}&source=2&liveType=3&defi=hd`);
    const offline = res.data.data[0].score === -1;

    const item = offline
        ? []
        : [
              {
                  title: liveInfo.data[0].text,
                  author: liveInfo.data[0].anchorInfo.nickname,
                  link: response.url,
                  guid: response.url + liveInfo.data[0].live_info.startDate,
              },
          ];

    ctx.state.data = {
        title: `${liveInfo.data[0].anchorInfo.nickname} - 黑白直播`,
        image: liveInfo.data[0].anchorInfo.portrait,
        description: liveInfo.data[0].anchorInfo.notice,
        link: response.url,
        item,
        allowEmpty: true,
    };
};
                const __INITIAL_STATE__ = Function(
                    'let window={};' +
                        $('*')
                            .html()
                            .match(/window\.__INITIAL_STATE__.*};/gm)[0] +
                        'return window.__INITIAL_STATE__'
                )();
                if ('video_info' in __INITIAL_STATE__.articleDetail.articleDetail) {
                    description += `<video height="100%" poster="${__INITIAL_STATE__.articleDetail.articleDetail.video_info.cover}" controls src="${__INITIAL_STATE__.articleDetail.articleDetail.video_info.hd_link}"></video>`;
                }
const got = require('@/utils/got');
const cheerio = require('cheerio');
const iconv = require('iconv-lite');

module.exports = async (ctx) => {
    const response = await got({
        url: 'https://data.163.com',
        method: 'get',
        responseType: 'buffer',
    });
    const $ = cheerio.load(iconv.decode(response.data, 'gbk'));
    const dataStr = $('*')
        .html()
        .match(/var\sohnofuchlist=\[(.|\n)*"a"\];$/gm)[0];
    const arr = new Function(dataStr + 'ohnofuchlist.pop();return ohnofuchlist;')();

    const item = await Promise.all(
        arr.slice(0, 20).map(async (item) => {
            const single = await ctx.cache.tryGet(item.url, async () => {
                const res = await got.get(item.url);
                const doc = cheerio.load(res.data);
                const description = doc('#main-content').html() || doc('.post_body').html();
                return {
                    title: item.title,
                    link: item.url,
                    pubDate: new Date(item.time),
                    description,
                };
            });
            return Promise.resolve(single);
        })
    );

    ctx.state.data = {
        title: '数读 - 网易新闻',
        link: 'https://data.163.com/',
        item,
    };
};
const got = require('@/utils/got');
const date = require('@/utils/date');

module.exports = async (ctx) => {
    const url = 'https://lemon.qq.com/lab/js/source.js';

    let data = (await got.get(url)).data.match(/(?<=(const list = ))(.*)(\])/gs)[0];

    // eslint-disable-next-line no-eval
    data = eval('(' + data + ')').slice(0, 10);

    const items = data.map((i) => ({
        title: i.name,
        description: `
        ${i.comment}</br>
        <img src="${i.logo}"></br>
        <a href="${i.downloadlink}">下载链接</a>
        `,
        link: `https://lemon.qq.com/lab/app/${i.shortname}.html`,
        author: i.referrer,
        pubDate: date(i.date),
    }));

    ctx.state.data = {
        title: '腾讯柠檬精选',
        link: 'https://lemon.qq.com/lab/',
        item: items,
    };
};

Fixed code:
        "no-prototype-builtins": 0,
        "no-new-func": 2
                // const __INITIAL_STATE__ = Function(
                //     'let window={};' +
                //         $('*')
                //             .html()
                //             .match(/window\.__INITIAL_STATE__.*};/gm)[0] +
                //         'return window.__INITIAL_STATE__'
                // )();
                // if ('video_info' in __INITIAL_STATE__.articleDetail.articleDetail) {
                //     description += `<video height="100%" poster="${__INITIAL_STATE__.articleDetail.articleDetail.video_info.cover}" controls src="${__INITIAL_STATE__.articleDetail.articleDetail.video_info.hd_link}"></video>`;
                // }