What's new
Runion

This is a sample guest message. Register a free account today to become a member! Once signed in, you'll be able to participate on this site by adding your own topics and posts, as well as connect with other members through your own private inbox!

Как ВзЛаМаТь СаЙт, или аудит безопастности для самых маленьких[1 часть]

k0priz

Light Weight
Депозит
$0
Шалом всем кто читает это произведение недопонятого гения.

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


Содержание:
1.Поиск таргета
2.Внешний осмотр таргета
3.Сбор информации о сервере и сайте
4.Поиск точек входа SQLinj
5.Поиск точек входа XSS
1.ЧАСТЬ

Так первое с чего начнем это поиск таргета у меня никаких заготовок нет так что будем использовать что есть.
На жестоком у меня завалялось 1,5к сайтов,выложу как печосницу вам для практики. СКАЧАТЬ


!!!Если там есть .ru или .uk домены.Я не работаю по ру, это мой парсер очень хочет меня подставить!!!

Код: Скопировать в буфер обмена
Code:
Скачиваемм cms-detector он валяется на гите

git clone https://github.com/0xWhoknows/cms-detector.git
cd cms-detector
nano site.txt
После этого копируем из линков которые я дал в этот файл
ctrl+x и Y все сайты загруженны
python3 cms-detector.py


1.png



В Enter Your Site List : вписываем site.txt который вы должы были создать как я показал сверху
В Enter the number of threads : вписываем не больше 5 чтобы не было проскоков

2.png



Жмем enter, выглядеть это должно примерно так

3.png



После этого у вас есть минут 5 выйти покурить, после окончания работы софта все будет сохранено в папку result
Наши результаты будут выглядеться вот так:


4.png


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

Код: Скопировать в буфер обмена
Code:
cat joomla_results.txt
Тут все сайты с joomla cms
Не спешим их открывать ведь наш детектор мог ошибиться

Теперь мы скачиваем CMSeek так же с github
git clone https://github.com/Tuhinshubhra/CMSeeK.git
cd CMSeek
pip3 install -r requirements.txt
python3 cmseek.py -l /home/{ваш_юзер}/cms-detector/results/joomla_results.txt

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

Вот таргет который нашел я, с ним и будем работать



5.png




2.ЧАСТЬ


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

---------------------

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

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

Впринцепе под кретерии которые я описал сайт подходит.

6.png



Так же считаю что стоит стразу проверить есть ли на сайте WAF(Web Application Firewall)
---------------------

Информационная сноска:
WAF(Web Aplication Firewall) - заваливать вас умными терминами я не буду, простым словами хуетень которая не дает юным гениям делать на сайте все что вздумается (Но мы его обойдем)
----------------------
Пишем
[SIZE=5]https://example.com/../../etc/passwd[/SIZE]
18.png


На мое удивление он здесь есть, запоминаем это летс гоу дальше

3.ЧАСТЬ

Далее мы начинаем сканировать сайт на открытые порты, смотрим доступные директори , поиск суб-доменов и т.п

Первое что мы будем использовать это всеми нами любимый nmap
Прописываем nmap -sT -A example.com

test.png


---------------------
Информационная сноска:
Для понимания зачем я использовал эти флаги в nmap
-sT полное сканирование TCP портов
-A Включение обнаружения ОС, определение версии, сканирование сценариев и трассировку.

----------------------

На данном этапе мы копируем всю информацию что дал нам nmap в .txt и идем далее.
Следюущим шагом мы посмотрим доступные нам директории через gobuster,
gobuster не установлен в kali изначально, но если просписать gobuster то kali предложит нам его что мы и делаем.

После установки пишем такую команду и ждем конца её выполнения

gobuster dir --url https://example.com/ -w /usr/share/wordlists/dirb/common.txt -t 20
Далее видим примерно такую картину:


8.png


---------------------
Информационная сноска:
Если кому то не понятна плашка (Status: тут код который дал нам сервер) то объяняю, зачастую вам будут попадаться 4 кода
200 - сервер отпарвил обычный ответ
403 - доступ к линку или директории недоступен
500 - Внутренняя ошибка сервера
301 - означает что переходе на эту ссылку вам перекинет на другую ссылку
406 - Not Acceptable ну или же WAF(Web Application Firewall) просто не дает вам доступ потому что считает что тем самым вы можете навердить системе

Короче сервер с вам общается таким методом, конечно этих кодов намного больше, если интересно заходите и читаете.

По gobuster
dir - режим работы программы, gobuster в своем арсенале имеет разные режимы работы например мы можем искать dns поставив вместо dir dns
--url ну собственно наш линк
-w путь до листа с директориями (В kali он уже есть '/usr/share/wordlists/dirb/common.txt')
-t кол во потоков, если сайт с виду слабенький не надо его ебать сотней потоков не нароком положите сервер а еще хуже если это заметит сис.,админ.

----------------------

На этом моменте у нас могут начать разбегаться глаза, но мы не поддаемся соблазнам просто копирем все в наш .txt и идем дальше.

Далее используем ffuf она уже есть в kali так что скачивать чего то из вне мы не будем

Неплохой subdomain wordlist => https://github.com/danielmiessler/SecLists/blob/master/Discovery/DNS/subdomains-top1million-5000.txt

Пишем
ffuf -w /home/{ваш_юзер}/Downloads/subdomains-top1million-5000.txt -u https://example.com/ -H "Host: FUZZ.example.com" -mc 200 -t 10

и уходим на перекур 5-10 минут в зависимости от кол-ва потоков которые вы поставили

по итогу получаем список subdomain

9.png


---------------------
Информационная сноска:
subdomain - это подсайт, который выглядит и работает как самостоятельный ресурс но находящийся на одном сервере с главным доменом.

По ffuf
-H - мы указываем хост с препиской FFUZ на линке который указали во флаге -u для того чтобы показать программе что мы хотим перебирать
-mc 200 - Matcher или все тот же ответ от сервера, мы просим программу выдавать нам сабдомены только которые присылают нам status code 200.

---------------------


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


4 ЧАСТЬ

Ну вот мы и перешли к самому интересному

Если посмотреть на основную страницу то на ней не большое кол-во информации, зато gobuster дал нам очень интересную директорию а именно /store/

10.png


После перехода туда уже можно сказать гуляй рука балдей писюн.

Куча окон ввода может свидетельствовать о xss, редиректы на продукты + они еще и преправленны php что может означать sqlinj и все это надо проверить.Так же есть сабдомены которые тоже надо проверять + не забываем про довольнотаки старую версию joomla cms которая тоже дырявая и сполиты на нее тоже надо будет проверять.

Тыкаем на любой продукт и смотрим как выглядит ссылка
https://example.com/store/index.php?main_page=product_info&cPath=&products_id=211

Так сразу видим параметр products_id, кормилица дамперов с ним может только если параметр id посоревноваться)

Так же пошерудив по сайту находим еще варинты

https://example.com/store/index.php?main_page=index&cPath=4_46
https://example.com/store/index.php?main_page=login
https://example.com/store/index.php?main_page=login&language=en
https://example.com/store/index.php?main_page=contact_us&action=success
https://example.com/store/index.php?main_page=advanced_search&keyword=23523&search_in_description=1&inc_subcat=0&sort=20a

Значит будем тестить через sqlmap

sqlmap -u 'сюда вставляем все поочередно 6 линков' -p products_id --level 5 --risk 3 -random-agent --dbs --dbms=mysql --tamper=apostrophemask,apostrophenullencode,base64encode

В конце всех тестов sqlmap ничего не дал, следовательно надо переходить к xss если есть вопросы по использования sqlmap прошу обратиться к этой статье, очень все классно и понятно рассказано
https://xssforumv3isucukbxhdhwz67hoa5e2voakcfkuieq4ch257vsburuid.onion/threads/39318/


5 ЧАСТЬ

Идем к XSS уязвимостям


Я не хочу заваливать вас мат частью, вам надо запомнить что в оснвном есть три вида xss и все тут пишеться на JavaScript


1.Stored XSS
Код: Скопировать в буфер обмена
Code:
<script>


/ Вредоносный код здесь


</script>

2.Reflected XSS
http://example.com/search?query=<script>alert('XSS')</script>

3.DOM-based XSS
http://example.com/page#<script>alert('XSS')</script>

Опять же если интересно берем и изучаем, лишним точно не будет

Так как мы знаем что сайт накрыт WAF в тупую писать <script>alert('XSS')</script> не имеет смысла

Мы увиди вот это


18.png




Нужно видоизменять наш запрос, например играя с верхним и нижним регистром

пробуем <ScRiPt>ALeRt()</ScRiPt>
18.png



Но мы не сдаемся, не бывает сайтов которые невозможно взломать

Путем попыток я понял что этот WAF неплохо защищает от Reflected XSS

Попробуем Stored XSS для этого будет использовать burpsuite

Открываем burpsuite (Он так же уже есть в kali)

12.png



открываем proxy --> proxy settings

13.png



видим тут в proxy listeners 127.0.0.1:8080

Далее в firefox скачиваем FoxyProxy через addons --> добавляем туда нашу проксю 127.0.0.1:8080 и так же не забываем добавить сертификат от бурпа

все для работы с burpsuite у нас готово

теперь нам нужно найти цель, ну начнем с самого верха сайта и тут аккурат лежит корзина или же View Cart



14.png



добавляем что то в корзину и на сайте нажимаем View Cart

15.png



ага есть окно ввода, ну пробуем найти его в burp

перед этим на сайте вводим туда что то (я ввел wete3) и далее

в бурпе во вкладке intercept --> нажимаем на intercept is off

кликаем на update

бурп выдает нам вот что


16.png


ага это строчка state

в строчку state пробуем ввести <ScRiPt>ALeRt()</ScRiPt>


17.png



БИНГО!!! WAF не индексирует эту строчку и мы смогли пронести наш код, вот мы и нашли один из векторов атаки на сайт а именно XSS
К сожаление из-за того что у автора есть еще дела он пока что уходит, но скоро будет продолжение
надеюсь читателям статья понравилась, никогда не отказываюсь от конструктивной критики.
 
Какие ещё есть техники кроме софта с гитхаба для выявления WAF ? Обход пути не всегда работает и зачастую посто режется и всё
 
The xss payload won't execute because it's within the value attribute of a text input field, which renders it as plain text rather than executable code.

HTML: Скопировать в буфер обмена
Code:
<input type="text" name="state" value="<ScRiPt>ALeRt()</ScRiPt>" size="33" maxlength="32" id="state" />
&nbsp;<span class="alert" id="stText">
&nbsp;
</span>
 
Скрипт не распознает нормально CMS, вручную смотришь, а там WP 6.3.1, а скрипт не распознает вообще. Может есть другой какой-то?
 
В общем дописал я скрипт сам, добавлено реальное обнаружение всех самых популярных CMS, так же версия WP считывается максимально корректно, если будет желание, то допилю и для остальных CMS корректное считывание версии.

Спойлер
Python: Скопировать в буфер обмена
Code:
import os
import requests
import concurrent.futures
from bs4 import BeautifulSoup
from queue import Queue
from colorama import Fore, Style
from platform import system
import re

requests.packages.urllib3.disable_warnings(requests.packages.urllib3.exceptions.InsecureRequestWarning)

class CMSDetector:
    def __init__(self, num_threads):
        self.num_threads = num_threads
        self.headers = {
            'User-Agent': "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
            'Accept-Language': 'en-US,en;q=0.5',
        }
        self.url_queue = Queue()
        self.results_folder = 'results'
        self.common_cms = {
            'wordpress': ['wp-content/themes', 'wp-includes/css'],
            'joomla': ['index.php?option=com_', 'Joomla!'],
            'drupal': ['sites/default', 'modules/system', 'Drupal'],
            'wix': ['wix.com'],
            'shopify': ['cdn.shopify.com/s/files/1/', 'Shopify'],
            'magento': ['skin/frontend', 'Magento'],
            'vbulletin': ['vbulletin', 'vBulletin'],
            'prestashop': ['PrestaShop'],
            'cmsmadesimple': ['CMS Made Simple'],
            'drupalcommerce': ['Drupal Commerce'],
            'opencart': ['OpenCart'],
            'phpbb': ['phpBB'],
            'typo3': ['typo3/'],
            'ghost': ['ghost/'],
            'squarespace': ['squarespace.com'],
            'bitrix': ['bitrix/']
        }

    def create_folders(self):
        if not os.path.exists(self.results_folder):
            os.makedirs(self.results_folder)

    def queue_urls(self, file_path):
        with open(file_path, 'r') as file:
            site_list = [line.strip() for line in file]
            for url in site_list:
                self.url_queue.put(url)

    def get_cms(self, url):
        try:
            if not url.startswith('http://') and not url.startswith('https://'):
                url = 'http://' + url
            response = requests.get(url, headers=self.headers, verify=False, timeout=10)
        except requests.RequestException:
            print(f"[!] {Fore.CYAN}{url}{Style.RESET_ALL} => {Fore.RED}Invalid{Style.RESET_ALL}")
            return

        if 'text/html' not in response.headers.get('content-type', ''):
            print(f"[-] {Fore.CYAN}{url}{Style.RESET_ALL} => {Fore.RED}Not detected!{Style.RESET_ALL}")
            return

        html_content = response.text
        cms_headers = response.headers.get('X-Powered-By', '').lower()
        soup = BeautifulSoup(html_content, 'html.parser')
        meta_generator = soup.find('meta', {'name': 'generator'})
        cms_meta = meta_generator.get('content').lower() if meta_generator else ''

        detected_cms = []
        cms_versions = {}

        for cms, indicators in self.common_cms.items():
            for indicator in indicators:
                if indicator in cms_headers or indicator in cms_meta or indicator in html_content:
                    detected_cms.append(cms)
                    version = self.get_cms_version(soup, cms, url)
                    if version:
                        cms_versions[cms] = version
                    break

        if 'wordpress' in detected_cms:
            wp_meta_version = self.get_wordpress_version_from_meta(soup)
            if wp_meta_version:
                cms_versions['wordpress'] = wp_meta_version
            wp_po_version = self.get_wordpress_version_from_po(url)
            if wp_po_version and (not wp_meta_version or self.compare_versions(wp_po_version, wp_meta_version)):
                cms_versions['wordpress'] = wp_po_version

        detected_cms = list(set(detected_cms))  # Remove duplicates
        if detected_cms:
            cms_with_versions = [f"{cms} {cms_versions.get(cms, '')}".strip() for cms in detected_cms]
            print(f"[+] {Fore.CYAN}{url}{Style.RESET_ALL} => {Fore.GREEN}{', '.join(cms_with_versions)}{Style.RESET_ALL}")
        else:
            print(f"[-] {Fore.CYAN}{url}{Style.RESET_ALL} => {Fore.RED}Not detected!{Style.RESET_ALL}")

    def get_cms_version(self, soup, cms, url):
        version_pattern = re.compile(r'^\d+(\.\d+){1,2}$')
        versions = []

        if cms == 'wordpress':
            versions = self.get_wordpress_versions_from_meta(soup)
        else:
            for tag in soup.find_all(['meta', 'link', 'script']):
                src_or_href = tag.get('content') or tag.get('href') or tag.get('src')
                if src_or_href and cms in src_or_href and '?ver=' in src_or_href:
                    version = src_or_href.split('?ver=')[1]
                    if version_pattern.match(version):
                        versions.append(version)

        if versions:
            # Return the highest version if multiple versions are found
            valid_versions = [v for v in versions if version_pattern.match(v)]
            if valid_versions:
                return max(valid_versions, key=lambda v: list(map(int, v.replace('.x', '.0').split('.'))))
        return None

    def get_wordpress_versions_from_meta(self, soup):
        version_pattern = re.compile(r'^\d+(\.\d+){1,2}$')
        versions = []

        # Check for version in script or link tags
        for tag in soup.find_all(['link', 'script']):
            src_or_href = tag.get('src') or tag.get('href')
            if src_or_href and ('wp-content/themes' in src_or_href or 'wp-includes/css' in src_or_href) and '?ver=' in src_or_href:
                version = src_or_href.split('?ver=')[1]
                if version_pattern.match(version):
                    versions.append(version)

        return versions

    def get_wordpress_version_from_meta(self, soup):
        versions = self.get_wordpress_versions_from_meta(soup)
        if versions:
            version_pattern = re.compile(r'^\d+(\.\d+){1,2}$')
            valid_versions = [v for v in versions if version_pattern.match(v)]
            if valid_versions:
                return max(valid_versions, key=lambda v: list(map(int, v.replace('.x', '.0').split('.'))))
        return None

    def get_wordpress_version_from_po(self, url):
        content = self.fetch_po_content(url)
        if content:
            version = self.extract_version_from_content(content)
            if version:
                return version
        return None

    def fetch_po_content(self, url):
        try:
            po_url = url.rstrip('/') + '/wp-content/languages/ru_RU.po'
            response = requests.get(po_url, headers=self.headers, verify=False, timeout=10)
            if response.status_code == 200:
                return response.text
            else:
                return None
        except requests.RequestException:
            return None

    def extract_version_from_content(self, content):
        try:
            lines = content.split('\n')[:2]  # Only check the first two lines
            for line in lines:
                match = re.search(r'# Translation of WordPress - (\d+\.\d+\.\d+|\d+\.\d+\.x)', line)
                if match:
                    version = match.group(1)
                    return version
            return None
        except Exception:
            return None

    def compare_versions(self, v1, v2):
        v1_parts = [int(part) for part in v1.replace('.x', '.0').split('.')]
        v2_parts = [int(part) for part in v2.replace('.x', '.0').split('.')]
        return v1_parts > v2_parts

    def process_sites(self):
        with concurrent.futures.ThreadPoolExecutor(max_workers=self.num_threads) as executor:
            executor.map(self.get_cms, iter(self.url_queue.get, None))

    def main(self, file_path):
        self.create_folders()
        self.queue_urls(file_path)
        self.process_sites()

def banner() -> None:
    print(f"""
    ██████╗███╗   ███╗███████╗
    ██╔════╝████╗ ████║██╔════╝
    ██║     ██╔████╔██║███████╗
    ██║     ██║╚██╔╝██║╚════██║
    ╚██████╗██║ ╚═╝ ██║███████║
     ╚═════╝╚═╝     ╚═╝╚══════╝
                           - detector v0.1
    """)

def clear():
    if system() == 'Linux':
        os.system('clear')
    if system() == 'Windows':
        os.system('cls')

if __name__ == "__main__":
    clear()
    banner()
    file_path = input("Enter Your Site List: ")
    num_threads = int(input("Enter the number of threads: "))
    cms_detector = CMSDetector(num_threads)
    cms_detector.main(file_path)
 
по cms-detector. В nano сначала засунул 10кк доменов, раза 3-4 запускал, после чекания 600-650 доменов всё виснет, окей, вснул миллион доменов, как у автора указал 'number of threads 5', работает не виснет, но жутко медленно за 5 часов всего 33 тысячи доменов прочекал. Хотелось бы что-нибудь, побыстрее чтобы весь инет сканить.

по исправленному коду версий wp,часто показывает версию wp и 7 и 8 и 21 и 26 итд, что не соответствует действительности и результаты в папку result перестают сохраняться.
 
mlx сказал(а):
по cms-detector. В nano сначала засунул 10кк доменов, раза 3-4 запускал, после чекания 600-650 доменов всё виснет, окей, вснул миллион доменов, как у автора указал 'number of threads 5', работает не виснет, но жутко медленно за 5 часов всего 33 тысячи доменов прочекал. Хотелось бы что-нибудь, побыстрее чтобы весь инет сканить.

по исправленному коду версий wp, в основном вроде всё корректно работает, но часто показывает версию wp и 7 и 8 и 21 и 26 итд, что не соответсвует действительности.
Нажмите, чтобы раскрыть...
map/reduce ? ;-)
разбить задачу на части, запустить в параллель, результаты склеить.
так много что работает в этом мире.
 
mlx сказал(а):
по cms-detector. В nano сначала засунул 10кк доменов, раза 3-4 запускал, после чекания 600-650 доменов всё виснет, окей, вснул миллион доменов, как у автора указал 'number of threads 5', работает не виснет, но жутко медленно за 5 часов всего 33 тысячи доменов прочекал. Хотелось бы что-нибудь, побыстрее чтобы весь инет сканить.

по исправленному коду версий wp, в основном вроде всё корректно работает, но часто показывает версию wp и 7 и 8 и 21 и 26 итд, что не соответствует действительности.
Нажмите, чтобы раскрыть...
Потоки не больше 5 ставь
 
mlx сказал(а):
по cms-detector. В nano сначала засунул 10кк доменов, раза 3-4 запускал, после чекания 600-650 доменов всё виснет, окей, вснул миллион доменов, как у автора указал 'number of threads 5', работает не виснет, но жутко медленно за 5 часов всего 33 тысячи доменов прочекал. Хотелось бы что-нибудь, побыстрее чтобы весь инет сканить.

по исправленному коду версий wp,часто показывает версию wp и 7 и 8 и 21 и 26 итд, что не соответствует действительности и результаты в папку result перестают сохраняться.
Нажмите, чтобы раскрыть...
Мне ссылки на этих пару сайтов и я исправлю проблему. Я понимаю в чем проблема, но нужно убедиться, что бы не перезаливать скрипт по 10 раз.
 
Перезалейте пожалуйста ссылку с сайтами или поделитесь пожалуйста.
 
Top