В этой статье я хочу рассказать о своем исследовании, посвященном tRPC. Сначала мы рассмотрим концепции tRPC, а затем перейдем к анализу поверхности атаки приложения tRPC.

tRPC с точки зрения разработчика

tRPC расшифровывается как "TypeScript Remote Procedure Call" и использует возможности TypeScript для обеспечения безопасности типов на границах клиент-сервер. Это означает, что tRPC позволяет разработчикам создавать API, в которых входные и выходные данные автоматически проверяются на соответствие типам, что значительно снижает риск возникновения ошибок во время выполнения из-за несовпадения типов данных или неожиданных структур данных.

Давайте узнаем, как это работает:

Определение API

Как разработчик, вы должны определить API на сервере с помощью TypeScript, указав типы ввода и вывода для функций, которые представляют конечные точки API. Эти функции, известные в tRPC как процедуры, могут выполнять различные операции, такие как получение данных (Queries), а также создание, удаление и обновление данных (Mutations).

Определение маршрутизаторов

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

В этом примере определены две процедуры: одна для чтения данных, другая - для их изменения. Возможно, что некоторые процедуры могут получать входные данные от клиента.

Интеграция на стороне клиента

tRPC автоматически генерирует клиентскую библиотеку на основе типов вашего API. Эта клиентская библиотека позволяет внешнему приложению вызывать серверные процедуры напрямую, как локальные функции, без необходимости беспокоиться о HTTP-методах, заголовках или путях. Когда вы вызываете серверную процедуру, типы выводятся непосредственно из определений TypeScript сервера. Это означает, что разработчик получает автозаполнение предложений в редакторе и проверку типов во время компиляции, что помогает предотвратить проблемы, связанные с предоставлением неправильного типа или структуры данных в запросах или ответах API. Вот пример:

Клиент вызывает метод 'addUser'; результат этого метода считывается из ответа и отображается в DOM. Ссылка httpBatchLink особенно полезна в сценариях, когда клиенту необходимо отправить несколько запросов на сервер одновременно. В качестве альтернативы httpLink можно использовать для выполнения стандартных отдельных HTTP-запросов от клиента к серверу tRPC.

Обмен данными между клиентом и сервером (транспортный уровень)

tRPC использует транспортные механизмы (обычно HTTP/HTTPS).

До сих пор мы изучали концепции tRPC. Далее мы рассмотрим tRPC с точки зрения исследователя.

tRPC с точки зрения исследователя

Чтобы выявить уязвимости в tRPC, мы должны принять во внимание следующие шаги:

  1. Определение стиля tRPC

  2. Исследование tRPC

  3. Анализ поверхности атаки

Шаг 1: Определение tRPC

Самый важный вопрос, который мы должны решить при тестировании API, это какой стиль используется. Каждый API имеет свой стиль. Поняв стиль, мы сможем легче провести разведку и обнаружить уязвимости. Как мы можем определить, что стиль API цели - tRPC?

Как мы видели в предыдущем разделе, процедуры в tRPC бывают двух видов: Query и Mutation. Метод GET используется для Querie, которые предполагают чтение данных, а метод POST - для Mutation, то есть для изменения данных. У нас есть следующие общие шаблоны:

GET  /ProcedureName
POST /ProcedureName

GET  /getUsers
POST /addUser

Иногда приложение использует httpBatchLink для отправки всех запросов вместе. В этом случае в строку запроса включается параметр 'batch':

GET  /getUsers?batch=1

Формат ошибки

Еще один способ определить tRPC - понаблюдать за тем, какую ошибку возвращает сервер для различных состояний. Если процедура не была определена для конечной точки на стороне сервера, изменение метода HTTP приведет к ошибке со стороны сервера:

Изменение типа параметра

Изменение типов параметров в запросе и выдача ошибки могут помочь нам в определении tRPC:

Формат данных запроса/ответа

Формат данных, который мы можем использовать в tRPC, - это JSON. В примере ниже показана структура данных в двух сценариях: httpLink и httpBatchLink:

Комбинируя вышеописанные техники, мы можем определить tRPC.

Шаг 2: tRPC Recon

Во время разведки API доступ к документации может помочь нам в анализе цели. Наша цель может сделать свой API общедоступным, к которому мы можем получить доступ, используя различные техники. Цель может использовать панель trpc. Этот инструмент предназначен специально для документирования конечных точек tRPC.
Вот простой вариант реализации:

Разработчик может определить промежуточное ПО для панели trpc и указать конечную точку для доступа к панели, как показано в примере ниже:

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

Google Dorking

site:TARGET.tld intitle:"tRPC.panel()" inurl:/panel
site:TARGET.tld intitle:"tRPC.panel()"

API Fuzzing

id: trpc-panel

info:
  name: Public trpc-panel
  author: LogicalHunter
  severity: info
  tags: exposure,trpc

http:
  - method: GET
    path:
      - "{{BaseURL}}/panel"
      - "{{BaseURL}}/trpc-panel"
      - "{{BaseURL}}/trpc"
      - "{{BaseURL}}/trpc/panel"
      - "{{BaseURL}}/api/panel"
      - "{{BaseURL}}/api/trpc-panel"
      - "{{BaseURL}}/docs"
      - "{{BaseURL}}/doc"
      - "{{BaseURL}}/api/docs"
      - "{{BaseURL}}/api/doc"
      - "{{BaseURL}}/api/trpc/panel"
  
    headers:
      Accept: text/html
    stop-at-first-match: true

    matchers-condition: and
    matchers:
      - type: word
        part: body
        words:
          - "tRPC.panel()"

      - type: status
        status:
          - 200

Сторонние источники

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

https://www.postman.com/explore
https://apis.guru/
https://github.com/public-apis/public-apis
https://rapidapi.com/search/

Анализ атаки

Нам необходимо рассмотреть два общих сценария:

  1. Документация доступна

  2. Документация недоступна

Документация цели доступна

В этом случае, имея доступ к документации, мы можем просмотреть все процедуры и структуру запросов и ответов. На данном этапе достаточно изучить различные уязвимости с помощью документации. Первый вопрос, который мы должны задать: Как работает процесс аутентификации в API и какая конечная точка используется? Чтобы получить доступ к различным конечным точкам, мы должны быть авторизованы в системе. В этом случае нам необходимо определить процедуру, которая позволяет нам войти в систему:

POST /AuthProcedure
POST /authUser 

При изучении документации необходимо исследовать различные компоненты, каждый из которых может выявить различные уязвимости:

Затем мы должны рассмотреть следующий вопрос: Какие уязвимости существуют в различных процедурах? На этом этапе, в зависимости от того, является ли операция Query или Mutation, мы пытаемся определить различные уязвимости. Для Query и Mutation мы должны изучить следующие классы уязвимостей:

Документация по цели недоступна

Во втором сценарии, при отсутствии доступа к документации, нам необходимо составить карту целевого API. Для этой задачи можно использовать такие инструменты, как Logger++. Как правило, мы просматриваем приложение и отфильтровываем протоколируемые запросы tRPC с помощью Logger++. Затем мы можем экспортировать конечные точки и приступить к поиску различных уязвимостей.

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

Fuzz(METHOD) /FUZZ(ProcedureName)

POST /addUsers

Possible Endpoints: 
METHOD /getUsers
METHOD /deleteUsers
METHOD /updateUsers

Более того, для практического опыта я создал уязвимое приложение tRPC, доступ к которому можно получить через мой GitHub.

Надеюсь, вам понравилось читать эту статью.

Комментарии (5)


  1. 19Zb84
    16.01.2024 22:18

    tRPC расшифровывается как "TypeScript Remote Procedure Call" и использует возможности TypeScript для обеспечения безопасности типов на границах клиент-сервер. 


    А trpc - client можно сгенерировать на основе openAPI спеки ?

    Я использую в проекте swagger client который генерирует апи на основе спеки и он так же обеспечивает безопасность типов на границах клиент-сервер. 

    У нас openAPI спека является обязательной для бэкенда.
    На бэкенде пока node.js не используется, но даже если будет, что мешает его написать с использованием typeScript ? При этом оставив на клиенте swagger client на основе openAPI спеки, которая в любом случае будет составляться.

    Мне хочется понять, в чем плюсы использования tRPC. Например я захочу его использовать. В чём я могу выиграть ?

    Мне нравится swagger client потому что

    1. Он экономит очень время. За счет автоматической генерации слиента.
    2. Есть валидация запросов. Обеспечение безопасности типов на границах клиент-сервер. 
    3. Есть версионирование api на клиенте относительно спеки, которая используется на бэкенде в данный момент. Не возникает проблем при разработке в расхождении в запросах.
    4. нет привязки к TypeScript. Если надо он используется, если нет то не используется. Например, на бэкенде может потребоваться AssemblyScript, тогда может быть будет актуально написать остальную часть сервера на js.


    1. dndred
      16.01.2024 22:18

      Мне хочется понять, в чем плюсы использования tRPC. Например я захочу его использовать.

      tRPC предназначен и очень удобен в монолитных фулл стак приложениях, например на основе Next.JS. Он позволяет быстро и без лишних действий писать API с проверкой типов с помошью TypeScript. Райнтайм валидацию так же можно использовать, но это не обязательно.

      А trpc - client можно сгенерировать на основе openAPI спеки ?

      trpc не генерирует никакого клиента. Вернее клиент есть и очень удобный, но весь он существует только в рамках TypeScript.

      Простой резолвер выглядит так:

      // server side
      const test = publicProcedure.query( async () => {
        return { string: 'test', number: 123 }
      })
      
      export const cart = router({
        test,
        //...другие роуты
      })

      После этого на клиенте можно сразу писать

      const { data } = trpc.cart.test.useQuery()
      // TypeScript видит тип возвращаемого значения!
      console.log(data?.number, data?.string) 

      Если же на клиенте вызвать отсутствующий роут

      const { data } = trpc.cart.test_404_NOT_FOUND.useQuery()

      То, браузер послушно отправит запрос на /api/trpc/cart.test_RANDOM_STRING. Но TypeScript поймает это в билд тайм и в прод ошибка не уйдёт.

      В чём я могу выиграть?

      Если у вас фулл стак монолит, то получается все преимущества Swagger со сгенерированным клиентом, только намного удобнее.


      1. 19Zb84
        16.01.2024 22:18

        Если у вас фулл стак монолит

        удобен в монолитных фулл стак приложениях

        То же самое что вы описали делается с тем же свагер клиентом, который я описал.
        Причем здесь монолит ?

        Next.JS

        Я правильно понимаю. Эта библиотека именно под next.js тогда в основном и ориентированна ?


        1. dndred
          16.01.2024 22:18

          Swagger (OpenAPI) это стандарт описания схемы API который позволяет соединять разные системы. В том числе генерировать клиент.

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

          Эта библиотека именно под next.js

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


          1. 19Zb84
            16.01.2024 22:18