PDA

Voir la version complète : Sécuriser un serveur asterisk - recapitulatif



jean
13/02/2014, 18h30
bonjour

j'aimerais reprendre dans ce post l'ensemble des éléments nécessaires à bien sécuriser son asterisk. Il y a sur le forum plein d'infos là-dessus, mais je vise à faire quelque chose de complet, sous forme de pointeurs vers d'autres articles quand c'est possible - c'est trop de temps de faire un article détaillé, mais vous saurez ou regarder.
Il convient de connaitre un peu linux pour le comprendre, et à défaut, google est votre ami.
L'idée est de mettre à jour ce message au fil du temps. N'hésitez pas à suggèrer d'autres points
Cdlt
J.

1/ la réalité du danger
Chaque IP est scannée par des robots entre 10 et 20 fois par jour, pour trouver un serveur voip. Un des packages les plus utilisés est sipvicious, mais il y en a d'autres. Une fois que le robot a trouvé un serveur qui réponde, il passe en mode bruteforce pour trouver un username, puis un password. Au mieux, il ne trouver rien, et pendant plusieurs jours votre bande passante est saturée, dégradant la qualité du service, au pire, il trouve un user/pass, je ne vous décris pas la suite...
Les attaques peuvent aussi venir de clients utilisant normalement le service, et recherchant des appels gratuits, des employés, etc...

2/ En premier
Mettez votre asterisk derrière un firewall, et n'ouvrez pas le port 5060 - c'est le plus sur. Vous n'avez pas besoin d'ouvrir le 5060 si vous vous enregistrez / connectez à un fournisseur de VOIP. Votre serveur va lancer les paquets qui ouvriront le firewall. Les seuls éléments nécéssaires alors sont: régler correctement (quand c'est possible) la durée de vie de l'ouverture du port UDP - généralement, 30s ou 60s. Pour maintenir le port ouvert, utilisez l'option qualify=yes et descendez qualifyfreq à une valeur inférieure à celle de fermeture auto du port

3/ En premier (c'est aussi important que le 2....)
lisez, et comprenez le fichier: README-SERIOUSLY.bestpractices.txt - il est sous la racine des sources, à défaut: http://bit.ly/NFnvga
en particulier, le "Proper Device Naming", "Secure Passwords".

4/ Sécurisez les autres applis (ssh, pas de telnet, http), en appliquant les best practices pour chaque appli, et en les tenant à jour

5/ Dans tous les cas, et surtout si votre asterisk est accessible depuis l'extérieur
Mettre en place des règles IPTABLES sérieuses. Pour cela, cet article du forum est pas mal: http://www.asterisk-france.org/content.php/25-Bloquer-les-scans
mais j'adore celui là: https://blog.ls20.com/securing-your-asterisk-voip-server-with-iptables/

les points intéressants dans ce message sont le filtrage par useragent, le filtrage des dos.

Idéalement, le mieux si vous avez des postes nomades, est de passer par un vpn:
http://www.asterisk-france.org/content.php/37-Protection-des-postes-nomades-Asterisk-un-exemple-configuration-de-vpn-pptpd

6/ Si vous avez bien implémenté les règles iptables, presqu'aucun scanner ne viendra troubler votre tranquillité. Cependant, de temps en temps, l'un deux arrivera à passer => installez fail2ban:
http://www.asterisk-france.org/content.php/29-asterisk-avec-fail2ban

pour ssh et asterisk

7/ Bien sur, évitez les mots de passe trop faciles; freepbx vous propose un module de détection des mots de passe faibles, utilisez le.

8/ faites un petit script shell qui compte le nombre de minutes sur votre asterisk, par heure (par exemple) et qui vous notifie par email au delà d'une certaine limite. vous pourrez toujours arreter les frais en cas de soucis, plutôt que de s'en appercevoir après plusieurs jours

9/ Allowguest=no, alwaysreject=yes
allowguest autorise un client sans user ni mot de passe à passer des appels => TRES DANGEREUX
alwaysreject permet de renvoyer la meme erreur, que ce soit le username ou le password qui soit incorrect. cela complique les attaques bruteforce

10/ Positionner le contexte par défaut
dans la section general de sip, rajouter une ligne
context=contexctquinexistepas
de façon à positionner le contexte par défaut pour les appels entrants à un contexte inexistant. les contextes corrects seront spécifiés au niveau de chaque trunk/peer

11/ Inclusions de contextes
Bien vérifier les inclusions de contextes, de manière à ne pas donner des droits excessifs à des postes qui n'en ont pas l'usage

12/ un article qui résume tout ca:
http://www.star2billing.com/securing-asterisk/

13/ On peut aussi bloquer les IP par origine géographique
http://www.asterisk-france.org/threads/3791-Origine-des-IP-des-scanners

Voila... ca ne vous garantira jamais à 100%, mais ne pas appliquer ces conseils vous garanti à 100% d'être pirtaté !!!

_AK_
18/02/2014, 10h08
salut,

Merci pour ce post qui devrait servir a beaucoup ;)
C'est marrant, je suis en train de faire un 'memoire' pour ma VAE et je parle dedans de sécurisation asterisk,
Grosso modo, je parle des mêmes choses que toi.

je rajouterai dans ton guide :

-attention au context par défaut et aux inclusions de contexte

olppp
19/02/2014, 18h16
Bonjour,

J'autorise pour ma part les appels entrants SIP non authentifiés. Je restreins la portée à ma sda dans le context sip-default. je rejette le reste en logeant ce qui ressemble à des tentatives d'injection.
extrait extensions.ael

context sip-default {
_33123456XXX => jump ${EXTEN:7:4}@default;
_0123456XXX => jump ${EXTEN:7:4}@default
_6XXX => jump ${EXTEN:7:4}@default
s => {
if(${REGEX("&,/|@" ${EXTEN})}) {
Log(WARNING, invalid extension ${EXTEN} from ${CHANNEL(peerip)});
Playback(invalid,noanswer);
Wait(.5);
Congestion();
}
Set(TIMEOUT(absolute)=15);
Answer();
Wait(2);
Playback(ss-noservice);
Playtones(congestion);
Congestion(5);
}
}

jean
19/02/2014, 19h49
Bonjour,

J'autorise pour ma part les appels entrants SIP non authentifiés. Je restreins la portée à ma sda dans le context sip-default. je rejette le reste en logeant ce qui ressemble à des tentatives d'injection.
extrait extensions.ael
}

cela reste un cas d'usage très particulier ! je note en tous cas l'usage du langage ael... c'est l'avenir

seb
20/11/2014, 13h29
Bonjour,

Merci pour le recueil d'info !

Je voudrais approfondir sur le point suivant :



8/ faites un petit script shell qui compte le nombre de minutes sur votre asterisk, par heure (par exemple) et qui vous notifie par email au delà d'une certaine limite. vous pourrez toujours arreter les frais en cas de soucis, plutôt que de s'en appercevoir après plusieurs jours


Comment faire pour interroger asterisk sur le nombre d'heure ? Est-il possible de le faire par user/numéro ?

jean
20/11/2014, 17h36
en fait, cela dépend de l'implémentation - freepbx, asterisk pur, asterisk avec mysql....

avec asterisk classique, qui genere tous les appels dans master.csv, un debut de prog en php donne le code plus bas - il suffit alors de filtrer par nom de trunk, accountcode, etc... et de faire les totaux et de les comparer avec ce qui tu estimes normal


<?Php

$logfile="/var/log/asterisk/cdr-csv/Master.csv"

$handle = fopen($logfile, "r");
while (($data = fgetcsv($handle, 1000, ",")) !== FALSE) {
list($accountcode,$src, $dst, $dcontext, $clid, $channel, $dstchannel, $lastapp, $lastdata, $start, $answer, $end, $duration, $billsec, $disposition, $amaflags, $uniqueid, $userfield ) = $data;
echo "\nAccountcode = " . $accountcode;
echo "\nsrc = " . $src;
echo "\ndst = " . $dst;
echo "\ndcontext = " . $dcontext;
echo "\nclid = " .$clid;
echo "\nchannel = " . $channel;
echo "\ndstchannel = " . $dstchannel;
echo "\nlastapp = " .$lastapp;
echo "\nlastdata = " .$lastdata;
echo "\nstart = " .$start;
echo "\nanswer = " .$answer;
echo "\nend = " .$end;
echo "\nduration = " . $duration;
echo "\nbillsec = " .$billsec;
echo "\ndisposition = " .$disposition;
echo "\namaflags = " .$amaflags;
echo "\nuniqueid = " . $uniqueid;
echo "\nuserfield = " .$userfield;
echo "\n";

}
fclose($handle);
?>

seb
21/11/2014, 13h35
Ok ok... J'utilise XiVO !

Voyons si je vais réussir a faire un truc similaire. En cli asterisk il est possible d'avoir le temps d'un user ou faut-il passer par la BDD du XiVO ?

jean
21/11/2014, 14h34
le fichier master.csv est je pense toujours généré - sinon, asterisk ne totalise rien, donc forcément via la db xivo

seb
21/11/2014, 17h08
Sur mon Xivo /var/log/asterisk/cdr-csv/Master.csv n'existe pas...

Pour le moment je n'ai un usage qu'avec des numéros interne, peut être Est-ce pour cela que je n'ai pas de génération de CDR ?

fastm3
22/11/2014, 15h21
cdr_csv est un module qui va s'occuper de genener les csv avec les cdrs. S'il n'est pas chargé, ce qui est problement le cas avec xivo qui doit utilisé une BD , il n'existera pas.
Il est tout de meme plus simple d'exploiter les données ensuite quand c'est stocké dans une base de données. Voir exemple plus bas.
Mais ne pas avoir besoin de RDBMS est aussi le coté pratique de ce module.

C'est une excellente idée de monitorer regulierement les appels sur son asterisk, ( meme si ca peut ne pas etre suffisant comme expliqué plus haut mais pas bien compris a priori , les mots de passe forts ne changent rien...).
Pour monitorer, on peut faire cela de maniere plus ou moins complete. Je vous joins un autre petit script que j'ai utilisé auparavant un petit peu plus évolué meme si tres tres vieux...
Attention , il est brut , adapté plutot pour freepbx mais hormis la partie connection, le principe serait le meme pour xivo. Il tournait sur des anciennes installs et suite au changement de amportal.conf dans les dernieres versions, il faut juste adapter ou coder en dur les identifiants d'acces qui ne s'y trouve plus. Il ne marchera pas sans adaptation à votre install.

On peut avoir 2 strategies. Soit, on envoie un rapport avec les stats du jour , soit on analyse les consos et on previent des qu'un changement des habitudes notable est intervenus.
Le declenchement par cron sera adapté en fonction de vos besoins.

Pour cela, on calcule les valeurs moyennes de conso vers l'etranger ou num surtaxés et on definit une variation autorisée en pourcentage. Des que cette variation est dépassée, on previent l'admin permettant d'analyser rapidement le soucis et d'intervenir ou pas. Certes , dans ce cas, on intervient à posteriori mais c'est toujours mieux que pas du tout.

My €0.02

Fastm3.



#!/usr/bin/env php
#Modification par infimo ( Francois Couque ) pour adaptation numero surtaxé francais.
#Basé sur un post sur le forum trixbox de schmoozecom

<?php
require_once "DB.php";
/******************************
* Begin User Configuration
*****************************/
$email = "fcouque@nospam_telisk.fr";
// If you would like a daily report, set this to true, otherwise, only email if percent thresholds are reached
$daily_report = true;
// Set each of these percentage thresholds. If they are met, you will receive a report of the abnormal volume of calls or total duration of calls
$normal_outbound_calls_threshold = "80%";
$normal_outbound_duration_threshold = "40%";
$international_outbound_calls_threshold = "20%";
$international_outbound_duration_threshold = "20%";
// If you set a unique hostname on each of your PBXs, you do not need to change the $hostname variable below. It will automatically use your
// system hostname. Otherwise, you should change this to something unique so you know where the emails are coming from ;)
$hostname = "telisk_brunoy";
/******************************
* End User Configuration
*****************************/

function parse_amportal_conf($filename) {
$file = file($filename);
foreach ($file as $line) {
if (preg_match("/^\s*([a-zA-Z0-9]+)\s*=\s*(.*)\s*([;#].*)?/",$line,$matches)) {
$conf[ $matches[1] ] = $matches[2];
}
}
return $conf;
}


if(!$hostname) {
$hostname = `hostname`;
}
$n_vol_theshold = ereg_replace("[^0-9]","",$normal_outbound_calls_threshold)/100;
$n_dur_theshold = ereg_replace("[^0-9]","",$normal_outbound_duration_threshold)/100;
$i_vol_theshold = ereg_replace("[^0-9]","",$international_outbound_calls_threshold)/100;
$i_dur_theshold = ereg_replace("[^0-9]","",$international_outbound_duration_threshold)/100;

$config = parse_amportal_conf("/etc/amportal.conf");
$engine = $config['AMPDBENGINE'];
$db_username = $config['AMPDBUSER'];
$db_password = $config['AMPDBPASS'];
$db_host = $config['AMPDBHOST'];
$db_database = "asteriskcdrdb";

$db_url = $engine."://".$db_username.":".$db_password."@".$db_host."/".$db_database;
$db = DB::connect($db_url);

$email_report = false;

$sql = "SELECT
ROUND(AVG(sum_duration))
FROM (
SELECT
SUM(duration)/60 AS sum_duration
FROM
cdr
WHERE
dst REGEXP '^[0-9]{7,}' AND
DATE_SUB(CURDATE(),INTERVAL 30 DAY) <= DATE(calldate) AND
(WEEKDAY(calldate) >= 0 AND WEEKDAY(calldate) <= 4 )
GROUP BY
DATE(calldate)
)
AS sum_query";

$result = $db->query($sql);
list($n_avg_duration) = $result->fetchRow(DB_FETCHMODE_ARRAY);

$sql = "SELECT ROUND(SUM(duration)/60) FROM cdr WHERE dst REGEXP '^[0-9]{7,}' AND DATE(calldate) >= DATE_SUB(CURDATE(),INTERVAL 1 DAY)";
$result = $db->query($sql);
list($n_last_duration) = $result->fetchRow(DB_FETCHMODE_ARRAY);

$n_duration_percent_change = ($n_last_duration/$n_avg_duration)-1;

$message = "\n\n";
if($n_duration_percent_change >= $n_dur_theshold) {
$email_report = true;
$message .= "WARNING: ";
}
$message .= "Duree totale des appels journaliers pour un jour de semaine ( moyenne des 30 derniers jours ) : $n_avg_duration minutes. ";
$message .= "Duree totale des appels des dernieres 24 heures: $n_last_duration minutes. Variation en pourcentage: ".(int)($n_duration_percent_change*100)."%";

$sql = "SELECT
ROUND(AVG(count_calls))
FROM (
SELECT
COUNT(*) as count_calls
FROM
cdr
WHERE
dst REGEXP '^[0-9]{7,}' AND
DATE_SUB(CURDATE(),INTERVAL 30 DAY) <= DATE(calldate) AND
(WEEKDAY(calldate) >= 0 AND WEEKDAY(calldate) <= 4 )
GROUP BY
DATE(calldate)
)
AS count_query";
$result = $db->query($sql);
list($n_avg_total_calls) = $result->fetchRow(DB_FETCHMODE_ARRAY);

$sql = "SELECT COUNT(*) FROM cdr WHERE dst REGEXP '^[0-9]{7,}' AND DATE(calldate) >= DATE_SUB(CURDATE(),INTERVAL 1 DAY)";
$result = $db->query($sql);
list($n_last_total_calls) = $result->fetchRow(DB_FETCHMODE_ARRAY);

$n_total_calls_percent_change = ($n_last_total_calls/$n_avg_total_calls)-1;

$message .= "\n\n";
if($n_total_calls_percent_change >= $n_vol_theshold) {
$email_report = true;
$message .= "WARNING: ";
}

$message .= "Nombre quotidien des appels sortants (moyenne des 30 derniers jours): $n_avg_total_calls. ";
$message .= "Nombre des appels sortants des dernieres 24 heures: $n_last_total_calls. Variation en pourcentage: ".(int)($n_total_calls_percent_change*100)."%";

// International

$sql = "SELECT
ROUND(AVG(sum_duration))
FROM (
SELECT
SUM(duration)/60 AS sum_duration
FROM
cdr
WHERE
dst REGEXP '^(00|011)[0-9]{10,}' AND
DATE_SUB(CURDATE(),INTERVAL 30 DAY) <= DATE(calldate) AND
(WEEKDAY(calldate) >= 0 AND WEEKDAY(calldate) <= 4 )
GROUP BY
DATE(calldate)
)
AS sum_query";
$result = $db->query($sql);
list($i_avg_duration) = $result->fetchRow(DB_FETCHMODE_ARRAY);

$sql = "SELECT ROUND(SUM(duration)/60) FROM cdr WHERE dst REGEXP '^(00|089)[0-9]{6,}' AND DATE(calldate) >= DATE_SUB(CURDATE(),INTERVAL 1 DAY)";
$result = $db->query($sql);
list($i_last_duration) = $result->fetchRow(DB_FETCHMODE_ARRAY);
if(!$i_last_duration) {
$i_last_duration = 0;
}
if($i_avg_duration && $i_avg_duration != 0) {
$i_duration_percent_change = ($i_last_duration/$i_avg_duration)-1;
}
else {
$i_duration_percent_change = 0;
$i_avg_duration = 0;
}
$message .= "\n\n";
if($i_duration_percent_change >= $i_dur_theshold) {
$email_report = true;
$message .= "WARNING: ";
}

$message .= "Duree des appels internationaux et 089X un jour de semaine (moyenne des 30 derniers jours): $i_avg_duration minutes. ";
$message .= "Duree des appels internationaux et 089X des dernieres 24 heures: $i_last_duration minutes. Variation en pourcentage: ".(int)($i_duration_percent_change*100)."%";

$sql = "SELECT
ROUND(AVG(count_calls))
FROM (
SELECT
COUNT(*) as count_calls
FROM
cdr
WHERE
dst REGEXP '^(00|011)[0-9]{10,}' AND
DATE_SUB(CURDATE(),INTERVAL 30 DAY) <= DATE(calldate) AND
(WEEKDAY(calldate) >= 0 AND
WEEKDAY(calldate) <= 4 )
GROUP BY
DATE(calldate)
)
AS count_query";
$result = $db->query($sql);
list($i_avg_total_calls) = $result->fetchRow(DB_FETCHMODE_ARRAY);
if(!$i_avg_total_calls) {
$i_avg_total_calls = 0;
}

$sql = "SELECT COUNT(*) FROM cdr WHERE dst REGEXP '^(00|089)[0-9]{6,}' AND DATE(calldate) >= DATE_SUB(CURDATE(),INTERVAL 1 DAY)";
$result = $db->query($sql);
list($i_last_total_calls) = $result->fetchRow(DB_FETCHMODE_ARRAY);

if($i_avg_total_calls && $i_avg_total_calls != 0) {
$i_total_calls_percent_change = ($i_last_total_calls/$i_avg_total_calls)-1;
}
else {
$i_total_calls_percent_change = 0;
$i_avg_total_calls = 0;
}
$message .= "\n\n";
if($i_total_calls_percent_change >= $i_vol_theshold) {
$email_report = true;
$message .= "WARNING: ";
}

$message .= "Nombre des appels internationaux et 089X sortants un jour de la semaine (moyenne des 30 derniers jours): $i_avg_total_calls. ";
$message .= "Nombre des appels internationaux et 089X des dernieres 24 heures: $i_last_total_calls. Variation en pourcentage: ".(int)($i_total_calls_percent_change*100)."%";

// si email_report, la variation max a été dépassée.
if($email_report)
$subject = "ALERTE: volume d'appel pour $hostname";
else
$subject = "Rapport quotidien du volume d'appel pour $hostname";

if($daily_report || $email_report) {
$from = "Rapport du volume d'appel <$email>";
$headers = "From: $from" . "\r\n" .
'Reply-To: noreply@pbx' . "\r\n";
mail($email,$subject,$message,$headers);
}
echo $message;
?>

seb
12/12/2014, 14h37
Je vais regarder ça !!

Merci :D

jean
13/10/2015, 21h51
bonjour à tous

nos amis de star2billing / a2billing viennent de publier un excellent article sur la sécurité asterisk:

http://www.star2billing.com/securing-asterisk/

Bonne lecture !

J

jean
05/01/2016, 15h40
je viens d'ajouter dans le post initial l'item 13, sur le blocage geographique des @ ip