Skip to content
KitploitKITPLOIT
OutilsExploitsBlog
Log in
Soumettre
OutilsExploitsBlog
Soumettre

Outils de Hacking, PenTest et Cybersécurité pour votre Arsenal de Sécurité !

Kitploit est un répertoire d'outils de hacking, de cybersécurité et de pentesting. Découvrez les dernières mises à jour des projets pour trouver des vulnérabilités, analyser des systèmes, automatiser les tests et renforcer votre sécurité.

··Flux·Contact·Confidentialité·© 2026 Kitploit

Répertoire d'outils

Catégories

Voir toutes les catégories
Loading categories
perl_spreadsheet_excel_rce_poc — POC pour une vulnérabilité RCE dans la bibliothèque ParseExcel, et ParseXLSX également, en tant que bibliothèque dépendante. | Kitploit
Outils/GitHubGitHub/haile01/perl_spreadsheet_excel_rce_poc
Analyse des VulnérabilitésAnalyse de CodeExploitationExploitation d'Applications WebDéveloppement de Charges UtilesExploitation de Binaires
GitHubhaile01/perl_spreadsheet_excel_rce_poc

perl_spreadsheet_excel_rce_poc

POC pour une vulnérabilité RCE dans la bibliothèque ParseExcel, et ParseXLSX également, en tant que bibliothèque dépendante.

Voir le dépôt
1869il y a 2 ansPas encore vérifié

Populaires

Voir tout →

Découvrez les outils les plus utilisés par notre communauté.

Explorer tous les outils

Parcourez notre collection d'outils

Voir tous les outils →
Partager

Vulnérabilité de sécurité ParseExcel

TL;DR : RCE à partir de la logique d'analyse des chaînes de format.

Brève explication de l'exploit

La cause racine de l'exploitation vient de l'appel à eval sur une entrée utilisateur non validée dans 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;
	}
    #...
}

D'après ce que j'ai inspecté, l'implémentation actuelle de ce flux manque de validation appropriée, alors que l'utilisation de eval pour gérer les logiques de comparaison est excessive (« over kill ») dans ce cas. De ce fait, ParseExcel::parse et ParseXLSX::parse (utilisés pour lire les données des fichiers Excel) sont tous deux vulnérables aux RCE.

Où se trouve $format_str ?

ValFmt est l'appelant le plus probable de ExcelFmt, je vais donc expliquer plus en détail cette méthode 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} ); 
    }
}

Si $oCell->{Type} est Date ou Number, ExcelFmt sera appelé.

La valeur $format_str est celle retournée par une autre méthode : 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;
}

Une autre fonction est appelée, nous allons donc examiner également 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
    #...
}

Toutes les variables sont claires, nous pouvons conclure le vecteur d'attaque comme suit :

  • Injecter la chaîne de format malveillante avec l'index $iFmtIdx
  • S'assurer qu'un format de cellule $oBook->{Format}[$cellFmtIdx] correspond à $iFmtIdx
  • S'assurer qu'une cellule correspond à ce format de cellule ($oCell->{FormatNo} = $cellFmtIdx) ![[flow 1.png]]

Dans les sections ci-dessous, je vais détailler comment la charge utile a propagé le code shell jusqu'à la commande eval. Il y aura 2 sections : l'analyse des fichiers .xls avec ParseExcel et l'analyse des fichiers .xlsx avec ParseXLSX.

PoC

Pour démontrer, voici le lien vers nos fichiers Excel malveillants conçus (en .xls et .xlsx) qui exécutent whoami et stockent le résultat dans le fichier /tmp/inject.txt. https://gist.github.com/haile01/0f4f19e4441895ef33ff27385080478b

Exploitation sur fichier XLS

Prenons un simple programme Perl pour analyser un fichier xls comme ci-dessous, qui utilise ParseExcel::parse. La RCE se produira pendant l'analyse, avant même que des données ne soient récupérées.

use strict;
use Spreadsheet::ParseExcel;

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

Injection de la chaîne de format

Les fichiers binaires Excel 97 sont structurés en blocs de données binaires appelés enregistrements BIFF. Chaque enregistrement commence par un en-tête appelé opCode (en little-endian), suivi de la longueur de l'enregistrement et de ses données réelles.

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;
}

Ensuite, un gestionnaire correspondant au type d'enregistrement est utilisé pour extraire les données de cet enregistrement 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 );
}

La chaîne de format est gérée par _subFormat, avec 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;
}

Je n'étais pas sûr de la version BIFF utilisée dans mon fichier .xls, mais d'après les données du fichier binaire, elle devrait correspondre au cas else (> verBIFF5).

La structure de l'enregistrement de chaîne de format dans les versions BIFF plus récentes devrait être 1E 04 [longueur d'enregistrement - 2 octets] [index de chaîne de format - 2 octets] [longueur de chaîne de format - 1 octet] [drapeaux de chaîne - 2 octets] [contenu de chaîne de format]

En suivant la structure correcte, je peux injecter n'importe quelle chaîne de format dans le fichier .xls.

Télécharger l’outil