[CVE-2021-24721] 취약점 분석 보고서
Loco Translate 버전 2.5.3 이하에서 발생하는 PHP 코드 주입 공격
1. Description
Loco Translate
플러그인은 번역 템플릿 파일(ex: .pot)을 생성하거나 저장할 때 파일 이름과 내용을 사용자 입력 데이터로 사용한다. 파일 이름에 .php 같은 확장자가 포함될 경우 PHP 코드가 포함된 파일이 실행되며 번역 파일 내부에 PHP 코드가 포함될 수 있다.
2. Affected Version
- Loco Translate < 2.5.4
3. Exploit
3.1 Translator 권한의 사용자 생성
3.2 Loco Translate > Themes로 이동
번역 템플릿 파일이 없는 테마를 선택한다.
3.3 Advanced 탭에서 프로젝트 이름에 PHP 코드 삽입
구성 저장 버튼을 클릭하여 저장한다.
3.3 고급 탭에서 프로젝트 이름에 PHP 코드 삽입
구성 저장 버튼을 클릭하여 저장한다.
3.4 개요 탭에서 템플릿 만들기 클릭
3.5 요청을 가로채어 파일 이름과 확장자 변경
3.6 브라우저를 통해 해당 경로 접근
wp-content/themes/twentytwentyfive/backdoor.php
경로를 실행하면 phpinfo 명령어가 작동된다.
4. Analysis
취약점이 발생한 버전의 코드는 번역 파일인지 확인하고 있다. 따라서, 검증 로직은 정상적인 .pot
파일의 보호를 목적으로 설계된 것이며 비정상적인 확장자 파일이 들어오는 경우를 처리하지 않고 있다는 것을 알 수 있다.
1
2
3
// src/ajax/XgettextController.php
$potfile = new Loco_fs_File( $target.'/'.$name );
1
2
3
4
5
// src/fs/FileWriter.php
if( 'pot' === $this->file->extension() && 1 < $opts->pot_protect ){
throw new Loco_error_WriteException( __('Modification of POT (template) files is disallowed by the plugin settings','loco-translate') );
}
이후 프로젝트 이름으로 작성한 PHP 코드는 Project-Id-Version
헤더에 설정되고 putContents
함수로 파일에 저장된다.
1
2
3
4
5
6
7
8
// src/ajax/XgettextController.php
// additional headers to set in new POT file
$head = $data->getHeaders();
$head['Project-Id-Version'] = $project->getName();
// write POT file to disk returning byte length
$potsize = $potfile->putContents( $data->msgcat(true) );
5. Patch Diff
취약점이 패치된 2.5.4 버전에서는 php 확장자 검증이 추가되었다.
1
2
3
4
5
6
7
// src/ajax/XgettextController.php
$potfile = new Loco_fs_File( $target.'/'.$name );
$ext = strtolower( $potfile->extension() );
if( 'pot' !== $ext && 'po' !== $ext ){
throw new Loco_error_Exception('Disallowed file extension');
}
1
2
3
4
5
6
7
8
9
10
// src/fs/FileWriter.php
$ext = strtolower( $this->file->extension() );
if( 'pot' === $ext && 1 < $opts->pot_protect ){
throw new Loco_error_WriteException( __('Modification of POT (template) files is disallowed by the plugin settings','loco-translate') );
}
// Deny list of sensitive file extensions, noting that specific actions may limit this further
else if( 'php' === $ext ){
throw new Loco_error_WriteException('Disallowed file extension in '.$this->file->basename() );
}
또한, 파일 헤더를 한 번 더 검증하여 다증척 보안을 적용하였다.
1
2
3
4
5
// finally allow headers to be modified via filter
$replaced = apply_filters( 'loco_po_headers', $headers );
if( $replaced instanceof LocoPoHeaders && $replaced !== $headers ){
$this->setHeaders($replaced);
}
6. Discussion
이 취약점은 사용자로부터 입력된 데이터를 번역 파일의 헤더와 본문 내용에 포함시키는 과정에서 발생하였다. 확장자 필터링의 부재로 인해, 공격자는 악성 PHP 코드를 번역 파일에 주입할 수 있었고, 해당 파일은 내용 검증 없이 저장되었다. 나아가, 파일이 실행 가능한 경로에 배치되어 악성 코드로 실행될 수 있는 상황이 초래되었다.