【问题标题】:Trying to tunnel through https via a proxy built from ground up browser never responds back?尝试通过从头开始构建的代理通过 https 隧道浏览器永远不会响应?
【发布时间】:2011-06-21 13:17:00
【问题描述】:

由于各种原因,有必要创建我们自己的代理。一切都通过 HTTP 正常工作。一旦我们收到一个通过 SSL 建立隧道的 CONNECT,一切都会出错。我们在逻辑上所做的是将 CONNECT 解析出主机和端口,以便我们知道我们将在哪里发送未来​​的 ssl 请求并创建一个请求以发送回浏览器,说明我们已经成功地进行了 ssl 握手,如下所示:

HTTP/1.0 200 已建立连接\r\n代理代理:测试\r\n\r\n

我们期望发生的是,一旦浏览器收到这个成功的消息,就会向我们发送下一个 https 请求。然而,相反,我们一遍又一遍地收到另一个 CONNECT 请求。很明显,它不喜欢我们发回的响应。问题是我不确定为什么?是否需要通过 https 套接字发回响应?我只是不太了解这个过程,无法继续前进。

这是我的服务器类:

public class HttpServer extends Observable implements IWebServer, Runnable
{
int Port = -1;
int State = HttpState.IDLE;
ArrayList<WebTransactionEvent> History = new ArrayList<WebTransactionEvent>();
ArrayList<HttpService> myServices = new ArrayList<HttpService>();

SocketChannel myChannel = null;
boolean needResponse = false;
boolean shouldStop;
Logger logger = OpsToolsLogger.getLogger(HttpServer.class.getName());
Selector selector ;
static Hashtable<String, HttpServer> myInstances = new Hashtable<String, HttpServer>(); 
Hashtable<HttpTransaction, HttpService> myTaskTable = new Hashtable<HttpTransaction, HttpService>(); 
Vector<HttpTransaction> transactionQueue = new Vector<HttpTransaction>(); 

private HttpServer(){}

private HttpServer(int Port) 
{
    logger.log(Level.WARNING, "HttpServer: startup - listening to port: " + Port);
    this.Port = Port;
    shouldStop = false;

    // Create the selector
    try {
            selector = Selector.open();
            ServerSocketChannel serverChannel = ServerSocketChannel.open();
            serverChannel.configureBlocking(false);
            serverChannel.socket().bind(new InetSocketAddress(Port));
            this.registerSocket(serverChannel);
    } catch (IOException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
    new Thread(this).start();
}

public static HttpServer getInstance(String port)
{
    if( !myInstances.containsKey( port ) )
    {   
        myInstances.put( port, new HttpServer(Integer.parseInt(port)));
    }

    return myInstances.get(port);
}

public int getState()
{
    return State;
}

public void stop()
{
    shouldStop = true;
}

public boolean needResponse()
{
    return needResponse;
}

public HttpTransaction getNextTransaction()
{
    if(transactionQueue.isEmpty())
    {
        return null;
    }
    //System.out.println("grabbing next trans");
    HttpTransaction temp = transactionQueue.firstElement();
    transactionQueue.remove(0);//pop trans from  queue
    return temp;
}

public void dropTransaction()
{
    myTaskTable.clear();
    needResponse = false;

}

public synchronized boolean respond(HttpTransaction transaction, IHttpResponse editedResponse, boolean closeConnection)
{
    logger.log(Level.FINE, "HttpServer: responding ");
    needResponse = false;
    if(myTaskTable.isEmpty())
    {
        return false;
    }

    //see if there isn't a service object registered with that transaction
    if(!myTaskTable.containsKey(transaction))
    {
        return false;
    }

    State = HttpState.SENDING_RESPONSE;
    ManipulatedHttpTransaction myTrans = (ManipulatedHttpTransaction) transaction;
    HttpResponse response = (HttpResponse) editedResponse;
    myTrans.setManipulatedResponse( response );
    HttpService serv = myTaskTable.get(transaction);
    if(!serv.respond(myTrans.getManipulatedResponse(), closeConnection))
    {
        History.add( new WebTransactionEvent( myTrans, WebTransactionEvent.TRANSACTION_ERROR ) );
        return false;
    }

    myTaskTable.remove(transaction);

    History.add( new WebTransactionEvent( myTrans, WebTransactionEvent.TRANSACTION_COMPLETED ) );

    needResponse = !myTaskTable.isEmpty();

    return true;
}

public void registerSocket(ServerSocketChannel theSocket)
{
    try {
        theSocket.register(selector, SelectionKey.OP_ACCEPT);
    } catch (ClosedChannelException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
}

@Override
public void run()
{
    try {

        while (!shouldStop ) {
            // Wait for an event
            selector.select();

            // Get list of selection keys with pending events
            Iterator it = selector.selectedKeys().iterator();

            // Process each key
            while (it.hasNext()) {
                // Get the selection key
                SelectionKey selKey = (SelectionKey)it.next();

                // Remove it from the list to indicate that it is being processed
                it.remove();

                // Check if it's a connection request
                if (selKey.isAcceptable()) {
                    // Get channel with connection request
                   ServerSocketChannel ssChannel = (ServerSocketChannel)selKey.channel();
                   SocketChannel theChannel =  ssChannel.accept();
                   if(theChannel != null)
                    {

                        logger.log(Level.FINEST, "HttpServer: Connection established");

                        try
                        {
                            theChannel.configureBlocking(false);
                        }
                        catch(Exception e)
                        {
                            logger.log(Level.WARNING, "myChannel = null ( configureBlocking() )");
                            //bytesRead = -1;
                        }

                        myServices.add( new HttpService(this, theChannel ) );
                        needResponse = true;

                    }

                    //needResponse = !myTaskTable.isEmpty();                
                    //System.out.println("need response: "+ needResponse);

                }
            }
        }
    } catch (IOException e) {
    }
    //shutdown
    logger.log(Level.WARNING, "Server stopping - " + Port);
}

public ArrayList<WebTransactionEvent> getHistory() 
{
    return new ArrayList<WebTransactionEvent>(History);
}

public boolean switchServerToSSL()
{
    //HttpService tempService = myTaskTable.get(PendingTransaction);
    //tempService.useSSL = true;
    return true;
}

/**
 * Adds the transaction from browser to the transaction queue and also ties it to a service by adding it to myTasks map
 * @param myTrans
 * @param httpService
 */
public void addTransaction(ManipulatedHttpTransaction myTrans,
        HttpService httpService) {
    // TODO Auto-generated method stub
    //ensure vector has room to add another transaction
    if(transactionQueue.capacity() <= transactionQueue.size())
        transactionQueue.ensureCapacity(transactionQueue.size() * 2);

    transactionQueue.add(myTrans);//add transaction to queue
    myTaskTable.put(myTrans, httpService);//tie the transaction toits service
   // System.out.println("server notifying proxy: " + myTrans.getFullURL());
    this.setChanged();
    this.notifyObservers(myTrans);
}

}

这是代理中处理 CONNECT 的部分:

if(tempTransaction.getOriginatingRequest().getMethod().contentEquals("CONNECT"))
            {
                /*tell the browser that the connection exists
                 * 
                 * Each time you connect to an SSL-protected website, Burp generates a server certificate for that host, signed by the CA certificate
                 * 
                 * The server certificates presented to the client (i.e. a web browser) are dynamically generated/signed by the proxy and contain most of the same fields as the original webserver certificate. The subject DN, serial number, validity dates, and extensions are preserved. However, the issuer DN is now set to the name of the proxy's self-signed 
                 * certificate and the public/private keys of the proxy are used in creating the forged certificate. These forged certificates are cached (in memory) by the proxy, for better performance
                 */
                HttpResponse tunnelResponse = new HttpResponse("HTTP/1.0 200 Connection established\r\nProxy-agent: Ops Assistant\r\n\r\n");
                tempTransaction.setResponse(tunnelResponse);

                if(!finishResponse2(tempTransaction,tempTransaction.getResponse(), false));
                {
                    //close the connection
                }

                myServer.switchServerToSSL();
            }

这里是向浏览器发送请求的部分:

 public boolean respond(IHttpResponse response, boolean closeConnection)
{
    isCloseConnectionRequested = closeConnection;

    try 
    {
        if(useSSL)
        {
            ByteBuffer tmpBuffer = response.getData();
            tmpBuffer.position(0);
            myConnection.SecureWrite( tmpBuffer );
        }
        else
        {
            ByteBuffer tmpBuffer = response.getData();
            tmpBuffer.position(0);
            myConnection.Write(tmpBuffer);
        }
        if(closeConnection)
        {
            myChannel.close();
            myChannel = null;
        }
    }
    catch (Exception e) 
    {
        isResponded = true;
        return false;
    }

    isResponded = true;
    return true;

}

可能最重要的套接字类:

 public class SocketConnection implements IConnection
   {

public SocketChannel theSocketChannel;
public InetSocketAddress theRemoteAddress;
public int TimeoutThreshold;

private int TimeOutThreshold = 30;
private SSLEngine theSSLEngine;
private SSLContext theSSLContext;
private ByteBuffer inNetworkDataBuffer;
private ByteBuffer inAppDataBuffer;
private ByteBuffer outNetworkDataBuffer;
private ByteBuffer outAppDataBuffer;

//create outbound connection to host/port
public SocketConnection(String Host, int Port ) throws IOException
{
    theRemoteAddress = new InetSocketAddress( Host, Port);
    theSocketChannel = SocketChannel.open();
    theSocketChannel.configureBlocking(false);
    theSocketChannel.connect( theRemoteAddress );
    theSocketChannel.finishConnect();
}

//use existing socket connection
public SocketConnection(SocketChannel existingChannel) throws IOException
{
    theSocketChannel = existingChannel;
    theSocketChannel.configureBlocking(false);
    theRemoteAddress = new InetSocketAddress( existingChannel.socket().getInetAddress(), existingChannel.socket().getPort() );
}

public boolean setTimeOut(int newTimeOutThreshold) 
{
    TimeOutThreshold = newTimeOutThreshold; 
    return true;
}

public void waitForSocketToConnect() throws Exception
{
    int i = 0;
    while( !this.isConnected() )
    {
        this.finishConnect();
        if(i>=3000)
        {
            throw new Exception();
        }
        i++;

        try{Thread.sleep(10);}catch(Exception e){}
    }
}

public boolean Write( ByteBuffer DataToSend )
{
    try 
    {
        //DataToSend.flip();
        int numBytesWritten = theSocketChannel.write(DataToSend);
        try
        {
            DataToSend.compact();
        }
        catch (ReadOnlyBufferException e)
        {
            DataToSend.rewind();
        }
    } 
    catch (IOException e)
    {
        // Connection may have been closed
    }

    return true;
}

public ByteBuffer Read()
{   
    ByteBuffer ResponseBytes = ByteBuffer.allocateDirect(0);

    try 
    {
        ByteBuffer netBuffer = ByteBuffer.wrap(new byte[10000]);


        // Clear the buffer and read bytes from socket
        netBuffer.clear();

        int numBytesRead = theSocketChannel.read(netBuffer);
        if(numBytesRead == -1)
            return null; //-1 means we done return null as the flag
        netBuffer.flip();

        ByteBuffer tempBuffer = ByteBuffer.wrap(new byte[ ResponseBytes.limit() + netBuffer.limit() ]);
        ResponseBytes.position(0);
        netBuffer.position(0);
        tempBuffer.put(ResponseBytes);
        tempBuffer.put(netBuffer);
        netBuffer.flip();
        ResponseBytes = tempBuffer;

    } 
    catch (IOException e) 
    {
        // Connection may have been closed
        e = e;
        return ByteBuffer.wrap( e.getMessage().getBytes() );
    }

    return (ByteBuffer) ResponseBytes.flip();

}

public boolean SecureWrite( ByteBuffer DataToSend )
{
    boolean writeSuccess = true;

    try
    {
        //if we don't have a SSLEngine make one
        if(theSSLEngine==null)
        {
            setupSSL();
        }

        //Convert Data 
        outAppDataBuffer.clear();
        outAppDataBuffer.put(DataToSend);
        outAppDataBuffer.flip();
        SSLEngineResult sslResult = theSSLEngine.wrap(outAppDataBuffer, outNetworkDataBuffer);
        outAppDataBuffer.compact();  
        //outNetworkDataBuffer.flip();
        //int numBytesWritten = theSocketChannel.write(outNetworkDataBuffer);
        if(sslResult.getStatus() == SSLEngineResult.Status.OK)
        {
            if(sslResult.getHandshakeStatus() != SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING)
            {   
                // Write bytes
                outNetworkDataBuffer.flip();
                int numBytesWritten = theSocketChannel.write(outNetworkDataBuffer);
                outNetworkDataBuffer.compact();

                if(finishHandshake(sslResult))
                {
                    DataToSend.rewind();
                    return SecureWrite(DataToSend);
                }
                else
                {
                    return false;
                }
            }
            else
            {
                // Write bytes
                outNetworkDataBuffer.rewind();
                Write(outNetworkDataBuffer);
            }

        }
        else
        {

        }

    }
    catch(Exception e)
    {
        writeSuccess = false;
    }

    return writeSuccess;

}

public ByteBuffer SecureRead() throws ReadTimedOutException
{
    int timeElapsed = 0;
    ByteBuffer ResponseBytes = ByteBuffer.allocateDirect(0);

    try 
    {
        //if we don't have a SSLEngine make one
        if(theSSLEngine==null)
        {
            setupSSL();
        }

        int consumedCount = 0;
        SSLEngineResult sslResult;
        do
        {
            //inNetworkDataBuffer.clear();
            inNetworkDataBuffer.put( Read() );
            inNetworkDataBuffer.flip();
            sslResult = theSSLEngine.unwrap( inNetworkDataBuffer, inAppDataBuffer );
            consumedCount += sslResult.bytesConsumed();     
            inNetworkDataBuffer.compact();

            if( sslResult.getStatus() == SSLEngineResult.Status.OK ) 
            {

                if(sslResult.getHandshakeStatus() != SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING)
                {

                    if(finishHandshake(sslResult))
                    {
                        return SecureRead();
                    }
                    else
                    {
                        return ByteBuffer.allocateDirect(0);
                    }
                }
                else
                {
                    timeElapsed = 0;
                    inAppDataBuffer.flip();
                    ByteBuffer tempBuffer = ByteBuffer.wrap(new byte[ ResponseBytes.limit() + inAppDataBuffer.limit() ]);
                    ResponseBytes.position(0);
                    inAppDataBuffer.position(0);
                    tempBuffer.put(ResponseBytes);
                    tempBuffer.put(inAppDataBuffer);
                    inAppDataBuffer.flip();
                    ResponseBytes = tempBuffer;
                    ResponseBytes.flip();       
                }
            }
            else
            {
                //the status wasn't ok
                timeElapsed++;
            }
        }while(consumedCount < inNetworkDataBuffer.limit() && sslResult.getStatus() != SSLEngineResult.Status.OK);


    }
    catch (Exception e) 
    {
        System.out.println(e.toString());
    }

    if(timeElapsed>=TimeOutThreshold)
    {
        throw new ReadTimedOutException();
    }


    return ResponseBytes;
}

public boolean Disconnect()
{
    try
    {
        theSocketChannel.close();
    }
    catch(Exception e)
    {
        return false;
    }

    return true;
}

public boolean isClosed()
{
    return !theSocketChannel.isOpen();
}

@Override
public String getHost() 
{
    return theRemoteAddress.getHostName();
}

@Override
public int getPort() 
{
    return theRemoteAddress.getPort();
}

public boolean isConnected() 
{
    return theSocketChannel.isConnected();
}


@Override
public boolean hasSecure() 
{
    return true;
}

public boolean finishConnect() throws Exception
{
    return theSocketChannel.finishConnect();
}

private void setupSSL() throws NoSuchAlgorithmException, KeyManagementException
{

    //create a new SSLEngine instance
    System.setProperty( "javax.net.debug", "ssl");
    TrustManager[] tm = new TrustManager[] { new NaiveTrustManager() };
    SSLContext theSSLContext = SSLContext.getInstance ("TLS");
    theSSLContext.init( new KeyManager[0], tm, new SecureRandom( ) );

    theSSLEngine = theSSLContext.createSSLEngine( theRemoteAddress.getHostName(), theRemoteAddress.getPort());
    theSSLEngine.setUseClientMode(true);

    inNetworkDataBuffer = ByteBuffer.wrap(new byte[theSSLEngine.getSession().getPacketBufferSize()]);
    inAppDataBuffer = ByteBuffer.wrap(new byte[theSSLEngine.getSession().getApplicationBufferSize()]);
    outNetworkDataBuffer = ByteBuffer.wrap(new byte[theSSLEngine.getSession().getPacketBufferSize()]);
    outAppDataBuffer = ByteBuffer.wrap(new byte[theSSLEngine.getSession().getApplicationBufferSize()]);

}

private boolean finishHandshake(SSLEngineResult sslResult)
{   
    boolean bFinished = false;

    while(sslResult.getHandshakeStatus() != SSLEngineResult.HandshakeStatus.FINISHED)
    {
        if( sslResult.getStatus() == SSLEngineResult.Status.CLOSED ) 
        {   
            bFinished = false;
            //break;
        }


        if(sslResult.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NEED_TASK)
        {
            Runnable task;
            while ((task=theSSLEngine.getDelegatedTask()) != null)
            {
                task.run();
            }

            try 
            {
                //outNetworkDataBuffer.flip();
                sslResult = theSSLEngine.wrap(outAppDataBuffer, outNetworkDataBuffer);
                //outNetworkDataBuffer.compact();
            } 
            catch (SSLException e) 
            {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }

        }
        else if(sslResult.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NEED_WRAP)
        {
            try 
            {
                outAppDataBuffer.flip();
                sslResult = theSSLEngine.wrap(outAppDataBuffer, outNetworkDataBuffer);
                outAppDataBuffer.compact();
            } catch (SSLException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            if((sslResult.getStatus() == SSLEngineResult.Status.BUFFER_OVERFLOW) || (outNetworkDataBuffer.position() > 0))
            {
                try 
                {
                    outNetworkDataBuffer.flip();
                    int numBytesWritten = theSocketChannel.write(outNetworkDataBuffer);
                    outNetworkDataBuffer.compact();
                } 
                catch (Exception e) 
                {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        }
        else if(sslResult.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NEED_UNWRAP)
        {
            try 
            {
                int numBytes;
                //read data from the socket into inNetworkBuffer                    
                inNetworkDataBuffer.flip();
                sslResult = theSSLEngine.unwrap( inNetworkDataBuffer, inAppDataBuffer );
                inNetworkDataBuffer.compact();
                if(theSSLEngine.isInboundDone())
                {

                }
                else
                {
                    numBytes = theSocketChannel.read(inNetworkDataBuffer);
                    numBytes = numBytes;
                }

            } 
            catch (Exception e) 
            {
                e.printStackTrace();
                return false;
            }
        }
    }

    return true;
}

}

有人对如何最好地与浏览器建立这种握手有任何提示吗?

【问题讨论】:

    标签: java browser


    【解决方案1】:

    你读过Internet draft吗? CONNECT 以明文形式接收。您形成上游连接并返回“HTTP/1.0 200 Connection established”响应。之后,代理不再处理请求和响应,它只是在两个方向上复制字节,无论它们可能是什么。具体来说,代理不以任何形式或形式关心 SSL。

    【讨论】:

    • 是的,我确实阅读了一堆不同的东西,就像当我尝试将响应发送回浏览器时,在 CONNECT 的情况下它只是不断地发送相同的请求。它不是喜欢某事。但是,我将所有其他 http 请求发送回浏览器,它可以毫无问题地接受。我一定是忽略了什么。也许是因为连接它假设新的握手需要证书或其他东西。老实说,这是我需要澄清的。
    • @steve 这听起来确实不对。如果客户端返回一些它不理解的东西,它应该关闭连接。 HTTP 协议中没有任何内容允许它在同一连接上发送第二个 CONNECT。您确定所有这些 CONNECT 都通过同一连接到达吗?
    • 您确定所有这些 CONNECT 都通过同一个连接到达吗?
    • 对不起,按错键了。不,这些连接来自不同的连接。我只是无法让该死的浏览器接受我的回复。有任何想法吗?我是否需要准备一个浏览器持有的证书,然后我以某种方式将其与连接发送回浏览器?
    • @steve 所以如果他们来自不同的连接,究竟是什么问题?这是你应该期待的。如果您获得任何数据,则不可能有 SSL 证书问题。我相信无论如何 CONNECT 都是以纯文本形式发送的。
    猜你喜欢
    • 2013-05-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-08-02
    • 1970-01-01
    • 2019-01-26
    • 2013-02-11
    相关资源
    最近更新 更多