11,704,315 members (63,367 online)

# Implementing a Multithreaded HTTP/HTTPS Debugging Proxy Server in C#

, 3 Feb 2011 Ms-PL 119.2K 10K 95
 Rate this:
A complete proxy server except instead of SSL Tunneling, will perform a "man-in-the-middle" decryption on SSL traffic allowing you to inspect the encrypted traffic

## Introduction

This article will show you how to implement a multithreaded HTTP proxy server in C# with a non-standard proxy server feature of terminating and then proxying HTTPS traffic. I've added a simple caching mechanism, and have simplified the code by ignoring http/1.1 requests for keeping connections alive, etc.

Disclaimer: Understand that this code is for debugging and testing purposes only. The author does not intend for this code or the executable to be used in any way that may compromise someone's sensitive information. Do not use this server in any environment which has users that are unaware of its use. By using this code or the executable found in this article, you are taking responsibility for the data which may be collected through its use.

## Background

If you are familiar with fiddler, then you already know how this proxy server works. It essentially performs a "man-in-the-middle" on the HTTP client to dump and debug HTTP traffic. The System.Net.Security.SslStream class is utilized to handle all the heavy lifting.

## Using the Code

The most important part about this code is that when the client asks for a CONNECT, instead of just passing TCP traffic, we're going to handle an SSL handshake and establish an SSL session and receive a request from the client. In the mean time, we'll send the same request to the destination HTTPS server.

First, let's look at creating a server that can handle multiple concurrent TCP connections. We'll use the System.Threading.Thread object to start listening for connections in a separate thread. This thread's job will be to listen for incoming connections, and then spawn a new thread to handle processing, thus allowing the listening thread to continue listening for new connections without blocking while one client is processed.

public sealed class ProxyServer
{
private TcpListener _listener;

public void Start()
{
}

public void Stop()
{
//stop listening for incoming connections
_listener.Stop();
//wait for server to finish processing current connections...
}

private static void Listen(Object obj)
{
TcpListener listener = (TcpListener)obj;
try
{
while (true)
{
TcpClient client = listener.AcceptTcpClient();
(new WaitCallback(ProxyServer.ProcessClient), client)) ;
}
}
catch (SocketException) { }
}

private static void ProcessClient(Object obj)
{
TcpClient client = (TcpClient)obj;
try
{
}
catch(Exception ex)
{
//handle exception
}
finally
{
client.Close();
}
}
}

And that's the beginning of the code to handle concurrent TCP clients in a multithreaded manner. Nothing really special there. The interesting bit is when we use SslStream to act as an HTTPS server and "trick" the client into believing it's talking to the destination server. Note that the browser should not actually be tricked because of the SSL certificate chain, but depending on their browser, it may or may not be apparent that the server's identity is in question.

Now let's take a look at the actual processing of the SSL request. Assume that we are somewhere inside the try block of the ProcessClient method shown above.

//read the first line HTTP command
Stream clientStream = client.GetStream();

//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];

//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
//....
}

## Points of Interest

You can see that I have an X509Certificate2 _certificate defined elsewhere in the code. To get a certificate for this, you need to use a tool like makecert.exe to create a self-signed certificate. I found makecert.exe in the Windows SDK and it also comes with Fiddler. I have included a certificate file in the source files to allow the server to run, but because it does not include the private key, to actually handle SSL traffic, You will need to run makecert.exe

Here is the syntax I used for makecert.exe:

makecert.exe cert.cer -a sha1 -n "CN=matt-dot-net" -sr LocalMachine -ss My -sky signature -pe -len 2048

One thing to note is that on your HttpWebRequest object, you will need to set the Proxy property to null if you are using Windows internet options to specify using your proxy server. This is because your HttpWebRequest will default to the Windows internet settings and you'll have a proxy server that is trying to use itself as a proxy server!

## History

I threw together this code on a Sunday afternoon, so I expect it to have errors and problems. Also, I make no claim that this is properly handling errors, cleaning up objects, or is in any way the best way to do anything.

## Share

 Software Developer (Senior) United States
No Biography provided

## You may also be interested in...

 FirstPrev Next
 Which Options do I need to use to make the certificate? alexjanjic9-Jul-11 6:07 alexjanjic 9-Jul-11 6:07
 My vote of 5 Kushan Ratnayake8-Jul-11 20:32 Kushan Ratnayake 8-Jul-11 20:32
 My vote of 4 KPetrosyan8-Jun-11 22:43 KPetrosyan 8-Jun-11 22:43
 facebook https pages are broken KPetrosyan8-Jun-11 22:43 KPetrosyan 8-Jun-11 22:43
 certificate mushonaaa9-May-11 1:21 mushonaaa 9-May-11 1:21
 Re: certificate matt-dot-net11-May-11 7:15 matt-dot-net 11-May-11 7:15
 Authentication Exception Mark Woan9-Feb-11 2:58 Mark Woan 9-Feb-11 2:58
 Re: Authentication Exception matt-dot-net14-Feb-11 4:10 matt-dot-net 14-Feb-11 4:10
 My vote of 5 Martin Thwaites4-Feb-11 13:10 Martin Thwaites 4-Feb-11 13:10
 Article Update matt-dot-net1-Feb-11 7:57 matt-dot-net 1-Feb-11 7:57
 Problems with POST data Martin Thwaites1-Feb-11 5:49 Martin Thwaites 1-Feb-11 5:49
 Re: Problems with POST data matt-dot-net1-Feb-11 6:34 matt-dot-net 1-Feb-11 6:34
 Re: Problems with POST data Martin Thwaites4-Feb-11 13:09 Martin Thwaites 4-Feb-11 13:09
 Error on ssl web sites. jlandrian26-Jul-10 13:17 jlandrian 26-Jul-10 13:17
 Hi, I'm receiving the following error when I'm trying to acces to an ssl web site. "System.NotSupportedException: The server mode SSL must use a certificate with the associated private key.\r\n at System.Net.Security.SecureChannel.AcquireServerCredentials(Byte[]& thumbPrint)\r\n at System.Net.Security.SecureChannel.GenerateToken(Byte[] input, Int32 offset, Int32 count, Byte[]& output)\r\n at System.Net.Security.SecureChannel.NextMessage(Byte[] incoming, Int32 offset, Int32 count)\r\n at System.Net.Security.SslState.StartSendBlob(Byte[] incoming, Int32 count, AsyncProtocolRequest asyncRequest)\r\n at System.Net.Security.SslState.ProcessReceivedBlob(Byte[] buffer, Int32 count, AsyncProtocolRequest asyncRequest)\r\n at System.Net.Security.SslState.StartReadFrame(Byte[] buffer, Int32 readBytes, AsyncProtocolRequest asyncRequest)\r\n at System.Net.Security.SslState.StartReceiveBlob(Byte[] buffer, AsyncProtocolRequest asyncRequest)\r\n at System.Net.Security.SslState.ForceAuthentication(Boolean receiveFirst, Byte[] buffer, AsyncProtocolRequest asyncRequest)\r\n at System.Net.Security.SslState.ProcessAuthentication(LazyAsyncResult lazyResult)\r\n at System.Net.Security.SslStream.AuthenticateAsServer(X509Certificate serverCertificate, Boolean clientCertificateRequired, SslProtocols enabledSslProtocols, Boolean checkCertificateRevocation)\r\n at HTTPProxyServer.ProxyServer.DoHttpProcessing(TcpClient client) in D:\\Work\\dot_NET_3.5\\HTTPProxyServer\\HTTPProxyServer\\ProxyServer.cs:line 207" string Thanks for your post.
 Re: Error on ssl web sites. matt-dot-net26-Jul-10 14:42 matt-dot-net 26-Jul-10 14:42
 My vote of 5 Member 432084420-Jul-10 22:01 Member 4320844 20-Jul-10 22:01
 Last Visit: 31-Dec-99 18:00     Last Update: 28-Aug-15 11:23 Refresh 12