• XSS.stack #1 – первый литературный журнал от юзеров форума

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

AGN

(L3) cache
Забанен
Регистрация
14.10.2024
Сообщения
160
Реакции
92
Пожалуйста, обратите внимание, что пользователь заблокирован
Всех приветствую, это моя вторая статья и я заранее извиняюсь, если что-то будет непонятно или повествование моих мыслей окажется неправильным. В этой статье мы рассмотрим, как создать защищенную и анонимную систему чатов, используя 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: Компонент для ввода текста сообщения, который использует пропсы для обработки ввода и отправки сообщения.
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;

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

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, хотя в коде это пока не реализовано в полной мере.

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.pro
 


Напишите ответ...
  • Вставить:
Прикрепить файлы
Верх