PHP Composer漏洞可能引發(fā)供應(yīng)鏈攻擊
Composer是PHP中管理和安全軟件依賴的主要工具,被開發(fā)團(tuán)隊(duì)廣泛應(yīng)用于更新過程等。因此,Composer使用名為Packagist 的在線服務(wù)來確定包下載供應(yīng)鏈的正確性。而Packagist每個(gè)月的下載請求在14億次左右。
研究人員在進(jìn)行安全研究時(shí),在 Packagist使用的Composer源碼中發(fā)現(xiàn)了一個(gè)嚴(yán)重的安全漏洞,漏洞CVE編號為CVE-2021-29472。攻擊者利用該漏洞可以在Packagist.org 服務(wù)器上執(zhí)行任意系統(tǒng)命令。此外,攻擊者還可以進(jìn)一步竊取維護(hù)者憑證,或?qū)螺d重定向到傳播后門依賴的第三方服務(wù)器。
漏洞分析
在請求下載包時(shí),Composer 首先會查詢Packagist來獲取元數(shù)據(jù)。元數(shù)據(jù)中包含2個(gè)獲取代碼源的域source和dist。Source只想開發(fā)庫,dist只想預(yù)構(gòu)建的庫。Composer在從庫中下載代碼時(shí)會使用外部系統(tǒng)命令來避免重新實(shí)現(xiàn)針對每隔版本控制軟件的邏輯。因此,這些調(diào)用都是用wrapper ProcessExecutor來執(zhí)行的:
- composer/src/Composer/Util/ProcessExecutor.php
- use Symfony\Component\Process\Process;
- // [...]
- class ProcessExecutor
- {
- // [...]
- public function execute($command, &$output = null, $cwd = null)
- {
- if (func_num_args() > 1) {
- return $this->doExecute($command, $cwd, false, $output);
- }
- return $this->doExecute($command, $cwd, false);
- }
- // [...]
- private function doExecute($command, $cwd, $tty, &$output = null)
- {
- // [...]
- if (method_exists('Symfony\Component\Process\Process', 'fromShellCommandline')) {
- // [1]
- $process = Process::fromShellCommandline($command, $cwd, null, null, static::getTimeout());
- } else {
- // [2]
- $process = new Process($command, $cwd, null, null, static::getTimeout());
- }
- if (!Platform::isWindows() && $tty) {
- try {
- $process->setTty(true);
- } catch (RuntimeException $e) {
- // ignore TTY enabling errors
- }
- }
- $callback = is_callable($output) ? $output : array($this, 'outputHandler');
- $process->run($callback);
在 [1]和[2]中,可以看到參數(shù) $command 是在shell中執(zhí)行的。大多數(shù)的ProcessExecutor 調(diào)用都是在版本控制軟件驅(qū)動中執(zhí)行的,版本控制軟件負(fù)載原創(chuàng)和本地庫的所有操作。比如,在Git驅(qū)動中:
- composer/src/Composer/Repository/Vcs/GitDriver.php
- public static function supports(IOInterface $io, Config $config, $url, $deep = false)
- {
- if (preg_match('#(^git://|\.git/?$|git(?:olite)?@|//git\.|//github.com/)#i', $url)) {
- return true;
- }
- // [...]
- try {
- $gitUtil->runCommand(function ($url) {
- return 'git ls-remote --heads ' . ProcessExecutor::escape($url); // [1]
- }, $url, sys_get_temp_dir());
- } catch (\RuntimeException $e) {
- return false;
- }
使用ProcessExecutor::escape() 可以將參數(shù)$url 逃逸以預(yù)防子命令($(...), `...`) ,但是無法預(yù)防用戶提供(--)開頭的值,只要加上其他的參數(shù)就可以成為最終的命令。這類漏洞就叫做參數(shù)注入。
類似的有漏洞的模式也出現(xiàn)在其他驅(qū)動中,用戶控制的數(shù)據(jù)可以成功繞過檢查并連接在一起成為系統(tǒng)命令:
- composer/src/Composer/Repository/Vcs/SvnDriver.php
- public static function supports(IOInterface $io, Config $config, $url, $deep = false)
- {
- $url = self::normalizeUrl($url);
- if (preg_match('#(^svn://|^svn\+ssh://|svn\.)#i', $url)) {
- return true;
- }
- // [...]
- $process = new ProcessExecutor($io);
- $exit = $process->execute(
- "svn info --non-interactive ".ProcessExecutor::escape($url),
- $ignoredOutput
- );
- composer/src/Composer/Repository/Vcs/HgDriver.php
- public static function supports(IOInterface $io, Config $config, $url, $deep = false)
- {
- if (preg_match('#(^(?:https?|ssh)://(?:[^@]+@)?bitbucket.org|https://(?:.*?)\.kilnhg.com)#i', $url)) {
- return true;
- }
- // [...]
- $process = new ProcessExecutor($io);
- $exit = $process->execute(sprintf('hg identify %s', ProcessExecutor::escape($url)), $ignored);
- return $exit === 0;
- }
更多技術(shù)細(xì)節(jié)參見:https://blog.sonarsource.com/php-supply-chain-attack-on-composer
補(bǔ)丁
研究人員將該漏洞提交給Packagist團(tuán)隊(duì)后,該團(tuán)隊(duì)快速反應(yīng),在12個(gè)小時(shí)內(nèi)就部署了安全補(bǔ)丁。
本文翻譯自:https://blog.sonarsource.com/php-supply-chain-attack-on-composer