Skip to content
KitploitKITPLOIT
ИнструментыЭксплойтыБлог
Log in
Отправить
ИнструментыЭксплойтыБлог
Отправить

Инструменты для хакинга, пентеста и кибербезопасности — ваш арсенал защиты!

Kitploit — это каталог инструментов для хакинга, кибербезопасности и пентестинга. Находите последние обновления проектов для поиска уязвимостей, анализа систем, автоматизации тестирования и усиления вашей безопасности.

··Ленты·Контакты·Конфиденциальность·© 2026 Kitploit

Каталог инструментов

Категории

Все категории
Loading categories
perl_spreadsheet_excel_rce_poc — PoC для уязвимости RCE в библиотеке ParseExcel, а также в ParseXLSX, как зависимой библиотеке. | Kitploit
Инструменты/GitHubGitHub/haile01/perl_spreadsheet_excel_rce_poc
Анализ уязвимостейАнализ КодаЭксплуатацияЭксплуатация веб-приложенийРазработка Полезной НагрузкиЭксплуатация Бинарных Файлов
GitHubhaile01/perl_spreadsheet_excel_rce_poc

perl_spreadsheet_excel_rce_poc

PoC для уязвимости RCE в библиотеке ParseExcel, а также в ParseXLSX, как зависимой библиотеке.

Репозиторий
18692 лет назадЕщё не проверено

Популярное

Смотреть все →

Откройте для себя самые используемые инструменты нашего сообщества.

Изучить все инструменты

Просмотрите нашу коллекцию инструментов

Смотреть все инструменты →
Поделиться

Уязвимость безопасности ParseExcel

TL;DR: Удаленное выполнение кода (RCE) из-за логики разбора строк форматирования.

Краткое объяснение эксплуатации

Коренная причина эксплуатации заключается в вызове eval для непроверенного пользовательского ввода в Utility.pm

https://github.com/jmcnamara/spreadsheet-parseexcel/blob/e33d626d9b9cec91be7520dec1686712313957fb/lib/Spreadsheet/ParseExcel/Utility.pm#L171

# Uitlity.pm
sub ExcelFmt {
	my ( $format_str, $number, $is_1904, $number_type, $want_subformats ) = @_;

	return $number unless $number =~ $qrNUMBER;
	
	my $conditional;
	if ( $format_str =~ /^\[([<>=][^\]]+)\](.*)$/ ) {
		$conditional = $1;
		$format_str  = $2;
	}

	#...

	if ($conditional) {
		# TODO. Replace string eval with a function.
		$section = eval "$number $conditional" ? 0 : 1;
	}
    #...
}

Согласно тому, что я изучил, текущая реализация этого потока не имеет надлежащей проверки, а использование eval для обработки логики сравнения в данном случае является слишком «over kill». Из-за этого как ParseExcel::parse, так и ParseXLSX::parse (используемые для чтения данных из файлов Excel) уязвимы к RCE.

Где $format_str?

ValFmt является наиболее вероятным вызывающим ExcelFmt, поэтому я подробнее объясню этот метод. https://github.com/jmcnamara/spreadsheet-parseexcel/blob/e33d626d9b9cec91be7520dec1686712313957fb/lib/Spreadsheet/ParseExcel/FmtDefault.pm#L141-L161

sub ValFmt {
    my ( $oThis, $oCell, $oBook ) = @_;

    my ( $Dt, $iFmtIdx, $iNumeric, $Flg1904 );

    if ( $oCell->{Type} eq 'Text' ) {
        $Dt =
          ( ( defined $oCell->{Val} ) && ( $oCell->{Val} ne '' ) )
          ? $oThis->TextFmt( $oCell->{Val}, $oCell->{Code} ) # Perform some encoding logic => doesn't cause RCE
          : '';

        return $Dt;
    }
    else {
        $Dt      = $oCell->{Val};
        $Flg1904 = $oBook->{Flg1904};
        my $sFmtStr = $oThis->FmtString( $oCell, $oBook );

        # where RCE lies => $oCell->{Type} must be either "Date" or "Number"
        return ExcelFmt( $sFmtStr, $Dt, $Flg1904, $oCell->{Type} ); 
    }
}

Если $oCell->{Type} равно Date или Number, будет вызван ExcelFmt.

Значение $format_str возвращается из другого метода: FmtString https://github.com/jmcnamara/spreadsheet-parseexcel/blob/e33d626d9b9cec91be7520dec1686712313957fb/lib/Spreadsheet/ParseExcel/FmtDefault.pm#L101-L136

sub FmtString {
    my ( $oThis, $oCell, $oBook ) = @_;

    my $sFmtStr =
      $oThis->FmtStringDef( $oBook->{Format}[ $oCell->{FormatNo} ]->{FmtIdx},
        $oBook ); # maps to the correct format string
        
    #...

    unless ( defined($sFmtStr) ) {
        # assigns default format string depending on the value, can ignore
        #...
    }
    return $sFmtStr;
}

Вызывается еще одна функция, поэтому мы также рассмотрим FmtStringDef https://github.com/jmcnamara/spreadsheet-parseexcel/blob/e33d626d9b9cec91be7520dec1686712313957fb/lib/Spreadsheet/ParseExcel/FmtDefault.pm#L87-L96

sub FmtStringDef {
    my ( $oThis, $iFmtIdx, $oBook, $rhFmt ) = @_;
    my $sFmtStr = $oBook->{FormatStr}->{$iFmtIdx}; # does the mapping

    # More with assigning default format string, can ignore
    #...
}

Все переменные ясны, можно сделать вывод, что вектор атаки выглядит следующим образом:

  • Внедрить вредоносную строку формата с индексом $iFmtIdx
  • Убедиться, что формат ячейки $oBook->{Format}[$cellFmtIdx] сопоставляется с $iFmtIdx
  • Убедиться, что ячейка сопоставляется с этим форматом ячейки ($oCell->{FormatNo} = $cellFmtIdx) ![[flow 1.png]]

В следующих разделах я подробно объясню, как полезная нагрузка распространяла шелл-код до команды eval. Будут два раздела: для разбора файла .xls с помощью ParseExcel и для разбора файла .xlsx с помощью ParseXLSX.

PoC (Proof of Concept)

Для демонстрации ниже приведена ссылка на созданные нами вредоносные файлы Excel (в форматах .xls и .xlsx), которые выполняют whoami и сохраняют результат в файл /tmp/inject.txt. https://gist.github.com/haile01/0f4f19e4441895ef33ff27385080478b

Эксплуатация XLS-файла

Возьмем простую программу на Perl для разбора xls-файла, как показано ниже, которая использует ParseExcel::parse. RCE произойдет во время разбора, еще до того, как будут получены какие-либо данные.

use strict;
use Spreadsheet::ParseExcel;

my $parser = Spreadsheet::ParseExcel->new();
# file.xls is malicious file from end user
my $workbook = $parser->parse("test.xls");

Внедрение строки форматирования

Двоичные файлы Excel 97 структурированы в блоки двоичных данных, называемые записями BIFF. Каждая запись начинается с заголовка, называемого opCode (в little-endian), затем следует длина записи и фактические данные.

https://github.com/jmcnamara/spreadsheet-parseexcel/blob/19ea68d2ebf640e06df4f6937fcb43d76a5ec96b/lib/Spreadsheet/ParseExcel.pm#L438

sub QueryNext {
    my ( $q ) = @_;


    if ( $q->{streamPos} + 4 >= $q->{streamLen} ) {
        return 0;
    }

    my $data = substr( $q->{stream}, $q->{streamPos}, 4 );

    ( $q->{opcode}, $q->{length} ) = unpack( 'v2', $data );

    # No biff record should be larger than around 20,000.
    if ( $q->{length} >= 20000 ) {
        return 0;
    }

    if ( $q->{length} > 0 ) {
        $q->{data} = substr( $q->{stream}, $q->{streamPos} + 4, $q->{length} );
    }
    else {
        $q->{data}                     = undef;
        $q->{dont_decrypt_next_record} = 1;
    }

    if ( $q->{encryption} == MS_BIFF_CRYPTO_RC4 ) {
        # Handles with decryption
    }
    elsif ( $q->{encryption} == MS_BIFF_CRYPTO_XOR ) {
        # not implemented
        return 0;
    }
    elsif ( $q->{encryption} == MS_BIFF_CRYPTO_NONE ) {

    }

    $q->{streamPos} += 4 + $q->{length};

    return 1;
}

После этого для извлечения данных записи BIFF используется соответствующий обработчик для типа записи. https://github.com/jmcnamara/spreadsheet-parseexcel/blob/19ea68d2ebf640e06df4f6937fcb43d76a5ec96b/lib/Spreadsheet/ParseExcel.pm#L576-L580

if ( defined $self->{FuncTbl}->{$record} && !$workbook->{_skip_chart} )
{
		$self->{FuncTbl}->{$record}
			->( $workbook, $record, $record_length, $record_header );
}

Строка форматирования обрабатывается _subFormat с opCode = 0x41E https://github.com/jmcnamara/spreadsheet-parseexcel/blob/19ea68d2ebf640e06df4f6937fcb43d76a5ec96b/lib/Spreadsheet/ParseExcel.pm#L1563-L1585

sub _subFormat {

    my ( $oBook, $bOp, $bLen, $sWk ) = @_;
    my $sFmt;

    if ( $oBook->{BIFFVersion} <= verBIFF5 ) {
        $sFmt = substr( $sWk, 3, unpack( 'c', substr( $sWk, 2, 1 ) ) );
        $sFmt = $oBook->{FmtClass}->TextFmt( $sFmt, '_native_' );
    }
    else {
        $sFmt = _convBIFF8String( $oBook, substr( $sWk, 2 ) );
    }

    my $format_index = unpack( 'v', substr( $sWk, 0, 2 ) );

    # Excel 4 and earlier used an index of 0 to indicate that a built-in format
    # that was stored implicitly.
    if ( $oBook->{BIFFVersion} <= verBIFF4 && $format_index == 0 ) {
        $format_index = keys %{ $oBook->{FormatStr} };
    }

    $oBook->{FormatStr}->{$format_index} = $sFmt;
}

Я не был уверен, какая версия BIFF используется в моем .xls-файле, но, согласно данным в двоичном файле, она должна соответствовать случаю else (> verBIFF5).

Структура записи строки форматирования в более новых версиях BIFF должна быть: 1E 04 [длина записи — 2 байта] [индекс строки форматирования — 2 байта] [длина строки форматирования — 1 байт] [флаги строки — 2 байта] [содержимое строки форматирования]

Следуя правильной структуре, я могу внедрить любую строку форматирования в .xls-файл.

Скачать инструмент