Аудит Текущих Уязвимостей Без Регистрации И Смс



Введение

Аудит текущих уязвимостей без регистрации и СМС

Как знает каждый, кто когда-либо подписывался на рассылки по информационной безопасности, количество найденных уязвимостей за день зачастую превышает возможности человека их анализировать.

Особенно если серверов много, особенно если зоопарк ОС и версий.

В этой теме я расскажу о том, как мы решили эту проблему.

И да, Перл * живой :)



Цели и задачи

При проектировании системы мы Мхлыстун ** решил две параллельные задачи:
  • Сбор информации о версиях пакетов в системах
  • Сбор информации о текущих уязвимостях в системах
Оба этих действия имели общую цель: правильно расставить приоритеты обновлений и выполнить эти обновления в гарантированное время простоя, чего мы и добились.



Схема работы



Аудит текущих уязвимостей без регистрации и СМС

  1. Раз в час каждый хост собирает информацию о текущих пакетах и отправляет ее в очередь.

  2. Сообщения удаляются из очереди и помещаются в базу данных.

  3. Раз в 3 часа робот приходит в базу данных и прогоняет пакеты через сервис аудита.

  4. По итогам дня формируется отчет и рассылается всем заинтересованным лицам.



Базовая схема

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 #информационная безопасность

Вместе с данным постом часто просматривают:

Автор Статьи


Зарегистрирован: 2019-12-10 15:07:06
Баллов опыта: 0
Всего постов на сайте: 0
Всего комментарий на сайте: 0
Dima Manisha

Dima Manisha

Эксперт Wmlog. Профессиональный веб-мастер, SEO-специалист, дизайнер, маркетолог и интернет-предприниматель.