414 - 请求的 uri 太长 - url 的长度为 70 个字符

414 - The requested uri is too long - the url is 70 characters long

我尝试使用 C# 进行身份验证。 它在尝试登录时发送一个 HTTP GET 请求,看起来像:

https://example.com/clogin.php?name=abc&password=abc

而且这只是几个字符。但是我收到“请求 URI 太长”错误。

模拟请求似乎有效,但在通过 TcpClient 发送时无效。

ClientManager.cs:

using System;
using System.Net;
using System.Net.Sockets;
using System.Threading.Tasks;
using System.IO;
using System.Text;
using System.Diagnostics;
using System.Security.Cryptography.X509Certificates;
using System.Net.Security;


public class ClientManager
{
    private static TcpListener listener;

    public static void Main()
    {

        ServicePointManager.ServerCertificateValidationCallback = RemoteCertificateValidationCallback;

        listener = new TcpListener(IPAddress.Any, 10250);
        listener.Start();
        Console.WriteLine("*** ClientManager started ***");
        Console.WriteLine("Listening to port 10250, make sure not used.");
        StartAccept();

        while (true)
        {
            System.Threading.Thread.Sleep(1000);
            string cmd = Console.ReadLine();

            if (cmd.Equals("exit"))
            {
                Console.WriteLine("*** Stopping cman... ***");
                Environment.Exit(0);
            }

            if (cmd.StartsWith("auth"))
            {

                Console.WriteLine("[" + "simulate" + "] Using auth cred to authenticate.");
                char c = '|';
                string[] args = cmd.Split(c);

                if (!(args.Length > 2))
                {
                    Console.WriteLine("INVALID_DATA_GIVEN");
                }
                string username = args[1];
                string password = args[2];

                Console.WriteLine("[" + "simulate" + "] Using credentials: " + username + " and " + password);

                string response = GetAsync(("https://example.com/clogin.php?name=" + username + "&password=" + password + "&test=1"));
                Console.WriteLine("https://example.com/clogin.php?name=" + username + "&password=" + password);
                Console.WriteLine(response);

                if (response.Equals("ERROR_FAILED_CONNECTION"))
                {
                    Console.WriteLine("ERROR_SERVERERROR");
                }
                else if (response.Equals("INVALID_USERNAME"))
                {
                    Console.WriteLine("INVALID_DATA_GIVEN");
                }
                else if (response.Equals("INVALID_PASSWORD"))
                {
                    Console.WriteLine("INVALID_DATA_GIVEN");
                }
                else if (response.Equals("INVALID_CRED"))
                {
                    Console.WriteLine("ERROR_AUTH_INVALID_CRED");
                }
                else if (response.Equals("IS_BANNED"))
                {
                    Console.WriteLine("ERROR_AUTH_BANNED");
                }
                else
                {
                    Console.WriteLine("[" + "simulate" + "] LOGIN OK | RESPONSE: " + response);
                }
            }
        }

    }
    private static void StartAccept()
    {
        listener.BeginAcceptSocket(HandleAsyncConnection, listener);
    }

    private static void HandleAsyncConnection(IAsyncResult res)
    {
        StartAccept();
        TcpClient client = listener.EndAcceptTcpClient(res);
        string clientSession = "NULL";
        string ip = ((IPEndPoint)client.Client.RemoteEndPoint).Address.ToString();

        Console.WriteLine("[" + ip + "] Incoming connection.");

        while (true)
        {
            try
            {
                System.Threading.Thread.Sleep(250);
                Console.WriteLine("Trying to read data from " + ip);
                NetworkStream stream = null;
                Byte[] data = new Byte[8192];
                String responseData = String.Empty;
                Int32 bytes = 0;

                stream = client.GetStream();
                bytes = stream.Read(data, 0, data.Length);
                Console.WriteLine("Bytes: " + bytes + " Data: " + System.Text.Encoding.ASCII.GetString(data));
                responseData = System.Text.Encoding.ASCII.GetString(data);


                Console.WriteLine("[" + ip + "] " + responseData);

                if (responseData.StartsWith("close"))
                {
                    Console.WriteLine("[" + ip + "] Connection closed.");
                    break;
                }else if (responseData.StartsWith("useauthtoken"))
                {
                    Console.WriteLine("[" + ip + "] Using auth token to authenticate.");
                    char c = '|';
                    string[] args = responseData.Split(c);

                    if (!(args.Length > 1))
                    {
                        SendMessage(stream, "INVALID_DATA_GIVEN");
                    }

                    string response = GetAsync("http://example.com/cauthtokencheck.php?auth=" + args[1]);

                    if (response.Equals("yes"))
                    {
                        clientSession = args[1];
                        SendMessage(stream, "OK");
                    }
                    else
                    {
                        SendMessage(stream, "ERROR_AUTH_INVALID");
                    }
                }
                else if (responseData.StartsWith("auth"))
                {

                    Console.WriteLine("[" + ip + "] Using auth cred to authenticate.");
                    char c = '|';
                    string[] args = responseData.Split(c);
                    Console.WriteLine("Data splitted");

                    if (!(args.Length > 2))
                    {
                        SendMessage(stream, "INVALID_DATA_GIVEN");
                    }
                    string username = args[1];
                    string password = args[2];

                    Console.WriteLine("[" + ip + "] Using credentials: " + username + " and " + password);

                    Console.WriteLine("Logging in...");
                    string response = GetAsync(("https://example.com/clogin.php?name=" + username + "&password=" + password + ""));
                    Console.WriteLine("Login attempt completed, with " + response);

                    if (response.Equals("ERROR_FAILED_CONNECTION"))
                    {
                        SendMessage(stream, "ERROR_SERVERERROR");
                    }
                    else if (response.Equals("INVALID_USERNAME"))
                    {
                        SendMessage(stream, "INVALID_DATA_GIVEN");
                    }
                    else if (response.Equals("INVALID_PASSWORD"))
                    {
                        SendMessage(stream, "INVALID_DATA_GIVEN");
                    }
                    else if (response.Equals("INVALID_CRED"))
                    {
                        SendMessage(stream, "ERROR_AUTH_INVALID_CRED");
                    }
                    else if (response.Equals("IS_BANNED"))
                    {
                        SendMessage(stream, "ERROR_AUTH_BANNED");
                    }
                    else
                    {
                        Console.WriteLine("[" + ip + "] LOGIN OK | RESPONSE: " + response);
                        if (response == null) response = "Response was null?";
                        SendMessage(stream, response);
                        Console.WriteLine("Sended message...");
                        clientSession = response;
                    }
                }

                if ((!responseData.StartsWith("auth") || !responseData.StartsWith("useauthtoken") || !responseData.StartsWith("close")) && clientSession.Equals("NULL"))
                {
                    SendMessage(stream, "ERROR_AUTH_MISSING");
                }

                if (responseData.Equals("endGame"))
                {
                    char c = '|';
                    string[] args = responseData.Split(c);

                    if (!(args.Length > 3))
                    {
                        SendMessage(stream, "INVALID_DATA_GIVEN");
                    }

                    string won = args[0];
                    string kills = args[1];
                    string singleplayer = args[2];

                    string response = GetAsync("https://example.com/cgameend.php?session=" + client + "&won=" + won + "&kills=" + kills + "&singleplayer=" + singleplayer);

                    if (response.Equals("ERROR_FAILED_CONNECTION"))
                    {
                        SendMessage(stream, "ERROR_SERVERERROR");
                    }
                    else if (response.Equals("SESSION_INVALID"))
                    {
                        SendMessage(stream, "ERROR_AUTH_MISSING");
                    }
                    else if (response.Equals("SUCCESS"))
                    {
                        SendMessage(stream, "SUCCESS");
                    }
                }


            }
            catch (Exception e)
            {
                Console.WriteLine("[" + ip + "] Connection closed: " + e.Message);
                break;
            }
        }
    }

    public static string GetAsync(string uri, Action<WebHeaderCollection> headers = null)
    {
        HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri);
        //request.Headers.Set(HttpRequestHeader.ContentLocation, uri);
        //request.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;

        Console.WriteLine(request.RequestUri);
        using (HttpWebResponse response = (HttpWebResponse) request.GetResponse())
        using (Stream stream = response.GetResponseStream())
        using (StreamReader reader = new StreamReader(stream))
        {
            return reader.ReadToEnd();
        }
    }

    public static void SendMessage(NetworkStream stream, string msg)
    {
        Byte[] sendBytes = Encoding.ASCII.GetBytes(msg);
        stream.Write(sendBytes, 0, sendBytes.Length);
    }

    public static bool RemoteCertificateValidationCallback(System.Object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
    {
        bool isOk = true;

        return isOk;
    }
}

clogin.php:

    <?php
$conn = new mysqli("localhost", "root", "not-the-real-password", "topdown");

if ($conn->connect_error) {
    die("ERROR_FAILED_CONNECTION");
} 
$name = $_GET["name"];
$password = $_GET["password"];

function generateRandomString($length = 10) {
    $characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
    $charactersLength = strlen($characters);
    $randomString = '';
    for ($i = 0; $i < $length; $i++) {
        $randomString .= $characters[rand(0, $charactersLength - 1)];
    }
    return $randomString;
}

function endsWith($haystack, $needle)
{
    $length = strlen($needle);
    if ($length == 0) {
        return true;
    }

    return (substr($haystack, -$length) === $needle);
}
    
    $sql = "SELECT `password`,`banned` FROM `accounts` WHERE `name`='".$name."';";
    $result = $conn->query($sql);

    if ($result->num_rows < 1) { 
        die("INVALID_CRED");
        die();
    }
    
    $found = "";
    $banned = false;
    
    while($row = $result->fetch_assoc()) {
        $found = $row["password"];
        $banned = $row["banned"];
    }
    
    if($found == $password){
        if($banned==1){
            die("IS_BANNED");
        }
        
        $session = generateRandomString(16);
        $sql = "UPDATE `accounts` SET `clientsession`='".$session."' WHERE `name`='".$name."'";

        if ($conn->query($sql) === TRUE) {
            die($session);
        }else{
            die("ERROR_FAILED_CONNECTION");
        }
    }else{
        die("INVALID_CRED");
    }
?>

该消息来自服务器,而不是客户端。请参阅此答案以获取可能的解决方案:

您的模拟没有反映您在非模拟情况下发送到服务器的内容,这就是您无法在模拟中重现错误的原因。

在模拟的情况下你读了一行,字符串的长度cmd就是行的长度。在非模拟情况下,您改为读入大小为 8192 字节的缓冲区 data。这意味着,如果您读取 auth|abc|abc 的响应, data 的内容将是 auth|abc|abc[=14=][=14=][=14=][=14=]....,即服务器发送的内容,然后是 8180(8192-12)个字符 [=15= ](即 \x00[=17=]0 或者可以用 C# 编写)。

在您 responseData.Split 之后,密码(即 args[2])将不会像您预期的那样是 abc,而是 abc[=21=][=21=][=21=][=21=]....。这再次意味着应该是 https://....?user=abc&pass=abc 的 URL 实际上是 https://....?user=abc&pass=abc[=23=][=23=][=23=]....[=15=] 需要用 URL 编码为 %00 进行编码,这会导致 https://....?user=abc&pass=abc%00%00%00....

并且已经所有这些 8180 %00 将在 URL 中产生 24540 个字符,这解释了为什么服务器抱怨 URL 太大。查看服务器访问日志或错误日志也可能会显示此类问题。