
Quand le code appelle named-checkzone pour le contrôle de syntaxe DNS, les messages renvoyés par cet utilitaire de contrôle sont lus et interprétés, puis remontés à l’utilisateur. Ce n’est pas parfait et ça donne une espèce de franglais, mais ça suffira pour la démo et tant qu’on ne laisse pas les élèves manipuler directement les fichiers de zone. Dans le cas contraire, il faudra améliorer cela.
217 lines
6.0 KiB
Perl
217 lines
6.0 KiB
Perl
package Email::SpoofingDemo::Web;
|
||
use Dancer2;
|
||
use Dancer2::Plugin::Deferred;
|
||
|
||
use JSON;
|
||
use REST::Client;
|
||
|
||
our $VERSION = '0.1';
|
||
|
||
sub call_api {
|
||
my ($method, $target, $url, $body_parameters) = @_;
|
||
|
||
my $host = config->{'api'}{$target};
|
||
die "Invalid target: $target" unless defined $host;
|
||
|
||
my $client = REST::Client->new();
|
||
$client->setHost($host);
|
||
$client->setTimeout(5);
|
||
$client->addHeader('Accept' => 'application/json');
|
||
$client->addHeader('Content-Type' => 'application/json');
|
||
|
||
my $body_data;
|
||
$body_data = encode_json($body_parameters) if defined $body_parameters;
|
||
|
||
$client->request($method, $url, $body_data);
|
||
|
||
my $status = $client->responseCode();
|
||
if ($status =~ /^2\d\d$/) {
|
||
my $response;
|
||
if ($client->responseContent() ne '') {
|
||
$response = decode_json($client->responseContent());
|
||
}
|
||
return ($response, $status);
|
||
}
|
||
else {
|
||
warn "API request returned $status";
|
||
return ($client->responseContent(), $status);
|
||
}
|
||
}
|
||
|
||
get '/' => sub {
|
||
template 'index' => { 'title' => 'Accueil' };
|
||
};
|
||
|
||
get '/dns/zone-edit/:zone' => sub {
|
||
my $zone = route_parameters->get('zone');
|
||
if (not (grep { $_ eq $zone } @{config->{'editable_zones'}})) {
|
||
pass;
|
||
}
|
||
|
||
my $zone_contents = deferred 'zone_contents';
|
||
|
||
if (not defined $zone_contents) {
|
||
my ($response, $status) = call_api(GET => 'dns', "/zone/${zone}/file");
|
||
$zone_contents = $response->{contents};
|
||
}
|
||
|
||
template 'dns/zone-edit' => {
|
||
'title' => 'Éditeur de zone DNS',
|
||
'zone_to_edit' => $zone // '',
|
||
'zone_contents' => $zone_contents // '',
|
||
};
|
||
};
|
||
|
||
post '/dns/zone-edit/:zone' => sub {
|
||
my $zone = route_parameters->{'zone'};
|
||
unless (grep { $_ eq $zone } @{config->{'editable_zones'}}) {
|
||
pass;
|
||
}
|
||
|
||
my $contents = body_parameters->{'zone-contents'};
|
||
my ($data, $status) = call_api(PUT => 'dns', "/zone/${zone}/file", { contents => $contents });
|
||
|
||
if ($status ne '200') {
|
||
$data = decode_json($data);
|
||
}
|
||
deferred status => {
|
||
outcome => $data->{outcome},
|
||
messages => $data->{messages},
|
||
};
|
||
deferred zone_contents => $data->{contents};
|
||
|
||
redirect "/dns/zone-edit/$zone", 303;
|
||
};
|
||
|
||
get '/sender/dkim-keys' => sub {
|
||
my ($installed_keys, $status) = call_api(GET => 'sender', '/installed-keys');
|
||
($status eq '200') or die "API returned $status";
|
||
|
||
template 'sender/dkim-keys' => {
|
||
active_role => 'sender',
|
||
title => 'Gestion des clefs DKIM',
|
||
installed_keys => $installed_keys,
|
||
};
|
||
};
|
||
|
||
get '/dkim-generator/sender' => sub {
|
||
template 'dkim-generator/sender' => {
|
||
title => 'Générateur de clefs DKIM'
|
||
};
|
||
};
|
||
|
||
post '/dkim-generator/sender' => sub {
|
||
my $api_params = {
|
||
domain => body_parameters->get('domain'),
|
||
selector => body_parameters->get('selector'),
|
||
key_size => body_parameters->get('key-size')
|
||
};
|
||
|
||
my ($response, $status) = call_api(POST => 'sender', '/generate-dkim-key', $api_params);
|
||
($status eq 200) or die "API returned $status";
|
||
|
||
template 'dkim-generator/sender' => {
|
||
title => 'Générateur de clefs DKIM',
|
||
txt_record => $response->{'txt_record'}
|
||
};
|
||
};
|
||
|
||
get '/attacker/spoof' => sub {
|
||
my ($config, $status) = call_api(GET => 'attacker', '/config');
|
||
($status eq 200) or die "Could not get attacker’s configuration";
|
||
|
||
template 'attacker/spoof' => {
|
||
title => 'Usurpateur d’identité de courriel',
|
||
default_helo => $config->{default_helo},
|
||
my_mail_from => $config->{my_mail_from},
|
||
scenarios => $config->{templates}
|
||
};
|
||
};
|
||
|
||
post '/attacker/spoof' => sub {
|
||
my $scenario = body_parameters->get('scenario');
|
||
my $replace_mail_from = (body_parameters->get('rfc5321-mailfrom') eq 'replace');
|
||
my $helo = body_parameters->get('helo');
|
||
|
||
my %api_params = (
|
||
scenario => $scenario,
|
||
replace_mail_from => ($replace_mail_from ? JSON::true : JSON::false),
|
||
helo => $helo
|
||
);
|
||
|
||
my ($logs, $status) = call_api(POST => 'attacker', '/spoof', \%api_params);
|
||
|
||
deferred 'logs' => $logs;
|
||
deferred 'selected_scenario' => $scenario;
|
||
deferred 'replace_mail_from' => $replace_mail_from;
|
||
|
||
redirect '/attacker/spoof', 303;
|
||
};
|
||
|
||
get '/sender/send-email' => sub {
|
||
my %template_params = (
|
||
title => 'Envoi de messages légitimes',
|
||
email_data => [
|
||
{
|
||
what => 'Confirmation de commande',
|
||
from => 'support@expediteur.example',
|
||
url => 'confirmation_email'
|
||
},
|
||
{
|
||
what => 'Newsletter',
|
||
from => 'info@newsletter.expediteur.example',
|
||
url => 'newsletter'
|
||
}
|
||
]
|
||
);
|
||
|
||
my $success = query_parameters->get('success');
|
||
if (defined $success) {
|
||
$template_params{success} = ($success eq 'success') ? 'success' : 'failure';
|
||
}
|
||
template 'sender/send-email' => \%template_params;
|
||
};
|
||
|
||
get '/recipient/settings' => sub {
|
||
my ($system_status, $http_code) = call_api(GET => 'recipient', '/status');
|
||
die if $http_code ne '200';
|
||
|
||
template 'recipient/settings' => {
|
||
title => 'Paramètres du système destinataire',
|
||
system_status => $system_status
|
||
};
|
||
};
|
||
|
||
post '/recipient/settings' => sub {
|
||
my %api_params = map {
|
||
$_ => body_parameters->{"$_-status"} ? 'enabled' : 'disabled'
|
||
} qw(spf dkim dmarc);
|
||
|
||
my (undef, $status) = call_api(PUT => 'recipient', '/status', \%api_params);
|
||
my $success = ($status eq 200) ? 'success' : 'failure';
|
||
redirect "/recipient/settings?success=$success", 303;
|
||
};
|
||
|
||
get '/sender/send-email/:email' => sub {
|
||
my $email = route_parameters->get('email');
|
||
|
||
my (undef, $response) = call_api(POST => 'sender', "/send-email/${email}");
|
||
my $success = ($response =~ /^2\d\d$/) ? 'success' : 'failure';
|
||
|
||
redirect "/sender/send-email?success=$success", 303;
|
||
};
|
||
|
||
get '/recipient/webmail' => sub {
|
||
template 'recipient/webmail' => {
|
||
title => 'Courriels'
|
||
};
|
||
};
|
||
|
||
any qr{.*} => sub {
|
||
template '404';
|
||
};
|
||
|
||
dance;
|
||
|
||
true;
|