[JS] Общаемся с таргетами на наших условиях или как самому написать панель для общения (чат) на React

D2

Администратор
Регистрация
19 Фев 2025
Сообщения
4,380
Реакции
0
Всех приветствую, это моя вторая статья и я заранее извиняюсь, если что-то будет непонятно или повествование моих мыслей окажется неправильным. В этой статье мы рассмотрим, как создать защищенную и анонимную систему чатов, используя Tor для обеспечения конфиденциальности пользователей и шифрование сообщений + сделаем удобную админ-панель на React для управления чатами и отображением сообщений в реальном времени.



Начало. Установка Node.js и NPM (Node Package Manager)

1. Скачиваем установщик с официального сайта Node.js ( https://nodejs.org/en )
2. Устанавливаем все строго по инструкциям из установщика.
3. После установки открываем командную строку и выполняем следующую команду: node -v
Если установка прошла успешно, то эта команда покажет версию установленного Node.js. Еще необходимо проверить наличие NPM командой npm -v
Нажмите, чтобы раскрыть...



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

1. Создадим React проект с помощью create-react-app:
Код: Скопировать в буфер обмена
npx create-react-app tor-chat
и перейдем в наш созданный проект
Код: Скопировать в буфер обмена
cd tor-chat
2. Установим библиотеки для работы с WebSocket и шифрованием:
Код: Скопировать в буфер обмена
npm install crypto-js websocket
3. Создаем структуру нашего проекта:
/src
├── /components
│ ├── Chat.js
│ ├── AdminPanel.js
│ └── MessageInput.js
├── /styles
│ └── App.css
├── App.js
└── index.js
Нажмите, чтобы раскрыть...



Реализация наших /components из проекта

1. Компонент Chat.js

Импортируем нужные нам библиотеки:

JavaScript: Скопировать в буфер обмена
Код:
import React, { useState, useEffect } from 'react';
import { WebSocket } from 'ws';
import CryptoJS from 'crypto-js';
import MessageInput from './MessageInput';
  • useState, useEffect: хуки React, используемые для управления состоянием и побочными эффектами.
  • WebSocket: Объект для создания WebSocket-соединения.
  • CryptoJS: Библиотека для работы с криптографией, в частности для шифрования и дешифрования сообщений.
  • MessageInput: Компонент для ввода сообщений.
Состояние компонента:
JavaScript: Скопировать в буфер обмена
Код:
const [messages, setMessages] = useState([]);
const [socket, setSocket] = useState(null);
const [messageInput, setMessageInput] = useState('');
  • messages: Массив, который хранит все сообщения чата.
  • socket: Состояние для хранения объекта WebSocket.
  • messageInput: Строка, которая хранит текущий ввод сообщения.
Шифрование и дешифрование сообщений:
JavaScript: Скопировать в буфер обмена
Код:
const secretKey = 'SK';

const encryptMessage = (message) => {
  return CryptoJS.AES.encrypt(message, secretKey).toString();
};

const decryptMessage = (encryptedMessage) => {
  const bytes = CryptoJS.AES.decrypt(encryptedMessage, secretKey);
  return bytes.toString(CryptoJS.enc.Utf8);
};
  • Для шифрования и дешифрования здесь используется библиотека CryptoJS.
  • Для шифрования используется AES.encrypt, для дешифрования AES.encrypt соответственно.
  • Параметр secretKey - это секретный ключ, который используется для шифрования и дешифрования сообщений.
WebSocket-соеденение
JavaScript: Скопировать в буфер обмена
Код:
useEffect(() => {
  const ws = new WebSocket('wss://example.onion');
  setSocket(ws);

  ws.onopen = () => {
    console.log('Connected to WebSocket server');
    ws.send(
      JSON.stringify({
        type: 'joinChat',
        chatId,
      })
    );
  };

  ws.onmessage = (event) => {
    const data = JSON.parse(event.data);

    if (data.type === 'message') {
      setMessages((prevMessages) => [
        ...prevMessages,
        { text: decryptMessage(data.message), sender: 'Admin' },
      ]);
    }

    if (data.type === 'file') {
      setMessages((prevMessages) => [
        ...prevMessages,
        {
          text: `${data.fileName} (${data.fileType})`,
          sender: 'Admin',
          isFile: true,
          fileData: data.fileData,
        },
      ]);
    }
  };

  return () => {
    ws.close();
  };
}, [chatId]);
  • В useEffect устанавливается WebSocket соединение с сервером по адресу wss://sample.onion. В моментах, когда соединение открыто скрипт отправляет запрос на подключение к чату с указанным chatId. После этого каждое полученное сообщение будет расшифровываться и добавляться в список сообщений.
  • ws.onopen: Когда соединение с WebSocket открыто, отправляется сообщение на сервер с запросом о присоединении к чату.
  • ws.onmessage: Когда сервер отправляет сообщение, оно расшифровывается и добавляется в список сообщений.
Отправка сообщений:
JavaScript: Скопировать в буфер обмена
Код:
const handleSendMessage = () => {
  if (messageInput.trim()) {
    const encryptedMessage = encryptMessage(messageInput);
    socket.send(
      JSON.stringify({
        type: 'message',
        chatId,
        message: encryptedMessage,
      })
    );
    setMessages((prevMessages) => [
      ...prevMessages,
      { text: messageInput, sender: 'You' },
    ]);
    setMessageInput('');
  }
};
  • Когда пользователь вводит сообщение и нажимает отправить, это сообщение шифруется с использованием encryptMessage и отправляется на сервер через WebSocket.
Отображение сообщений:
JavaScript: Скопировать в буфер обмена
Код:
return (
  <div className="chat-container">
    <div className="message-list">
      {messages.map((message, index) => (
        <div
          key={index}
          className={`message ${message.sender === 'You' ? 'you' : ''}`}
        >
          <p>{message.text}</p>
          {message.isFile && (
            <a href={message.fileData} download>
              Download {message.fileName}
            </a>
          )}
        </div>
      ))}
    </div>
    <MessageInput
      value={messageInput}
      onChange={(e) => setMessageInput(e.target.value)}
      onSend={handleSendMessage}
    />
  </div>
);
  • Все сообщения отображаются в списке, и если сообщение содержит файл (передается через WebSocket), оно будет отображено с ссылкой на файл для скачивания.
  • messages.map: Перебирает все сообщения и отображает их. Если сообщение содержит файл, добавляется ссылка для его скачивания.
  • MessageInput: Компонент для ввода текста сообщения, который использует пропсы для обработки ввода и отправки сообщения.
Спойлер: Полный код компонента Chat.js
JavaScript: Скопировать в буфер обмена
Код:
// src/components/Chat.js

import React, { useState, useEffect } from 'react';
import { WebSocket } from 'ws';
import CryptoJS from 'crypto-js';
import MessageInput from './MessageInput';

const Chat = ({ chatId }) => {
  const [messages, setMessages] = useState([]);
  const [socket, setSocket] = useState(null);
  const [messageInput, setMessageInput] = useState('');

  const secretKey = 'SK';

  const encryptMessage = (message) => {
    return CryptoJS.AES.encrypt(message, secretKey).toString();
  };

  const decryptMessage = (encryptedMessage) => {
    const bytes = CryptoJS.AES.decrypt(encryptedMessage, secretKey);
    return bytes.toString(CryptoJS.enc.Utf8);
  };

  useEffect(() => {
    const ws = new WebSocket('wss://example.onion');
    setSocket(ws);

    ws.onopen = () => {
      console.log('Connected to WebSocket server');
      ws.send(
        JSON.stringify({
          type: 'joinChat',
          chatId,
        })
      );
    };

    ws.onmessage = (event) => {
      const data = JSON.parse(event.data);

      if (data.type === 'message') {
        setMessages((prevMessages) => [
          ...prevMessages,
          { text: decryptMessage(data.message), sender: 'Admin' },
        ]);
      }

      if (data.type === 'file') {
        setMessages((prevMessages) => [
          ...prevMessages,
          {
            text: `${data.fileName} (${data.fileType})`,
            sender: 'Admin',
            isFile: true,
            fileData: data.fileData,
          },
        ]);
      }
    };

    return () => {
      ws.close();
    };
  }, [chatId]);

  const handleSendMessage = () => {
    if (messageInput.trim()) {
      const encryptedMessage = encryptMessage(messageInput);
      socket.send(
        JSON.stringify({
          type: 'message',
          chatId,
          message: encryptedMessage,
        })
      );
      setMessages((prevMessages) => [
        ...prevMessages,
        { text: messageInput, sender: 'You' },
      ]);
      setMessageInput('');
    }
  };

  return (
    <div className="chat-container">
      <div className="message-list">
        {messages.map((message, index) => (
          <div
            key={index}
            className={`message ${message.sender === 'You' ? 'you' : ''}`}
          >
            <p>{message.text}</p>
            {message.isFile && (
              <a href={message.fileData} download>
                Download {message.fileName}
              </a>
            )}
          </div>
        ))}
      </div>
      <MessageInput
        value={messageInput}
        onChange={(e) => setMessageInput(e.target.value)}
        onSend={handleSendMessage}
      />
    </div>
  );
};

export default Chat;

2. Компонент MessageInput.js

Импортируем React:
import React from 'react'; далее:
JavaScript: Скопировать в буфер обмена
Код:
const MessageInput = ({ value, onChange, onSend }) => {
  return (
    <div className="message-input">
      <input
        type="text"
        value={value}
        onChange={onChange}
        placeholder="Type your message"
      />
      <button onClick={onSend}>Send</button>
    </div>
  );
};
  • value: Текущее значение поля ввода (в нем хранится текст сообщения).
  • onChange: Функция для обработки изменений в поле ввода.
  • onSend: Функция для отправки сообщения.
Экспорт компонента: export default MessageInput;

Спойлер: Полный код компонента MessageInput
JavaScript: Скопировать в буфер обмена
Код:
// src/components/MessageInput.js

import React from 'react';

const MessageInput = ({ value, onChange, onSend }) => {
  return (
    <div className="message-input">
      <input
        type="text"
        value={value}
        onChange={onChange}
        placeholder="Type your message"
      />
      <button onClick={onSend}>Send</button>
    </div>
  );
};

export default MessageInput;

3. Компонент AdminPanel.js

Компонент AdminPanel предназначен для отображения списка активных чатов и управления ими. Он использует WebSocket для получения данных о чатов и их отображения в интерфейсе.

Импорт React и необходимых зависимостей:
JavaScript: Скопировать в буфер обмена
Код:
import React, { useState, useEffect } from 'react';
import { WebSocket } from 'ws';
  • Как и в других компонентах, в начале импортируются React, хук useState для управления состоянием, и хук useEffect для выполнения побочных эффектов.
  • Импортируется WebSocket для создания подключения к серверу WebSocket.
Укажем состояние компонента:
JavaScript: Скопировать в буфер обмена
Код:
const [chats, setChats] = useState([]);
const [socket, setSocket] = useState(null);
  • chats: Это состояние, которое хранит список активных чатов. Его начальное значение — пустой массив.
  • socket: Это состояние для хранения объекта WebSocket.
Добавим WebSocket-соединение:
JavaScript: Скопировать в буфер обмена
Код:
useEffect(() => {
  const ws = new WebSocket('wss://example.onion');
  setSocket(ws);

  ws.onopen = () => {
    console.log('Connected to WebSocket server');
  };

  ws.onmessage = (event) => {
    const data = JSON.parse(event.data);
    if (data.type === 'chatList') {
      setChats(data.chatIds);
    }
  };

  return () => {
    ws.close();
  };
}, []);
  • В useEffect создается WebSocket-соединение с сервером. После успешного открытия соединения в onopen выводится сообщение в консоль.
  • В onmessage обрабатываются данные, полученные от сервера. Если это список чатов (chatList), то обновляется состояние chats.
  • ws.onopen: Когда соединение с сервером открыто, выводится сообщение в консоль.
  • ws.onmessage: Когда сервер отправляет данные, они обрабатываются. Если данные содержат список чатов, обновляется состояние chats.
Сделаем обработку входа в чат:
JavaScript: Скопировать в буфер обмена
Код:
const handleJoinChat = (chatId) => {
  socket.send(
    JSON.stringify({
      type: 'joinChat',
      chatId,
    })
  );
};

Сделаем отображение списка чатов:
JavaScript: Скопировать в буфер обмена
Код:
return (
  <div className="admin-panel">
    <h2>Active Chats</h2>
    <ul>
      {chats.map((chatId) => (
        <li key={chatId} onClick={() => handleJoinChat(chatId)}>
          Chat {chatId}
        </li>
      ))}
    </ul>
  </div>
);
  • chats.map: Перебирает список чатов и для каждого чата создает элемент списка (<li>). Когда элемент списка кликается, вызывается функция handleJoinChat с соответствующим chatId.
Закроем WebSocket соединение:
JavaScript: Скопировать в буфер обмена
Код:
return () => {
  ws.close();
};

Спойлер: Пример админ-панели
yDJ4Cmk.png


Спойлер: Готовый код компонента AdminPanel.js
JavaScript: Скопировать в буфер обмена
Код:
// src/components/AdminPanel.js

import React, { useState, useEffect } from 'react';
import { WebSocket } from 'ws';

const AdminPanel = () => {
  const [chats, setChats] = useState([]);
  const [socket, setSocket] = useState(null);

  useEffect(() => {
    const ws = new WebSocket('wss://example.onion');
    setSocket(ws);

    ws.onopen = () => {
      console.log('Connected to WebSocket server');
    };

    ws.onmessage = (event) => {
      const data = JSON.parse(event.data);
      if (data.type === 'chatList') {
        setChats(data.chatIds);
      }
    };

    return () => {
      ws.close();
    };
  }, []);

  const handleJoinChat = (chatId) => {
    socket.send(
      JSON.stringify({
        type: 'joinChat',
        chatId,
      })
    );
  };

  return (
    <div className="admin-panel">
      <h2>Active Chats</h2>
      <ul>
        {chats.map((chatId) => (
          <li key={chatId} onClick={() => handleJoinChat(chatId)}>
            Chat {chatId}
          </li>
        ))}
      </ul>
    </div>
  );
};

export default AdminPanel;



Добавим красивые стили для нашего проекта (src/styles/App.css) :
CSS: Скопировать в буфер обмена
Код:
/* src/styles/App.css */

body {
  font-family: Arial, sans-serif;
  background-color: #f4f4f9;
  color: #333;
}

.app {
  width: 100%;
  max-width: 800px;
  margin: 0 auto;
  padding: 20px;
  text-align: center;
}

h1 {
  color: #5c6bc0;
}

.chat-container {
  background-color: #fff;
  border-radius: 8px;
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
  padding: 20px;
  margin-top: 20px;
  min-height: 300px;
}

.message-list {
  max-height: 400px;
  overflow-y: auto;
  margin-bottom: 20px;
}

.message {
  padding: 10px;
  background-color: #e3e3e3;
  border-radius: 5px;
  margin: 5px 0;
}

.message.you {
  background-color: #b2ff59;
  align-self: flex-end;
}

.message-input {
  display: flex;
  justify-content: space-between;
}

.message-input input {
  padding: 10px;
  width: 80%;
  border: 1px solid #ccc;
  border-radius: 4px;
}

.message-input button {
  padding: 10px;
  background-color: #5c6bc0;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}

.message-input button:hover {
  background-color: #3f4b8f;
}

.admin-panel {
  text-align: left;
  padding: 20px;
  background-color: #fff;
  border-radius: 8px;
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}

.admin-panel ul {
  list-style: none;
  padding: 0;
}

.admin-panel li {
  padding: 10px;
  background-color: #5c6bc0;
  color: white;
  border-radius: 4px;
  margin-bottom: 5px;
  cursor: pointer;
}

.admin-panel li:hover {
  background-color: #3f4b8f;
}

body {
  font-family: Arial, sans-serif;
  margin: 0;
  padding: 0;
  background-color: #f4f4f9;
}

#root {
  display: flex;
  justify-content: center;
  align-items: center;
  min-height: 100vh;
  background-color: #fafafa;
}
}



Теперь напишем код для App.js
В начале импорты стандартных зависимостей: React, хук useState, useEffect для состояния и побочных эффектов, а также другие зависимости - WebSocket для соединения и CryptoJS для шифрования сообщений.
Импортируются компоненты Chat и AdminPanel, которые будут использоваться в зависимости от роли пользователя.
JavaScript: Скопировать в буфер обмена
Код:
import React, { useState, useEffect } from 'react';
import './App.css';
import { WebSocket } from 'ws';
import CryptoJS from 'crypto-js';
import Chat from './components/Chat';
import AdminPanel from './components/AdminPanel';

Добавим состояния компонента:
JavaScript: Скопировать в буфер обмена
Код:
const [isAdmin, setIsAdmin] = useState(false);
const [chatId, setChatId] = useState(null);
const [socket, setSocket] = useState(null);
  • isAdmin: Указывает, является ли пользователь администратором (по умолчанию — нет).
  • chatId: Хранит идентификатор чата, который генерируется при старте чата.
  • socket: Хранит объект WebSocket, который используется для связи с сервером.
Сделаем функцию генерации chatId:
JavaScript: Скопировать в буфер обмена
Код:
const generateChatId = () => {
  const id = Math.random().toString(36).substring(7);
  setChatId(id);
  return id;
};
Функция generateChatId генерирует уникальный идентификатор чата с использованием метода Math.random() и устанавливает его в состояние chatId.

Добавим WebSocket соединение:
JavaScript: Скопировать в буфер обмена
Код:
useEffect(() => {
  if (chatId) {
    const ws = new WebSocket(`wss://example.onion/chat/${chatId}`);
    setSocket(ws);

    ws.onopen = () => {
      console.log('Connected to WebSocket server');
    };

    ws.onmessage = (event) => {
      const data = JSON.parse(event.data);
      if (data.type === 'message') {
        console.log('Received message: ', data.message);
      }
    };

    return () => {
      ws.close();
    };
  }
}, [chatId]);
  • В useEffect создается WebSocket-соединение при наличии chatId. Соединение устанавливается по URL, включающему этот chatId.
  • После открытия соединения выводится сообщение в консоль.
  • Когда приходит сообщение от сервера, оно обрабатывается в onmessage, где можно, например, вывести его в консоль (в реальном приложении можно обновить состояние чата с новым сообщением).
  • Важно, что соединение закрывается при размонтировании компонента, что предотвращает утечки памяти.
Добавим отображение интерфейса:
JavaScript: Скопировать в буфер обмена
Код:
return (
  <div className="app">
    <h1>Secure Tor Chat</h1>

    {isAdmin ? (
      <AdminPanel />
    ) : chatId ? (
      <Chat chatId={chatId} socket={socket} secretKey={secretKey} />
    ) : (
      <div>
        <button onClick={() => setIsAdmin(true)}>Admin Login</button>
        <button onClick={generateChatId}>Start Chat</button>
      </div>
    )}
  </div>
);
  • Если isAdmin равно true, то отображается компонент AdminPanel, который предоставляет администратору доступ к управлению чатами.
  • Если есть chatId, отображается компонент Chat, передавая ему chatId, socket и secretKey для работы с шифрованием.
  • Если ни одно из этих условий не выполнено (например, еще не создан чат), показываются кнопки для входа как администратор и создания нового чата.
Сделаем шифрование сообщений:
JavaScript: Скопировать в буфер обмена
const secretKey = 'Sk';
В компоненте используется секретный ключ secretKey, который может быть использован для шифрования сообщений в чате. Это делается с помощью библиотеки CryptoJS, хотя в коде это пока не реализовано в полной мере.

Спойлер: Готовый код компонента App.js
JavaScript: Скопировать в буфер обмена
Код:
// src/App.js

import React, { useState, useEffect } from 'react';
import './App.css';
import { WebSocket } from 'ws';
import CryptoJS from 'crypto-js';
import Chat from './components/Chat';
import AdminPanel from './components/AdminPanel';

const App = () => {
  const [isAdmin, setIsAdmin] = useState(false);
  const [chatId, setChatId] = useState(null);
  const [socket, setSocket] = useState(null);

  const secretKey = 'Sk';

  const generateChatId = () => {
    const id = Math.random().toString(36).substring(7);
    setChatId(id);
    return id;
  };

  useEffect(() => {
    if (chatId) {
      const ws = new WebSocket(`wss://example.onion/chat/${chatId}`); // Подключение через Tor
      setSocket(ws);

      ws.onopen = () => {
        console.log('Connected to WebSocket server');
      };

      ws.onmessage = (event) => {
        const data = JSON.parse(event.data);
        if (data.type === 'message') {
          console.log('Received message: ', data.message);
        }
      };

      return () => {
        ws.close();
      };
    }
  }, [chatId]);

  return (
    <div className="app">
      <h1>Secure Tor Chat</h1>

      {isAdmin ? (
        <AdminPanel />
      ) : chatId ? (
        <Chat chatId={chatId} socket={socket} secretKey={secretKey} />
      ) : (
        <div>
          <button onClick={() => setIsAdmin(true)}>Admin Login</button>
          <button onClick={generateChatId}>Start Chat</button>
        </div>
      )}
    </div>
  );
};

export default App;

И напишем index.js для рендеринга нашего React-приложения.
JavaScript: Скопировать в буфер обмена
Код:
// src/index.js

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';

ReactDOM.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
  document.getElementById('root')
);

reportWebVitals();




Установка Tor:

1. Добавим репозиторий Tor:
Код: Скопировать в буфер обмена
Код:
sudo add-apt-repository ppa:torproject/tor-browser
sudo apt update

2. Установим Tor:
sudo apt install tor

3. Запустим Tor:
sudo service tor start



Проверим, работает ли наш проект? Введем команду npm start в консоль, и если все работает, то ваш сайт будет доступен по адресу: wss://example.onion.




В заключении я хотел бы хотел подметить плюсы использования такой панели:
  • Безопасность: использование этой панели затрудняет ваш поиск различными OSINT организациями + позволяет оставаться анонимным (не разглашать свои контакты и т.д.)
  • Экономия времени: таргету не придется устанавливать XMPP-клиент, регистрировать аккаунт, включать OTR шифрование и т.д.
  • Удобство использования: таргету будет гораздо удобнее перейти по ссылке и вести диалог.
Мы подробно рассмотрели:
  • как самостоятельно создать безопасную панель для общения между "админ - таргет" ;
  • как выполнить деплой проекта в инфраструктуре Tor ;
Теперь у вас есть работающее решение для анонимных и безопасных онлайн-чатов, которое можно развернуть и использовать в любых условиях, требующих конфиденциальности.
С этим проектом вы можете быть уверены, что ваш чат будет защищен от внешнего вмешательства и легко доступен пользователям по всему миру через сеть Tor. Благодарю за прочтение статьи, с радостью почитаю ваши комментарии, а особенно профессиональных кодеров на JS.

Автор: AGN
Специально для xss.is
 
Сверху Снизу