//This is called when requests are routed through router to this endpoint //For ssl requests private static void HandleClient(TransparentProxyEndPoint endPoint, TcpClient tcpClient) { Stream clientStream = tcpClient.GetStream(); CustomBinaryReader clientStreamReader = null; StreamWriter clientStreamWriter = null; X509Certificate2 certificate = null; if (endPoint.EnableSsl) { var sslStream = new SslStream(clientStream, true); //if(endPoint.UseServerNameIndication) //{ // //implement in future once SNI supported by SSL stream // certificate = CertManager.CreateCertificate(endPoint.GenericCertificateName); //} //else certificate = CertManager.CreateCertificate(endPoint.GenericCertificateName); try { //Successfully managed to authenticate the client using the fake certificate sslStream.AuthenticateAsServer(certificate, false, SslProtocols.Tls, false); clientStreamReader = new CustomBinaryReader(sslStream, Encoding.ASCII); clientStreamWriter = new StreamWriter(sslStream); //HTTPS server created - we can now decrypt the client's traffic } catch (Exception) { if (sslStream != null) sslStream.Dispose(); Dispose(tcpClient, sslStream, clientStreamReader, clientStreamWriter, null); return; } clientStream = sslStream; } else { clientStreamReader = new CustomBinaryReader(clientStream, Encoding.ASCII); } var httpCmd = clientStreamReader.ReadLine(); //Now create the request HandleHttpSessionRequest(tcpClient, httpCmd, clientStream, clientStreamReader, clientStreamWriter, true); }
/// <summary> /// Copies the specified bytes to the stream from the input stream /// </summary> /// <param name="reader"></param> /// <param name="count"></param> /// <param name="onCopy"></param> /// <returns></returns> private async Task CopyBytesFromStream(CustomBinaryReader reader, long count, Action <byte[], int, int> onCopy) { var buffer = reader.Buffer; long remainingBytes = count; while (remainingBytes > 0) { int bytesToRead = buffer.Length; if (remainingBytes < bytesToRead) { bytesToRead = (int)remainingBytes; } int bytesRead = await reader.ReadBytesAsync(buffer, bytesToRead); if (bytesRead == 0) { break; } remainingBytes -= bytesRead; await WriteAsync(buffer, 0, bytesRead); onCopy?.Invoke(buffer, 0, bytesRead); } }
/// <summary> /// Copies the streams chunked /// </summary> /// <param name="reader"></param> /// <param name="removeChunkedEncoding"></param> /// <param name="onCopy"></param> /// <returns></returns> private async Task CopyBodyChunkedAsync(CustomBinaryReader reader, bool removeChunkedEncoding, Action <byte[], int, int> onCopy) { while (true) { string chunkHead = await reader.ReadLineAsync(); int chunkSize = int.Parse(chunkHead, NumberStyles.HexNumber); if (!removeChunkedEncoding) { await WriteLineAsync(chunkHead); } if (chunkSize != 0) { await CopyBytesFromStream(reader, chunkSize, onCopy); } if (!removeChunkedEncoding) { await WriteLineAsync(); } //chunk trail await reader.ReadLineAsync(); if (chunkSize == 0) { break; } } }
/// <summary> /// Copies the streams chunked /// </summary> /// <param name="inStreamReader"></param> /// <returns></returns> internal async Task WriteResponseBodyChunkedAsync(CustomBinaryReader inStreamReader) { while (true) { string chunkHead = await inStreamReader.ReadLineAsync(); int chunkSize = int.Parse(chunkHead, NumberStyles.HexNumber); if (chunkSize != 0) { var chunkHeadBytes = Encoding.ASCII.GetBytes(chunkSize.ToString("x2")); await BaseStream.WriteAsync(chunkHeadBytes, 0, chunkHeadBytes.Length); await BaseStream.WriteAsync(ProxyConstants.NewLineBytes, 0, ProxyConstants.NewLineBytes.Length); await inStreamReader.CopyBytesToStream(BaseStream, chunkSize); await BaseStream.WriteAsync(ProxyConstants.NewLineBytes, 0, ProxyConstants.NewLineBytes.Length); await inStreamReader.ReadLineAsync(); } else { await inStreamReader.ReadLineAsync(); await BaseStream.WriteAsync(ProxyConstants.ChunkEnd, 0, ProxyConstants.ChunkEnd.Length); break; } } }
private static void Dispose(TcpClient client, Stream clientStream, CustomBinaryReader clientStreamReader, StreamWriter clientStreamWriter, SessionEventArgs args) { if (args != null) args.Dispose(); if (clientStreamReader != null) clientStreamReader.Dispose(); if (clientStreamWriter != null) clientStreamWriter.Dispose(); if (clientStream != null) clientStream.Dispose(); if (client != null) client.Close(); }
/// <summary> /// Copies the specified content length number of bytes to the output stream from the given inputs stream /// optionally chunked /// </summary> /// <param name="bufferSize"></param> /// <param name="inStreamReader"></param> /// <param name="isChunked"></param> /// <param name="contentLength"></param> /// <returns></returns> internal async Task WriteResponseBodyAsync(int bufferSize, CustomBinaryReader inStreamReader, bool isChunked, long contentLength) { if (!isChunked) { //http 1.0 if (contentLength == -1) { contentLength = long.MaxValue; } await inStreamReader.CopyBytesToStream(BaseStream, contentLength); } else { await WriteResponseBodyChunkedAsync(inStreamReader); } }
/// <summary> /// Copies the specified content length number of bytes to the output stream from the given inputs stream /// optionally chunked /// </summary> /// <param name="streamReader"></param> /// <param name="isChunked"></param> /// <param name="contentLength"></param> /// <param name="removeChunkedEncoding"></param> /// <param name="onCopy"></param> /// <returns></returns> internal Task CopyBodyAsync(CustomBinaryReader streamReader, bool isChunked, long contentLength, bool removeChunkedEncoding, Action <byte[], int, int> onCopy) { //For chunked request we need to read data as they arrive, until we reach a chunk end symbol if (isChunked) { //Need to revist, find any potential bugs //send the body bytes to server in chunks return(CopyBodyChunkedAsync(streamReader, removeChunkedEncoding, onCopy)); } //http 1.0 if (contentLength == -1) { contentLength = long.MaxValue; } //If not chunked then its easy just read the amount of bytes mentioned in content length header return(CopyBytesFromStream(streamReader, contentLength, onCopy)); }
private static void HandleClientRequest(TcpClient Client) { string connectionGroup = null; Stream clientStream = null; CustomBinaryReader clientStreamReader = null; StreamWriter connectStreamWriter = null; string tunnelHostName = null; int tunnelPort = 0; try { connectionGroup = Dns.GetHostEntry(((IPEndPoint)Client.Client.RemoteEndPoint).Address).HostName; clientStream = Client.GetStream(); clientStreamReader = new CustomBinaryReader(clientStream, Encoding.ASCII); string securehost = null; List<string> requestLines = new List<string>(); string tmpLine; while (!String.IsNullOrEmpty(tmpLine = clientStreamReader.ReadLine())) { requestLines.Add(tmpLine); } //read the first line HTTP command String httpCmd = requestLines.Count > 0 ? requestLines[0] : null; if (String.IsNullOrEmpty(httpCmd)) { throw new EndOfStreamException(); } //break up the line into three components String[] splitBuffer = httpCmd.Split(spaceSplit, 3); String method = splitBuffer[0]; String remoteUri = splitBuffer[1]; Version version; string RequestVersion; if (splitBuffer[2] == "HTTP/1.1") { version = new Version(1, 1); RequestVersion = "HTTP/1.1"; } else { version = new Version(1, 0); RequestVersion = "HTTP/1.0"; } if (splitBuffer[0].ToUpper() == "CONNECT") { //Browser wants to create a secure tunnel //instead = we are going to perform a man in the middle "attack" //the user's browser should warn them of the certification errors, //so we need to install our root certficate in users machine as Certificate Authority. remoteUri = "https://" + splitBuffer[1]; tunnelHostName = splitBuffer[1].Split(':')[0]; int.TryParse(splitBuffer[1].Split(':')[1], out tunnelPort); if (tunnelPort == 0) tunnelPort = 80; var isSecure = true; for (int i = 1; i < requestLines.Count; i++) { var rawHeader = requestLines[i]; String[] header = rawHeader.ToLower().Trim().Split(colonSpaceSplit, 2, StringSplitOptions.None); if ((header[0] == "host")) { var hostDetails = header[1].ToLower().Trim().Split(':'); if (hostDetails.Length > 1) { isSecure = false; } } } requestLines.Clear(); connectStreamWriter = new StreamWriter(clientStream); connectStreamWriter.WriteLine(RequestVersion + " 200 Connection established"); connectStreamWriter.WriteLine(String.Format("Timestamp: {0}", DateTime.Now.ToString())); connectStreamWriter.WriteLine(String.Format("connection:close")); connectStreamWriter.WriteLine(); connectStreamWriter.Flush(); if (tunnelPort != 443) { TcpHelper.SendRaw(tunnelHostName, tunnelPort, clientStreamReader.BaseStream); if (clientStream != null) clientStream.Close(); return; } Monitor.Enter(certificateAccessLock); var _certificate = CertificateHelper.GetCertificate(RootCertificateName, tunnelHostName); Monitor.Exit(certificateAccessLock); SslStream sslStream = null; if (!pinnedCertificateClients.Contains(tunnelHostName) && isSecure) { sslStream = new SslStream(clientStream, true); try { sslStream.AuthenticateAsServer(_certificate, false, SslProtocols.Tls | SslProtocols.Ssl3 | SslProtocols.Ssl2, false); } catch (AuthenticationException ex) { if (pinnedCertificateClients.Contains(tunnelHostName) == false) { pinnedCertificateClients.Add(tunnelHostName); } throw ex; } } else { TcpHelper.SendRaw(tunnelHostName, tunnelPort, clientStreamReader.BaseStream); if (clientStream != null) clientStream.Close(); return; } clientStreamReader = new CustomBinaryReader(sslStream, Encoding.ASCII); //HTTPS server created - we can now decrypt the client's traffic clientStream = sslStream; while (!String.IsNullOrEmpty(tmpLine = clientStreamReader.ReadLine())) { requestLines.Add(tmpLine); } //read the new http command. httpCmd = requestLines.Count > 0 ? requestLines[0] : null; if (String.IsNullOrEmpty(httpCmd)) { throw new EndOfStreamException(); } securehost = remoteUri; } int count = 0; SessionEventArgs args = new SessionEventArgs(BUFFER_SIZE); while (!String.IsNullOrEmpty(httpCmd)) { count++; MemoryStream mw = null; StreamWriter sw = null; args = new SessionEventArgs(BUFFER_SIZE); try { splitBuffer = httpCmd.Split(spaceSplit, 3); if (splitBuffer.Length != 3) { TcpHelper.SendRaw(httpCmd, tunnelHostName, ref requestLines, args.IsSecure, clientStreamReader.BaseStream); if (clientStream != null) clientStream.Close(); return; } method = splitBuffer[0]; remoteUri = splitBuffer[1]; if (splitBuffer[2] == "HTTP/1.1") { version = new Version(1, 1); } else { version = new Version(1, 0); } if (securehost != null) { remoteUri = securehost + remoteUri; args.IsSecure = true; } //construct the web request that we are going to issue on behalf of the client. args.ProxyRequest = (HttpWebRequest)HttpWebRequest.Create(remoteUri.Trim()); args.ProxyRequest.Proxy = null; args.ProxyRequest.UseDefaultCredentials = true; args.ProxyRequest.Method = method; args.ProxyRequest.ProtocolVersion = version; args.ClientStream = clientStream; args.ClientStreamReader = clientStreamReader; for (int i = 1; i < requestLines.Count; i++) { var rawHeader = requestLines[i]; String[] header = rawHeader.ToLower().Trim().Split(colonSpaceSplit, 2, StringSplitOptions.None); if ((header[0] == "upgrade") && (header[1] == "websocket")) { TcpHelper.SendRaw(httpCmd, tunnelHostName, ref requestLines, args.IsSecure, clientStreamReader.BaseStream); if (clientStream != null) clientStream.Close(); return; } } ReadRequestHeaders(ref requestLines, args.ProxyRequest); int contentLen = (int)args.ProxyRequest.ContentLength; args.ProxyRequest.AllowAutoRedirect = false; args.ProxyRequest.AutomaticDecompression = DecompressionMethods.None; if (BeforeRequest != null) { args.Hostname = args.ProxyRequest.RequestUri.Host; args.RequestURL = args.ProxyRequest.RequestUri.OriginalString; args.RequestLength = contentLen; args.HttpVersion = version; args.Port = ((IPEndPoint)Client.Client.RemoteEndPoint).Port; args.ipAddress = ((IPEndPoint)Client.Client.RemoteEndPoint).Address; args.IsAlive = args.ProxyRequest.KeepAlive; BeforeRequest(null, args); } if (args.Cancel) { if (args.IsAlive) { requestLines.Clear(); while (!String.IsNullOrEmpty(tmpLine = clientStreamReader.ReadLine())) { requestLines.Add(tmpLine); } httpCmd = requestLines.Count > 0 ? requestLines[0] : null; continue; } else break; } args.ProxyRequest.ConnectionGroupName = connectionGroup; args.ProxyRequest.AllowWriteStreamBuffering = true; args.FinishedRequestEvent = new ManualResetEvent(false); if (method.ToUpper() == "POST" || method.ToUpper() == "PUT") { args.ProxyRequest.BeginGetRequestStream(new AsyncCallback(GetRequestStreamCallback), args); } else { args.ProxyRequest.BeginGetResponse(new AsyncCallback(HandleServerResponse), args); } if (args.IsSecure) { if (args.ProxyRequest.Method == "POST" || args.ProxyRequest.Method == "PUT") args.FinishedRequestEvent.WaitOne(); else args.FinishedRequestEvent.Set(); } else args.FinishedRequestEvent.WaitOne(); httpCmd = null; if (args.ProxyRequest.KeepAlive) { requestLines.Clear(); while (!String.IsNullOrEmpty(tmpLine = clientStreamReader.ReadLine())) { requestLines.Add(tmpLine); } httpCmd = requestLines.Count() > 0 ? requestLines[0] : null; } if (args.ServerResponse != null) args.ServerResponse.Close(); } catch (IOException ex) { throw ex; } catch (UriFormatException ex) { throw ex; } catch (WebException ex) { throw ex; } finally { if (sw != null) sw.Close(); if (mw != null) mw.Close(); if (args.ProxyRequest != null) args.ProxyRequest.Abort(); if (args.ServerResponseStream != null) args.ServerResponseStream.Close(); } } } catch (AuthenticationException ex) { Debug.WriteLine(ex.Message); } catch (EndOfStreamException ex) { Debug.WriteLine(ex.Message); } catch (IOException ex) { Debug.WriteLine(ex.Message); } catch (UriFormatException ex) { Debug.WriteLine(ex.Message); } catch (WebException ex) { Debug.WriteLine(ex.Message); } finally { if (connectStreamWriter != null) connectStreamWriter.Close(); if (clientStreamReader != null) clientStreamReader.Close(); if (clientStream != null) clientStream.Close(); } }
private static void HandleClient(TcpClient client) { Stream clientStream = client.GetStream(); var clientStreamReader = new CustomBinaryReader(clientStream, Encoding.ASCII); var clientStreamWriter = new StreamWriter(clientStream); Uri httpRemoteUri; try { //read the first line HTTP command var httpCmd = clientStreamReader.ReadLine(); if (string.IsNullOrEmpty(httpCmd)) { Dispose(client, clientStream, clientStreamReader, clientStreamWriter, null); return; } //break up the line into three components (method, remote URL & Http Version) var httpCmdSplit = httpCmd.Split(SpaceSplit, 3); var httpVerb = httpCmdSplit[0]; if (httpVerb.ToUpper() == "CONNECT") httpRemoteUri = new Uri("http://" + httpCmdSplit[1]); else httpRemoteUri = new Uri(httpCmdSplit[1]); var httpVersion = httpCmdSplit[2]; var excluded = ExcludedHttpsHostNameRegex.Any(x => Regex.IsMatch(httpRemoteUri.Host, x)); //Client wants to create a secure tcp tunnel (its a HTTPS request) if (httpVerb.ToUpper() == "CONNECT" && !excluded && httpRemoteUri.Port == 443) { httpRemoteUri = new Uri("https://" + httpCmdSplit[1]); clientStreamReader.ReadAllLines(); WriteConnectResponse(clientStreamWriter, httpVersion); var certificate = CertManager.CreateCertificate(httpRemoteUri.Host); SslStream sslStream = null; try { sslStream = new SslStream(clientStream, true); //Successfully managed to authenticate the client using the fake certificate sslStream.AuthenticateAsServer(certificate, false, SslProtocols.Tls | SslProtocols.Ssl3 | SslProtocols.Ssl2, false); clientStreamReader = new CustomBinaryReader(sslStream, Encoding.ASCII); clientStreamWriter = new StreamWriter(sslStream); //HTTPS server created - we can now decrypt the client's traffic clientStream = sslStream; } catch { if (sslStream != null) sslStream.Dispose(); Dispose(client, clientStream, clientStreamReader, clientStreamWriter, null); return; } httpCmd = clientStreamReader.ReadLine(); } else if (httpVerb.ToUpper() == "CONNECT") { clientStreamReader.ReadAllLines(); WriteConnectResponse(clientStreamWriter, httpVersion); TcpHelper.SendRaw(clientStreamReader.BaseStream, null, null, httpRemoteUri.Host, httpRemoteUri.Port, false); Dispose(client, clientStream, clientStreamReader, clientStreamWriter, null); return; } //Now create the request HandleHttpSessionRequest(client, httpCmd, clientStream, clientStreamReader, clientStreamWriter, httpRemoteUri.Scheme == Uri.UriSchemeHttps ? httpRemoteUri.OriginalString : null); } catch { Dispose(client, clientStream, clientStreamReader, clientStreamWriter, null); } }
private static void HandleHttpSessionRequest(TcpClient client, string httpCmd, Stream clientStream, CustomBinaryReader clientStreamReader, StreamWriter clientStreamWriter, string secureTunnelHostName) { TcpConnection connection = null; string lastRequestHostName = null; while (true) { if (string.IsNullOrEmpty(httpCmd)) { Dispose(client, clientStream, clientStreamReader, clientStreamWriter, null); break; } var args = new SessionEventArgs(BUFFER_SIZE); args.Client.TcpClient = client; try { //break up the line into three components (method, remote URL & Http Version) var httpCmdSplit = httpCmd.Split(SpaceSplit, 3); var httpMethod = httpCmdSplit[0]; var httpRemoteUri = new Uri(secureTunnelHostName == null ? httpCmdSplit[1] : (secureTunnelHostName + httpCmdSplit[1])); var httpVersion = httpCmdSplit[2]; Version version; if (httpVersion == "HTTP/1.1") { version = new Version(1, 1); } else { version = new Version(1, 0); } if (httpRemoteUri.Scheme == Uri.UriSchemeHttps) { args.IsHttps = true; } args.ProxySession.Request.RequestHeaders = new List<HttpHeader>(); string tmpLine; while (!string.IsNullOrEmpty(tmpLine = clientStreamReader.ReadLine())) { var header = tmpLine.Split(new char[] { ':' }, 2); args.ProxySession.Request.RequestHeaders.Add(new HttpHeader(header[0], header[1])); } SetRequestHeaders(args.ProxySession.Request.RequestHeaders, args.ProxySession); if (args.ProxySession.Request.UpgradeToWebSocket) { TcpHelper.SendRaw(clientStreamReader.BaseStream, httpCmd, args.ProxySession.Request.RequestHeaders, httpRemoteUri.Host, httpRemoteUri.Port, httpRemoteUri.Scheme == Uri.UriSchemeHttps); Dispose(client, clientStream, clientStreamReader, clientStreamWriter, args); return; } args.ProxySession.Request.RequestUri = httpRemoteUri; args.ProxySession.Request.Method = httpMethod; args.ProxySession.Request.HttpVersion = httpVersion; args.Client.ClientStream = clientStream; args.Client.ClientStreamReader = clientStreamReader; args.Client.ClientStreamWriter = clientStreamWriter; args.ProxySession.Request.Hostname = args.ProxySession.Request.RequestUri.Host; args.ProxySession.Request.Url = args.ProxySession.Request.RequestUri.OriginalString; args.Client.ClientPort = ((IPEndPoint)client.Client.RemoteEndPoint).Port; args.Client.ClientIpAddress = ((IPEndPoint)client.Client.RemoteEndPoint).Address; //If requested interception if (BeforeRequest != null) { args.ProxySession.Request.Encoding = args.ProxySession.GetEncoding(); BeforeRequest(null, args); } args.ProxySession.Request.RequestLocked = true; if (args.ProxySession.Request.CancelRequest) { Dispose(client, clientStream, clientStreamReader, clientStreamWriter, args); break; } //construct the web request that we are going to issue on behalf of the client. connection = connection == null ? TcpConnectionManager.GetClient(args.ProxySession.Request.RequestUri.Host, args.ProxySession.Request.RequestUri.Port, args.IsHttps) : lastRequestHostName != args.ProxySession.Request.Hostname ? TcpConnectionManager.GetClient(args.ProxySession.Request.RequestUri.Host, args.ProxySession.Request.RequestUri.Port, args.IsHttps) : connection; lastRequestHostName = args.ProxySession.Request.Hostname; args.ProxySession.SetConnection(connection); args.ProxySession.SendRequest(); //If request was modified by user if (args.ProxySession.Request.RequestBodyRead) { args.ProxySession.Request.ContentLength = args.ProxySession.Request.RequestBody.Length; var newStream = args.ProxySession.ProxyClient.ServerStreamReader.BaseStream; newStream.Write(args.ProxySession.Request.RequestBody, 0, args.ProxySession.Request.RequestBody.Length); } else { //If its a post/put request, then read the client html body and send it to server if (httpMethod.ToUpper() == "POST" || httpMethod.ToUpper() == "PUT") { SendClientRequestBody(args); } } HandleHttpSessionResponse(args); //if connection is closing exit if (args.ProxySession.Response.ResponseKeepAlive == false) { connection.TcpClient.Close(); Dispose(client, clientStream, clientStreamReader, clientStreamWriter, args); return; } // read the next request httpCmd = clientStreamReader.ReadLine(); } catch { Dispose(client, clientStream, clientStreamReader, clientStreamWriter, args); break; } } if (connection != null) TcpConnectionManager.ReleaseClient(connection); }
//Send chunked response private static void WriteResponseBodyChunked(CustomBinaryReader inStreamReader, Stream outStream) { while (true) { var chuchkHead = inStreamReader.ReadLine(); var chunkSize = int.Parse(chuchkHead, NumberStyles.HexNumber); if (chunkSize != 0) { var buffer = inStreamReader.ReadBytes(chunkSize); var chunkHead = Encoding.ASCII.GetBytes(chunkSize.ToString("x2")); outStream.Write(chunkHead, 0, chunkHead.Length); outStream.Write(NewLineBytes, 0, NewLineBytes.Length); outStream.Write(buffer, 0, chunkSize); outStream.Write(NewLineBytes, 0, NewLineBytes.Length); inStreamReader.ReadLine(); } else { inStreamReader.ReadLine(); outStream.Write(ChunkEnd, 0, ChunkEnd.Length); break; } } }
private static void WriteResponseBody(CustomBinaryReader inStreamReader, Stream outStream, bool isChunked, int BodyLength) { if (!isChunked) { int bytesToRead = BUFFER_SIZE; if (BodyLength < BUFFER_SIZE) bytesToRead = BodyLength; var buffer = new byte[BUFFER_SIZE]; var bytesRead = 0; var totalBytesRead = 0; while ((bytesRead += inStreamReader.BaseStream.Read(buffer, 0, bytesToRead)) > 0) { outStream.Write(buffer, 0, bytesRead); totalBytesRead += bytesRead; if (totalBytesRead == BodyLength) break; bytesRead = 0; var remainingBytes = (BodyLength - totalBytesRead); bytesToRead = remainingBytes > BUFFER_SIZE ? BUFFER_SIZE : remainingBytes; } } else WriteResponseBodyChunked(inStreamReader, outStream); }
private static void HandleHttpSessionRequest(TcpClient client, string httpCmd, Stream clientStream, CustomBinaryReader clientStreamReader, StreamWriter clientStreamWriter, string secureTunnelHostName) { if (String.IsNullOrEmpty(httpCmd)) { Dispose(client, clientStream, clientStreamReader, clientStreamWriter, null); return; } var args = new SessionEventArgs(BUFFER_SIZE); args.client = client; try { //break up the line into three components (method, remote URL & Http Version) String[] httpCmdSplit = httpCmd.Split(spaceSplit, 3); var httpMethod = httpCmdSplit[0]; var httpRemoteUri = new Uri(secureTunnelHostName == null ? httpCmdSplit[1] : (secureTunnelHostName + httpCmdSplit[1])); var httpVersion = httpCmdSplit[2]; Version version; if (httpVersion == "HTTP/1.1") { version = new Version(1, 1); } else { version = new Version(1, 0); } if (httpRemoteUri.Scheme == Uri.UriSchemeHttps) { args.isHttps = true; } args.requestHeaders = new List<HttpHeader>(); string tmpLine = null; while (!String.IsNullOrEmpty(tmpLine = clientStreamReader.ReadLine())) { String[] header = tmpLine.Split(colonSpaceSplit, 2, StringSplitOptions.None); args.requestHeaders.Add(new HttpHeader(header[0], header[1])); } for (int i = 0; i < args.requestHeaders.Count; i++) { var rawHeader = args.requestHeaders[i]; //if request was upgrade to web-socket protocol then relay the request without proxying if ((rawHeader.Name.ToLower() == "upgrade") && (rawHeader.Value.ToLower() == "websocket")) { TcpHelper.SendRaw(clientStreamReader.BaseStream, httpCmd, args.requestHeaders, httpRemoteUri.Host, httpRemoteUri.Port, httpRemoteUri.Scheme == Uri.UriSchemeHttps); Dispose(client, clientStream, clientStreamReader, clientStreamWriter, args); return; } } //construct the web request that we are going to issue on behalf of the client. args.proxyRequest = (HttpWebRequest)HttpWebRequest.Create(httpRemoteUri); args.proxyRequest.Proxy = null; args.proxyRequest.UseDefaultCredentials = true; args.proxyRequest.Method = httpMethod; args.proxyRequest.ProtocolVersion = version; args.clientStream = clientStream; args.clientStreamReader = clientStreamReader; args.clientStreamWriter = clientStreamWriter; args.proxyRequest.AllowAutoRedirect = false; args.proxyRequest.AutomaticDecompression = DecompressionMethods.None; args.requestHostname = args.proxyRequest.RequestUri.Host; args.requestURL = args.proxyRequest.RequestUri.OriginalString; args.clientPort = ((IPEndPoint)client.Client.RemoteEndPoint).Port; args.clientIpAddress = ((IPEndPoint)client.Client.RemoteEndPoint).Address; args.requestHttpVersion = version; args.requestIsAlive = args.proxyRequest.KeepAlive; args.proxyRequest.ConnectionGroupName = args.requestHostname; args.proxyRequest.AllowWriteStreamBuffering = true; //If requested interception if (BeforeRequest != null) { args.requestEncoding = args.proxyRequest.GetEncoding(); BeforeRequest(null, args); } args.RequestLocked = true; if (args.cancelRequest) { Dispose(client, clientStream, clientStreamReader, clientStreamWriter, args); return; } SetRequestHeaders(args.requestHeaders, args.proxyRequest); //If request was modified by user if (args.requestBodyRead) { args.proxyRequest.ContentLength = args.requestBody.Length; Stream newStream = args.proxyRequest.GetRequestStream(); newStream.Write(args.requestBody, 0, args.requestBody.Length); args.proxyRequest.BeginGetResponse(new AsyncCallback(HandleHttpSessionResponse), args); } else { //If its a post/put request, then read the client html body and send it to server if (httpMethod.ToUpper() == "POST" || httpMethod.ToUpper() == "PUT") { SendClientRequestBody(args); } //Http request body sent, now wait asynchronously for response args.proxyRequest.BeginGetResponse(new AsyncCallback(HandleHttpSessionResponse), args); } //Now read the next request (if keep-Alive is enabled, otherwise exit this thread) //If client is pipeling the request, this will be immediately hit before response for previous request was made httpCmd = clientStreamReader.ReadLine(); //Http request body sent, now wait for next request Task.Factory.StartNew(() => HandleHttpSessionRequest(args.client, httpCmd, args.clientStream, args.clientStreamReader, args.clientStreamWriter, secureTunnelHostName)); } catch { Dispose(client, clientStream, clientStreamReader, clientStreamWriter, args); } }
private static void HandleHttpSessionRequest(TcpClient Client, string httpCmd, Stream clientStream, string tunnelHostName, List<string> requestLines, CustomBinaryReader clientStreamReader, string securehost) { if (httpCmd == null) return; var args = new SessionEventArgs(BUFFER_SIZE); args.Client = Client; args.tunnelHostName = tunnelHostName; args.securehost = securehost; try { //break up the line into three components (method, remote URL & Http Version) var splitBuffer = httpCmd.Split(spaceSplit, 3); if (splitBuffer.Length != 3) { TcpHelper.SendRaw(httpCmd, tunnelHostName, ref requestLines, args.IsSSLRequest, clientStreamReader.BaseStream); if (clientStream != null) clientStream.Close(); return; } var method = splitBuffer[0]; var remoteUri = splitBuffer[1]; Version version; if (splitBuffer[2] == "HTTP/1.1") { version = new Version(1, 1); } else { version = new Version(1, 0); } if (securehost != null) { remoteUri = securehost + remoteUri; args.IsSSLRequest = true; } //construct the web request that we are going to issue on behalf of the client. args.ProxyRequest = (HttpWebRequest)HttpWebRequest.Create(remoteUri.Trim()); args.ProxyRequest.Proxy = null; args.ProxyRequest.UseDefaultCredentials = true; args.ProxyRequest.Method = method; args.ProxyRequest.ProtocolVersion = version; args.ClientStream = clientStream; args.ClientStreamReader = clientStreamReader; for (int i = 1; i < requestLines.Count; i++) { var rawHeader = requestLines[i]; String[] header = rawHeader.ToLower().Trim().Split(colonSpaceSplit, 2, StringSplitOptions.None); //if request was upgrade to web-socket protocol then relay the request without proxying if ((header[0] == "upgrade") && (header[1] == "websocket")) { TcpHelper.SendRaw(httpCmd, tunnelHostName, ref requestLines, args.IsSSLRequest, clientStreamReader.BaseStream); if (clientStream != null) clientStream.Close(); return; } } ReadRequestHeaders(ref requestLines, args.ProxyRequest); int contentLen = (int)args.ProxyRequest.ContentLength; args.ProxyRequest.AllowAutoRedirect = false; args.ProxyRequest.AutomaticDecompression = DecompressionMethods.None; //If requested interception if (BeforeRequest != null) { args.RequestHostname = args.ProxyRequest.RequestUri.Host; args.RequestURL = args.ProxyRequest.RequestUri.OriginalString; args.RequestLength = contentLen; args.RequestHttpVersion = version; args.ClientPort = ((IPEndPoint)Client.Client.RemoteEndPoint).Port; args.ClientIpAddress = ((IPEndPoint)Client.Client.RemoteEndPoint).Address; args.RequestIsAlive = args.ProxyRequest.KeepAlive; BeforeRequest(null, args); } string tmpLine; if (args.CancelRequest) { if (args.RequestIsAlive) { requestLines.Clear(); while (!String.IsNullOrEmpty(tmpLine = clientStreamReader.ReadLine())) { requestLines.Add(tmpLine); } httpCmd = requestLines.Count > 0 ? requestLines[0] : null; return; } else return; } args.ProxyRequest.ConnectionGroupName = args.RequestHostname; args.ProxyRequest.AllowWriteStreamBuffering = true; //If request was modified by user if (args.RequestWasModified) { ASCIIEncoding encoding = new ASCIIEncoding(); byte[] requestBytes = encoding.GetBytes(args.RequestHtmlBody); args.ProxyRequest.ContentLength = requestBytes.Length; Stream newStream = args.ProxyRequest.GetRequestStream(); newStream.Write(requestBytes, 0, requestBytes.Length); args.ProxyRequest.BeginGetResponse(new AsyncCallback(HandleHttpSessionResponse), args); } else { //If its a post/put request, then read the client html body and send it to server if (method.ToUpper() == "POST" || method.ToUpper() == "PUT") { args.ProxyRequest.BeginGetRequestStream(new AsyncCallback(GetRequestStreamCallback), args); } else { //otherwise wait for response asynchronously args.ProxyRequest.BeginGetResponse(new AsyncCallback(HandleHttpSessionResponse), args); //Now read the next request (if keep-Alive is enabled, otherwise exit thus thread) //If client is pipeling the request, this will be immediately hit before response for previous request was made if (args.ProxyRequest.KeepAlive) { requestLines = new List<string>(); requestLines.Clear(); while (!String.IsNullOrEmpty(tmpLine = args.ClientStreamReader.ReadLine())) { requestLines.Add(tmpLine); } httpCmd = requestLines.Count() > 0 ? requestLines[0] : null; HandleHttpSessionRequest(Client, httpCmd, args.ClientStream, args.tunnelHostName, requestLines, args.ClientStreamReader, args.securehost); } } } } catch (IOException) { return; } catch (UriFormatException) { return; } catch (WebException) { return; } finally { } }
private static void HandleClient(TcpClient Client) { Stream clientStream = null; CustomBinaryReader clientStreamReader = null; StreamWriter connectStreamWriter = null; string tunnelHostName = null; int tunnelPort = 0; try { clientStream = Client.GetStream(); clientStreamReader = new CustomBinaryReader(clientStream, Encoding.ASCII); string securehost = null; List<string> requestLines = new List<string>(); string tmpLine; while (!String.IsNullOrEmpty(tmpLine = clientStreamReader.ReadLine())) { requestLines.Add(tmpLine); } //read the first line HTTP command String httpCmd = requestLines.Count > 0 ? requestLines[0] : null; if (String.IsNullOrEmpty(httpCmd)) { throw new EndOfStreamException(); } //break up the line into three components (method, remote URL & Http Version) String[] splitBuffer = httpCmd.Split(spaceSplit, 3); String method = splitBuffer[0]; String remoteUri = splitBuffer[1]; Version version; string RequestVersion; if (splitBuffer[2] == "HTTP/1.1") { version = new Version(1, 1); RequestVersion = "HTTP/1.1"; } else { version = new Version(1, 0); RequestVersion = "HTTP/1.0"; } //Client wants to create a secure tcp tunnel (its a HTTPS request) if (splitBuffer[0].ToUpper() == "CONNECT") { //Browser wants to create a secure tunnel //instead = we are going to perform a man in the middle "attack" //the user's browser should warn them of the certification errors, //to avoid that we need to install our root certficate in users machine as Certificate Authority. remoteUri = "https://" + splitBuffer[1]; tunnelHostName = splitBuffer[1].Split(':')[0]; int.TryParse(splitBuffer[1].Split(':')[1], out tunnelPort); if (tunnelPort == 0) tunnelPort = 80; var isSecure = true; for (int i = 1; i < requestLines.Count; i++) { var rawHeader = requestLines[i]; String[] header = rawHeader.ToLower().Trim().Split(colonSpaceSplit, 2, StringSplitOptions.None); if ((header[0] == "host")) { var hostDetails = header[1].ToLower().Trim().Split(':'); if (hostDetails.Length > 1) { isSecure = false; } } } requestLines.Clear(); connectStreamWriter = new StreamWriter(clientStream); connectStreamWriter.WriteLine(RequestVersion + " 200 Connection established"); connectStreamWriter.WriteLine(String.Format("Timestamp: {0}", DateTime.Now.ToString())); connectStreamWriter.WriteLine(String.Format("connection:close")); connectStreamWriter.WriteLine(); connectStreamWriter.Flush(); //If port is not 443 its not a HTTP request, so just relay if (tunnelPort != 443) { TcpHelper.SendRaw(tunnelHostName, tunnelPort, clientStreamReader.BaseStream); if (clientStream != null) clientStream.Close(); return; } //Create the fake certificate signed using our fake certificate authority Monitor.Enter(certificateAccessLock); var _certificate = ProxyServer.CertManager.CreateCertificate(tunnelHostName); Monitor.Exit(certificateAccessLock); SslStream sslStream = null; //Pinned certificate clients cannot be proxied //Example dropbox.com uses certificate pinning //So just relay the request after identifying it by first failure if (!pinnedCertificateClients.Contains(tunnelHostName) && isSecure) { sslStream = new SslStream(clientStream, true); try { //Successfully managed to authenticate the client using the fake certificate sslStream.AuthenticateAsServer(_certificate, false, SslProtocols.Tls | SslProtocols.Ssl3 | SslProtocols.Ssl2, false); } catch (AuthenticationException ex) { //if authentication failed it could be because client uses pinned certificates //So add the hostname to this list so that next time we can relay without touching it (tunnel the request) if (pinnedCertificateClients.Contains(tunnelHostName) == false) { pinnedCertificateClients.Add(tunnelHostName); } throw ex; } } else { //Hostname was a previously failed request due to certificate pinning, just relay (tunnel the request) TcpHelper.SendRaw(tunnelHostName, tunnelPort, clientStreamReader.BaseStream); if (clientStream != null) clientStream.Close(); return; } clientStreamReader = new CustomBinaryReader(sslStream, Encoding.ASCII); //HTTPS server created - we can now decrypt the client's traffic clientStream = sslStream; while (!String.IsNullOrEmpty(tmpLine = clientStreamReader.ReadLine())) { requestLines.Add(tmpLine); } //read the new http command. httpCmd = requestLines.Count > 0 ? requestLines[0] : null; if (String.IsNullOrEmpty(httpCmd)) { throw new EndOfStreamException(); } securehost = remoteUri; } //Now create the request HandleHttpSessionRequest(Client, httpCmd, clientStream, tunnelHostName, requestLines, clientStreamReader, securehost); } catch (AuthenticationException) { return; } catch (EndOfStreamException) { return; } catch (IOException) { return; } catch (UriFormatException) { return; } catch (WebException) { return; } finally { } }