Учимся создавать агентов для пентеста с использованием агента на React от LangGraph.

Моя цель — создать AI-агентов, которые помогут автоматизировать часть задач, выполняемых в рамках пентеста.

Для стартового проекта я решил создать агента, который умеет анализировать JavaScript файлы, находить скрытые API эндпоинты и проверять их на потенциальные уязвимости.

Выбор фреймворка

В качестве фреймворка я выбрал LangGraph, потому что я уже завершил LangGraph Academy и хорошо понял его. Он популярен, хорошо документирован и есть множество примеров его использования, поэтому начать лучше всего именно с него.

До этого мой опыт ограничивался простыми LLM-воркфлоу и связыванием нескольких вызовов. Я все еще не понимал как спроектировать AI-агента, который мог бы «думать» самостоятельно и использовать правильные инструменты.

Все изменилось, когда я наткнулся на статью Аншумана Бхартии, которая познакомила меня с React агентом в LangGraph. ReAct расшифровывается как Reasoning + Acting ( оригинальная статья ). Этот подход позволяет агенту использовать инструменты, анализировать результаты и циклически проходить шаги до достижения цели. Метод показался мне достаточно простым для реализации, и я решил построить на его основе свой проект.

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

Настройка уязвимого приложения

Чтобы протестировать своего агента, я создал уязвимое веб-приложение с помощью Python Flask. Внешне приложение выглядит простым, но его исходный код содержит JavaScript файл с несколькими скрытыми API эндпоинтами.

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

from flask import Flask, request, jsonify
 
 app = Flask(__name__)
 
 # Vulnerable JavaScript file that will be served
 VULNERABLE_JS = """
 // API Configuration
 const API_CONFIG = {
     userInfo: '/api/v1/user-info',  // Leaks sensitive data without auth
     adminPanel: '/api/v1/admin',    // Requires specific admin key
     userProfile: '/api/v1/profile', // Requires X-User-Id header
 };
 
 // Admin key hardcoded (security vulnerability)
 const ADMIN_KEY = 'super_secret_admin_key_123';
 
 // Function to fetch user info (no auth required - vulnerability)
 async function fetchUserInfo() {
     const response = await fetch('/api/v1/user-info');
     return response.json();
 }
 
 // Function to access admin panel
 async function accessAdminPanel() {
     const headers = {
         'Content-Type': 'application/json',
         'X-Admin-Key': ADMIN_KEY  // Hardcoded admin key usage
     };
     
     const response = await fetch('/api/v1/admin', {
         headers: headers
     });
     return response.json();
 }
 
 // Function to get user profile
 async function getUserProfile(userId) {
     const headers = {
         'X-User-Id': userId  // Required custom header
     };
     
     const response = await fetch('/api/v1/profile', {
         headers: headers
     });
     return response.json();
 }
 
 """
 
 @app.route('/main.js')
 def serve_js():
     return VULNERABLE_JS, 200, {'Content-Type': 'application/javascript'}
 
 @app.route('/api/v1/user-info')
 def user_info():
     # Vulnerable: Returns sensitive information without authentication
     return jsonify({
         "users": [
             {"id": "1", "name": "John Doe", "ssn": "123-45-6789", "salary": 75000},
             {"id": "2", "name": "Jane Smith", "ssn": "987-65-4321", "salary": 82000}
         ],
         "database_connection": "mongodb://admin:password@localhost:27017",
         "api_keys": {
             "stripe": "sk_test_123456789",
             "aws": "AKIA1234567890EXAMPLE"
         }
     })
 
 @app.route('/api/v1/profile')
 def user_profile():
     # Requires X-User-Id header
     user_id = request.headers.get('X-User-Id')
     if not user_id:
         return jsonify({"error": "X-User-Id header is required"}), 401
     
     return jsonify({
         "id": user_id,
         "name": f"User {user_id}",
         "email": f"@example.com">user{user_id}@example.com",
         "role": "user"
     })
 
 @app.route('/api/v1/admin')
 def admin_panel():
     # Requires specific admin key value
     admin_key = request.headers.get('X-Admin-Key')
     if not admin_key:
         return jsonify({"error": "X-Admin-Key header is required"}), 401
     if admin_key != 'super_secret_admin_key_123':  # Hardcoded key check
         return jsonify({"error": "Invalid admin key"}), 403
     return jsonify({
         "sensitive_data": "This is sensitive admin data",
         "internal_keys": {
             "database": "root:password123",
             "api_gateway": "private_key_xyz"
         }
     })
 
 if name == '__main__':
     app.run(host='0.0.0.0', port=5000, debug=True)

Архитектура агента

Я начал с формулировки ключевых задач при создании своего агента:

1.   Получить JavaScript файл

2.   Найти в нём захардкоденные API эндпоинты

3.   Выполнить запросы к обнаруженным эндпоинтам

4.   Выявить наличие конфиденциальных данных в ответах

Для proof of concept я выбрал простой подход — передача JavaScript файла напрямую агенту с применением двух кастомных инструментов:

  • Endpoint Finder — regex-функция для поиска API путей в JavaScript коде

  • Sensitive Data Detector — инструмент, который делает запрос к эндпоинту и использует LLM для определения и классификации конфиденциальных данных в ответе

Подробнее об инструментах

Endpoint Finder
Простой regex-сканер для извлечения путей вида /api/... из JavaScript файлов. Хотя решение базовое, оно хорошо справляется с несжатым кодом. В перспективе я хочу проверить, сможет ли LLM «реверсить» сжатый JavaScript подобно человеку.

def find_endpoints_tool(url: str) -> list[str]:
     """
     Find all API endpoints in the JavaScript code.
 
     Args:
         url (str): The URL of the JavaScript file to analyze.
     """
     log_progress(f"Fetching JavaScript file from {url}")
     resp = requests.get(url)
     if resp.status_code == 200:
         js = resp.text
         patterns = [
             r'["\']/(api/[^"\']*)["\']',
             r':\s*["\']/(api/[^"\']*)["\']',
             r'fetch\(["\']/(api/[^"\']*)["\']',
         ]
 
         endpoints = []
         for pattern in patterns:
             matches = re.findall(pattern, js)
             endpoints.extend(matches)
             
         unique_endpoints = list(set(endpoints))
         log_progress(f"Discovered endpoints: {unique_endpoints}")
         return unique_endpoints
     else:
         raise Exception(f"Failed to fetch JavaScript file: {resp.status_code}")

Sensitive Data Detector
Этот инструмент выполняет HTTP запрос, а затем задействует GPT-4 для обнаружения конфиденциальной информации (SSN, API keys, email адреса и т.д.). Результат возвращается в JSON формате для удобства обработки.

Для этой модели я установил параметр temperature=0, что делает её выводы более определенными и предсказуемыми — хотя в будущем этот параметр потребует дополнительной настройки.

def sensitive_data_detection_tool(url: str):
     """
     Analyze the HTTP response for sensitive data.
 
     Args:
         url (str): The URL of the HTTP response to analyze.
     """
     log_progress(f"Fetching and analyzing data from {url}")
 
     resp = requests.get(url)
 
     if resp.status_code == 200:
         model = ChatOpenAI(model="gpt-4", temperature=0)
         prompt = f"""
 You are a security researcher analyzing a web application for sensitive data vulnerabilities.
 Your task is to identify any sensitive data in the HTTP response. Sensitive data includes, but not limited to:
 - names
 - email addresses
 - phone numbers
 - SSN
 - credit card numbers
 - API keys
 
 ALWAYS return your response as a JSON object with the following structure (do NOT use markdown formatting):
 ``
 {{
   "data": "<sensitive data>",
   "type": "<data type>"
 }}
 `
 Example response:
 `
 {{
   "data": "111-123-3201",
   "type": "phone"
 }}
 `
 
 HTTP Response:
 `
 {resp.text}
 ``
 """
         return model.invoke(prompt)

Интеграция в LangGraph

Финальным шагом стала сборка агента с помощью функции create_react_agent:

1.   Инициализация GPT-4 с детерминированными настройками (temperature=0)

2.   Настройка system prompt с пошаговой инструкцией для агента

3.   Регистрация кастомных инструментов

4.   Передача пользовательского сообщения с задачей проанализировать JavaScript файл

def main():
     sys_msg = SystemMessage(content="""
 You are a security researcher analyzing a web application for potential vulnerabilities. 
 Your task is to identify new API endpoints to be analyzed for potential security weaknesses.
                             
 Follow these steps PRECISELY:
 
 1. Use the find_endpoints tool to hidden discovery API endpoints from JS files. This will return a list of API endpoints
 2. For each discovered API endpoint use the sensitive_data_detection tool to search for sensitive data.
 3. Print out ONLY the sensitive data and the type of data found. Include which endpoint it was found at.
 """)
     model = ChatOpenAI(model="gpt-4", temperature=0)
     model_sysmsg = model.bind(system_message=sys_msg)
     agent = create_react_agent(
         model_sysmsg,
         tools=[find_endpoints_tool, sensitive_data_detection_tool],
     )
 
     msg = HumanMessage(content="Analyze the JS file at http://localhost:5000/main.js for API endpoints and search them for sensitive data.")
     result = agent.invoke({
         "messages": [msg],
         "config": {"recursion_limit": 20}
     })
 
     # Print out system messages from the agent's output
     for message in result.get("messages", []):
         print("=" * 50)
         print(message.content)

Запуск и тестирование

Запуск тестового сервера выглядит следующим образом:

Запуск vuln_api.py уязвимого API сервера
Запуск vuln_api.py уязвимого API сервера

После его старта я запустил свой агент:

Запуск agent.py и вывод отладочной информации
Запуск agent.py и вывод отладочной информации

В процессе работы агент:

  • Обнаружил три эндпоинта (/api/v1/user-info, /api/v1/profile, /api/v1/admin)

  • Корректно идентифицировал конфиденциальные данные (API keys, SSN, имена) в эндпоинте /api/v1/user-info

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

Результат работы агента представлены в следующем фрагменте:

The analysis of the JavaScript file at http://localhost:5000/main.js revealed three API endpoints:

1.
http://localhost:5000/api/v1/admin
2. http://localhost:5000/api/v1/user-info
3. http://localhost:5000/api/v1/profile

Upon further analysis for sensitive data, the following was found:

In the
http://localhost:5000/api/v1/user-info endpoint, the following sensitive data was detected:

- API Key: AKIA1234567890EXAMPLE
- API Key: sk_test_123456789
- Name: John Doe
- SSN: 123-45-6789
- Name: Jane Smith
- SSN: 987-65-4321

No sensitive data was detected in the other two endpoints.

Лог сервера с отправкой HTTP запросов от агента выглядит следующим образом:

Лог сервера с результатами работы агента
Лог сервера с результатами работы агента

Итоги и выводы

Что получилось

  • Интуитивный интерфейс: React агент LangGraph оказался простым в использовании — я мог сосредоточиться на логике, а не на технических деталях

  • Рабочий прототип: Агент успешно выполнил базовые задачи по обнаружению эндпоинтов и идентификации конфиденциальных данных

Что требует доработки

·       Неполный анализ: Агент пропустил захардкоженную строку подключения к БД в 
/api/v1/user-info. Вероятно, нужны более точные примеры в промптах или эксперименты с настройками параметров модели.

·       Чувствительность к формулировкам: Изначально агент использовал только инструмент поиска эндпоинтов. Мне пришлось явно указать в промпте «search for sensitive data», несмотря на наличие этой инструкции в system prompt. Это показывает важность грамотного составления промптов

Планы по улучшению

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

  • Изучить продвинутые техники ReAct промптов для повышения точности работы

  • Экспериментировать с разными подходами к prompt engineering, включая создание тестовых окружений для оценки эффективности различных промптов

  • Протестировать альтернативные LLM модели для поиска оптимального решения

Заключение

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

Для всех, кому понравилась статья, исходный код агента для собственных тестов доступен по ссылке.

Хотите следить за новыми материалами и новостями из мира информационной безопасности?

В Telegram‑канале AP Security Вы найдёте большое количество материалов по наступательной безопасности, защите корпоративной сети, компьютерной криминалистике, а также статьи, созданные командой лаборатории кибербезопасности компании AP Security.

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


  1. axelmaker
    12.10.2025 19:38

    Спасибо за статью
    Поправьте форматирование кода, очень тяжело читается


    1. ap_security Автор
      12.10.2025 19:38

      Спасибо за отзыв, рады, что статья оказалась полезной для Вас!
      Текст также отредактировали для более удобного ознакомления.