编程

解析用户提供的 INI 字符串及文件时的安全考虑

1002 2023-11-03 16:28:00

PHP 提供了 parse_ini_stringparse_ini_file 函数,用来重用 PHP 内置的用于 PHP 自己的 INI 配置文件的解析器。

除了解析文本之外,INI 解析器支持也支持系统环境变量及文本解析时的 PHP 常量。从 PHP 8.3 起,它也支持环境变量的回退值语法。

; Normal string literals
my_config_name = normal

; Inherit SESSION_NAME environment variable, or "" if unavailable
my_config_name = ${SESSION_NAME}

; Inherit SESSION_NAME environment variable with fallback value "MyDefaultValue"
my_config_name = ${SESSION_NAME:-MyDefaultValue}

; String interpolation with environment variables
my_config_name = "${MAIL_FROM_USER}@${MAIL_FROM_DOMAIN}"

; Inherit PHP_VERSION PHP constant
my_config_name = PHP_VERSION

虽然这些增强对于使用环境变量配置 PHP 很有用,如果 PHP 内置的解析器解析用户提供的 INI 值时使用 PHP 常量可能是一个安全漏洞,因为 PHP 可能会暴露环境变量和可能包含不应暴露的敏感数据的 PHP 常量。

例如,由用户或不完全信任的远程服务器提供的配置文件可以利用此漏洞欺骗解析服务器公开其自己的环境变量和 PHP 常量:

; config.ini
plugin.name = "Free plugin ${DATABASE_NAME} / ${DATABASE_PASSWORD}"
plugin.description = DATABASE_PASSWORD
$conig = parse_ini_file('config.ini');
array(2) {
  ["plugin.name"]=> string(33) "Free plugin MyDbName / MyPa$$word"
  ["plugin.description"]=> string(10) "MyPa$$word"
}

然而,PHP 提供了配置参数,用来禁用 PHP 的类型强制转换和环境/常量替换。parse_ini_fileparse_ini_string 函数的第三个参数接受一个位掩码和一个标记 INI_SCANNER_RAW,用来禁用 PHP 的解析类型、环境变量及 PHP 常量:

; config.ini
plugin.name = "Free plugin ${DATABASE_NAME} / ${DATABASE_PASSWORD}"
plugin.description = DATABASE_PASSWORD
$conig = parse_ini_file('config.ini', scanner_mode: INI_SCANNER_RAW);
// or
$conig = parse_ini_file('config.ini', false, INI_SCANNER_RAW);
array(2) {
  ["plugin.name"]=> string(51) "Free plugin ${DATABASE_NAME} / ${DATABASE_PASSWORD}"
  ["plugin.description"]=> string(17) "DATABASE_PASSWORD"
}

这里的安全预防措施是,PHP 默认不使用 INI_SCANNER_RAW 标志,这意味着如果解析用户提供的 INI 值,所有未显式传递 INI_SCANNER_RAW 标志的函数调用都会受到攻击。