Введение
Как знает каждый, кто когда-либо подписывался на рассылки по информационной безопасности, количество найденных уязвимостей за день зачастую превышает возможности человека их анализировать.
Особенно если серверов много, особенно если зоопарк ОС и версий.
В этой теме я расскажу о том, как мы решили эту проблему.
И да, Перл * живой :)
Цели и задачи
При проектировании системы мы Мхлыстун ** решил две параллельные задачи:- Сбор информации о версиях пакетов в системах
- Сбор информации о текущих уязвимостях в системах
Схема работы
- Раз в час каждый хост собирает информацию о текущих пакетах и отправляет ее в очередь.
- Сообщения удаляются из очереди и помещаются в базу данных.
- Раз в 3 часа робот приходит в базу данных и прогоняет пакеты через сервис аудита.
- По итогам дня формируется отчет и рассылается всем заинтересованным лицам.
Базовая схема
pkgs.sqlК столу хозяева вносится информация о хостах (один хост = одна запись), параллельно заполняется информация упаковка (один пакет – одна запись), во избежание дублирования информации.CREATE TABLE hosts ( hostname character varying(255) NOT NULL PRIMARY KEY, os character varying(255), pkg_id integer[] ); CREATE INDEX hosts_pkg_id_idx ON hosts USING gin (pkg_id); CREATE TABLE pkg ( id SERIAL NOT NULL PRIMARY KEY, name character varying(255) NOT NULL ); CREATE UNIQUE INDEX pkg_name_idx ON pkg USING btree (name); CREATE TABLE vulners ( id character varying(255) NOT NULL PRIMARY KEY, cvss_score double precision DEFAULT 0.0 NOT NULL, cvss_vector character varying(255), description text, cvelist text ); CREATE TABLE v2p ( pkg_id integer NOT NULL REFERENCES pkg(id), vuln_id character varying(255) NOT NULL REFERENCES vulners(id) );
Массив был выбран исторически; с этим вполне можно жить.
Параллельно столу уязвимости вносится информация о текущих уязвимостях пакетов, таблица подключений v2p позволяет выполнять привязку «многие ко многим».
Сбор информации о посылках
Grabber.pl #!/usr/bin/perl
# (C) Ivan Agarkov 2016
use strict;
use warnings;
use JSON;
# config
our %grabs = (
'centos|oraclelinux|redhat|fedora' => q(rpm -aq),
'debian|ubuntu' => q(dpkg-query -W -f='${Package} ${Version} ${Architecture}\n'),
'osx' => q(pkgutil --pkgs)
);
our %unames = (
'linux' => q(lsb_release -a),
'darwin' => q(echo "Distributor ID: OSX")
);
# global vars
our $hostname = `hostname -f`;
our ($vercmd, $grabcmd, $operatingsystem, $version);
# do uname
my $uname = `uname`;
chomp $uname;
foreach (keys %unames) {
$vercmd = $unames{$_} if $uname =~ /$_/i;
}
die "Version CMD not found" unless $vercmd;
# do version check
foreach (`$vercmd`) {
chomp;
/^Distributor ID:\s*(\S[\S\s]+)$/ and $operatingsystem = $1;
/^Release:\s*(\S[\S\s]+)$/ and $version = $1;
}
die "Opetating System not found" unless $operatingsystem;
foreach (keys %grabs) {
$grabcmd = $grabs{$_} if $operatingsystem =~ /$_/i;
}
# grab pkgs
die "Opetating System not found" unless $grabcmd;
my @pkgs;
foreach (`$grabcmd`) {
chomp;
push @pkgs, $_;
}
chomp $hostname;
my $result = {
hostname => $hostname,
os => $version ? qq($operatingsystem $version) : $operatingsystem,
pkgs => [ sort @pkgs ]
};
#
print JSON->new->encode($result);
# done
1;
Для тех, кто не знает Perl: просто последовательное выполнение команд имя хоста -f , lsb_release -a И об/мин -aq|dpkg-запрос -W все это упаковывается в JSON и выводится для отправки в очередь сообщений.
Преобразование JSON в базу данных
Transform.pl #!/usr/bin/perl
# (C) Ivan Agarkov 2016
use strict;
use warnings;
use JSON;
use DBI;
use constant DB => 'dbi:Pg:dbname=pkgs';
# 0. create connection
my $dbh
= DBI->connect( DB, "", "", { RaiseError => 1, AutoCommit => 0 } );
# 1. read from stdin and parse
my $data = JSON->new->decode( join( "", <STDIN> ) );
# if data == array parse foeach
if ( ref($data) eq "ARRAY" ) {
foreach (@$data) {
parse_host($_);
}
}
else {
parse_host($data);
}
# Done
1;
### SUBS ###
sub parse_host {
$_ = shift;
# do parse packages
my ( $hostname, $os, @pkgs )
= ( $_->{hostname}, $_->{os}, @{ $_->{pkgs} } );
my @pkgids;
eval {
foreach (@pkgs) {
my $sth = $dbh->prepare("SELECT id FROM pkg WHERE name=?");
$sth->execute( ($_) );
if ( my ($id) = $sth->fetchrow_array ) {
push @pkgids, int($id);
}
else {
$dbh->do( "INSERT INTO pkg (name) VALUES(?)", undef, $_ );
push @pkgs, $_;
}
}
};
$dbh->rollback and die "$@" if $@;
$dbh->commit;
# do parse host
eval {
my $sth = $dbh->prepare("SELECT os FROM hosts WHERE hostname=?");
$sth->execute( ($hostname) );
if ( my ($os2) = $sth->fetchrow_array ) {
if ( lc($os2) ne lc($os) ) {
$dbh->do( "UPDATE hosts SET os=? WHERE hostname=?",
undef, $os, $hostname );
}
}
else {
$dbh->do( "INSERT INTO hosts (hostname, os) VALUES(?, ?)",
undef, $hostname, $os );
}
};
$dbh->rollback and die "$@" if $@;
$dbh->commit;
# do set packages
eval {
$dbh->do( "UPDATE hosts SET pkg_id=? WHERE hostname=?",
undef, [@pkgids], $hostname );
};
$dbh->rollback and die "$@" if $@;
$dbh->commit;
}
Этот скрипт принимает на вход полученный из очереди json, а затем в три этапа расширяет его в базу данных: — Сначала выкладывает пакеты, проверяя их уникальность - Затем устанавливает хосты, обновляя версию при необходимости - Затем связывает хосты и пакеты через массив
Аудит
Когда мы искали способ провести аудит наших посылок, мы долго перебирали варианты, пока на какой-то конференции нам на глаза не попалась визитка.Это агрегатор уязвимостей, который делает изокс И виденс .
Я связался с ними и попросил о помощи.
Результат был Аудит API .
Аудит API Чтобы получить информацию об уязвимостях, просто соберите пакет пакетов в формате json и отправьте его на адрес /api/v3/аудит/аудит/ .
POST /api/v3/audit/audit/ HTTP/1.0
Host: vulners.com
Content-Type: application/json
Content-Length: 377
{ "os":"CentOS", "version":"7", "package":["kernel-3.10.0-229.el7.x86_64"]}
В ответ сервер вернет json со списком текущих уязвимостей в следующем формате: {
"result": "OK",
"data": {
"packages": {
"kernel-3.10.0-229.el7.x86_64": {
"CESA-2015:2152": [
{
"package": "kernel-3.10.0-229.el7.x86_64",
"providedVersion": "0:3.10.0-229.el7",
"bulletinVersion": "3.10.0-327.el7",
"providedPackage": "kernel-3.10.0-229.el7.x86_64",
"bulletinPackage": "kernel-3.10.0-327.el7.x86_64.rpm",
"operator": "lt",
"bulletinID": "CESA-2015:2152"
}
],
"CESA-2015:1978": [
{
"package": "kernel-3.10.0-229.el7.x86_64",
"providedVersion": "0:3.10.0-229.el7",
"bulletinVersion": "3.10.0-229.20.1.el7",
"providedPackage": "kernel-3.10.0-229.el7.x86_64",
"bulletinPackage": "kernel-3.10.0-229.20.1.el7.src.rpm",
"operator": "lt",
"bulletinID": "CESA-2015:1978"
},
// skipped
],
"CESA-2016:0064": [
{
"package": "kernel-3.10.0-229.el7.x86_64",
"providedVersion": "0:3.10.0-229.el7",
"bulletinVersion": "3.10.0-327.4.5.el7",
"providedPackage": "kernel-3.10.0-229.el7.x86_64",
"bulletinPackage": "kernel-3.10.0-327.4.5.el7.src.rpm",
"operator": "lt",
"bulletinID": "CESA-2016:0064"
},
// skipped
],
// skipped
],
// skipped
"cvss": {
"score": 10.0,
"vector": "AV:NETWORK/AC:LOW/Au:NONE/C:COMPLETE/I:COMPLETE/A:COMPLETE/"
},
"cvelist": [
"CVE-2014-9644",
"CVE-2016-2384",
// skipped
],
"id": "F777"
}
}
После тестирования API был написан код, который закидывает список пакетов в ОС и в ответ получает список уязвимостей и помещает их в базу данных.
аудит.pl #!/usr/bin/perl
# (C) Ivan Agarkov 2016
use strict;
use warnings;
use lib 'perl5';
use HTTP::Tiny;
use DBI;
use JSON;
use constant VULNERS_AUDIT_API => ' http://vulners.com/api/v3/audit/audit/ ';
use constant VULNERS_ID_API => ' http://vulners.com/api/v3/search/id/ ';
use constant DB => ' dbi:Pg:dbname=pkgs ';
our %VULNS;
our $dbh;
our %pkgs = ();
# 0. connect to DB
$dbh = DBI->connect( DB, "", "", { RaiseError => 1, AutoCommit => 0 } );
# get all OS variations
my @os = get_os();
# for each OS get all packages and ask vulners for its vulnerabilities
foreach my $os (@os) {
eval {
my ( $o, $ver ) = split( / /, $os );
my $res = HTTP::Tiny->new->request(
'POST',
VULNERS_AUDIT_API,
{ headers => { 'Content-Type' => 'application/json' },
content => JSON->new->encode(
{ os => $o,
version => $ver,
package => [ get_packages($os) ]
}
)
}
);
if ( !$res->{success} ) {
die "HTTP Error: $res->{content}";
}
my $data = JSON->new->decode( $res->{content} );
my $vulns = $data->{data}->{packages};
return undef unless defined $vulns;
foreach ( keys %$vulns ) {
my $o = $vulns->{$_};
if ( defined( $pkgs{$_} ) ) {
$VULNS{ $pkgs{$_} } = [ keys %$o ];
}
}
};
print $@ if $@;
}
# Now get info on each vuln ID ( CESA, USN, etc ) .
my @result;
my $res = HTTP::Tiny->new->request(
'POST',
VULNERS_ID_API,
{ headers => { 'Content-Type' => 'application/json' },
content => JSON->new->encode( { id => [ map {@$_} values %VULNS ] } )
}
);
if ( !$res->{success} ) {
die "HTTP Error: $res->{content}";
}
my $data = JSON->new->decode( $res->{content} );
foreach ( values %{ $data->{data}->{documents} } ) {
push @result,
{
id => $_->{id},
cvss_score => $_->{cvss}->{score},
cvss_vector => $_->{cvss}->{vector},
description => $_->{description},
cvelist => join( ', ', @{ $_->{cvelist} } ),
};
}
# Insert the data to DB
eval {
$dbh->do( "DELETE FROM v2p", undef );
$dbh->do( "DELETE FROM vulners", undef );
# insert prepared data to vulners table
foreach (@result) {
$dbh->do(
"INSERT INTO vulners (id, cvss_score, cvss_vector, description, cvelist) VALUES (?,?,?,?,?)",
undef,
$_->{id},
$_->{cvss_score},
$_->{cvss_vector},
$_->{description},
$_->{cvelist}
);
}
# and link pkg and vuls into v2p
foreach my $pkg_id ( keys %VULNS ) {
foreach my $vuln_id ( @{ $VULNS{$pkg_id} } ) {
$dbh->do( "INSERT INTO v2p(pkg_id,vuln_id) VALUES(?,?)",
undef, $pkg_id, $vuln_id );
}
}
};
$dbh->rollback and die "Error $@" if $@;
$dbh->commit;
# All done
1;
### SUBS ####
sub get_os {
my @os;
my $sth = $dbh->prepare("SELECT DISTINCT os FROM hosts");
$sth->execute();
while ( my ($os) = $sth->fetchrow_array ) {
push @os, $os;
}
return @os;
}
sub get_packages {
my $os = shift;
my $sth
= $dbh->prepare(
"select DISTINCT p.id,p.name FROM pkg p RIGHT JOIN hosts h ON (p.id=ANY(h.pkg_id)) WHERE h.os=?"
);
$sth->execute( ($os) );
my @pkgs;
while ( my ( $id, $name ) = $sth->fetchrow_array ) {
$pkgs{$name} = $id;
push @pkgs, $name;
}
return @pkgs;
}
Алгоритм:
— Список ОС берём из таблицы хостов
— Для каждой ОС получаем список пакетов
— Отправьте пакеты в Audit API, получите список (id) уязвимостей
— Отправляем уязвимости в ID Api, получаем метаданные по каждой
— Записываем метаданные уязвимостей в базу данных в таблицу уязвимостей.
— Записываем в базу связи между пакетами и уязвимостями в v2p-таблицу
Отчеты
Поскольку нашей главной целью было получить список хостов для приоритетного обновления, первым отчетом, который я сделал, был «10 лучших хостов для обновления», выбранных по общему баллу CVSS. *** .
report.pl #!/usr/bin/perl
# (C) Ivan Agarkov 2016
use strict;
use warnings;
use lib 'perl5';
use HTTP::Tiny;
use DBI;
use JSON;
use constant DB => 'dbi:Pg:dbname=pkgs';
our $dbh;
our @hosts;
# 0. connect to DB
$dbh = DBI->connect( DB, "", "", { RaiseError => 1, AutoCommit => 0 } );
# get top10 hosts
my $sth = $dbh->prepare("SELECT h.hostname,SUM(v.cvss_score) as sum FROM hosts h INNER JOIN pkg p ON(p.id=ANY(h.pkg_id)) INNER JOIN v2p vp ON(vp.pkg_id=p.id) INNER JOIN vulners v ON (v.id=vp.vuln_id) GROUP BY h.hostname ORDER BY sum DESC LIMIT 10");
$sth->execute();
while (my ($host, $sum) = $sth->fetchrow_array) {
push @hosts, { hostname => $host, score => $sum, pkgs => [] };
}
foreach (@hosts) {
$sth = $dbh->prepare("SELECT p.name,SUM(v.cvss_score) AS score FROM pkg p RIGHT JOIN hosts h ON (p.id=ANY(h.pkg_id)) INNER JOIN v2p ON (v2p.pkg_id=p.id) INNER JOIN vulners v ON (v.id=v2p.vuln_id) WHERE h.hostname=? GROUP BY p.name ORDER BY score DESC LIMIT 10");
$sth->execute(($_->{hostname}));
while(my ($pkg,$sum) = $sth->fetchrow_array) {
push @{$_->{pkgs}}, { package => $pkg, score => $sum };
}
}
print <<EOF
TOP 10 SERVERS TO UPDATE
EOF
;
foreach (@hosts) {
print <<EOF
--------------------------------------------------
Hostname: $_->{hostname}
Score : $_->{score}
Packages:
EOF
;
foreach (@{$_->{pkgs}}) {
print <<EOF
Name : $_->{package}
Score: $_->{score}
EOF
}
}
Этот код просто берет 10 лучших хостов и для каждого из них берет 10 лучших пакетов с общей скоростью.
Затем вы сможете создавать задачи и обновлять их.
Итоги года использования
Мы отправляем наши данные ребятам из Vulners уже больше года.В настоящее время они постоянно проверяют более 30 000 уникальных пакетов.
Что приятно, все найденные ошибки оперативно исправлялись, а скорость обработки каждой тысячи выросла с 30 секунд до 400 миллисекунд. Именно благодаря им эта тема названа «.
без регистрации и смс») Что касается бизнес-целей, только с внедрением этой системы мы начали испытывать процесс постоянных обновлений.
Обновить все — слишком большая задача для дежурного инженера, а вот обновить первые 10 — вполне посильная задача.
За год мы снизили общий балл cvss более чем вдвое, чего и вам желаем.
**** )
Сноски и пояснения
* — Так сложилось исторически, я начал писать автоматизацию на Perl и никто не успел меня остановить.** — Миша не очень активный пользователь Хабра, но как инженер он незаменим :) *** — Цифровая метрика опасности уязвимости от 1,0 (можно оценить) до 10,0 (критическая уязвимость) **** — Весь код можно найти здесь: github.com/annmuor/freeaudit P.S. И все же — Перл жив! Теги: #audit #vulners #packages #security #linux #perl #postgresql #perl #информационная безопасность
-
Какие Ошибки Распространены В Ит-Руководстве
19 Oct, 24 -
Зачем Вам Нужна Pos-Система?
19 Oct, 24 -
К Вопросу О Защите Цифровой Информации
19 Oct, 24 -
Тостер. Пользовательский Стиль. Обновлять
19 Oct, 24 -
О Передаче Данных Через Аудиоразъем
19 Oct, 24 -
Пакет Для Вас: История Подписного Бизнеса
19 Oct, 24 -
Как Легко Расстаться С 1000 Долларами
19 Oct, 24 -
Кто Есть Кто На Рынке Облачных Ide?
19 Oct, 24