使用C#实现多线程HTTP/HTTPS调试代理服务器






4.86/5 (34投票s)
一个完整的代理服务器,但它不进行SSL隧道,而是对SSL流量进行“中间人”解密,允许您检查加密流量。
引言
本文将向您展示如何使用C#实现一个多线程HTTP代理服务器,该服务器具有终止并代理HTTPS流量的非标准代理服务器功能。我添加了一个简单的缓存机制,并通过忽略保持连接活跃的http/1.1请求等来简化代码。
免责声明:请理解,此代码仅用于调试和测试目的。作者无意将此代码或可执行文件用于任何可能危及他人敏感信息的方式。请勿在任何用户不知情的情况下使用此服务器的环境中使用此服务器。通过使用本文中的此代码或可执行文件,您将对可能通过其使用而收集的数据负责。
背景
如果您熟悉Fiddler,那么您已经知道此代理服务器的工作原理。它本质上对HTTP客户端执行“中间人”攻击,以转储和调试HTTP流量。System.Net.Security.SslStream 类用于处理所有繁重的工作。
Using the Code
此代码最重要的部分是,当客户端请求CONNECT时,我们不会仅仅传递TCP流量,而是将处理SSL握手,建立SSL会话并接收来自客户端的请求。同时,我们将相同的请求发送到目标HTTPS服务器。
首先,让我们看看如何创建一个可以处理多个并发TCP连接的服务器。我们将使用System.Threading.Thread 对象在一个单独的线程中开始监听连接。这个线程的工作是监听传入的连接,然后生成一个新线程来处理处理,从而允许监听线程继续监听新的连接,而不会在处理一个客户端时阻塞。
public sealed class ProxyServer
{
private TcpListener _listener;
private Thread _listenerThread;
public void Start()
{
_listener = new TcpListener(IPAddress.Loopback, 8888);
_listenerThread = new Thread(new ParameterizedThreadStart(Listen));
_listenerThread.Start(_listener);
}
public void Stop()
{
//stop listening for incoming connections
_listener.Stop();
//wait for server to finish processing current connections...
_listenerThread.Abort();
_listenerThread.Join();
}
private static void Listen(Object obj)
{
TcpListener listener = (TcpListener)obj;
try
{
while (true)
{
TcpClient client = listener.AcceptTcpClient();
while (!ThreadPool.QueueUserWorkItem
(new WaitCallback(ProxyServer.ProcessClient), client)) ;
}
}
catch (ThreadAbortException) { }
catch (SocketException) { }
}
private static void ProcessClient(Object obj)
{
TcpClient client = (TcpClient)obj;
try
{
//do your processing here
}
catch(Exception ex)
{
//handle exception
}
finally
{
client.Close();
}
}
}
这就是以多线程方式处理并发TCP客户端的代码的开始。没有什么特别之处。有趣的是,当我们使用SslStream充当HTTPS服务器并“欺骗”客户端使其相信它正在与目标服务器通信时。请注意,由于SSL证书链,浏览器实际上不应被欺骗,但根据其浏览器,服务器身份是否存疑可能并不明显。
现在让我们看看实际的SSL请求处理过程。假设我们在上面显示的ProcessClient
方法的try
块中。
//read the first line HTTP command
Stream clientStream = client.GetStream();
StreamReader clientStreamReader = new StreamReader(clientStream);
String httpCmd = clientStreamReader.ReadLine();
//break up the line into three components
String[] splitBuffer = httpCmd.Split(spaceSplit, 3);
String method = splitBuffer[0];
String remoteUri = splitBuffer[1];
Version version = new Version(1, 0); //force everything to HTTP/1.0
//this will be the web request issued on behalf of the client
HttpWebRequest webReq;
if (method == "CONNECT")
{
//Browser wants to create a secure tunnel
//instead = we are going to perform a man in the middle
//the user's browser should warn them of the certification errors however.
//Please note: THIS IS ONLY FOR TESTING PURPOSES -
//you are responsible for the use of this code
//this is the URI we'll request on behalf of the client
remoteUri = "https://" + splitBuffer[1];
//read and ignore headers
while (!String.IsNullOrEmpty(clientStreamReader.ReadLine())) ;
//tell the client that a tunnel has been established
StreamWriter connectStreamWriter = new StreamWriter(clientStream);
connectStreamWriter.WriteLine("HTTP/1.0 200 Connection established");
connectStreamWriter.WriteLine
(String.Format("Timestamp: {0}", DateTime.Now.ToString()));
connectStreamWriter.WriteLine("Proxy-agent: matt-dot-net");
connectStreamWriter.WriteLine();
connectStreamWriter.Flush();
//now-create an https "server"
sslStream = new SslStream(clientStream, false);
sslStream.AuthenticateAsServer(_certificate,
false, SslProtocols.Tls | SslProtocols.Ssl3 | SslProtocols.Ssl2, true);
//HTTPS server created - we can now decrypt the client's traffic
//....
}
关注点
您可以看到我在代码的其他地方定义了一个X509Certificate2 _certificate。要获取证书,您需要使用makecert.exe之类的工具创建自签名证书。我在Windows SDK中找到了makecert.exe,它也随Fiddler一起提供。我在源文件中包含了一个证书文件以允许服务器运行,但由于它不包含私钥,因此要实际处理SSL流量,您需要运行makecert.exe
这是我使用的makecert.exe语法
makecert.exe cert.cer -a sha1 -n "CN=matt-dot-net" -sr LocalMachine -ss My -sky signature -pe -len 2048
需要注意的是,如果您使用Windows Internet选项指定使用您的代理服务器,则需要在您的HttpWebRequest
对象上将Proxy属性设置为null
。这是因为您的HttpWebRequest
将默认为Windows Internet设置,并且您将拥有一个尝试将自身用作代理服务器的代理服务器!
另一个有趣且令人沮丧的障碍是cookie的处理。当我以为我已经完美地处理了请求/响应时,我发现我无法维护任何网站的状态,因为cookie没有被客户端正确发送。我(通过使用Fiddler和Firefox实时HTTP标头)确定需要单独设置cookie。服务器在一个Set-Cookie标头中返回多个cookie,但我需要将它们解析出来并为每个cookie返回一个单独的Set-Cookie标头。我没有研究找出原因,或者HTTP中cookie的正确处理方法是什么,但我只能假设当浏览器设置为使用代理服务器时,它期望代理服务器将cookie处理成单独的Set-Cookie标头,如果没有配置为使用代理,则浏览器本身会这样做。
历史
我在一个周日下午匆匆编写了这段代码,所以我预计它会有错误和问题。此外,我不保证这能正确处理错误,清理对象,或者以任何方式是执行任何操作的最佳方式。