В 2016 году я работал над проектом сканера веб-приложений, который очень похож на Burp Suite, - он проксировал HTTP-запросы от браузера и инструментов для автоматизации, вроде Selenium, и отправлял их в различные модули / плагины для сканирования на уязвимости. Большая часть приложения была написана на Python и имела модульную архитектуру, а интерфейс написан с использованием Django и Celery для асинхронных задач.
Я пишу этот пост с целью помощи специалистам по информационной безопасности писать сканеры уязвимостей как для себя, так и для сообщества.
Перед началом программирования, нам стоит начать с теоритических основ и ответить на следующие вопросы:
Как это работает
Весь сканер можно разделить на модули:
Теперь перейдем к поэтапному созданию каждого модуля, которые в итоге мы свяжем вместе.
Для начала создадим виртуальное окружение для проекта:
Активируем его:
Создадим новую папку вне папки virtualenv:
Парсер необработанных запросов
После настройки виртуальной среды, можно перейти к написанию кода. Первый модуль - это анализатор необработанных HTTP-запросов, который принимает входные данные из файла и преобразует их в объект запроса. Для его написания мы будем использовать библиотеку http:
Приведенный выше класс берет необработанную HTTP-строку и преобразует ее в объект запроса.
Теперь сохраним его в request.txt
Попробуйте сами сохранить вышеуказанный код в файл request_parser.py и выполните его, используя следующую команду:
Мы успешно взяли HTTP-запрос из файла и повторно отправили его в неизменном виде. Теперь нужно сделать еще одну вещь: преобразовать его тело и отправить параметры в DICT, чтобы получить возможность добавлять свою собственную полезную нагрузку и анализировать результаты.
Исходный код можно так же скопировать отсюда
Первичный тестер
Итак, мы закончили первый модуль, написав скрипт, который будет брать сырой запрос из текстового файла и отправлять его в неизменном виде, отображая ответ. Теперь нам нужно научиться редактировать запрос, вставляя XSS-локатор в параметры запроса и тело сообщения и проверять, будет ли он отображаться в ответе. Для этого мы будем использовать пакет requests в Python.
Создайте новый файл с именем create_insertions.py и вставьте в него следующий код:
Данный код анализирует параметры и тело запроса для создания списка объектов запросов с флагами в качестве полезной нагрузки.
Теперь давайте отправим каждый запрос и проверим, отображаются ли наши параметры в ответе.
Итак, если наш пэйлоад обнаружится в ответе, то вы увидите что - то вроде этого:
На этом все. Встретимся в следующей серии.
Оригинал
Перевод @Moody канал @ Cybred
Я пишу этот пост с целью помощи специалистам по информационной безопасности писать сканеры уязвимостей как для себя, так и для сообщества.
Перед началом программирования, нам стоит начать с теоритических основ и ответить на следующие вопросы:
- Как это работает? - Создайте простую блок-схему того, как должен функционировать ваш сканер - начните с того, что он будет принимать на входе и что будет отдавать на выходе, а потом проработайте детали.
- Какие технологии будут использованы? Выбор правильных технологий критически важен. Выбор технологий подразумевает выбор библиотек, с которыми вы будете работать, и предположения о том, как масштабировать проект в дальнейшем, отталкиваясь от потребностей. Особенно важную роль играет то, ощущаете ли вы себя в зоне комфорта, работая с тем или иным языком программирования. Я мог бы потратить 20 мучительных часов на написание кода на Golang и получить результат чуть лучше, чем если бы я писал свой проект на Python, который я бы завершил за 5 часов.
Как это работает
Весь сканер можно разделить на модули:
- Парсер необработанных запросов
- Первичный тестер
- Контекстный анализатор
- Генератор полезной нагрузки
- Чекер наличия полезной нагрузки
Теперь перейдем к поэтапному созданию каждого модуля, которые в итоге мы свяжем вместе.
Для начала создадим виртуальное окружение для проекта:
Код:
pip3 install virtualenv
python3 -m virtualenv xss_env
Код:
cd xss_env/Scripts && activate
Код:
mkdir rxss
Парсер необработанных запросов
После настройки виртуальной среды, можно перейти к написанию кода. Первый модуль - это анализатор необработанных HTTP-запросов, который принимает входные данные из файла и преобразует их в объект запроса. Для его написания мы будем использовать библиотеку http:
Код:
from __future__ import absolute_import, unicode_literalsfrom http.server import BaseHTTPRequestHandler
from io import BytesIO
class HTTPRequest(BaseHTTPRequestHandler):
def __init__(self, request_text):
self.rfile = BytesIO(request_text)
self.raw_requestline = self.rfile.readline()
self.error_code = self.error_message = None
self.parse_request()
def send_error(self, code, message):
self.error_code = code
self.error_message = message
Код:
POST /search.php?test=query HTTP/1.1
Host: testphp.vulnweb.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:75.0) Gecko/20100101 Firefox/75.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded
Content-Length: 27
Origin: http://testphp.vulnweb.com
Connection: close
Referer: http://testphp.vulnweb.com/search.php?test=query
Upgrade-Insecure-Requests: 1searchFor=asdas&goButton=go
Код:
from __future__ import absolute_import, unicode_literals
from http.server import BaseHTTPRequestHandler
from io import BytesIO
class HTTPRequest(BaseHTTPRequestHandler):
def __init__(self, request_text):
self.rfile = BytesIO(request_text)
self.raw_requestline = self.rfile.readline()
self.error_code = self.error_message = None
self.parse_request()
def send_error(self, code, message):
self.error_code = code
self.error_message = message
with open("requests.txt", "rb") as f:
request = HTTPRequest(f.read())
if not request.error_code:
print(request.command) # отображает метод запроса
print(request.path) # отображаем путь
print(request.headers.keys()) # отображаем заголовки запроса
print(request.headers['host']) # prints requests host
content_len = int(request.headers.get('Content-Length'))
print(request.rfile.read(content_len)) # prints request body
Код:
python3 request_parser.pyPOST
/search.php?test=query
['Host', 'User-Agent', 'Accept', 'Accept-Language', 'Accept-Encoding', 'Content-Type', 'Content-Length', 'Origin', 'Connection', 'Referer', 'Upgrade-Insecure-Requests']
testphp.vulnweb.com
b'searchFor=asdas&goButton=go'
Исходный код можно так же скопировать отсюда
Код:
from __future__ import absolute_import, unicode_literals
from http.server import BaseHTTPRequestHandler
from io import BytesIO
from urllib import parse
class Request:
def __init__(self):
self.headers = None
self.params = None
self.data = None
self.path = None
def replace(self, string, payload):
for k, v in self.headers.items():
k.replace(string, payload)
v.replace(string, payload)
for k, v in self.params.items():
self.params[k] = self.params[k].replace(string, payload)
for k, v in self.data.items():
self.data[k] = self.data[k].replace(string, payload)
print(self.data)
class RequestParser(object):
def __init__(self, request_text):
self.request = Request()
try:
self.raw_request = HTTPRequest(request_text)
if self.raw_request.error_code:
raise Exception("failed parsing request")
self.request.method = self.raw_request.command
self.request.path = self.construct_path()
self.request.headers = self.raw_request.headers
self.request.data = self.convert(self.construct_data())
self.request.params = self.convert(self.construct_params())
except Exception as e:
raise e
def convert(self, data):
if isinstance(data, bytes):
return data.decode()
if isinstance(data, (str, int)):
return str(data)
if isinstance(data, dict):
return dict(map(self.convert, data.items()))
if isinstance(data, tuple):
return tuple(map(self.convert, data))
if isinstance(data, list):
return list(map(self.convert, data))
if isinstance(data, set):
return set(map(self.convert, data))
def construct_path(self):
return parse.urlsplit(self.raw_request.path).path
def construct_data(self):
return dict(parse.parse_qsl(self.raw_request.rfile.read(int(self.raw_request.headers.get('content-length')))))
def construct_params(self):
return dict(parse.parse_qsl(parse.urlsplit(self.raw_request.path).query))
with open("requests.txt", "rb") as f:
parser = RequestParser(f.read())
print(parser.request.method) # prints method
print(parser.request.path) # prints request.path
print(parser.request.headers) # prints requests headers
print(parser.request.data) # prints requests body
print(parser.request.params) # prints requests params
Первичный тестер
Итак, мы закончили первый модуль, написав скрипт, который будет брать сырой запрос из текстового файла и отправлять его в неизменном виде, отображая ответ. Теперь нам нужно научиться редактировать запрос, вставляя XSS-локатор в параметры запроса и тело сообщения и проверять, будет ли он отображаться в ответе. Для этого мы будем использовать пакет requests в Python.
Код:
pip3 install requests
Код:
import copy
class GetInsertionPoints:
def __init__(self, request):
self.request = request
self.requests = []
self.params(append=True)
self.body(append=True)
def params(self, append: bool = False) -> None:
if self.request.params:
for q in self.request.params:
request = copy.deepcopy(self.request)
if append:
request.params[q] = str(request.params[q])+" teyascan"
else:
request.params[q] = "teyascan"
request.insertion = q
request.iplace = 'params'
self.requests.append(request)
def body(self, append: bool = False) -> None:
if self.request.data:
for q in self.request.data:
request = copy.deepcopy(self.request)
if append:
request.data[q] = str(request.data[q])+" teyascan"
else:
request.data[q] = "teyascan"
request.insertion = q
request.iplace = 'body'
self.requests.append(request)
with open("requests.txt", "rb") as f:
parser = RequestParser(f.read())
print(parser.request.method) # prints method
print(parser.request.path) # prints request.path
print(parser.request.headers) # prints requests headers
print(parser.request.data) # prints requests body
print(parser.request.params) # prints requests params
i_p = GetInsertionPoints(parser.request)
print(i_p.requests)
Код:
python3 create_insertions.py [<__main__.HTTPRequest object at 0x0000021E8AD34A30>, <__main__.HTTPRequest object at 0x0000021E8AD34B80>, <__main__.HTTPRequest object at 0x0000021E8AD34BB0>]
Код:
import requestsdef send_request(request, scheme):
url = "{}://{}{}".format(scheme, request.headers.get("host"), request.path)
req = requests.Request(request.method, url, params=request.params, data=request.data, headers=request.headers)
r = req.prepare()
s = requests.Session()
response = s.send(r, allow_redirects=False, verify=False)
return responsewith open("requests.txt", "rb") as f:
parser = RequestParser(f.read())
i_p = GetInsertionPoints(parser.request)
for request in i_p.requests:
response = send_request(request, "http")
if "teyascan" in response.text:
print("probe reflection found in "+request.insertion)
Код:
python .\test.py
probe reflection found in searchFor
Оригинал
Перевод @Moody канал @ Cybred