Risk of code injection in RSSHub
Description
RSSHub before commit 7f1c430 allowed code injection via eval/Function constructor in certain routes, leading to server-side compromise.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
RSSHub before commit 7f1c430 allowed code injection via eval/Function constructor in certain routes, leading to server-side compromise.
Vulnerability
RSSHub, an open-source RSS feed generator, contained a code injection vulnerability in versions prior to commit 7f1c430. The issue arose because some routes used eval or the Function constructor to process data from target sites. This allowed an attacker to inject arbitrary JavaScript code that would be executed on the server [1][4].
Exploitation
An attacker could exploit this by crafting a malicious RSS feed or controlling a target site that RSSHub fetches. When RSSHub processes the feed, the injected code would be passed to eval or Function, leading to remote code execution (RCE) on the server. No authentication is required, as the vulnerability is triggered during normal feed processing [1][4].
Impact
Successful exploitation allows an attacker to execute arbitrary code on the RSSHub server, potentially leading to data theft, server compromise, or further lateral movement within the network. The severity is high due to the ease of exploitation and the potential for full server takeover [1][4].
Mitigation
The vulnerability was fixed in commit 7f1c430 by temporarily removing the problematic routes and adding a no-new-func rule to the ESLint configuration to prevent future use of eval or Function constructor [2]. Users are advised to upgrade to version 7f1c430 or later immediately [4].
AI Insight generated on May 21, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
rsshubnpm | <= 1.0.0 | — |
Affected products
2Patches
17f1c43094e8afeat: remove some routes for security reasons, !!! no-eval no-new-func !!!
15 files changed · +12 −329
docs/game.md+0 −16 modified@@ -432,22 +432,6 @@ Example: `https://store.steampowered.com/search/?specials=1&term=atelier` 中的 <Route author="KotoriK" example="/pcr/news-cn" path="/pcr/news-cn"/> -## 篝火营地 - -### 游戏资讯 - -<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"/> - ## 怪物猎人世界 ### 更新情报
docs/live.md+0 −6 modified@@ -38,12 +38,6 @@ pageClass: routes <Route author="DIYgod" example="/douyu/room/24422" path="/douyu/room/:id" :paramsDesc="['直播间 id, 可在主播直播间页 URL 中找到']"/> -## 黑白直播 - -### 直播间开播 - -<Route author="laampui" example="/heibaizhibo/room/195976" path="/heibaizhibo/room/:id" :paramsDesc="['直播间 id, 可在主播直播间页 URL 中找到']"/> - ## 虎牙直播 ### 直播间开播
docs/new-media.md+0 −4 modified@@ -1936,10 +1936,6 @@ column 为 third 时可选的 category: </Route> -### 数读 - -<Route author="laampui" example="/netease/news/data" path="/netease/news/data"/> - ## 维基百科 ### 中国大陆新闻动态
docs/programming.md+0 −5 modified@@ -678,11 +678,6 @@ GitHub 官方也提供了一些 RSS: <Route author="hellodword" example="/galaxylab" path="/galaxylab"> </Route> -## 前端技术文章 - -<Route author="laampui" example="/front-end-rss" path="/front-end-rss" :paramsDesc="['分类']"> -</Route> - ## 前端艺术家 && 飞冰早报 ### 列表
docs/program-update.md+0 −6 modified@@ -418,12 +418,6 @@ pageClass: routes <Route author="mrbruce516" example="/tesla" path="/tesla/update"/> -## 腾讯柠檬 Lab - -### 柠檬精选 Mac Apps - -<Route author="HenryQW" example="/tencent/lemon" path="/tencent/lemon"/> - ## 腾讯云移动直播 SDK ### 更新日志
.eslintrc+2 −1 modified@@ -53,6 +53,7 @@ "prettier/prettier": 0, "no-await-in-loop": 2, "require-atomic-updates": 0, - "no-prototype-builtins": 0 + "no-prototype-builtins": 0, + "no-new-func": 2 } }
lib/router.js+0 −14 modified@@ -2072,10 +2072,6 @@ router.get('/pingwest/user/:uid/:type?', require('./routes/pingwest/user')); // Hanime router.get('/hanime/video', require('./routes/hanime/video')); -// 篝火营地 -router.get('/gouhuo/news/:category', require('./routes/gouhuo')); -router.get('/gouhuo/strategy', require('./routes/gouhuo/strategy')); - // Soul router.get('/soul/:id', require('./routes/soul')); router.get('/soul/posts/hot', require('./routes/soul/hot')); @@ -2141,9 +2137,6 @@ router.get('/szse/rule', require('./routes/szse/rule')); // 前端艺术家每日整理&&飞冰早报 router.get('/jskou/:type?', require('./routes/jskou/index')); -// 前端 -router.get('/front-end-rss', require('./routes/frontend/index')); - // 国家应急广播 router.get('/cneb/yjxx', require('./routes/cneb/yjxx')); router.get('/cneb/guoneinews', require('./routes/cneb/guoneinews')); @@ -2178,9 +2171,6 @@ router.get('/sohu/mp/:id', require('./routes/sohu/mp')); // 腾讯企鹅号 router.get('/tencent/news/author/:mid', require('./routes/tencent/news/author')); -// 腾讯柠檬精选 -router.get('/tencent/lemon', require('./routes/tencent/lemon/index')); - // 奈菲影视 router.get('/nfmovies/:id?', require('./routes/nfmovies/index')); @@ -3056,7 +3046,6 @@ router.get('/jike/user/:id', require('./routes/jike/user')); // 网易新闻 router.get('/netease/news/rank/:category?/:type?/:time?', require('./routes/netease/news/rank')); router.get('/netease/news/special/:type?', require('./routes/netease/news/special')); -router.get('/netease/news/data', require('./routes/netease/news/data')); // 网易 - 网易号 router.get('/netease/dy/:id', require('./routes/netease/dy')); @@ -3169,9 +3158,6 @@ router.get('/touhougarakuta/:language/:type', require('./routes/touhougarakuta') // 猎趣TV router.get('/liequtv/room/:id', require('./routes/liequtv/room')); -// 黑白直播 -router.get('/heibaizhibo/room/:id', require('./routes/heibaizhibo/room')); - // Behance router.get('/behance/:user/:type?', require('./routes/behance/index'));
lib/routes/frontend/index.js+0 −27 removed@@ -1,27 +0,0 @@ -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, - }; -};
lib/routes/gouhuo/cache.js+0 −44 removed@@ -1,44 +0,0 @@ -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;
lib/routes/gouhuo/index.js+0 −62 removed@@ -1,62 +0,0 @@ -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; - }), - }; -};
lib/routes/gouhuo/strategy.js+0 −35 removed@@ -1,35 +0,0 @@ -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, - }; -};
lib/routes/heibaizhibo/room.js+0 −31 removed@@ -1,31 +0,0 @@ -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, - }; -};
lib/routes/huxiu/utils.js+10 −10 modified@@ -15,13 +15,13 @@ const ProcessFeed = async (list, cache) => { const $ = cheerio.load(response.data); - const __INITIAL_STATE__ = Function( - 'let window={};' + - $('*') - .html() - .match(/window\.__INITIAL_STATE__.*};/gm)[0] + - 'return window.__INITIAL_STATE__' - )(); + // const __INITIAL_STATE__ = Function( + // 'let window={};' + + // $('*') + // .html() + // .match(/window\.__INITIAL_STATE__.*};/gm)[0] + + // 'return window.__INITIAL_STATE__' + // )(); $('.article__content') .find('.lazyImg') @@ -42,9 +42,9 @@ const ProcessFeed = async (list, cache) => { let description = ''; - 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>`; - } + // 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>`; + // } if ($('.top-img').html() !== null) { description += $('.top-img').html(); }
lib/routes/netease/news/data.js+0 −39 removed@@ -1,39 +0,0 @@ -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, - }; -};
lib/routes/tencent/lemon/index.js+0 −29 removed@@ -1,29 +0,0 @@ -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, - }; -};
Vulnerability mechanics
Root cause
"Server-side code injection via unsanitized use of `eval()` and `Function` constructor on data fetched from external websites."
Attack vector
An attacker can inject arbitrary JavaScript code into the RSSHub server by crafting a response from a target website that the vulnerable route fetches. For example, in the gouhuo routes, the server uses `eval()` on data extracted from the target site's HTML (`body.match(...)`) [patch_id=1699138]. Similarly, the heibaizhibo route constructs a `Function` from the target's `window.__NUXT__` payload, and the netease/news/data route uses `new Function()` on a string parsed from the page [patch_id=1699138]. Because the input is not sanitized before being passed to `eval` or `Function`, a malicious site can achieve server-side code injection [CWE-74].
Affected code
The vulnerable code paths were in `lib/routes/gouhuo/cache.js` (line using `eval(body.match(...))`), `lib/routes/gouhuo/strategy.js` (same `eval` pattern), `lib/routes/netease/news/data.js` (uses `new Function(dataStr + ...)`), `lib/routes/heibaizhibo/room.js` (uses `Function('const window={};' + ...)`), `lib/routes/frontend/index.js` (uses `Function($($('script')[1]).html() + ...)`), and `lib/routes/tencent/lemon/index.js` (uses `eval('(' + data + ')')`). The routes were registered in `lib/router.js` and the corresponding documentation in `docs/game.md` was also removed [patch_id=1699138].
What the fix does
The fix [patch_id=1699138] removes the entire vulnerable route files (`lib/routes/gouhuo/index.js`, `lib/routes/gouhuo/cache.js`, `lib/routes/gouhuo/strategy.js`, `lib/routes/netease/news/data.js`, `lib/routes/heibaizhibo/room.js`, `lib/routes/frontend/index.js`, `lib/routes/tencent/lemon/index.js`) and their corresponding route registrations in `lib/router.js`. In `lib/routes/huxiu/utils.js`, the `Function`-based code extraction is commented out rather than removed. Additionally, the ESLint rule `no-new-func` is added to the project configuration to prevent future use of `Function` constructor and similar dynamic code execution patterns [ref_id=1].
Preconditions
- inputThe attacker must control or compromise a target website whose content is fetched by one of the vulnerable RSSHub routes.
- configThe RSSHub instance must be running a version prior to commit 7f1c430.
Generated on May 23, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
5- github.com/advisories/GHSA-pgjj-866w-fc5cghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2021-21278ghsaADVISORY
- github.com/DIYgod/RSSHub/commit/7f1c43094e8a82e4d8f036ff7d42568fed00699dghsax_refsource_MISCWEB
- github.com/DIYgod/RSSHub/security/advisories/GHSA-pgjj-866w-fc5cghsax_refsource_CONFIRMWEB
- www.npmjs.com/package/rsshubghsax_refsource_MISCWEB
News mentions
0No linked articles in our index yet.