LIMB
Вход Регистрация

тема

Часть 2: Хаслим баги благодаря JS файлам

YmbMember · · 0 ответов · 757 просмотров

Вступление
Не удалось прогрузить медиа

Привет LIMB, привет читатели. С вами YmbMember. В прошлой части мы с вами научились собирать JS файлы и узнавать технологии используемые таргетом, вытягивать с них эндпоинты, ссылки файлы и параметры, какие в итоге нам дали профит в виде бага. В этой части мы продолжим анализировать JS файлы всякими инструментами. В следующей статье мы скачаем все js файлы и проанализируем их ручкамм

Поиск секретов
Скорее всего, об этом на LIMB уже писали (точно не знаю). Что же за секреты такие ?
Дело в том, что иногда в JS файлах могут хранится различные апи ключи / креды. Их используют разработчики либо в комментариях для удобства, либо при работе с внешними сервисами. Помню так находил на таргете креды от dom.ru и авито, но увы, это было давно и я уже не найду вам таргет чтобы показать свежий пример :slight_frown:

Также искать эндпоинты на каких хранятся юзерские креды/апишки очень полезно для дальнейших тестов. Эти эндпоинты важно тестить продвинуто, скрытые параметры, cors misconfiguration, возможно строить какие-то цепочки и т.д …

  1. Trufflehog (Расширение на Burp Suite)
Этот инструмент специализируется на поиске вот таких вот проебов. Мы с вами его использовали при приске слитых секретов на гитхабе, но давайте не ограничивать мощность инструмента только этим
Расширение для бурпа мне очень нравится. Мы просто его инсталлим и лазим по сайту как ни в чём не бывало. При найденных секретах расширение нам просто сообщит об этом
Для того чтобы его поставить, ставим сам trufflehog на устройство
https://github.com/trufflesecurity/trufflehog/releases
И указываем расширению путь к нему. После чего убираем галочки, как на скрине ниже
Не удалось прогрузить медиа

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

  1. Trufflehog (расширение для браузера)
Также вы можете использовать расширение для браузера от trufflesecurity
Не удалось прогрузить медиа

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

Просто кормим этому инструменту все .js ссылки какие у нас есть. Дает много ложных срабатываний, но все их советую прочекать на валидность, даже если это кажется пустой тратой времени
  1. Secretfinder 
А вот это мой любимый
js_links.txt | while read url;do python3 SecretFinder.py -i $url -o cli ; done | tee secretfinder.txt
Не удалось прогрузить медиа

Люблю его за то что имеет довольно большое кол-во regex паттернов и за то что можно скормить скрипту ссылки с бурпа. Ну и за то что можно все результаты сохранить в красивый html файл. Хотя я такой фичей не пользуюсь, но если вам нравится - используйте

Проверка найденных секретов
Для того чтобы вам выплатили баунти за найденный секрет - он должен быть валидным. Какие-то не актуальные креды не подойдут
Специально для этого был создан репозитоиий https://github.com/streaak/keyhacks
Там показано как проверить на валидность креды с 89-ти популярных сервисов

Lazyegg
В прошлой части через браузерное расширение от lazyegg мы получали js файлы, какие подключены к страницам. Сейчас же мы будем использовать lazyegg чтобы получить больше инфы с найденных .js файлов

cat js_links.txt | xargs -I{} bash -c 'echo -e "\ntarget : {}\n" && python3 lazyegg.py "{}" --js_urls --domains --ips --leaked_creds' | tee lazyegg.txt
Не удалось прогрузить медиа

На выходе получим файл lazyegg.txt, где просто экспорт всего что выдал lazyegg в терминал. 
Я написал свой скрипт какой поможет распределить эти результаты по файлам leaked_creds.txt domains.txt js_urls.txt ips.txt для удобства. Смотреть все в куче не очень удобно

Code:
import re

import argparse

import os


def extract_data(file_path):

    with open(file_path, 'r', encoding='utf-8') as file:

        data = file.read()


    # Убираем цветовые коды терминала

    cleaned_data = re.sub(r'\x1b\[\d+m', '', data)


    # Шаблон для поиска блоков данных

    pattern = re.compile(

        r'Js_urls:\s*(.*?)\s*Domains:\s*(.*?)\s*Ips:\s*(.*?)\s*Leaked_creds:\s*(.*?)\s*(?=target|$)',

        re.DOTALL

    )


    matches = pattern.findall(cleaned_data)

    

    js_urls_list = []

    domains_list = []

    ips_list = []

    leaked_creds_list = []


    for match in matches:

        js_urls, domains, ips, leaked_creds = match

        js_urls_list.append(js_urls.strip())

        domains_list.append(domains.strip())

        ips_list.append(ips.strip())

        leaked_creds_list.append(leaked_creds.strip())


    return js_urls_list, domains_list, ips_list, leaked_creds_list



def save_to_file(file_name, data):

    with open(file_name, 'w', encoding='utf-8') as file:

        for item in data:

            if item.strip():  # Игнор пустых строк

                file.write(item + '\n')



def main():

    parser = argparse.ArgumentParser(description="Извлекает данные из логов laztegg и сохраняет их в отдельные файлы.")

    parser.add_argument('-i', '--input', required=True, help='Путь к файлу для обработки')

    args = parser.parse_args()


    file_path = args.input


    if not os.path.isfile(file_path):

        print(f"Ошибка: файл '{file_path}' не найден.")

        return


    js_urls_list, domains_list, ips_list, leaked_creds_list = extract_data(file_path)


    save_to_file('js_urls.txt', js_urls_list)

    save_to_file('domains.txt', domains_list)

    save_to_file('ips.txt', ips_list)

    save_to_file('leaked_creds.txt', leaked_creds_list)


    print("""

    YmbMember для LIMB

    """)

    print("Сделано! Результаты в директории")



if __name__ == '__main__':

    main()


Не удалось прогрузить медиа

В leaked creds часто добавляются переменные какие могут раскрыть технологии на таргете. Хотя, это роли не играет так как поиском переменных мы с вами сейчас займемся, но по серьезному

Поиск переменных
Эту идею я взял с инструмента JSFScan. Но там она реализована, ну мягко говоря гавёно.
Часто в динамических веб приложениях и всяких апи переменные отвечают за параметры в url, и по переменным можно больше узнать о используемых таргетом технологиях. Но на просторах интернета я не нашёл скрипта какой мог бы это сделать должным образом, поэтому его я сделал сам еще пару месяцев назад на скорую руку. основную функцию он выполняет
Code:
import argparse

import requests

import re

import sys

from pathlib import Path


REQUEST_TIMEOUT = 15

USER_AGENT = "js-var-extractor/1.2 (+https://example.com)"

DEFAULT_MIN_LEN = 3


DEFAULT_BLACKLIST = {

    'window','document','console','location','navigator','history','self','globalThis',

    'module','exports','require','__dirname','__filename','process',

    '$','jQuery','React','ReactDOM','_',

    'event','e','el','element','target','props','state','setState',

    'length','prototype','constructor','then','catch','push','pop','map','filter','reduce','forEach',

    '_ga','_gid','dataLayer',

    'index','value','key','id','name','count','type','result','results','item','items',

    'parseInt','parseFloat','Number','String','Boolean','Math',

    'jsx','useState','useEffect','useContext','useMemo','useCallback','useRef','useReducer','children',

    'propTypes','defaultProps','context','memo','forwardRef','hydrate',

    'Vue','vm','$attrs','$el','$slots','$refs','computed','watch','propsData','$mount','$emit','$nextTick',

    'ng','ngOnInit','ngOnDestroy','ngAfterViewInit','ngIf','ngFor','ngClass','ngModel','Zone','zone',

    '$$props','$$restProps','$$slots','onMount','beforeUpdate','afterUpdate','onDestroy',

    'Ember','RSVP','DS','EmberENV','component','route','controller',

    'Backbone','Marionette','model','collection','views',

    '__NEXT_DATA__','getStaticProps','getServerSideProps','getInitialProps','nuxt','gatsby',

    'Meteor','Mongo','Tracker','Accounts',

    'Observable','Subject','BehaviorSubject','Subscription','pipe','subscribe','next','error','complete',

    'store','reducer','dispatch','getState','selectors','actions','mutations',

    'lodash','moment','dayjs','axios',

    'webpackJsonp','__webpack_require__','__webpack_modules__','__dirname','__filename'

}


RE_VAR_DECL_MULTI = re.compile(r'\b(?:var|let|const)\s+([A-Za-z_][A-Za-z0-9_]*(?:\s*,\s*[A-Za-z_][A-Za-z0-9_]*)*)', re.MULTILINE)

RE_VAR_DECL = re.compile(r'\b(?:var|let|const)\s+([A-Za-z_][A-Za-z0-9_]*)', re.MULTILINE)

RE_ASSIGN = re.compile(r'\b([A-Za-z_][A-Za-z0-9_]*)\s*=')

RE_FUNC = re.compile(r'\bfunction\b[^(]*\(([^)]*)\)', re.MULTILINE)

RE_ARROW = re.compile(r'\(([^)]*?)\)\s*=>', re.MULTILINE)

RE_SIMPLE_ARROW = re.compile(r'\b([A-Za-z_][A-Za-z0-9_]*)\s*=>')



def load_blacklist_file(path: Path):

    s = set()

    patterns = set()

    try:

        with path.open('r', encoding='utf-8') as fh:

            for l in fh:

                ln = l.strip()

                if not ln or ln.startswith('#'):

                    continue

                ln_low = ln.lower()

                if ln_low.endswith('*'):

                    patterns.add(ln_low[:-1])  # убираем * и сохраняем префикс

                else:

                    s.add(ln_low)

    except Exception as e:

        print(f"Warning: cannot read blacklist file {path}: {e}", file=sys.stderr)

    return s, patterns



def norm_names(names, min_len, blacklist):

    s = []

    seen = set()

    if isinstance(blacklist, tuple):

        blacklist_exact, blacklist_prefixes = blacklist

    else:

        blacklist_exact = set(blacklist)

        blacklist_prefixes = set()


    for n in names:

        n = n.strip()

        if not n:

            continue

        parts = [p.strip() for p in n.split(',')] if ',' in n else [n]


        for p in parts:

            if not p:

                continue

            pl = p.lower()

            if p.startswith('_') or p.startswith('__'):

                continue

            if len(p) > 10:

                continue

            if not re.match(r'^[A-Za-z_][A-Za-z0-9_]*$', p):

                continue

            if pl in blacklist_exact:

                continue

            if any(pl.startswith(pref) for pref in blacklist_prefixes):

                continue

            if len(p) >= min_len and p not in seen:

                seen.add(p)

                s.append(p)


    return s




def extract_from_text(text, min_len, blacklist):

    results = []

    lines = text.splitlines()


    for i, line in enumerate(lines, start=1):

        found_names = []


        m_multi = RE_VAR_DECL_MULTI.search(line)

        if m_multi:

            group = m_multi.group(1)

            parts = [p.split('=')[0].strip() for p in group.split(',')]

            found_names.extend(parts)

        else:

            m = RE_VAR_DECL.search(line)

            if m:

                found_names.append(m.group(1))


        for mf in RE_FUNC.finditer(line):

            params = mf.group(1)

            if params.strip():

                found_names.append(params)


        for ma in RE_ARROW.finditer(line):

            params = ma.group(1)

            if params.strip():

                found_names.append(params)


        for ms in RE_SIMPLE_ARROW.finditer(line):

            found_names.append(ms.group(1))


        if any(k in line for k in ('fetch(', 'axios(', 'XMLHttpRequest', 'new URLSearchParams', '$.ajax', '$.get', '$.post')):

            for ma in RE_ASSIGN.finditer(line):

                found_names.append(ma.group(1))


        normed = norm_names(found_names, min_len, blacklist)

        if normed:

            display_line = line.strip()

            if len(display_line) > 400:

                display_line = display_line[:397] + '...'

            results.append((i, display_line, normed))


    return results



def process_url(url, session, min_len, blacklist):

    try:

        headers = {"User-Agent": USER_AGENT}

        r = session.get(url, timeout=REQUEST_TIMEOUT, headers=headers, allow_redirects=True)

        r.raise_for_status()

        text = r.text

    except Exception as e:

        return False, f"ERROR: {e}"


    matches = extract_from_text(text, min_len, blacklist)

    if not matches:

        return True, []

    return True, matches



def main():

    p = argparse.ArgumentParser(description="Download .js URLs and extract variable declarations/params with blacklist support.")

    p.add_argument('-t', '--targets', required=True, help='File with one .js URL per line')

    p.add_argument('--blacklist', help='Optional file with one variable name per line to blacklist (case-insensitive)')

    p.add_argument('--minlen', type=int, default=DEFAULT_MIN_LEN, help='Minimum variable name length (default 3)')

    p.add_argument('-o', '--output', help='Output file to write unique variable names only')

    p.add_argument('--quiet', action='store_true', help='Quiet header when printing')

    p.add_argument('--no-blacklist', action='store_true', help='Disable default blacklist')

    args = p.parse_args()


    print(r"""

                                  _ _           _             

__   ____ _ _ __ ___ ___ | | | ___  ___| |_ ___  _ __

\ \ / / _` | '__/ __/ _ \| | |/ _ \/ __| __/ _ \| '__|

 \ V / (_| | | | (_| (_) | | |  __/ (__| || (_) | |   

  \_/ \__,_|_|  \___\___/|_|_|\___|\___|\__\___/|_|   

                                                      

                YmbMember для LIMB

    """)


    try:

        with open(args.targets, 'r', encoding='utf-8') as fh:

            urls = [l.strip() for l in fh if l.strip() and not l.strip().startswith('#')]

    except Exception as e:

        print(f"Cannot open targets file: {e}", file=sys.stderr)

        sys.exit(2)


    if not urls:

        print("No URLs found in targets file.", file=sys.stderr)

        sys.exit(2)


    # формируем блэклист как (exact_set, prefix_set)

    if args.no_blacklist:

        base_exact = set()

        base_prefixes = set()

    else:

        base_exact = {x.lower() for x in DEFAULT_BLACKLIST}

        base_prefixes = set()


    user_exact = set()

    user_prefixes = set()

    if args.blacklist:

        bl_path = Path(args.blacklist)

        if bl_path.exists():

            u_exact, u_prefixes = load_blacklist_file(bl_path)

            user_exact.update(u_exact)

            user_prefixes.update(u_prefixes)

        else:

            print(f"Warning: blacklist file {bl_path} not found, ignoring.", file=sys.stderr)


    blacklist = (base_exact | user_exact, base_prefixes | user_prefixes)


    session = requests.Session()

    session.headers.update({"User-Agent": USER_AGENT})


    all_vars = set()


    for url in urls:

        if url.startswith('/'):

            print(f"[SKIP] Relative URL (no base): {url}")

            continue


        ok, data = process_url(url, session, args.minlen, blacklist)

        if not ok:

            print(f"\nURL: {url}\n  {data}")

            continue


        if not args.quiet:

            print('\n' + '='*80)

            print(f"URL: {url}")

            print('-'*80)


        if not data:

            print('  (no variables detected)')

            continue


        for lineno, linen, names in data:

            print(f"[{lineno:4d}] {linen}")

            print(f"       variables: {', '.join(names)}")

            all_vars.update(names)


    if args.output and all_vars:

        try:

            with open(args.output, 'w', encoding='utf-8') as out:

                for v in sorted(all_vars):

                    # каждая переменная — отдельная строка без лишних кавычек или escape-последовательностей

                    out.write(f"{v.strip()}\n")

            print(f"\n[+] Saved {len(all_vars)} unique variable names to {args.output}")

        except Exception as e:

            print(f"Error writing to {args.output}: {e}", file=sys.stderr)




if __name__ == '__main__':

    main()

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

Сейчас все сайты используют какие-то js фреймворки. В этих фреймворках очень часто одинаковое название у переменных, какие не связаны с именами параметров в URL. Для этого я нашёл выход из ситуации:
--blacklist <файл>
Можно запихнуть названия переменных какие стоит игнорировать в двух форматах:
переменная
перемен* (скрипт будет смотреть блэклист по префиксу)
Пример такого файла:
Code:
_*

__*

aamidcalltimedout
abort
activefeatureflags
add*
adobe
ajax,analytics*
anchor
animate*
api
app*
array*
attr*
background*
banner*
base*
block*
body*
border*
btn*
callback*
card*
category*
checkbox*
click*
color*
component*
config*
connect*
consent*
container*
cookie*
course*
css*
data*
debug*
default*
destination*
dialog*
document
dom*
element*
event*
field*
filter*
form*
frame*
function
get*
handler*
header*
height
html
id
image*
input*
intl*
js*
lang*
layout
left
link*
load*
local*
logger*
map*
margin*
menu*
modal*
module*
name
node*
on*
page*
param*
parse*
path*
plugin*
post*
price*
privacy*
product*
query*
react*
redirect*
ref
region*
render*
request*
resize*
result*
return
row
script*
scroll*
search*
select*
send
session*
set*
show*
sku*
sort*
state*
style*
submit*
success
svg*
tab*
target*
template*
text*
theme*
title
toggle*
tooltip*
track*
translate*
update*
url*
user*
valid*
value*
var
width*
window
wrapper
oR*


--minlen минимальная длина переменных (советую ставить 2)
-o файл в какой будут сохранены все переменные
--no-blacklist отключаем блэклист какой идёт по дефолту (не советую)

Для демонстрации я запустил скрипт на таргете
Не удалось прогрузить медиа

Получил 400 переменных. Так же скрипт предоставляет небольшую частичку кода, чтобы вы могли определить где используется переменная и иметь примерное представление за что она отвечает и какую инфу можно вытянуть. Пробрутил собранные переменные через param miner и нашел что существует параметр mode. Оказалось он уязвим
Не удалось прогрузить медиа

Скажу честно - для демонстрации взял сайт 2016 года без WaF, но сути это особо не меняет. Метод поиска параметров рабочий

Не удалось прогрузить медиа

У меня важный анонс к веб пентестерам, кодерам и веб кодерам: допилить этот скрипт до ума, выставить на GitHub и пропиарить где-то в твиттере.
Идея поиска параметров через переменные отчасти уникальная, а нормальной реализации ее нету
Нужно, чтобы этот скрипт более точно отсеивал false positives, мог определять технологии по содержимому js файла, добавить всякие фичи по типу оффлайн анализ и так далее. Вообщем, сделать его максимальным монстром всем комьюнити
Оставим вотермарку, что скрипт был сделан участниками форума LIMB. Этот форум надо пропиарить, чтобы про него знали все люди на земле и будущие люди на марсе 
Если есть такие волонтеры - пишите мне в ЛС. Уверен, админ оценит наши старания
Не удалось прогрузить медиа


Заключение
В этой статье мы научились собирать секреты, еще детальнее определять технологии. Также я предоставил свой скрипт какой ищет переменные. В следующей статье мы будем проводить ручной оффлайн анализ файлов. Удачи
👍73 ❤️14 😮8 👏17 🤔9 💯14

Гостевой просмотр. Чтобы отвечать и ставить реакции, войдите или зарегистрируйтесь.