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;
+ }
}
/**