X11 auth spoofing implementation

Andre Charbonneau <[email protected]>
Newsgroups gmane.comp.java.sshtools.user
Message-ID <[email protected]>
Hi,
I've written a bit of code to implement x11 auth spoofing in sshtools 
and I though I'd contribute this back to the sshtools project.  But I'm 
using a modified version of the sshtools code, so I can't simply create 
a diff using the official sshtools sources.  So attached is a diff of 
the code that was modified against my own version of sshtools, but it 
should be easy to port it to the official sshtools head branch.

Cheers!
	Andre



-- 
Andre Charbonneau

Grid Computing and Applications Specialist
Research Computing Support, IMSB
National Research Council Canada
Ottawa, ON, Canada K1A 0R6

-- Linux uptime: 111 days 20:31 --
diff.txt (text/plain, 14.3 KB)
Index: src/com/sshtools/common/util/X11Util.java
===================================================================
--- src/com/sshtools/common/util/X11Util.java	(revision 238)
+++ src/com/sshtools/common/util/X11Util.java	(working copy)
@@ -28,6 +28,7 @@
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 import com.sshtools.j2ssh.configuration.ConfigurationLoader;
+import java.util.Vector;
 
 /**
  *
@@ -137,4 +138,38 @@
 
     return b.toString();
   }
+  
+  public static String bytesToHex(byte[] bytes)
+  {
+    StringBuffer buf = new StringBuffer();
+    for(byte b : bytes)
+    {
+      String h = Integer.toHexString(b & 0xff);
+
+      if (h.length() == 1) {
+        buf.append(0);
+      }
+
+      buf.append(h);      
+    }
+    return buf.toString();
+  }
+  
+  /**
+   * This method is used to take a hex string and convert it into an
+   * array of bytes, taking 2 characters at a time.
+   */
+  public static byte[] hexToBytes(String s)
+  {
+    String cs = new String(s);
+    byte[] bytes = new byte[cs.length()/2];
+    int i = 0;
+    while(cs.length() >= 2)
+    {
+      bytes[i] = (byte)Integer.parseInt(cs.substring(0,2).toUpperCase(), 16);
+      cs = cs.substring(2);
+      i++;
+    }
+    return bytes;
+  }
 }
Index: src/com/sshtools/j2ssh/connection/Channel.java
===================================================================
--- src/com/sshtools/j2ssh/connection/Channel.java	(revision 238)
+++ src/com/sshtools/j2ssh/connection/Channel.java	(working copy)
@@ -21,7 +21,10 @@
 
 package com.sshtools.j2ssh.connection;
 
+import com.sshtools.common.util.X11Util;
+import com.sshtools.j2ssh.forwarding.ForwardingChannel;
 import java.io.IOException;
+import java.util.Arrays;
 import java.util.Iterator;
 import java.util.Vector;
 
@@ -67,6 +70,10 @@
   private boolean remoteHasClosed = false;
   private String name = "Unnamed Channel";
   private Vector eventListeners = new Vector();
+  
+  // The attributes below are used to implement x11 auth spoofing. -Andre-
+  private boolean xauthSpoofingDone = false;
+  protected String fakeCookie = null;
 
   /**
    * Creates a new Channel object.
@@ -160,6 +167,83 @@
           localWindow.increaseWindowSpace(windowSpace);
         }
 
+        /*
+         * Do X11 auth spoofing cookie replacement. -Andre-
+         *
+         * This code is based on the code of the X11_open_helper(Buffer *b)
+         * function from channels.c in the OpenSSH sources.
+         */
+        if(getChannelType().equals(ForwardingChannel.X11_FORWARDING_CHANNEL) && X11AuthSpoofer.getInstance().doAuthSpoofing() && !xauthSpoofingDone)
+        {
+          // Try to find fake cookie in channel data...
+          byte[] messageData = msg.getChannelData();
+          
+          int protocolLength;
+          int dataLength;
+          if(msg.getChannelDataLength() >= 12)
+          {
+            if((messageData[0]&0xff) == 0x42) // Byte order MSB first
+            {
+              protocolLength = (messageData[6]&0xff) * 256 + (messageData[7]&0xff);
+              dataLength = (messageData[8]&0xff) * 256 + (messageData[9]&0xff);
+            }
+            else if((messageData[0]&0xff) == 0x6c) // Byte order LSB first.
+            {
+              protocolLength = (messageData[6]&0xff) + 256 * (messageData[7]&0xff);
+              dataLength = (messageData[8]&0xff) + 256 * (messageData[9]&0xff);
+            }
+            else
+            {
+              throw new IOException("Initial X11 packet contains bad byte order byte: " + messageData[0]);
+            }
+            
+            log.debug("Protocol length: " + protocolLength + ", data length: " + dataLength);
+            
+            // Check if whole packet is there.
+            if(messageData.length < (12 + ((protocolLength + 3) & ~3) + ((dataLength + 3) & ~3)))
+            {
+              throw new IOException("Attempt to do x11 auth spoofing on incomplete ssh message.");
+            }
+            
+            // Check auth protocol name.
+            byte[] protocolName = new byte[protocolLength];
+            System.arraycopy(messageData, 12, protocolName, 0, protocolLength);
+            if(!Arrays.equals(protocolName, new String("MIT-MAGIC-COOKIE-1").getBytes()))
+            {
+              throw new IOException("X11 authentication protocol mismatch.  This version of sshtools only supports MIT-MAGIC-COOKIE-1.");
+            }
+            
+            // Check to see if received cookie matches the a fake cookie.
+            byte[] receivedCookie = new byte[dataLength];
+            System.arraycopy(messageData, 12 + protocolLength + ((-protocolLength) & 3), receivedCookie, 0, dataLength);
+            String receivedCookieAsString = X11Util.bytesToHex(receivedCookie);
+            log.debug("Received X11 cookie: " + receivedCookieAsString);
+            String realCookie = X11AuthSpoofer.getInstance().getRealCookie(receivedCookieAsString);
+            if(realCookie != null)
+            {
+              log.debug("X11 fake cookie matches.");
+              
+              // Replace the fake cookie with the real cookie in the SSH message.
+              // We need to go from a string representation back to a byte representation.
+              byte[] realCookieAsBytes = X11Util.hexToBytes(realCookie);
+              
+              // Make sure byte array for real cookie is the same length as byte array for the received
+              // fake cookie.
+              if(realCookieAsBytes.length != receivedCookie.length)
+              {
+                throw new IOException("Cookie length mismatch.");
+              }
+              
+              System.arraycopy(realCookieAsBytes, 0, messageData, 12 + protocolLength + ((-protocolLength) & 3), dataLength);
+            }
+            else
+            {
+              log.warn("Could not match fake X11 cookie " + receivedCookieAsString);
+            }
+          }
+          xauthSpoofingDone = true;
+        }
+        
         onChannelData(msg);
 
         Iterator it = eventListeners.iterator();
Index: src/com/sshtools/j2ssh/connection/SocketChannel.java
===================================================================
--- src/com/sshtools/j2ssh/connection/SocketChannel.java	(revision 238)
+++ src/com/sshtools/j2ssh/connection/SocketChannel.java	(working copy)
@@ -70,6 +70,8 @@
    */
   protected void onChannelData(SshMsgChannelData msg) throws IOException {
     try {
+      // Do X11 auth spoofing cookie replacement here? -Andre-
+      
       socket.getOutputStream().write(msg.getChannelData());
     }
     catch (IOException ex) {
Index: src/com/sshtools/j2ssh/connection/X11AuthSpoofer.java
===================================================================
--- src/com/sshtools/j2ssh/connection/X11AuthSpoofer.java	(revision 0)
+++ src/com/sshtools/j2ssh/connection/X11AuthSpoofer.java	(revision 0)
@@ -0,0 +1,182 @@
+/**
+ * Copyright (c) 2006, National Research Council of Canada
+ * All rights reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this
+ * software and associated documentation files (the "Software"), to deal in the Software
+ * without restriction, including without limitation the rights to use, copy, modify, merge,
+ * publish, distribute, and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice(s) and this licence appear in all copies of the Software or
+ * substantial portions of the Software, and that both the above copyright notice(s) and this
+ * license appear in supporting documentation.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS NOTICE BE LIABLE
+ * FOR ANY CLAIM, OR ANY DIRECT, INDIRECT, SPECIAL OR CONSEQUENTIAL
+ * DAMAGES, OR ANY DAMAGES WHATSOEVER (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWSOEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN AN ACTION OF
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OF THE SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ * Except as contained in this notice, the name of a copyright holder shall NOT be used in
+ * advertising or otherwise to promote the sale, use or other dealings in this Software
+ * without specific prior written authorization.  Title to copyright in this software and any
+ * associated documentation will at all times remain with copyright holders.
+ */
+
+
+/*
+ * X11CookieContainer.java
+ *
+ * Created on May 24, 2006, 3:09 PM
+ *
+ */
+
+package com.sshtools.j2ssh.connection;
+import java.util.HashMap;
+import java.util.Map;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * This singleton class is used to implement X11 authentication spoofing.
+ * It will act as a cookie container that will hold the fake X11 cookies that
+ * are generated when connecting to remote systems.
+ *
+ * Note that authentication spoofing is enabled by default, and can be switched
+ * off by setting the X11_NO_AUTH_SPOOFING system property to a non-null
+ * value.
+ *
+ * @author andre
+ */
+public class X11AuthSpoofer
+{
+  private static Log log = LogFactory.getLog(X11AuthSpoofer.class);
+  private static X11AuthSpoofer instance = null;
+  
+  // The internal mapping that will hold the fake cookies and map them to the
+  // real cookies.  Currently they should all map to the same real cookie, but
+  // the mapping is implemented anyways for added flexibility.
+  private Map cookieMap = null;
+  
+  
+  /**
+   * Use this method to get the singleton instance of this class.
+   */
+  static public synchronized X11AuthSpoofer getInstance()
+  {
+    if(instance == null)
+    {
+      instance = new X11AuthSpoofer();
+    }
+    
+    return instance;
+  }
+  
+  
+  
+  /** Creates a new instance of X11CookieContainer */
+  protected X11AuthSpoofer()
+  {
+    cookieMap = new HashMap();
+    log.debug("Instance created.");
+  }
+  
+  /**
+   * Test method that determines if X11 authentication spoofing should be
+   * done or not.
+   *
+   * @return true if X11 authentication spoofing will be performed, false
+   * otherwise
+   */
+  public boolean doAuthSpoofing()
+  {
+    return (System.getProperty("X11_NO_AUTH_SPOOFING") == null);
+  }
+  
+  
+  /**
+   * Adds a fake cookie to real cookie mapping into the container.  If the
+   * fake cookie is already present in the container it will be overwritten.
+   *
+   * @param fakeCookie the fake cookie
+   * @param realCookie the real cookie
+   */
+  public void addCookiePair(String fakeCookie, String realCookie)
+  {
+    if(fakeCookie == null || realCookie == null)
+    {
+      throw new IllegalArgumentException("fakeCookie or realCookie cannot be null");
+    }
+    
+    cookieMap.put(fakeCookie, realCookie);
+    log.debug("Fake X11 cookie " + fakeCookie + " added to container.");
+  }
+  
+  
+  /**
+   * Gets the real cookie that is mapped to a fake cookie.
+   *
+   * @param fakeCookie the fake cookie to look for in the container
+   * @return the real cookie that is mapped to the fake cookie, or null if the
+   * fake cookie could not be found in the container
+   */
+  public String getRealCookie(String fakeCookie)
+  {
+    if(fakeCookie == null)
+    {
+      throw new IllegalArgumentException("fakeCookie cannot be null");
+    }
+    
+    if(cookieMap != null)
+    {
+      return (String)cookieMap.get(fakeCookie);
+    }
+    return null;
+  }
+  
+  
+  /**
+   * Removes a cookie mapping from the container.  If the cookie does not exist,
+   * then this method does nothing.
+   *
+   * @param fakeCookie the fake cookie to remove from the container
+   */
+  public void remove(String fakeCookie)
+  {
+    if(fakeCookie == null)
+    {
+      throw new IllegalArgumentException("fakeCookie cannot be null");
+    }
+    
+    if(cookieMap != null)
+    {
+      if(cookieMap.remove(fakeCookie) != null)
+      {
+        log.debug("Fake X11 cookie " + fakeCookie + " removed from container.");
+      }
+    }
+  }
+  
+  
+  /**
+   * Removes all cookies from the container.
+   */
+  public void clear()
+  {
+    if(cookieMap != null)
+    {
+      cookieMap.clear();
+      log.debug("X11 cookie container cleared.");
+    }    
+  }
+}
Index: src/com/sshtools/j2ssh/session/SessionChannelClient.java
===================================================================
--- src/com/sshtools/j2ssh/session/SessionChannelClient.java	(revision 238)
+++ src/com/sshtools/j2ssh/session/SessionChannelClient.java	(working copy)
@@ -41,6 +41,8 @@
 import com.sshtools.j2ssh.io.UnsignedInteger32;
 import com.sshtools.j2ssh.subsystem.SubsystemClient;
 import com.sshtools.j2ssh.transport.SshMessageStore;
+import com.sshtools.common.util.X11Util;
+import com.sshtools.j2ssh.connection.X11AuthSpoofer;
 
 /**
  *
@@ -219,7 +221,19 @@
       IOException {
     log.debug("Requesting X11 forwarding for display " + display
               + " using cookie " + cookie);
-
+    
+    if(X11AuthSpoofer.getInstance().doAuthSpoofing())
+    {
+      // Do cookie spoofing here.  Create a fake cookie and send that to the
+      // remote system instead of the real cookie.  But keep the mapping in
+      // memory in order to be able to map it back to the real cookie later when
+      // authentication happens. -Andre-
+      fakeCookie = X11Util.createCookie(Integer.toString(display));
+      X11AuthSpoofer.getInstance().addCookiePair(fakeCookie, cookie);
+      cookie = fakeCookie;
+      log.debug("Using fake X11 cookie " + cookie);
+    }
+    
     ByteArrayWriter baw = new ByteArrayWriter();
     baw.writeBoolean(false);
     baw.writeString("MIT-MAGIC-COOKIE-1");
@@ -473,6 +487,13 @@
     if (exitCode != null) {
       log.debug("Exit code " + exitCode.toString());
     }
+    
+    // Remove fake cookie from cookie container, if applicable.
+    if(X11AuthSpoofer.getInstance().doAuthSpoofing() && (fakeCookie != null))
+    {
+      X11AuthSpoofer.getInstance().remove(fakeCookie);
+      fakeCookie = null;
+    }
   }
 
   /**
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.