Вступление
Привет LIMB, привет читатели. С вами YmbMember. В прошлой части мы с вами научились собирать JS файлы и узнавать технологии используемые таргетом, вытягивать с них эндпоинты, ссылки файлы и параметры, какие в итоге нам дали профит в виде бага. В этой части мы продолжим анализировать JS файлы всякими инструментами. В следующей статье мы скачаем все js файлы и проанализируем их ручкамм
Поиск секретов
Скорее всего, об этом на LIMB уже писали (точно не знаю). Что же за секреты такие ?
Дело в том, что иногда в JS файлах могут хранится различные апи ключи / креды. Их используют разработчики либо в комментариях для удобства, либо при работе с внешними сервисами. Помню так находил на таргете креды от dom.ru и авито, но увы, это было давно и я уже не найду вам таргет чтобы показать свежий пример :slight_frown:
Также искать эндпоинты на каких хранятся юзерские креды/апишки очень полезно для дальнейших тестов. Эти эндпоинты важно тестить продвинуто, скрытые параметры, cors misconfiguration, возможно строить какие-то цепочки и т.д …
Расширение для бурпа мне очень нравится. Мы просто его инсталлим и лазим по сайту как ни в чём не бывало. При найденных секретах расширение нам просто сообщит об этом
Для того чтобы его поставить, ставим сам trufflehog на устройство
https://github.com/trufflesecurity/trufflehog/releases
И указываем расширению путь к нему. После чего убираем галочки, как на скрине ниже
Так мы добавим в результаты больше ложных срабатываний, но и валидных срабатываний будет больше. После этого расширение будет вам показывать все что удалось найти
Но мне оно не очень нравится из-за чересчур большого количества ложных срабатываний, а при любом срабатывании на пол экрана выбивает alert
Просто кормим этому инструменту все .js ссылки какие у нас есть. Дает много ложных срабатываний, но все их советую прочекать на валидность, даже если это кажется пустой тратой времени
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 для удобства. Смотреть все в куче не очень удобно
В leaked creds часто добавляются переменные какие могут раскрыть технологии на таргете. Хотя, это роли не играет так как поиском переменных мы с вами сейчас займемся, но по серьезному
Поиск переменных
Эту идею я взял с инструмента JSFScan. Но там она реализована, ну мягко говоря гавёно.
Часто в динамических веб приложениях и всяких апи переменные отвечают за параметры в url, и по переменным можно больше узнать о используемых таргетом технологиях. Но на просторах интернета я не нашёл скрипта какой мог бы это сделать должным образом, поэтому его я сделал сам еще пару месяцев назад на скорую руку. основную функцию он выполняет
Вот вам мини руководство по использованию:
-t указываем файл где лежат .js файлы
Сейчас все сайты используют какие-то js фреймворки. В этих фреймворках очень часто одинаковое название у переменных, какие не связаны с именами параметров в URL. Для этого я нашёл выход из ситуации:
--blacklist <файл>
Можно запихнуть названия переменных какие стоит игнорировать в двух форматах:
переменная
перемен* (скрипт будет смотреть блэклист по префиксу)
Пример такого файла:
--minlen минимальная длина переменных (советую ставить 2)
-o файл в какой будут сохранены все переменные
--no-blacklist отключаем блэклист какой идёт по дефолту (не советую)
Для демонстрации я запустил скрипт на таргете
Получил 400 переменных. Так же скрипт предоставляет небольшую частичку кода, чтобы вы могли определить где используется переменная и иметь примерное представление за что она отвечает и какую инфу можно вытянуть. Пробрутил собранные переменные через param miner и нашел что существует параметр mode. Оказалось он уязвим
Скажу честно - для демонстрации взял сайт 2016 года без WaF, но сути это особо не меняет. Метод поиска параметров рабочий
У меня важный анонс к веб пентестерам, кодерам и веб кодерам: допилить этот скрипт до ума, выставить на GitHub и пропиарить где-то в твиттере.
Идея поиска параметров через переменные отчасти уникальная, а нормальной реализации ее нету
Нужно, чтобы этот скрипт более точно отсеивал false positives, мог определять технологии по содержимому js файла, добавить всякие фичи по типу оффлайн анализ и так далее. Вообщем, сделать его максимальным монстром всем комьюнити
Оставим вотермарку, что скрипт был сделан участниками форума LIMB. Этот форум надо пропиарить, чтобы про него знали все люди на земле и будущие люди на марсе
Если есть такие волонтеры - пишите мне в ЛС. Уверен, админ оценит наши старания
Заключение
В этой статье мы научились собирать секреты, еще детальнее определять технологии. Также я предоставил свой скрипт какой ищет переменные. В следующей статье мы будем проводить ручной оффлайн анализ файлов. Удачи
Не удалось прогрузить медиа
Привет LIMB, привет читатели. С вами YmbMember. В прошлой части мы с вами научились собирать JS файлы и узнавать технологии используемые таргетом, вытягивать с них эндпоинты, ссылки файлы и параметры, какие в итоге нам дали профит в виде бага. В этой части мы продолжим анализировать JS файлы всякими инструментами. В следующей статье мы скачаем все js файлы и проанализируем их ручкамм
Поиск секретов
Скорее всего, об этом на LIMB уже писали (точно не знаю). Что же за секреты такие ?
Дело в том, что иногда в JS файлах могут хранится различные апи ключи / креды. Их используют разработчики либо в комментариях для удобства, либо при работе с внешними сервисами. Помню так находил на таргете креды от dom.ru и авито, но увы, это было давно и я уже не найду вам таргет чтобы показать свежий пример :slight_frown:
Также искать эндпоинты на каких хранятся юзерские креды/апишки очень полезно для дальнейших тестов. Эти эндпоинты важно тестить продвинуто, скрытые параметры, cors misconfiguration, возможно строить какие-то цепочки и т.д …
- Trufflehog (Расширение на Burp Suite)
Расширение для бурпа мне очень нравится. Мы просто его инсталлим и лазим по сайту как ни в чём не бывало. При найденных секретах расширение нам просто сообщит об этом
Для того чтобы его поставить, ставим сам trufflehog на устройство
https://github.com/trufflesecurity/trufflehog/releases
И указываем расширению путь к нему. После чего убираем галочки, как на скрине ниже
Не удалось прогрузить медиа
Так мы добавим в результаты больше ложных срабатываний, но и валидных срабатываний будет больше. После этого расширение будет вам показывать все что удалось найти
Не удалось прогрузить медиа
- Trufflehog (расширение для браузера)
Не удалось прогрузить медиа
Но мне оно не очень нравится из-за чересчур большого количества ложных срабатываний, а при любом срабатывании на пол экрана выбивает alert
- Mantra
Не удалось прогрузить медиа
Просто кормим этому инструменту все .js ссылки какие у нас есть. Дает много ложных срабатываний, но все их советую прочекать на валидность, даже если это кажется пустой тратой времени
- 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
LIMB