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

тема

Bug Bounty: ищим новые фичи в JS файлах. Часть 2. Crawler

nipaah · · 3 ответов · 443 просмотров

Автоматизация мониторинга JavaScript-файлов для Bug Bounty. Часть 2. Crawler​

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

Начнем с модуля, отвечающего за сбор трафика с нужными нам JS-файлами, на примере баг-баунти таргета Udemy — https://hackerone.com/udemy/

Хантить там я порекомендовать не могу, но сервис отлично подходит для демонстрационных целей, поскольку использует и Cloudflare, и manifest-файлы. Это означает, что другими методами (без анализа трафика браузера) собрать все JS-файлы мы не сможем.

В качестве языка программирования используем Bun — современный и удобный движок для Node.js, поддерживающий TypeScript из коробки.

Установка camoufox​

Bash:
❯ pip install -U camoufox
❯ python3 -m camoufox fetch
После команды fetch бинарник будет сохранен в директории ~/.cache/camoufox/camoufox-bin

Создание проекта​

Bash:
❯ git init .
❯ bun init     

✓ Select a project template: Blank

 + CLAUDE.md
 + index.ts
 + tsconfig.json (for editor autocomplete)
 + README.md

To get started, run:

    bun run index.ts

bun install v1.2.22 (6bafe260)

+ @types/[email protected]
+ [email protected]

7 packages installed [3.50s]

Установка зависимостей​

Bash:
❯ bun add playwright yargs @babel/parser @babel/traverse @babel/generator
bun add v1.2.22 (6bafe260)

installed [email protected] with binaries:
 - playwright
installed [email protected]
installed @babel/[email protected] with binaries:
 - parser
installed @babel/[email protected]
installed @babel/[email protected]

33 packages installed [4.49s]

Описание зависимостей:
- playwright — фреймворк для E2E-тестирования, который будем использовать для навигации по сайту и перехвата сетевого трафика
- yargs — библиотека для парсинга аргументов командной строки
- babel (@babel/parser, @babel/traverse, @babel/generator) — инструменты для разбора и анализа JS-файлов в поиске манифест-файлов

Создадим базовую структуру модуля crawler​

Bash:
❯ mkdir -p crawler/scenarios/manifest
❯ touch crawler/scenarios/{anon.ts,manifest.ts,manifest/manifest-parser.ts} crawler/{cli.ts,crawler.ts,scenario.d.ts}
❯ tree crawler/                     
crawler/
├── cli.ts
├── crawler.ts
├── scenario.d.ts
└── scenarios
    ├── anon.ts
    ├── manifest
    │   └── manifest-parser.ts
    └── manifest.ts
3 directories, 6 files

Описание файлов:
- cli.ts — точка входа в программу. Здесь парсим аргументы командной строки и передаем управление в crawler.ts
- crawler.ts — основной код, запускающий сценарии сбора JS-файлов
- scenario.d.ts — типы TypeScript для описания структуры сценариев
- anon.ts — сценарий для сбора JS-файлов без аутентификации. Описывает, какие страницы необходимо посетить
- manifest.ts — сценарий поиска webpack-манифеста. Посещает главную страницу сайта, ищет файл с манифестом webpack, формирует список URL всех модулей и открывает их в браузере для записи трафика
- manifest-parser.ts — вспомогательный модуль, содержащий парсер JS-кода, который извлекает названия всех модулей из манифеста

Файл cli.ts​

Создаем новые директории:
Bash:
❯ mkdir -p revisions/{previous,current}

В директории revisions мы будем хранить данные (в контексте краулера — HAR-файлы) от нашей утилиты для различных версий: текущий запуск и предыдущий.

Также добавим их в .gitignore:

Code:
revisions/*
node_modules

Спойлер: Полный код файла crawler/cli.ts
JavaScript:
#!/usr/bin/env bun

import yargs from 'yargs';
import { hideBin } from 'yargs/helpers';
import { CrawlRevision, main } from './crawler';
import { existsSync, readdirSync } from 'fs';

const browserPath = `${process.env.HOME}/.cache/camoufox/camoufox-bin`;

const builder = (yargs) => {
    return yargs
        .option('scenarios', {
            alias: 's',
            type: 'array',
            describe: 'Specify scenario to process (path to file or scenario name in scenarios directory with/without .ts extension)',
            string: true,
        })
        .option('visible', {
            alias: 'v',
            type: 'boolean',
            describe: 'Run browser in non-headless mode',
            default: false
        })
        .option('browserPath', {
            alias: 'b',
            type: 'string',
            describe: 'Path to browser executable',
            default: browserPath
        });
};
const handler = revision => (args) => {
    main({ revision, ...args });
}
yargs(hideBin(process.argv))
    .strict()
    .scriptName('crawler')
    .usage('Usage: $0 <revision> [options]')
    .command({
        command: 'current [options]',
        aliases: ['c'],
        describe: 'Run crawler and save HARs as current revision',
        builder,
        handler: handler(CrawlRevision.Current)
    })
    .command({
        command: 'previous [options]',
        aliases: ['p'],
        describe: 'Run crawler and save HARs as previous revision',
        builder: builder,
        handler: handler(CrawlRevision.Previous)
    })
    .demandCommand(1, 'You must specify a command (current/c or previous/p)')
    .help('h')
    .alias('h', 'help')
    .parseSync();

Код достаточно говорит сам за себя. Теперь примеры использования:

Bash:
# Запуск сценариев и сохранение HAR-файлов в текущую ревизию
bun run crawler/cli.ts current

# Сохранение в предыдущую ревизию
bun run crawler/cli.ts previous

# Ключ -v отключает headless-режим, т.е. окно браузера Camoufox будет отображаться — полезно для дебага
bun run crawler/cli.ts current -v

# По умолчанию выполняются все сценарии, но можно указать конкретный, например:
bun run crawler/cli.ts current -s anon


Сценарии​

Определим интерфейс экспорта файла сценария:
JavaScript:
// файл crawler/scenario.d.ts
import { Page } from "playwright"

export interface Scenario {
  customScenario: (page: Page, options: Record<string, string>) => Promise<void>
  urls: string[]
  beforeEachUrl?: (page: Page, url: string) => Promise<void>
  proxy?: string
  headers: Record<string, string>
  waitForTimeout?: number
}

Основные поля здесь — urls и customScenario.

Первой выполняется функция customScenario: она получает объект page от Playwright, который является интерфейсом для взаимодействия с браузером — переход по ссылкам, нажатие кнопок, заполнение форм и так далее. Конкретный пример будет дан в файле anon.ts.

urls — просто список ссылок, которые нам нужно открыть в браузере. Во многих случаях дополнительная логика не требуется, и это просто автоматизация того, что можно сделать через customScenario.
beforeEachUrl — функция, которая выполняется перед посещением каждой ссылки из urls. Используется для проставления заголовка E-Tag, чтобы избежать кэширования JS-файлов.
headers — здесь размещаем наши авторизационные куки.
waitForTimeout — пауза между посещением ссылок из urls.

Далее разберем базовый пример с посещением двух ссылок без авторизации. В реальности для полного покрытия этим ограничиваться нельзя, но дописать дополнительные сценарии труда не составит, посидев достаточно времени за Burp'ом и документацией Playwright.

Сценарий anon.ts​


Как следует из названия, здесь мы просто посещаем страницы без авторизации. Для наглядности оставим только две ссылки — главная страница и форма логина.

Содержимое customScenario по сути дублирует функционал urls и оставлено только для наглядности и примера того, как можно использовать Playwright, т.к. клик по кнопке "Log in" перекинет нас на страницу /join/passwordless-auth.

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

Спойлер: Полный код файла crawler/scenarios/anon.ts
JavaScript:
import { Page } from 'playwright';
import { Scenario } from '../scenario';

const proxy = ''; // socks5://localhost:2080
const COOKIES = '';

const urls = [
  'https://www.udemy.com',
  'https://www.udemy.com/join/passwordless-auth/?locale=en_US&next=https%3A%2F%2Fwww.udemy.com%2F&response_type=html&action=login&mode',
];

async function customScenario(page: Page, options: Record<string, string>) {
  /*
  console.log('* Visiting https://www.udemy.com');
  let url = 'https://www.udemy.com';
  await page.goto(url, { waitUntil: 'load', timeout: 60000 });
  await page.waitForTimeout(9424);
  await page.getByText('Log in').click();
  await page.waitForTimeout(9424);
  */
}

const headers = {'Cookie':COOKIES};

export default {
  urls,
  proxy,
  customScenario,
  headers,
}  as Scenario

Сценарий manifest.ts​


Опишем общий алгоритм работы. Сперва посещаем страницу логина, с которой подгружается webpack-манифест. Ставим коллбэк на ответы с сервера и ищем среди них манифест по подстроке entry-main-manifest в пути.

Далее получаем тело ответа, парсим код манифеста и извлекаем все ID модулей. С помощью функции fillUrls формируем список ссылок на все JS-файлы и складываем их в поле urls нашего сценария. Следом после customScenario, согласно реализации в crawler, будут посещены все ссылки из urls, которые мы добавили. С помощью beforeEachUrl отключаем кэширование, т.к. некоторые JS-файлы уже были загружены.

Спойлер: Полный код файла файла crawler/scenarios/manifest.ts
JavaScript:
import { Page } from 'playwright';
import { Scenario } from '../scenario';
import { manifestParser } from './manifest/manifest-parser';

const COOKIE = '';

//const proxy = 'socks5://127.0.0.1:2080';
const proxy = '';
const headers = {}; // Cookie': COOKIE};
const urls: string[] = [];//.slice(0,1)

// базовый путь для модулей, извлеченных из манифеста
const base = 'https://www.udemy.com/staticx/udemy/js/webpack/';

const fillUrls = (obj) => {
  Object.entries(obj).forEach(([key, value]) => {
    if (!key.includes('-server-side')) { // дополнительная фильтрация неинтересущих нас файлов
      const url = `${base}${key}.${value}.js`;
      urls.push(url);
    }
  })
}

async function customScenario(page: Page, options: Record<string, string>) {
  console.log('customScenario');
  page.on('response', async response => {
      const url = response.url();
      if (!url.includes('entry-main-manifest')) {
        return;
      }

      console.log('Downloaded manifest file', url);

      try {
        const bodyBuf = await response.body();
        const body = new TextDecoder().decode(bodyBuf);
        const obj = manifestParser(body);
        fillUrls(obj);
        console.log(`Found ${urls.length} URLs to download`);
      } catch (err) {
        console.error('Failed to read response body:', err);
      }
  });
  console.log('Visiting main page to download manifest file');
  await page.goto('https://www.udemy.com/join/passwordless-auth/', { waitUntil: 'networkidle', timeout: 60000 });
  await page.waitForTimeout(1112);
}

const beforeEachUrl = async (page: Page, url: string) => {
  function generateEtag() {
    return Array.from({length: 32}, () => Math.floor(Math.random() * 16).toString(16)).join('');
  }

  await page.setExtraHTTPHeaders({
    'If-None-Match': `W/"${generateEtag()}"`
  });
}

export default {
  urls,
  proxy,
  customScenario,
  headers,
  waitForTimeout: 1000,
  beforeEachUrl
} as Scenario

Парсер манифеста​


Чтобы понять, как работает парсер, сначала рассмотрим код самого манифеста и найдем участок, отвечающий за ID (хеш) модулей.

Скачиваем файл и форматируем его:
Bash:
❯ wget https://www.udemy.com/s4taticx/udemy/js/webpack/entry-main-manifest.e080d99058059da9d5f0.js
❯ f="./entry-main-manifest.e080d99058059da9d5f0.js"; bunx js-beautify $f > $f.tmp && mv $f{.tmp,}

Открываем в редакторе кода и листаем, пока не наткнемся на такой код:
JavaScript:
i.u = e => "" + e + "." + {
    "common-app-css": "6aba0217b0532ace1dce",
    "activity-notifications-server-side": "03d1a23e2dec29b5cc3e",
    "activity-notifications-udlite-app": "36183e97e8a8a3d357be",
    "assessment-not-available-udlite-app": "3cf544de4da61135d048"
    <SNIP>
      "node_modules_storybook_blocks_dist_Color-6VNJS4EI_mjs": "81308e96f40e9436df67",
    "node_modules_storybook_components_dist_syntaxhighlighter-NMPM6SWI_mjs": "c44add36fd696ac4c558",
    "vendors-node_modules_storybook_components_dist_formatter-SWP5E3XI_mjs": "798be7d81f01b9f4410b",
    "node_modules_storybook_components_dist_WithTooltip-4HIR6TLV_mjs": "d0cb52092f343ab43026"
  } [e] + ".js"
})();

Здесь определена функция, которая отображает название модуля в имя файла с помощью хешмапа, например:
Code:
assessment-not-available-udlite-app
в
Code:
assessment-not-available-udlite-app.3cf544de4da61135d048.js

Для обхода всех нод AST воспользуемся функцией traverse из библиотеки Babel:
JavaScript:
traverse(ast, {
  ObjectExpression(path) {
    const properties = path.node.properties;
    for (const prop of properties) {
          if (prop.type === "ObjectProperty") {

Предварительно конвертируем все ключи, которые встретятся, в строку:

JavaScript:
let keyValue = "";

if (prop.key.type === "Identifier") {
  keyValue = prop.key.name;
} else if (prop.key.type === "StringLiteral") {
  keyValue = prop.key.value;
} else if (prop.key.type === "NumericLiteral") {
  keyValue = prop.key.value.toString();
}

Как нашли — генерируем из него валидный JavaScript-код и запускаем eval, так как в ключах или значениях могут присутствовать не только константы, но и выражения, например:
Code:
[some_var]: "string",
"asdf": "string" + "2",

eval выполнит их и вернет готовый JS object:

JavaScript:
const objectCode = generator(path.node, {
    compact: false,
    minified: false
}).code;

const jsObject = eval(`(${objectCode})`);

Далее уберём из объекта неинтересующие нас JS‑файлы — например, библиотеки и содержимое node_modules:

JavaScript:
for (const k of Object.keys(jsObject)) {
    if (ignorePatterns.some(p => k.match(new RegExp(p)))) {
      delete jsObject[k]
    }
}

Хешмап со всеми данными для формирования списка URL с JS-файлами готов. Передаём его обратно в код сценария.

Спойлер: Полный код парсера
JavaScript:
// file crawler/scenarios/manifest/manifest-parser.ts
import parser from "@babel/parser";
import traverse from "@babel/traverse";
import generator from "@babel/generator";

// регулярки для фильтрации ненужных нам файлов
const DEFAULT_PATTERNS = [
  '^vendors-node_modules_',
  '^react-syntax-highlighter_',
  '^brace',
  '^vendor-highcharts',
]

const KEY_MATCH = '_mjs'

export const manifestParser = (code: string, ignorePatterns = DEFAULT_PATTERNS) => {
  try {
    const ast = parser.parse(code, {
      sourceType: "unambiguous",
      allowReturnOutsideFunction: true,
      allowAwaitOutsideFunction: true,
      errorRecovery: true,
      // список плагинов для расширения синтаксиса, поддерживаемого babel
      // много из списка ниже не пригодится в данном случае можно убрать для увеличения производительности, но сейчас это некритично
      plugins: [
        "jsx",
        "classProperties",
        "dynamicImport",
        "optionalChaining",
        "nullishCoalescingOperator",
        "objectRestSpread"
      ]
    });

    let result = {}
    traverse(ast, {
      ObjectExpression(path) {
        const properties = path.node.properties;
        for (const prop of properties) {
          if (prop.type === "ObjectProperty") {
            let keyValue = "";

            if (prop.key.type === "Identifier") {
              keyValue = prop.key.name;
            } else if (prop.key.type === "StringLiteral") {
              keyValue = prop.key.value;
            } else if (prop.key.type === "NumericLiteral") {
              keyValue = prop.key.value.toString();
            }

            if (keyValue.includes(KEY_MATCH)) {
              const objectCode = generator(path.node, {
                compact: false,
                minified: false
              }).code;

              const jsObject = eval(`(${objectCode})`);
              for (const k of Object.keys(jsObject)) {
                if (ignorePatterns.some(p => k.match(new RegExp(p)))) {
                  delete jsObject[k]
                }
              }
              result = jsObject;
              return;
            }
          }
        }
      }
    });
    return result;
  } catch (error) {
    console.error(`Error processing manifest`, error.message);
  }
}

Файл crawler.ts​

Функция main вызывается из cli.ts, динамически подгружает JS модули из папки scenarios и выполняет их последовательно в цикле, передавая в функцию crawl.  
Если название сценария не было указано как аргумент через ключ -s, по умолчанию подгружаются все сценарии из директории scenarios.  
Функция crawl инициализирует сессию Playwright, прописывает необходимые куки и сначала выполняет customScenario, а затем обрабатывает каждый URL из urls с помощью функции visitUrls.
Спойлер: Полный код файла crawler/crawler.ts
JavaScript:
// file crawler/crawler.ts
import { readdir, lstat } from "node:fs/promises";
import { firefox, Page } from 'playwright';
import process from 'node:process';

// логирование времени выполнения
async function runWithTiming(asyncFn) {
    const start = Date.now();
    try {
        const result = await asyncFn();
        const ms = Date.now() - start;

        const minutes = Math.floor(ms / 60000);
        const seconds = Math.floor((ms % 60000) / 1000);
        const millis = ms % 1000;

        let output = '';
        if (minutes) output += `${minutes}m `;
        if (seconds || minutes) output += `${seconds}s`;
        if (!minutes && !seconds) output = `${millis}ms`;

        console.log(`   Execution time: ${output.trim()}`);
        return result;
    } catch (err) {
        //console.error("   Error during execution:", err);
        throw err;
    }
}

// при ошибке выполнения сценария даем 5 попыток на повтор и добавляем паузу в минуту между повторами
const retrying = async (fn, maxRetries = 5) => {
    const retryDelay = 60 *  1000
    let tries = 0;
    while (tries < maxRetries) {
        try {
            await fn();
            if (tries) {
              console.log(`   Success on ${tries+1} try`);
            }
            break;
        } catch (err) {
            console.error(err);
            console.error(`   Error fetching URL. Sleep for a while and retrying ${tries+1}/${maxRetries}...`);
            await new Promise(resolve => setTimeout(resolve, retryDelay));
            tries++;
        }
    }
}


// автоматизация рутины посещения ссылок
async function visitUrls(page: Page, urls: string[], waitForTimeout: number, beforeEachUrl: (page: Page, url: string) => Promise<void>) {
    if (!urls.length) {
        return;
    }
    console.log('- Visiting URLs...');
    let i = 1;
    for (const url of urls) {
        const n = `${i}`.padStart(`${urls.length}`.length, ' ');
        console.log(`* ${n}/${urls.length} - ${url}`)
        await runWithTiming(async () => {
            await retrying(async () => {
                if (beforeEachUrl) {
                    await beforeEachUrl(page, url);
                }
                const response = await page.goto(url, { waitUntil: 'load', timeout: 60000 });               
                if (!response.ok()) {
                  console.error(`   [${response.status()}] ${url}`);
                  if (response.status() === 500) {
                    //throw new Error(response.status());
                  }
                }
            })
        });
        await page.waitForTimeout(waitForTimeout + Math.random() * 1000);
        i++;
    }
}

interface CrawlInput {
    outputPath: string
    headers: Record<string, string>
    proxy?: string
    urls: string[]
    customScenario?: (page: Page) => Promise<void>
    browserPath: string
    visible?: boolean
    waitForTimeout?: number
    beforeEachUrl?: (page: Page, url: string) => Promise<void>
}
async function crawl({ urls, outputPath, headers, proxy, customScenario, browserPath, visible, beforeEachUrl, waitForTimeout = 9000 }: CrawlInput) {
    const browser = await firefox.launch({
        headless: !visible,
        executablePath: browserPath,
        args: ['--no-sandbox'],
        proxy: proxy ? {
            server: proxy
        } : undefined
    });

    const context = await browser.newContext({
        recordHar: {
            path: outputPath
        },
        extraHTTPHeaders: {
            ...headers
        }
    });
    try {
        let page = await context.newPage();
        if (customScenario) {
            console.log('- Running custom scenario...');
            await customScenario(page);
        }
        page = await context.newPage();
        await visitUrls(page, urls, waitForTimeout, beforeEachUrl);
        await context.close();
    } catch (err) {
        await browser.close();
        throw err;
    }
}

export enum CrawlRevision {
    Current = 'current',
    Previous = 'previous'
}
export interface MainInput {
    revision: CrawlRevision
    scenarios: string[]
    browserPath: string
    visible: boolean
    maxRetries?: number
    retryDelay?: number
}
export async function main({ revision, scenarios, browserPath, visible, maxRetries = 5, retryDelay = 10_000 }: MainInput) {
    const cwd = process.cwd();

    if (!scenarios || !scenarios.length) {
        const content = await readdir(`${cwd}/crawler/scenarios`);
        scenarios = [];
        for (const item of content) {
                   // пропускаем директории
             const stat = await lstat(`${cwd}/crawler/scenarios/${item}`);
             if (stat.isFile()) {
               scenarios.push(item);
             }
        }
            // фиксируем порядок выполнения
        scenarios.sort();
    } else {
        scenarios = scenarios.map(f => f.endsWith('.ts') ? f : `${f}.ts`);
    }
    console.log('// Scenarios ', scenarios);
    for (const file of scenarios) {
        const path = file.includes('/') ? file : `${cwd}/crawler/scenarios/${file}`;
        console.log(`// Importing ${path}...`);
        const scenarioModules = await import(path);
        const scenario = scenarioModules.default;
        const name = file.replace('.ts', '');
        const input: CrawlInput = {
            outputPath: `${cwd}/revisions/${revision}/${name}.har`,
            headers: scenario.headers,
            urls: scenario.urls,
            customScenario: scenario.customScenario,
            browserPath,
            visible,
            waitForTimeout: scenario.waitForTimeout
        }
        if (scenario.proxy) {
            input.proxy = scenario.proxy;
        }
        if (scenario.beforeEachUrl) {
            input.beforeEachUrl = scenario.beforeEachUrl;
        }
        console.log(`// Start execution of ${name} scenario...`);
        let tries = 0;
        while (tries < maxRetries) {
            try {
                await crawl(input);
                break;
            } catch (err) {
                console.error(err);
                console.error(`   Error during execution of ${name} scenario, sleep for a while and retrying ${tries+1}/${maxRetries}...`);
                await new Promise(resolve => setTimeout(resolve, retryDelay));
                tries++;
            }
        }
    }
    console.log('// All scenarios executed.');
    process.exit(0);
}

Запуск​


Bash:
❯ bun run crawler/cli.ts current
// Scenarios  [ "anon.ts", "manifest.ts" ]
// Importing /home/nipaah/df-js-monitor/crawler/scenarios/anon.ts...
// Start execution of anon scenario...
- Running custom scenario...
- Visiting URLs...
* 1/2 - https://www.udemy.com
   Execution time: 9s
* 2/2 - https://www.udemy.com/join/passwordless-auth/?locale=en_US&next=https%3A%2F%2Fwww.udemy.com%2F&response_type=html&action=login&mode
   Execution time: 4s
// Importing /home/nipaah/df-js-monitor/crawler/scenarios/manifest.ts...
// Start execution of manifest scenario...
- Running custom scenario...
customScenario
Visiting main page to download manifest file
Downloaded manifest file https://www.udemy.com/staticx/udemy/js/webpack/entry-main-manifest.e080d99058059da9d5f0.js
Found 230 URLs to download
- Visiting URLs...
*   1/230 - https://www.udemy.com/staticx/udemy/js/webpack/common-app-css.6aba0217b0532ace1dce.js
   [404] https://www.udemy.com/staticx/udemy/js/webpack/common-app-css.6aba0217b0532ace1dce.js
   Execution time: 316ms
*   2/230 - https://www.udemy.com/staticx/udemy/js/webpack/activity-notifications-udlite-app.36183e97e8a8a3d357be.js
   Execution time: 263ms
<SNIP>
* 230/230 - https://www.udemy.com/staticx/udemy/js/webpack/node_modules_storybook_components_dist_WithTooltip-4HIR6TLV_mjs.d0cb52092f343ab43026.js
   Execution time: 459ms
// All scenarios executed.

Теперь выведем список urls, перехваченных в сценарии anon:
Спойлер: Вывод
Bash:
❯ cat ./revisions/current/anon.har | jq -r '.log.entries.[].request.url' | grep -P 'udemy(cdn)?\.com' | grep '\.js'
https://frontends.udemycdn.com/design-system/icons/v18/icon-spritemap.js
https://refer.udemy.com/core.js
https://frontends.udemycdn.com/frontends-homepage/_next/static/chunks/webpack-46f0915a09962666.js
https://frontends.udemycdn.com/frontends-homepage/_next/static/chunks/framework-8b7974553b1447ee.js
https://frontends.udemycdn.com/frontends-homepage/_next/static/chunks/main-f9ce038182d57638.js
https://frontends.udemycdn.com/frontends-homepage/_next/static/chunks/pages/_app-ba0f46c98872ebce.js
https://frontends.udemycdn.com/frontends-homepage/_next/static/chunks/840-b9e035e0e7d6f06a.js
https://frontends.udemycdn.com/frontends-homepage/_next/static/chunks/630-63623b1d9afeff28.js
https://frontends.udemycdn.com/frontends-homepage/_next/static/chunks/pages/lohp-bad55e859a6f5a82.js
https://frontends.udemycdn.com/frontends-homepage/_next/static/eZg9KmI1m8-_I47wyBKaZ/_buildManifest.js
https://frontends.udemycdn.com/frontends-homepage/_next/static/eZg9KmI1m8-_I47wyBKaZ/_ssgManifest.js
https://www.udemy.com/cdn-cgi/challenge-platform/scripts/jsd/main.js
https://frontends.udemycdn.com/frontends-homepage/_next/static/chunks/1edf0483.9dfe51f2fe978bc8.js
https://frontends.udemycdn.com/frontends-homepage/_next/static/chunks/918.9c725618c4e6c2d9.js
https://www.udemy.com/cdn-cgi/challenge-platform/h/b/scripts/jsd/e8e65e95f26d/main.js?
https://frontends.udemycdn.com/design-system/icons/v18/icon-spritemap.js
https://www.udemy.com/staticx/udemy/js/webpack/auth-passwordless-udlite-app.5977588272251d371a8e.js
https://www.udemy.com/staticx/udemy/js/webpack/common-desktop-udlite-app.3f63a2dff7a836293be1.js
https://www.udemy.com/staticx/udemy/js/webpack/entry-main-manifest.e080d99058059da9d5f0.js
https://www.udemy.com/staticx/udemy/js/webpack/entry-main-vendor.a88c543cec6c2406c84b.js
https://www.udemy.com/staticx/udemy/js/webpack/jsi18n-en-us.c30ac933270f04f0c158.js
https://www.udemy.com/staticx/udemy/js/webpack/entry-main.4b375aa0c675c0d33f9f.js
https://www.udemy.com/cdn-cgi/challenge-platform/scripts/jsd/main.js
https://www.udemy.com/staticx/udemy/js/webpack/vendors-node_modules_udemy_auth_dist_esm_auth-form-error-field_auth-form-error-field_react_co-ba89dc.310912a2b3da97ad1277.js
https://www.udemy.com/staticx/udemy/js/webpack/auth-passwordless-udlite-app.5977588272251d371a8e.js
https://www.udemy.com/cdn-cgi/challenge-platform/h/b/scripts/jsd/e8e65e95f26d/main.js?
https://www.udemy.com/staticx/udemy/js/webpack/vendors-node_modules_udemy_react-header_dist_esm_events_js.b63a003c64daf2bf0cb4.js
https://www.udemy.com/staticx/udemy/js/webpack/vendors-node_modules_udemy_braze_dist_esm_index_js-node_modules_udemy_browse-event-tracking_d-ee19de.222fa5bd015a3bc96633.js
https://www.udemy.com/staticx/udemy/js/webpack/vendors-node_modules_udemy_activity-notifications_dist_esm_activity-notifications_mobx-store_-fb67a7.74295ed1a3638d0ad1dc.js
https://www.udemy.com/staticx/udemy/js/webpack/vendors-node_modules_udemy_event-tracking_dist_esm_debugger_tracker-debugger_react-component_-91dd42.d40a6b30bd88d5486094.js
https://www.udemy.com/staticx/udemy/js/webpack/vendors-node_modules_udemy_react-header_dist_esm_desktop_desktop-header_react-component_js-no-aaf7ee.376f7e75ff729d7153bf.js
https://www.udemy.com/staticx/udemy/js/webpack/common-desktop-udlite-app.3f63a2dff7a836293be1.js
https://www.udemy.com/staticx/udemy/js/webpack/vendors-node_modules_amplitude_analytics-browser_lib_esm_index_js.bdcf00d02cd01ac4e130.js

Спойлер: и сценарий manifest
Bash:
❯ cat ./revisions/current/manifest.har | jq -r '.log.entries.[].request.url' | grep -P 'udemy(cdn)?\.com' | grep '\.js'
https://frontends.udemycdn.com/design-system/icons/v18/icon-spritemap.js
https://www.udemy.com/staticx/udemy/js/webpack/auth-passwordless-udlite-app.5977588272251d371a8e.js
https://www.udemy.com/staticx/udemy/js/webpack/common-desktop-udlite-app.3f63a2dff7a836293be1.js
https://www.udemy.com/staticx/udemy/js/webpack/entry-main-manifest.e080d99058059da9d5f0.js
https://www.udemy.com/staticx/udemy/js/webpack/entry-main-vendor.a88c543cec6c2406c84b.js
https://www.udemy.com/staticx/udemy/js/webpack/jsi18n-en-us.c30ac933270f04f0c158.js
https://www.udemy.com/staticx/udemy/js/webpack/entry-main.4b375aa0c675c0d33f9f.js
https://www.udemy.com/cdn-cgi/challenge-platform/scripts/jsd/main.js
https://www.udemy.com/staticx/udemy/js/webpack/vendors-node_modules_udemy_auth_dist_esm_auth-form-error-field_auth-form-error-field_react_co-ba89dc.310912a2b3da97ad1277.js
https://www.udemy.com/staticx/udemy/js/webpack/auth-passwordless-udlite-app.5977588272251d371a8e.js
https://www.udemy.com/cdn-cgi/challenge-platform/h/b/scripts/jsd/e8e65e95f26d/main.js?
https://www.udemy.com/staticx/udemy/js/webpack/vendors-node_modules_udemy_react-header_dist_esm_events_js.b63a003c64daf2bf0cb4.js
https://www.udemy.com/staticx/udemy/js/webpack/vendors-node_modules_udemy_braze_dist_esm_index_js-node_modules_udemy_browse-event-tracking_d-ee19de.222fa5bd015a3bc96633.js
https://www.udemy.com/staticx/udemy/js/webpack/vendors-node_modules_udemy_activity-notifications_dist_esm_activity-notifications_mobx-store_-fb67a7.74295ed1a3638d0ad1dc.js
https://www.udemy.com/staticx/udemy/js/webpack/vendors-node_modules_udemy_event-tracking_dist_esm_debugger_tracker-debugger_react-component_-91dd42.d40a6b30bd88d5486094.js
https://www.udemy.com/staticx/udemy/js/webpack/vendors-node_modules_udemy_react-header_dist_esm_desktop_desktop-header_react-component_js-no-aaf7ee.376f7e75ff729d7153bf.js
https://www.udemy.com/staticx/udemy/js/webpack/common-desktop-udlite-app.3f63a2dff7a836293be1.js
https://www.udemy.com/staticx/udemy/js/webpack/vendors-node_modules_amplitude_analytics-browser_lib_esm_index_js.bdcf00d02cd01ac4e130.js
https://www.udemy.com/staticx/udemy/js/webpack/common-app-css.6aba0217b0532ace1dce.js
https://www.udemy.com/staticx/udemy/js/webpack/activity-notifications-udlite-app.36183e97e8a8a3d357be.js
https://www.udemy.com/staticx/udemy/js/webpack/assessment-not-available-udlite-app.3cf544de4da61135d048.js
https://www.udemy.com/staticx/udemy/js/webpack/assessments-udlite-app.386a208bb0731b441d38.js
https://www.udemy.com/staticx/udemy/js/webpack/auth-passwordless-udlite-app.5977588272251d371a8e.js
https://www.udemy.com/staticx/udemy/js/webpack/auth-turnstile-udlite-app.963e887173bdf3fa17df.js
https://www.udemy.com/staticx/udemy/js/webpack/auth-two-factor-udlite-app.7ca6e5cfa39fc9315057.js
https://www.udemy.com/staticx/udemy/js/webpack/auth-udlite-app.9f14426d117d616cf739.js
https://www.udemy.com/staticx/udemy/js/webpack/browse-udlite-app.5fe8e6346b209235cbd1.js
https://www.udemy.com/staticx/udemy/js/webpack/cart-pages-cart-udlite-app.272f5e99a61d6d1e587e.js
https://www.udemy.com/staticx/udemy/js/webpack/cart-pages-success-udlite-app.ee228570f62e2bfd34e9.js
https://www.udemy.com/staticx/udemy/js/webpack/category-free-udlite-app.a343d39d3771fc829078.js
https://www.udemy.com/staticx/udemy/js/webpack/discovery-common.3f7bedf84fdf9173e4a6.js
https://www.udemy.com/staticx/udemy/js/webpack/category-udlite-app.085421df27485bd7431d.js
https://www.udemy.com/staticx/udemy/js/webpack/checkout-apps-payment-method-management-udlite-app.5903dfffe8a1237a3f03.js
https://www.udemy.com/staticx/udemy/js/webpack/checkout-marketplace-udlite-app.a53b19a6096aa1bd8785.js
https://www.udemy.com/staticx/udemy/js/webpack/checkout-teamplan-checkout-udlite-app.c23f27c26cf6e10986fb.js
https://www.udemy.com/staticx/udemy/js/webpack/checkout-teamplan-resubscribe-udlite-app.850e5a8d0880e887a8f7.js
https://www.udemy.com/staticx/udemy/js/webpack/checkout-teamplan-upsell-udlite-app.7d275606ab73422e25b2.js
https://www.udemy.com/staticx/udemy/js/webpack/coaching-join-booking-udlite-app.7af66ed427d9727a202d.js
https://www.udemy.com/staticx/udemy/js/webpack/coaching-udlite-app.950a67de42c8b7361637.js
https://www.udemy.com/staticx/udemy/js/webpack/collections-udlite-app.7e3c6629fea599c46bdb.js
https://www.udemy.com/staticx/udemy/js/webpack/common-desktop-udlite-app.3f63a2dff7a836293be1.js
https://www.udemy.com/staticx/udemy/js/webpack/common-mobile-udlite-app.3145a8df36bcd92a9bf4.js
https://www.udemy.com/staticx/udemy/js/webpack/common-ufb-desktop-udlite-app.360ed41f4c725e6e2606.js
https://www.udemy.com/staticx/udemy/js/webpack/common-ufb-mobile-udlite-app.68b44b0daad4f9038b6b.js
https://www.udemy.com/staticx/udemy/js/webpack/course-auto-enroll-udlite-app.aef498b5868d9858f6e7.js
https://www.udemy.com/staticx/udemy/js/webpack/course-certificate-udlite-app.5cc1bafb13bf90314fbd.js
https://www.udemy.com/staticx/udemy/js/webpack/course-extract-udlite-app.0bb2128618b104871040.js
https://www.udemy.com/staticx/udemy/js/webpack/course-landing-page-free-udlite-app.db35667c2a1fd5ba7a01.js
https://www.udemy.com/staticx/udemy/js/webpack/course-landing-page-private-udlite-app.76f233cbfb0a6a17f245.js
https://www.udemy.com/staticx/udemy/js/webpack/course-landing-page-udlite-app.0c38832c8480c9771abf.js
https://www.udemy.com/staticx/udemy/js/webpack/course-manage-announcements-udlite-app.9000a834ec9c8f704d53.js
https://www.udemy.com/staticx/udemy/js/webpack/course-manage-coding-exercise-udlite-app.41d678ea3bfa60db4546.js
https://www.udemy.com/staticx/udemy/js/webpack/course-manage-create-udlite-app.6eface683e23c44c3781.js
https://www.udemy.com/staticx/udemy/js/webpack/course-manage-practice-test-udlite-app.c47faf8582dcdd8cbfa8.js
https://www.udemy.com/staticx/udemy/js/webpack/course-manage-practice-udlite-app.4149ac68c0c0c1968b06.js
https://www.udemy.com/staticx/udemy/js/webpack/course-manage-role-play-udlite-app.fbdc8d7b2329d15e801c.js
https://www.udemy.com/staticx/udemy/js/webpack/course-manage-v2-udlite-app.56aa06288c0b4c7a11ec.js
https://www.udemy.com/staticx/udemy/js/webpack/course-preview-udlite-app.7d5ebb1e9f0168f86eb4.js
https://www.udemy.com/staticx/udemy/js/webpack/course-qa-fullpage-udlite-app.c4900ab9e5b46cefe2cb.js
https://www.udemy.com/staticx/udemy/js/webpack/course-taking-udlite-app.53786adfbe7981236460.js
https://www.udemy.com/staticx/udemy/js/webpack/credit-history-udlite-app.e16b5b88442b31409c89.js
https://www.udemy.com/staticx/udemy/js/webpack/documentation-udlite-app.8b06042c8f4003a8ef3c.js
https://www.udemy.com/staticx/udemy/js/webpack/examples-react-codelab-udlite-app.59cb332ecb0eb7fa0ec4.js
https://www.udemy.com/staticx/udemy/js/webpack/examples-react-with-typescript-udlite-app.87e2ffe88842b3a0513d.js
https://www.udemy.com/staticx/udemy/js/webpack/examples-udlite-app.d03b2d7d9c8168733323.js
https://www.udemy.com/staticx/udemy/js/webpack/experimental-no-adaptive-assessment-udlite-app.0dab47386ddc3b1f39f0.js
https://www.udemy.com/staticx/udemy/js/webpack/file-upload-udlite-app.9024342c050e5e018eb6.js
https://www.udemy.com/staticx/udemy/js/webpack/forgot-password-udlite-app.3f6d3841f218b7e98931.js
https://www.udemy.com/staticx/udemy/js/webpack/form-fields-udlite-app.7733b0fbbdf535c08e01.js
https://www.udemy.com/staticx/udemy/js/webpack/get-mobile-app-udlite-app.371022785c40aefcd18d.js
https://www.udemy.com/staticx/udemy/js/webpack/gift-udlite-app.97ce7798a335c13d4017.js
https://www.udemy.com/staticx/udemy/js/webpack/home-udlite-app.d5e2ba32d7198de0652b.js
https://www.udemy.com/staticx/udemy/js/webpack/instructor-onboarding-udlite-app.de2d3c9039eb89aa73ea.js
https://www.udemy.com/staticx/udemy/js/webpack/instructor-verification-udlite-app.3dd4b37d3dc075e27f8b.js
https://www.udemy.com/staticx/udemy/js/webpack/instructor-coaching-udlite-app.98d6bc60e17bbb769b2a.js
https://www.udemy.com/staticx/udemy/js/webpack/instructor-create-course-bundle-udlite-app.59b85441771f152b413d.js
https://www.udemy.com/staticx/udemy/js/webpack/instructor-header-udlite-app.93d314edf0fb278ffb54.js
https://www.udemy.com/staticx/udemy/js/webpack/instructor-multiple-coupons-creation-udlite-app.1bb5d7303ee05300cf7d.js
https://www.udemy.com/staticx/udemy/js/webpack/instructor-qrp-reactivate-udlite-app.65f0e32585da135fc7c1.js
https://www.udemy.com/staticx/udemy/js/webpack/instructor-side-nav-udlite-app.b59f8e9da352daa9fc83.js
https://www.udemy.com/staticx/udemy/js/webpack/vendor-moment.21fb0c76cf912f74172d.js
https://www.udemy.com/staticx/udemy/js/webpack/instructor-udlite-app.04e191e81614f313c55d.js
https://www.udemy.com/staticx/udemy/js/webpack/invite-udlite-app.12f46a22ffba98fa673d.js
https://www.udemy.com/staticx/udemy/js/webpack/lab-manage-udlite-app.320b03888a8064054f55.js
https://www.udemy.com/staticx/udemy/js/webpack/lab-reference-code-udlite-app.3fa9a4afb9e6120819b9.js
https://www.udemy.com/staticx/udemy/js/webpack/lab-taking-udlite-app.6103aa77c650ae0c8226.js
https://www.udemy.com/staticx/udemy/js/webpack/lab-workspace-udlite-app.6150504e0556af4b5feb.js
https://www.udemy.com/staticx/udemy/js/webpack/labs-landing-udlite-app.1889f2b0f3be12381e6c.js
https://www.udemy.com/staticx/udemy/js/webpack/labs-loading-udlite-app.c8cdc738a98a602777bb.js
https://www.udemy.com/staticx/udemy/js/webpack/learner-activity-v1-udlite-app.64320db184336d9ee631.js
https://www.udemy.com/staticx/udemy/js/webpack/learning-assistant-udlite-app.f9beb5d5987cefdc38d1.js
https://www.udemy.com/staticx/udemy/js/webpack/learning-path-udlite-app.34d237c10622e774813a.js
https://www.udemy.com/staticx/udemy/js/webpack/lecture-landing-page-desktop-udlite-app.c1627530ecfea672d37b.js
https://www.udemy.com/staticx/udemy/js/webpack/lecture-landing-page-mobile-udlite-app.b23dee6bcadd9926c10f.js
https://www.udemy.com/staticx/udemy/js/webpack/lecture-preview-udlite-app.8e338a7348f506700bc4.js
https://www.udemy.com/staticx/udemy/js/webpack/lihp-udlite-app.606f7a8d7e7f9ba2133e.js
https://www.udemy.com/staticx/udemy/js/webpack/lohp-udlite-app.9713898c11d83d90cb1a.js
https://www.udemy.com/staticx/udemy/js/webpack/messaging-udlite-app.a21fb55c8cf9652fa2f8.js
https://www.udemy.com/staticx/udemy/js/webpack/mobile-app-download-udlite-app.d5b31d99b67f5747f7a8.js
https://www.udemy.com/staticx/udemy/js/webpack/mobile-app-variables-udlite-app.26784dc0220f75139100.js
https://www.udemy.com/staticx/udemy/js/webpack/my-courses-v3-udlite-app.d647e1b5a0f02db23911.js
https://www.udemy.com/staticx/udemy/js/webpack/open-badges-udlite-app.dc64a8ed57282a54ef06.js
https://www.udemy.com/staticx/udemy/js/webpack/organization-admin-landing-experience-udlite-app.e0778d7e0c64b933651a.js
https://www.udemy.com/staticx/udemy/js/webpack/organization-common-team-plan-payment-redirect-udlite-app.64ceaa301f67dfe37d8e.js
https://www.udemy.com/staticx/udemy/js/webpack/organization-common-team-plan-renewal-notice-udlite-app.c6cbb3e52364b8dd2e15.js
https://www.udemy.com/staticx/udemy/js/webpack/organization-course-not-available-udlite-app.3c4a8eac0d21dcb3a2b7.js
https://www.udemy.com/staticx/udemy/js/webpack/organization-global-login-udlite-app.755d2f192ee53caee2e3.js
https://www.udemy.com/staticx/udemy/js/webpack/organization-growth-team-plan-sign-up-udlite-app.439efa0b3959654d941f.js
https://www.udemy.com/staticx/udemy/js/webpack/organization-home-udlite-app.84251084679ae6881fe4.js
https://www.udemy.com/staticx/udemy/js/webpack/organization-insights-export-reports-udlite-app.1431cb00d63faeacab4d.js
https://www.udemy.com/staticx/udemy/js/webpack/organization-insights-udlite-app.9a792397041c869368a3.js
https://www.udemy.com/staticx/udemy/js/webpack/organization-invitation-verification-udlite-app.c8e0297967f770b7e190.js
https://www.udemy.com/staticx/udemy/js/webpack/organization-login-udlite-app.73b73c98dcccc126930b.js
https://www.udemy.com/staticx/udemy/js/webpack/organization-manage-assigned-udlite-app.93de36b1e75445c18b4d.js
https://www.udemy.com/staticx/udemy/js/webpack/organization-manage-courses-udlite-app.598b3ab2dc3bfcffed6d.js
https://www.udemy.com/staticx/udemy/js/webpack/organization-manage-settings-approved-email-domains-udlite-app.405873b78009ea23eb01.js
https://www.udemy.com/staticx/udemy/js/webpack/organization-manage-settings-custom-error-message-udlite-app.05d83849edada7d8c97a.js
https://www.udemy.com/staticx/udemy/js/webpack/organization-manage-settings-customize-appearance-udlite-app.d302d3ac2697f2b1a784.js
https://www.udemy.com/staticx/udemy/js/webpack/organization-manage-settings-email-reminders-udlite-app.f6fcb62495e55bbfa689.js
https://www.udemy.com/staticx/udemy/js/webpack/organization-manage-settings-genai-features-udlite-app.7387ff3c4a6b00bffbb1.js
https://www.udemy.com/staticx/udemy/js/webpack/organization-manage-settings-integrations-udlite-app.6b4314dfe54f2a57686f.js
https://www.udemy.com/staticx/udemy/js/webpack/organization-manage-settings-lms-integration-udlite-app.551f81d9da4ac82d1cdf.js
https://www.udemy.com/staticx/udemy/js/webpack/organization-manage-settings-mcp-udlite-app.6072e75c38f5bff2f5dd.js
https://www.udemy.com/staticx/udemy/js/webpack/organization-manage-settings-multi-factor-authentication-udlite-app.d6c2a93c589b49c63d72.js
https://www.udemy.com/staticx/udemy/js/webpack/organization-manage-settings-notification-preferences-udlite-app.7e05c69c2f24f9863f94.js
https://www.udemy.com/staticx/udemy/js/webpack/organization-manage-settings-provisioning-scim-udlite-app.7dd260920fecb1b0de87.js
https://www.udemy.com/staticx/udemy/js/webpack/organization-manage-settings-single-sign-on-self-serve-udlite-app.c704250ef8a5c923c27b.js
https://www.udemy.com/staticx/udemy/js/webpack/organization-manage-settings-sso-cert-utility-udlite-app.d5295eba88a5d9484eab.js
https://www.udemy.com/staticx/udemy/js/webpack/organization-manage-users-udlite-app.87f6d2d27ecd88f674d9.js
https://www.udemy.com/staticx/udemy/js/webpack/organization-merge-consent-udlite-app.42882a6415b782bd38ed.js
https://www.udemy.com/staticx/udemy/js/webpack/organization-merge-frozen-udlite-app.2eb126a00ac2d7dc4ce6.js
https://www.udemy.com/staticx/udemy/js/webpack/organization-onboarding-pro-udlite-app.b7aa11428e20d1809c86.js
https://www.udemy.com/staticx/udemy/js/webpack/organization-resources-udlite-app.e3cea48b7fcc4a5076a1.js
https://www.udemy.com/staticx/udemy/js/webpack/organization-team-plan-billing-udlite-app.9e7800e1a0f66ad5304e.js
https://www.udemy.com/staticx/udemy/js/webpack/personal-plan-home-udlite-app.1e8a877b0cc430a63d96.js
https://www.udemy.com/staticx/udemy/js/webpack/personalize-udlite-app.87fdeb34cd3b4cef6fc4.js
https://www.udemy.com/staticx/udemy/js/webpack/prepaid-code-udlite-app.a28d7ace0e8a709d35ae.js
https://www.udemy.com/staticx/udemy/js/webpack/purchase-history-udlite-app.794cb08969e795b8c7bf.js
https://www.udemy.com/staticx/udemy/js/webpack/report-abuse-udlite-app.eceb2aaacfea661583fc.js
https://www.udemy.com/staticx/udemy/js/webpack/revenue-report-udlite-app.73f4425b5c16b81c799d.js
https://www.udemy.com/staticx/udemy/js/webpack/search-udlite-app.bbe4070feac2cd1a2b21.js
https://www.udemy.com/staticx/udemy/js/webpack/shaka-video-player-tester-udlite-app.eed3ca4f3bb4e18de509.js
https://www.udemy.com/staticx/udemy/js/webpack/student-refund-udlite-app.4aa5ad5d5112e5f85110.js
https://www.udemy.com/staticx/udemy/js/webpack/subscription-browse-pages-subscription-logged-in-home-udlite-app.0a48ce02dd44481292d2.js
https://www.udemy.com/staticx/udemy/js/webpack/subscription-checkout-v2-udlite-app.16dbb7fe2e5a60551079.js
https://www.udemy.com/staticx/udemy/js/webpack/subscription-checkout-header-udlite-app.368481fd5e80998f65ea.js
https://www.udemy.com/staticx/udemy/js/webpack/subscription-checkout-pages-checkout-success-udlite-app.c719fe59565f3b31675a.js
https://www.udemy.com/staticx/udemy/js/webpack/subscription-checkout-udlite-app.57ba2e67807ab1eb07f3.js
https://www.udemy.com/staticx/udemy/js/webpack/subscription-management-settings-udlite-app.7e03dc0d137237bf5c1c.js
https://www.udemy.com/staticx/udemy/js/webpack/subscription-plan-selection-udlite-app.cf6947169458bdb0635a.js
https://www.udemy.com/staticx/udemy/js/webpack/subscription-required-udlite-app.8b5b02be0c49a5b71cd7.js
https://www.udemy.com/staticx/udemy/js/webpack/subscription-upgrade-checkout-udlite-app.65d4bb93e6f1fce392b4.js
https://www.udemy.com/staticx/udemy/js/webpack/support-system-check-udlite-app.30dbe368083ab499b555.js
https://www.udemy.com/staticx/udemy/js/webpack/tapen-autocomplete-admin-udlite-app.dc3947f1ce9c38a82763.js
https://www.udemy.com/staticx/udemy/js/webpack/tapen-change-gateway-payment-transaction-admin-udlite-app.5a3d34e71adecac13090.js
https://www.udemy.com/staticx/udemy/js/webpack/tapen-change-payment-transaction-admin-udlite-app.3efa14afd6f9060401c2.js
https://www.udemy.com/staticx/udemy/js/webpack/tapen-customer-data-platform-admin-attribute-udlite-app.9d7dcf9832b24800fc67.js
https://www.udemy.com/staticx/udemy/js/webpack/tapen-customer-data-platform-admin-graph-udlite-app.05310419e5296786a89e.js
https://www.udemy.com/staticx/udemy/js/webpack/tapen-customer-data-platform-admin-s3-catalog-udlite-app.97956783adb8058af62a.js
https://www.udemy.com/staticx/udemy/js/webpack/tapen-delete-abn-admin-udlite-app.6956149dd9ea935be564.js
https://www.udemy.com/staticx/udemy/js/webpack/tapen-discovery-cache-admin-udlite-app.199ada704dbfe5a7897d.js
https://www.udemy.com/staticx/udemy/js/webpack/tapen-discovery-context-admin-udlite-app.0077e04966c1630f169f.js
https://www.udemy.com/staticx/udemy/js/webpack/tapen-discovery-unit-form-admin-udlite-app.7ff8c0f25da289ed23b3.js
https://www.udemy.com/staticx/udemy/js/webpack/tapen-es-tooling-admin-udlite-app.bf589825a0909d11d945.js
https://www.udemy.com/staticx/udemy/js/webpack/tapen-experiment-form-admin-udlite-app.29f0bb3ca789a5a899a9.js
https://www.udemy.com/staticx/udemy/js/webpack/tapen-experimentation-platform-admin-abn-experiment-management-udlite-app.91a48def076bb52ecfb1.js
https://www.udemy.com/staticx/udemy/js/webpack/tapen-experimentation-platform-admin-attribute-collector-udlite-app.9e9cfee39f185bbea740.js
https://www.udemy.com/staticx/udemy/js/webpack/tapen-experimentation-platform-admin-business-domain-udlite-app.7326db3fb1e66c12cf9a.js
https://www.udemy.com/staticx/udemy/js/webpack/tapen-experimentation-platform-admin-configuration-context-udlite-app.982e7f48aa70fdceb90c.js
https://www.udemy.com/staticx/udemy/js/webpack/tapen-experimentation-platform-admin-configuration-domain-udlite-app.2fba1bb56048e45ecbe7.js
https://www.udemy.com/staticx/udemy/js/webpack/tapen-experimentation-platform-admin-dashboard-udlite-app.ba5066e8793fae11344a.js
https://www.udemy.com/staticx/udemy/js/webpack/tapen-experimentation-platform-admin-experiment-group-udlite-app.b276d70669eb7d0cb385.js
https://www.udemy.com/staticx/udemy/js/webpack/tapen-experimentation-platform-admin-experiment-management-udlite-app.3b5b7e5cac17c1126a1d.js
https://www.udemy.com/staticx/udemy/js/webpack/tapen-experimentation-platform-admin-feature-udlite-app.31c40beea08fc9238be5.js
https://www.udemy.com/staticx/udemy/js/webpack/tapen-experimentation-platform-admin-isolation-group-udlite-app.23e5e733e9b21ae085c2.js
https://www.udemy.com/staticx/udemy/js/webpack/tapen-experimentation-platform-admin-ledger-udlite-app.b55322af7544e1281223.js
https://www.udemy.com/staticx/udemy/js/webpack/tapen-experimentation-platform-admin-plan-udlite-app.77a50c79360fe44a6930.js
https://www.udemy.com/staticx/udemy/js/webpack/tapen-experimentation-platform-admin-previews-udlite-app.11caa9e16d465a544405.js
https://www.udemy.com/staticx/udemy/js/webpack/tapen-instructor-course-retirement-notification-admin-udlite-app.9c9cff27f5112a72b141.js
https://www.udemy.com/staticx/udemy/js/webpack/tapen-jsoneditor-admin-udlite-app.5c923f97c64839318f79.js
https://www.udemy.com/staticx/udemy/js/webpack/tapen-labs-create-new-lab-admin-udlite-app.42df84486afdf80a5000.js
https://www.udemy.com/staticx/udemy/js/webpack/tapen-marketing-tools-admin-udlite-app.693ca90d4a5992d730d8.js
https://www.udemy.com/staticx/udemy/js/webpack/tapen-measure-competence-admin-udlite-app.9bc0fc54c39d143b6332.js
https://www.udemy.com/staticx/udemy/js/webpack/tapen-organization-collections-admin-udlite-app.15565fa75deada55c7aa.js
https://www.udemy.com/staticx/udemy/js/webpack/tapen-organization-insights-admin-data-export-reports-udlite-app.420d6f86b9353184afc0.js
https://www.udemy.com/staticx/udemy/js/webpack/tapen-organization-insights-admin-insights-udlite-app.1a46fc752893fdf6790d.js
https://www.udemy.com/staticx/udemy/js/webpack/tapen-organization-merge-admin-create-merge-request-udlite-app.2606842a962f42e3e9f3.js
https://www.udemy.com/staticx/udemy/js/webpack/tapen-organization-merge-admin-merge-request-udlite-app.1d5ef01c18654c9e4dd4.js
https://www.udemy.com/staticx/udemy/js/webpack/tapen-organization-new-owner-widget-admin-udlite-app.811f6614112d3fa39c44.js
https://www.udemy.com/staticx/udemy/js/webpack/tapen-organization-overview-admin-admin-overview-udlite-app.6e12438bbb35dc86f2f1.js
https://www.udemy.com/staticx/udemy/js/webpack/tapen-organization-owner-widget-admin-udlite-app.428468ec380ad45cd12c.js
https://www.udemy.com/staticx/udemy/js/webpack/tapen-organization-subscription-admin-udlite-app.4cad0c710e0f52636794.js
https://www.udemy.com/staticx/udemy/js/webpack/tapen-organization-support-admin-udlite-app.4e5d8e4ef05ee5971fb4.js
https://www.udemy.com/staticx/udemy/js/webpack/tapen-payment-method-admin-udlite-app.aa6ddce0b5d5a781851c.js
https://www.udemy.com/staticx/udemy/js/webpack/tapen-payment-method-config-admin-udlite-app.52bbf22477b3ddf39f21.js
https://www.udemy.com/staticx/udemy/js/webpack/tapen-payment-story-admin-udlite-app.d77c05e59abd13a9de83.js
https://www.udemy.com/staticx/udemy/js/webpack/tapen-payouts-admin-udlite-app.ca428b34e86837fa3930.js
https://www.udemy.com/staticx/udemy/js/webpack/tapen-prepaid-code-admin-prepaid-code-management-udlite-app.fba197d0e46309ad9bb1.js
https://www.udemy.com/staticx/udemy/js/webpack/tapen-prepaid-code-admin-prepaid-code-request-create-udlite-app.481afe5de1e1f8a79f15.js
https://www.udemy.com/staticx/udemy/js/webpack/tapen-prepaid-code-admin-prepaid-code-request-management-udlite-app.e6d6209cbf57e6653b9a.js
https://www.udemy.com/staticx/udemy/js/webpack/tapen-pricing-admin-coupon-management-udlite-app.6b051ce6570985fc3b8e.js
https://www.udemy.com/staticx/udemy/js/webpack/tapen-quality-review-admin-udlite-app.9b3c01f1e8615519f802.js
https://www.udemy.com/staticx/udemy/js/webpack/tapen-refund-admin-udlite-app.b1d9027b613410cd7865.js
https://www.udemy.com/staticx/udemy/js/webpack/tapen-refund-gateway-transaction-admin-udlite-app.7d96f0f14c46d665c7a8.js
https://www.udemy.com/staticx/udemy/js/webpack/tapen-refund-transaction-admin-udlite-app.8168890322e60d45b228.js
https://www.udemy.com/staticx/udemy/js/webpack/tapen-sherlock-admin-udlite-app.41ee683328e9e3836640.js
https://www.udemy.com/staticx/udemy/js/webpack/tapen-structured-data-search-admin-udlite-app.543174f9aa3884563959.js
https://www.udemy.com/staticx/udemy/js/webpack/tapen-subscription-management-admin-udlite-app.3e0fd9a0e4776763d6e7.js
https://www.udemy.com/staticx/udemy/js/webpack/tapen-support-admin-udlite-app.424188ae3fafc9ff0954.js
https://www.udemy.com/staticx/udemy/js/webpack/tapen-task-management-admin-udlite-app.c74b43f4d72cbb7d3806.js
https://www.udemy.com/staticx/udemy/js/webpack/tapen-user-admin-udlite-app.ad15aa40e41f93979ff6.js
https://www.udemy.com/staticx/udemy/js/webpack/tapen-user-test-video-admin-udlite-app.976ed85c624bdc258922.js
https://www.udemy.com/staticx/udemy/js/webpack/tapen-video-player-admin-udlite-app.8f97566500e4f43d661e.js
https://www.udemy.com/staticx/udemy/js/webpack/teach-page-udlite-app.8ece14ca6e7e13e7ceaa.js
https://www.udemy.com/staticx/udemy/js/webpack/teaching-courses-test-video-udlite-app.857970d829cbd81f8245.js
https://www.udemy.com/staticx/udemy/js/webpack/terms-bar-udlite-app.20e10c0062ca9a2dc454.js
https://www.udemy.com/staticx/udemy/js/webpack/topic-udlite-app.39e7484746447778143a.js
https://www.udemy.com/staticx/udemy/js/webpack/user-manage-ajax-modal-udlite-app.a0ff1677daf84acf3190.js
https://www.udemy.com/staticx/udemy/js/webpack/user-manage-udlite-app.7e4f75d7a646212f6bbc.js
https://www.udemy.com/staticx/udemy/js/webpack/user-profile-instructor-udlite-app.2119364dea5d6b8b529c.js
https://www.udemy.com/staticx/udemy/js/webpack/user-profile-udlite-app.1e1a25440468d08e6067.js
https://www.udemy.com/staticx/udemy/js/webpack/versioned-image-upload-with-preview-udlite-app.45d398cc7496798224c3.js
https://www.udemy.com/staticx/udemy/js/webpack/node_modules_amplitude_engagement-browser_index_esm_js.49b8cdad9f0c8c75d03a.js
https://www.udemy.com/staticx/udemy/js/webpack/braze.132680f27e8e69ab2079.js
https://www.udemy.com/staticx/udemy/js/webpack/src_udemy_js_pendo_bootstrap_ts.473612c26800e515eb0b.js
https://www.udemy.com/staticx/udemy/js/webpack/src_udemy_js_my-courses-v3_measure-competence_assessment-card-context-menu-lazy_react-component_js.bbeb00a552ed5f08ed79.js
https://www.udemy.com/staticx/udemy/js/webpack/node_modules_udemy_mathjax_dist_esm_load-mathjax_js.5573126e1da665eea6a0.js
https://www.udemy.com/staticx/udemy/js/webpack/node_modules_udemy_react-rich-text-editor_dist_esm_rich-text-editor_ud-prosemirror_index_js.0c53f76addd33edc02ed.js
https://www.udemy.com/staticx/udemy/js/webpack/zxcvbn-common.5f88745a8de6d5c2cc60.js
https://www.udemy.com/staticx/udemy/js/webpack/zxcvbn-en.d04c0778a2aa33873fa7.js
https://www.udemy.com/staticx/udemy/js/webpack/zxcvbn-de.01c17d8c425f156368b8.js
https://www.udemy.com/staticx/udemy/js/webpack/zxcvbn-fr.c222939ef20afac11d1b.js
https://www.udemy.com/staticx/udemy/js/webpack/zxcvbn-es.4f6310bef9789f1fdc67.js
https://www.udemy.com/staticx/udemy/js/webpack/node_modules_udemy_react-discovery-units_dist_esm_professional-skills-unit_subcategory-conten-2b559b.9e7956b77ffa97b348a7.js
https://www.udemy.com/staticx/udemy/js/webpack/create-ufb-context-menu.0d9af80fc2ab45672303.js
https://www.udemy.com/staticx/udemy/js/webpack/course-landing-page-lazy.4b0ea6eaf46816a37bb6.js
https://www.udemy.com/staticx/udemy/js/webpack/course-landing-page-lazy-course-context-menu.2afb5df17083625547f6.js
https://www.udemy.com/staticx/udemy/js/webpack/marketplace-social-share.61ebb8685a28827eecdc.js
https://www.udemy.com/staticx/udemy/js/webpack/shaka-video-player.a951c6c1ddd629b52e30.js
https://www.udemy.com/staticx/udemy/js/webpack/video-mashup-asset.1fa9fa5947e271b57fb1.js
https://www.udemy.com/staticx/udemy/js/webpack/react-syntax-highlighter/lowlight-import.588ea811ae57da2cc0b3.js
https://www.udemy.com/staticx/udemy/js/webpack/throw-error.e534a479f9d99b9894cb.js
https://www.udemy.com/staticx/udemy/js/webpack/node_modules_udemy_react-student-retention_node_modules_progressbar_js_dist_progressbar_js.33c959eeee42c18fff40.js
https://www.udemy.com/staticx/udemy/js/webpack/search-unavailable.2dd641dfecaa6de13110.js
https://www.udemy.com/staticx/udemy/js/webpack/search-no-results.95e687be68791ffb0fa6.js
https://www.udemy.com/staticx/udemy/js/webpack/node_modules_storybook_blocks_dist_Color-6VNJS4EI_mjs.81308e96f40e9436df67.js
https://www.udemy.com/staticx/udemy/js/webpack/node_modules_storybook_components_dist_syntaxhighlighter-NMPM6SWI_mjs.c44add36fd696ac4c558.js
https://www.udemy.com/staticx/udemy/js/webpack/node_modules_storybook_components_dist_WithTooltip-4HIR6TLV_mjs.d0cb52092f343ab43026.js


Заключение​

Таким образом, с помощью анти-детект браузера, фреймворка Playwright и парсинга манифест-файла из Webpack мы собрали максимально возможное количество JS-файлов с нашего таргета за Cloudflare. И хотя может показаться, что можно обойтись только манифест-файлом, полагаться на него не стоит — в зависимости от настроек сборки некоторые файлы могут отсутствовать, а на многих таргетах такого файла может не быть вовсе. Чанки могут быть описаны и без манифеста внутри бандла, но метод их извлечения будет точно таким же; изменится только алгоритм поиска в бандле и формирование итогового списка URL.

В следующей части рассмотрим работу с HAR-файлами для извлечения из них JS, их форматирование, извлечение source maps и подготовку к работе с JS extractors.
👍39 😁4 😮2 👏4 🤔9 😢6
Ответnipaah

Автоматизация мониторинга JavaScript-файлов для Bug Bounty. Часть 2. Crawler​ Ознакомьтесь с первой частью статьи , где рассказывается об проблеме автоматизации мониторинга JS-файлов на наличие новых фич и общей…

Здравствуйте! Вы мне написали в личные сообщения, чтобы я дал обратную статью по вашей статье. Мне проще будет это сделать прям тут, нежели в личных сообщениях. Дело в том, что нужно процитировать некоторые куски статьи, чтобы стало понятнее вам.


Ответnipaah

Хантить там я порекомендовать не могу

Почему? Есть какие-то аргументы, люди ценят пользовательский опыт.


Ответnipaah

Как следует из названия, здесь мы просто посещаем страницы без авторизации. Для наглядности оставим только две ссылки — главная страница и форма логина.

Скриншоты главной страницы и формы логина?

Ответnipaah

Таймауты здесь расставлены с запасом и учетом моих довольно медленных прокси. Ну и как показывает практика, многие E2E-тесты только на таймаутах и держатся — стоит попытаться оптимизировать, все разваливается. Так что…

Ну как показывает практика и показали бы люди за этим и пришли

Коротко скажу: нужно добавить скриншоты, если это скрипт, то очень желательно приложить видеодемонстрацию как вы запускаете этот скрипт и он отрабатывает на таргете. По итогу результат должен быть положительным чем вам эти JS файлы помогли, какой импакт? Что может сделать злоумышленник? 

Многие скипнут ее, особенно те кто не умеют программировать, такая статья не продается думаю вы понимаете. 

Доработайте, пожалуйста если материал авторский администратор оплатит, главное исправьте то о чем я сообщил. И не используйте неройсети для исправления ошибок. Лучше пишите как есть, свои мысли. Это будет приятнее читать, если даже с ошибками. 

@Rehub проверьте работу на копипаст.

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

Вот обратите внимание на статьи от @YmbMember человек пишет статьи, у него есть своя подача, оформление и все его работы оплачиваются. Вы один раз сделаете как надо и далее поймете, чего ждут люди.
ОтветModerator

Здравствуйте! Вы мне написали в личные сообщения, чтобы я дал обратную статью по вашей статье. Мне проще будет это сделать прям тут, нежели в личных сообщениях. Дело в том, что нужно процитировать некоторые куски…

Благодарю за развернутный ответ! Про импакт я кратко обозначил, зачем и почему, какие цели мы преследуем в предыдущей части - https://forum.LIMB.cc/threads/1468/, работа же с самими JS файлами и извлечение полезного будет в следующей. Тогда, может, объединить эти две части в одну для наглядности, добавить скриншоты и видео, поработать еще над остальными замечаниями и создать новую тему? Как создам, эти две можно будет удалить. Попробую охватить чуть большую аудиторию, просто изначально нацеливался на людей с опытом в багбаунти или просто веб уязвимостях, которые хотели бы автоматизировать некоторые процессы, и поэтому решил, что достаточно сухое изложение метода и кода вполне подойдет. Опыт и код достаточно уникальный, похожего комплексного решения в открытом доступе я не находил, думаю многим должно быть интересно, но опять же, в первую очередь тем, кто сам сталкивался с похожими вызовами.
P.S. Маленькая техническая просьба - сделать вотермарки на скриншотах более прозрачными, они мешают чтению кода, ну или того что на скриншотах.
👍4 🔥1
Ответnipaah

Благодарю за развернутный ответ! Про импакт я кратко обозначил, зачем и почему, какие цели мы преследуем в предыдущей части - https://forum.LIMB.cc/threads/1468/ , работа же с самими JS файлами и извлечение полезного…

Да, подготовьте

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