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

тема

Часть 1: Врываемся в next.js приложения со свистом

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

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

Вступление
Эйо! С вами опять YmbMember. Очень часто мне попадаются сайты на next.js. Когда я наконец-то прогуглил наличие каких-то докладов/статей по топику пентеста next.js веб приложений, на мое удивление, почти ничего я не нашёл. И я подумал, что это могло бы стать хорошим поводом для написания новой интересной статьи для вас. В этой части из серии статей по пентесту next.js мы ознакомимся с поверхностной работой next.js, пентестом веб приложений на его базе, с особенностями данного фреймворка и не только.
В следующей части мы пройдемся и разберем много разных СVЕх включая их эксплуатацию на реальных примерах

Я попытаюсь вам донести эту информацию как веб пентестер, а не кодер, и остановлюсь на тех моментах, какие мне были непонятны.  Начнём

Что же такое ваш next.js
Next.js - очень мощный фреймворк на базе React для создания full stack веб приложений (front end + back end). Чтобы вы лучше въехали:

React - это js библиотека для разработки UI на front end’е
Next.js - уже фреймворк на базе React, который добавляет много возможностей, которых нет “из коробки” в обычном React

Во всем мире примерно 1,300,000+ сайтов построены на его базе. А это около 2% от всех сайтов в интернете. Впечатляет, не так-ли?

Маршрутизация
Маршрутизация - механизм, какой связывает URL с кодом, какой должен выполнятся по этому URL. В next.js для этого используется механизм App Router, какой стал стандартом.
Код всех страниц находится находится в папке app. Вот пример:
Не удалось прогрузить медиа

Папки marketing, shop - группировщики маршрутов, которые никак не влияют на URL и нужны для более удобной группировки кода.
Папки about, blog, account формируют маршрут
Внутри этих папок можно увидеть page.js . Это уже фронтенд код нашего сайта.
Не удалось прогрузить медиа

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

Next.js поддерживает следующие расширения файлов для страниц:
.js .tsx .ts .jsx .mdx

Типы рендеринга страниц
SSG (Static Site Generation) - создается при этапе сборки и отдается как простой HTML. Проще говоря: стандартная статическая страница (Кэшируется)

ISR (Incremental Static Regeneration) - статическая страница, какая обновляется со временем (Кэшируется)

SSR (Server-Side Rendering) — это способ, при котором страница генерируется на сервере при каждом запросе, а браузер получает уже готовый HTML
Не удалось прогрузить медиа

По умолчанию SSR страницы не кэшируются, т.к в SSR страницах есть динамические данные. Но редко есть исключения

Кэширование
В нексте даже есть свой механизм кэширования, какой включен по дефолту. Для того чтобы понять, страница в кэше или нет, ориентируемся на стандартные веб заголовки и на заголовок x-nextjs-cache (он отключен в Dev Mode). Но зачастую, разработчики ставят поверх веб приложений на нексте еще и CDN. Вот тут возникает проблема

Как например:
CVE-2023-46298
Уязвимость связана с prefetch-запросами. При отправке GET запроса с заголовком x-middleware-prefetch: 1 В ответе возвращался пустой json объект
Не удалось прогрузить медиа

Если есть CDN или другой кэширующий движок, то этот пустой ответ может попасть в кэш (в зависимости от настроек). Тогда нужная страница перестанет нормально работать и её содержимое станет недоступным. Простенький DOS
Остальные дыры связанные с кешем мы рассмотрим в след. части

Middleware
Middleware - мощный инструмент, какой позволяет нам перехватывать и контролировать поток запросов и ответов в нашем приложении
С его помощью можно редиректить, рейт лимитить, перезаписывать URL, управлять заголовками, перезаписывать куки и не только
Но самое его главное предназначение - Авторизация / доступ к страницам.
За все время было довольно много ЦВЕХ связанных с мидлваром. Вот например, относительно недавно нашумела одна CVEха, связанная с этим - CVE-2025-29927. Уязвимость позволяет обойти проверки авторизации или другие логики в middleware благодаря подделке HTTP-заголовка x-middleware-subrequest

Не вижу смысла разжевывать это, т.к про это уже писали на форуме. Советую к прочтению: https://forum.LIMB.cc/threads/253/

Так-же советую посмотреть как работает middleware внутри - 
Не удалось прогрузить медиа

Поиск таргетов на next.js
Для того чтобы попрактиковаться, можем найти таргеты через поисковики типа shodan/fofa. В этом фреймворке все служебные файлы храняться в папке _next. Воспользуемся этим для построения дорков

Shodan:
"http.html:_next"

Fofa:
body="/_next/"

Так можно найти 99% всех таргетов. Очень редко разработчики переименовают папку _next на кастомное название. Но это единичные случаи, для массовой охоты это для нас не преграда

Определение версии
Тут все тоже предельно просто, с этим за нас справится вапалайзер
Не удалось прогрузить медиа

Версия нам поможет понять специфики версии и поможет в поиске CVE

Поиск эндпоинтов. Манифесты
О ценности манифестов для пентестеров я не нашел в открытых источниках, поэтому написал о всем что расспросил у знакомых кодеров и смог изучить самостоятельно. Кому есть что дополнить - буду только рад

Например __BUILD_MANIFEST и __SSG_MANIFEST — это клиентские манифесты Next.js, какие создаются автоматически и  помогают браузеру понять,какие чанки JS/CSS относятся к каким страницам. Манифестов, какие созданы автоматически около десятка, но есть еще и манифесты какие разработчики ставят сами
Манифесты встречаются на этапах сборки build и production

Публично раскрытый манифест - не баг, но он дает нам кучу эндпоинтов и немного второстепенной инфы. Среди этих эндпоинтов могут быть как и какие-то эндпоинты с бета функционалом так и какие то пути к админкам (редко)
Не удалось прогрузить медиа



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

1. Консольный скрипт
Code:
javascript:(()=>{const G=(typeof globalThis!=='undefined')?globalThis:(typeof window!=='undefined'?window:this);const out={build:null,ssg:null};try{const bm=G.__BUILD_MANIFEST;if(bm){if(Array.isArray(bm.sortedPages))out.build=bm.sortedPages.slice();else if(bm&&typeof bm==='object')out.build=Object.keys(bm).filter(k=>k.startsWith('/'));}}catch(e){console.warn('Error reading __BUILD_MANIFEST',e);}try{const s=G.__SSG_MANIFEST;if(s){if(s instanceof Set)out.ssg=Array.from(s);else if(Array.isArray(s))out.ssg=s.slice();}}catch(e){console.warn('Error reading __SSG_MANIFEST',e);}if(out.build&&out.build.length){console.log('FOUND __BUILD_MANIFEST:');out.build.forEach(x=>console.log(x));}else{console.log('No __BUILD_MANIFEST found or it has no pages.');}if(out.ssg&&out.ssg.length){console.log('FOUND __SSG_MANIFEST:');out.ssg.forEach(x=>console.log(x));}else{console.log('No __SSG_MANIFEST found or it is empty.');}})();

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

Он простой, сканирует на наличие самых важных для нас манифестов - SSG и Build. Мне показалось, что было бы полезно сделать скрипт какой сможет сканировать список таргетов через этот консольный скрипт
Не удалось прогрузить медиа

Code:
import asyncio, argparse

from playwright.async_api import async_playwright

from colorama import Fore, Style, init

init()


JS = """(()=>{const G=globalThis||window;const o={build:null,ssg:null};

try{const b=G.__BUILD_MANIFEST;if(b){if(Array.isArray(b.sortedPages))o.build=b.sortedPages;else if(b&&typeof b=='object')o.build=Object.keys(b).filter(k=>k.startsWith('/'));}}catch{}

try{const s=G.__SSG_MANIFEST;if(s){if(s instanceof Set)o.ssg=[...s];else if(Array.isArray(s))o.ssg=s;}}catch{}

return o;})();"""


print("""

=========================

YmbMember для LIMB

=========================

  

""")


async def check(browser, url):

    if not url.startswith("http"): url="https://"+url

    page = await browser.new_page()

    try: await page.goto(url, timeout=15000)

    except:

        print(Fore.RED + f"[{url}] load error" + Style.RESET_ALL)

        return

    try: d = await page.evaluate(JS)

    except:

        print(Fore.RED + f"[{url}] js error" + Style.RESET_ALL)

        return

  

    b, s = d.get("build") or [], d.get("ssg") or []

    if b or s:

        print(Fore.GREEN + f"[{url}] manifest found" + Style.RESET_ALL)

        if b:

            print(Fore.CYAN + "  __BUILD_MANIFEST:" + Style.RESET_ALL)

            for i in b: print("    •", i)

        if s:

            print(Fore.YELLOW + "  __SSG_MANIFEST:" + Style.RESET_ALL)

            for i in s: print("    •", i)

    else:

        print(Fore.RED + f"[{url}] no manifest" + Style.RESET_ALL)


async def main():

    p = argparse.ArgumentParser()

    p.add_argument("--list")

    p.add_argument("--url")

    a = p.parse_args()

    targets = []

    if a.list:

        with open(a.list) as f: targets = [x.strip() for x in f if x.strip()]

    if a.url: targets.append(a.url)

    if not targets:

        print("use --list or --url")

        return


    async with async_playwright() as pw:

        b = await pw.chromium.launch(headless=True)

        for t in targets: await check(b, t)

        await b.close()


if __name__=="__main__":

    asyncio.run(main())


2. Через сурс код
Я считаю этот метод более надёжным. Заходим на главную страницу сайта, жмем CTRL + U
Не удалось прогрузить медиа

Где-то вверху вы сможете увидеть пути типа
/_next/static/BUILD-ID/манифест.js
Build ID в Next.js — это уникальный хэш, который присваивается каждой сборке приложения при выполнении next build. Поэтому пробрутить его не получится. То есть, если консольный скрипт не дал результатов - ищем build id и перебираем манифесты стандартным словарём
Code:
build-manifest.json
react-loadable-manifest.json
prerender-manifest.json
routes-manifest.json
ssg-manifest.js
build-manifest.js
_buildManifest.js
_buildManifest.js.map
middleware-manifest.json
font-manifest.json
image-manifest.json
Не удалось прогрузить медиа

Но тут стоит вопрос - как же найти билд айди? Он же встречается во всех версиях некст джс после 9-ой версии. И тут все не так однозначно. Билд айди всегда встречается в путях:
/_next/static/чтототам
/_next/data/<BUILD_ID>/чтототам

А уже как вы найдете пути с билд айди - дело ваше. Можно через sitemap/robots.txt, сурс код, вейбек машина. Все это зависит от вашей фантазии
Не удалось прогрузить медиа


Но бывает и такое, что манифесты лежат по пути без build id, а в корневой директории. На такую “аномалию” может быть много разных причин, как например, особенности версии. Но нас это не сильное интересует
В таком случае брутим этим словарем
Code:
/_next/build-manifest.json
/_next/prerender-manifest.json
/_next/routes-manifest.json
/_next/server/middleware-manifest.json
/_next/server/font-manifest.json
/_next/server/image-manifest.json
Не удалось прогрузить медиа

Кстати, часто в таких манифестах вы как раз сможете получить BUILD ID. Если даже с билд айди вы не смогли найти манифест - возможно, он попросту не был сгенерирован автоматически или его намеренно скрыли.

Брут директорий
Вообще как такового словаря для ффазинга, как например под AEM тут нету. Есть только эндпоинты, какие могут подсыпать немного информации о таргете. Но ничего сверх критичного они не раскроют
Code:
/_next/image
/_next/data/
/_next/data/<build-id>/
/_next/static/webpack/
/_next/development/
/_next/static/development/
/_next/webpack-hmr
/__next/static/runtime/
/manifest.json
/next.config.js
/.next/server/
/.next/standalone
/_next/trace
/route-handler
/route
/handler
/server

И обязательно ффазим веб приложения на его основе вагими API словарями! Очень часто даже в самых простых next js апках используются API Routes, стандартный префикс для API Routes /api/<тут ффазим>

Заключение
В этой части мы рассмотрели как работает next js, ознакомились с CVE-2023-46298, с манифестами и советами по пентесту таких веб приложух. Если есть в чате веб кодеры/пентестеры , каким есть что добавить/где меня поправить - буду очень признателен.
В следующей части мы с вами ознакомимся с множеством разных СVЕх на живых примерах. Удачи
👍16 🔥6 😮2

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