Strapi RCE 漏洞链分析

基本信息

该漏洞利用链由两个漏洞组成:Strapi 远程代码执行漏洞(CVE-2023-22621)和Strapi 信息泄露漏洞(CVE-2023-22894)

Strapi 远程代码执行漏洞(CVE-2023-22621)为后台SSTI模板注入,在Strapi管理面板的Users & Permissions plugin可以设置确认邮件模板,在模板处存在模板注入,当开启邮件确认选项时将渲染该模板,触发漏洞,利用该漏洞需要后台管理员权限,以修改模板。

Strapi 信息泄露漏洞(CVE-2023-22894),Strapi 信息泄露漏洞(CVE-2023-22894)是由于Strapi在查询时,只是在查询结果中删除了敏感字段,在实际查询语句中仍然可以使用该敏感字段,所以攻击者可以通过观察Strapi服务端返回的数据,猜测所输入的敏感字段是否正确,即攻击者可以通过暴力破解,将敏感字段爆破出来

影响版本

Strapi ≤ 4.5.5

环境搭建

参考 https://razinj.dev/how-to-run-strapi-4-in-a-docker-container-using-docker-compose/,使用npx create-strapi-app@4.5.5 app 命令启动strapi v4.5.5并且用docker起数据库。

docker-compose.yml:

version: '3'

services:
  postgres:
    image: postgres
    restart: always
    volumes:
      - pgdata:/var/lib/postgresql/data
    environment:
      POSTGRES_USER: strapi
      POSTGRES_PASSWORD: strapi
      POSTGRES_DB: strapi
    ports:
      - '5432:5432'

  mysql:
    image: mysql:5
    restart: always
    command: --default-authentication-plugin=mysql_native_password
    environment:
      MYSQL_DATABASE: strapi
      MYSQL_USER: strapi
      MYSQL_PASSWORD: strapi
      MYSQL_ROOT_HOST: '%'
      MYSQL_ROOT_PASSWORD: strapi
    volumes:
      - mysqldata:/var/lib/mysql
    ports:
      - '3306:3306'

volumes:
  pgdata:
  mysqldata:

由于strapi监听在localhost,通过ssh把1337端口转发出来

ssh -CfNg -L 127.0.0.1:1337:127.0.0.1:1337 [root@192.168.59.197]

技术分析&调试

该漏洞利用链由两个漏洞组成,分别是Strapi 远程代码执行漏洞(CVE-2023-22621)和Strapi 信息泄露漏洞(CVE-2023-22894)。

Strapi 远程代码执行漏洞(CVE-2023-22621)是由服务端模板注入导致,在Strapi的管理面板的Users & Permissions plugin可以设置确认邮件模板,该模板由lodash 模板引擎渲染,代码如下

'use strict';

const _ = require('lodash');

const getProviderSettings = () => {
  return strapi.config.get('plugin.email');
};

const send = async (options) => {
  return strapi.plugin('email').provider.send(options);
};

/**
 * fill subject, text and html using lodash template
 * @param {object} emailOptions - to, from and replyto...
 * @param {object} emailTemplate - object containing attributes to fill
 * @param {object} data - data used to fill the template
 * @returns {{ subject, text, subject }}
 */
const sendTemplatedEmail = (emailOptions = {}, emailTemplate = {}, data = {}) => {
  const attributes = ['subject', 'text', 'html'];
  const missingAttributes = _.difference(attributes, Object.keys(emailTemplate));
  if (missingAttributes.length > 0) {
    throw new Error(
      `Following attributes are missing from your email template : ${missingAttributes.join(', ')}`
    );
  }

  const templatedAttributes = attributes.reduce(
    (compiled, attribute) =>
      emailTemplate[attribute]
        ? Object.assign(compiled, { [attribute]: _.template(emailTemplate[attribute])(data) })
        : compiled,
    {}
  );

  return strapi.plugin('email').provider.send({ ...emailOptions, ...templatedAttributes });
};

module.exports = () => ({
  getProviderSettings,
  send,
  sendTemplatedEmail,
});

该段代码获取模板及输入内容,调用_.template(emailTemplate[attribute])(data) })渲染内容, 在此之前,渲染内容由如下代码进行校验,以避免危险内容渲染:

'use strict';

const _ = require('lodash');

const invalidPatternsRegexes = [/<%[^=]([^<>%]*)%>/m, /\${([^{}]*)}/m];
const authorizedKeys = [
  'URL',
  'ADMIN_URL',
  'SERVER_URL',
  'CODE',
  'USER',
  'USER.email',
  'USER.username',
  'TOKEN',
];

const matchAll = (pattern, src) => {
  const matches = [];
  let match;

  const regexPatternWithGlobal = RegExp(pattern, 'g');
  // eslint-disable-next-line no-cond-assign
  while ((match = regexPatternWithGlobal.exec(src))) {
    const [, group] = match;

    matches.push(_.trim(group));
  }
  return matches;
};

const isValidEmailTemplate = (template) => {
  for (const reg of invalidPatternsRegexes) {
    if (reg.test(template)) {
      return false;
    }
  }

  const matches = matchAll(/<%=([^<>%=]*)%>/, template);
  for (const match of matches) {
    if (!authorizedKeys.includes(match)) {
      return false;
    }
  }

  return true;
};

module.exports = {
  isValidEmailTemplate,
};

该代码使用正则表达式检查邮件模板是否有非法内容,但由于正则表达式错误,导致攻击者可以使用排除列表的字符绕过检查,利用lodash的模板注入,向模板内注入恶意nodejs代码,当新注册用户时,Strapi将读取模板内容并渲染,从而导致代码执行。

Strapi 信息泄露漏洞(CVE-2023-22894)是由于Strapi在查询时,只是在查询结果中删除了敏感字段,在实际查询语句中仍然可以使用该敏感字段,所以攻击者可以通过观察Strapi服务端返回的数据,猜测所输入的敏感字段是否正确,即攻击者可以通过暴力破解,将敏感字段爆破出来,以上利用需要一定权限。当Strapi存在管理员创建的collection时,其关系字段会映射到管理员用户,如果该collection被配置为允许未授权用户访问,则未授权攻击者可以利用该collection访问Strapi管理员用户的密码哈希和重置密码token。

结合两个漏洞,攻击者可以利用Strapi 信息泄露漏洞(CVE-2023-22894)爆破得到管理员用户的邮箱,之后发送重置密码请求,在通过信息泄露获取到重置密码token,通过该token强制重置管理员密码并得到管理面板访问权限,之后利用Strapi 远程代码执行漏洞(CVE-2023-22621)向邮件模板里面注入恶意代码,在注册用户触发漏洞,执行恶意nodejs代码。

PoC

https://github.com/Chestnuts4/POC

参考链接

https://www.ghostccamm.com/blog/multi_strapi_vulns/#cve-2023-22894-leaking-sensitive-user-information-by-filtering-on-private-fields-in-strapi-versions-471

https://github.com/strapi/strapi/security/advisories/GHSA-2h87-4q2w-v4hf https://strapi.io/blog/security-disclosure-of-vulnerabilities-cve

Created at 2023-05-05T21:06:55+08:00

创建于:Friday, May 5,2023
最后修改于: Monday, January 22,2024