标题: Java底层修改对XXE利用FTP通道的影响 创建: 2019-11-01 11:22 更新: 2019-11-12 14:17 链接: https://scz.617.cn/web/201911011122.txt -------------------------------------------------------------------------- 目录: ☆ 背景介绍 ☆ sun.net.ftp.impl.FtpClient.issueCommand() ☆ sun.net.www.protocol.ftp.FtpURLConnection.checkURL() ☆ 参考资源 -------------------------------------------------------------------------- ☆ 背景介绍 本篇没有讲述XXE利用FTP通道,也不提供任何额外XXE利用技巧,仅仅是从Java逆向 工程角度探究一下Java底层修改对XXE利用FTP通道的影响,属于"无用"知识,当然, 这要看你如何定义"有用"。 过去在XXE利用中,有时会利用FTP命令向外传递被窃取的数据,这些数据本身可能包 含\r、\n等字符,xxer([2])就是用来接收这些数据的。 不知从何时开始,Java底层对FTP通道做了修改,比如FTP命令中部不得包含\n,这使 得XXE利用FTP通道时被严重干挠。 ☆ sun.net.ftp.impl.FtpClient.issueCommand() 参[1],tint0提到Java底层对FtpClient的修改。 用JD-GUI查看: X:\Java\jdk1.8.0_221\jre\lib\rt.jar sun.net.ftp.impl.FtpClient.class -------------------------------------------------------------------------- private boolean issueCommand ( String paramString ) throws IOException, FtpProtocolException { if ( !isConnected() ) { throw new IllegalStateException( "Not connected" ); } if ( this.replyPending ) { try { completePending(); } catch ( FtpProtocolException localFtpProtocolException1 ) { } } /* * * 如果FTP命令中包含\n,抛出异常 */ if ( paramString.indexOf( '\n' ) != -1 ) { FtpProtocolException localFtpProtocolException2 = new FtpProtocolException( "Illegal FTP command" ); localFtpProtocolException2.initCause( new IllegalArgumentException( "Illegal carriage return" ) ); throw localFtpProtocolException2; } sendServer( paramString + "\r\n" ); return readReply(); } -------------------------------------------------------------------------- X:\Java\jdk1.8.0_221\src.zip 这里面没有FtpClient.java。网上找到一个老版本源码: http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/jdk8-b132/src/share/classes/sun/net/ftp/impl/FtpClient.java -------------------------------------------------------------------------- /** * Sends a command to the FTP server and returns the error code * (which can be a "success") sent by the server. * * @param cmd * @return true if the command was successful * @throws IOException */ private boolean issueCommand ( String cmd ) throws IOException { if ( !isConnected() ) { throw new IllegalStateException( "Not connected" ); } if ( replyPending ) { try { completePending(); } catch ( sun.net.ftp.FtpProtocolException e ) { // ignore... } } sendServer( cmd + "\r\n" ); return readReply(); } -------------------------------------------------------------------------- 老版JDK 8源码中没有检查FTP命令中是否包含\n,JDK 1.8.0_221有检查。 按leadroyal的说法([4]),2018年3月,7u141、8u162修改了issueCommand()。 http://hg.openjdk.java.net/jdk7u/jdk7u/jdk/file/jdk7u141-b00/src/share/classes/sun/net/ftp/impl/FtpClient.java http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/jdk8u162-b00/src/share/classes/sun/net/ftp/impl/FtpClient.java ☆ sun.net.www.protocol.ftp.FtpURLConnection.checkURL() vulnd_xxe([3])是个XXE靶场,跟xxer是同一个作者,如果在最新版Java 8上测试 vulnd_xxe+xxer,FTP通道已经无法向外传递被窃取的数据。 最初发现FTP通道被阻断,以为就是tint0说的issueCommand()中的修改所致。当时想 "redefine sun.net.ftp.impl.FtpClient",把对\n的检查临时Patch掉,先测了 vulnd_xxe+xxer再说。为此对issueCommand()设断,试图看其形参内容,结果发现数 据中包含\n时,根本断不下来,同时在xxer中看不到任何FTP数据进来;说明在流程 到达issueCommand()之前另有一些检查存在,想把它找出来。 先用不带\n的数据断在issueCommand(),获取其调用栈回溯: @sun.net.ftp.impl.FtpClient.issueCommand() at sun.net.ftp.impl.FtpClient.issueCommandCheck(FtpClient.java:550) at sun.net.ftp.impl.FtpClient.tryLogin(FtpClient.java:1029) at sun.net.ftp.impl.FtpClient.login(FtpClient.java:1056) at sun.net.www.protocol.ftp.FtpURLConnection.connect(FtpURLConnection.java:328) at sun.net.www.protocol.ftp.FtpURLConnection.getInputStream(FtpURLConnection.java:417) at com.sun.org.apache.xerces.internal.impl.XMLEntityManager.setupCurrentEntity(XMLEntityManager.java:623) at com.sun.org.apache.xerces.internal.impl.XMLEntityManager.startEntity(XMLEntityManager.java:1304) at com.sun.org.apache.xerces.internal.impl.XMLEntityManager.startEntity(XMLEntityManager.java:1240) at com.sun.org.apache.xerces.internal.impl.XMLDTDScannerImpl.startPE(XMLDTDScannerImpl.java:741) at com.sun.org.apache.xerces.internal.impl.XMLDTDScannerImpl.skipSeparator(XMLDTDScannerImpl.java:2110) at com.sun.org.apache.xerces.internal.impl.XMLDTDScannerImpl.scanDecls(XMLDTDScannerImpl.java:2073) at com.sun.org.apache.xerces.internal.impl.XMLDTDScannerImpl.scanDTDInternalSubset(XMLDTDScannerImpl.java:363) 对各层函数设断,用带\n的数据测试,在前述调用栈回溯中找到最后一个被调用到的 函数: sun.net.www.protocol.ftp.FtpURLConnection.getInputStream // 无命中 com.sun.org.apache.xerces.internal.impl.XMLEntityManager.setupCurrentEntity // 有命中 简单跟踪setupCurrentEntity()内部执行时(只遍历一层),发现抛出异常: java.lang.IllegalArgumentException 重新调试,查看IllegalArgumentException的调用栈回溯: @java.lang.IllegalArgumentException.() at sun.net.www.protocol.ftp.FtpURLConnection.checkURL(FtpURLConnection.java:164) at sun.net.www.protocol.ftp.FtpURLConnection.(FtpURLConnection.java:188) at sun.net.www.protocol.ftp.Handler.openConnection(Handler.java:61) at sun.net.www.protocol.ftp.Handler.openConnection(Handler.java:56) at java.net.URL.openConnection(URL.java:1001) at com.sun.org.apache.xerces.internal.impl.XMLEntityManager.setupCurrentEntity(XMLEntityManager.java:621) at com.sun.org.apache.xerces.internal.impl.XMLEntityManager.startEntity(XMLEntityManager.java:1304) sun.net.www.protocol.ftp.FtpURLConnection.checkURL()中检查是否出现\n,检查 失败时,根本不会创建FTP连接,issueCommand()新增的\n检查没有派上用场。 这是1.8.0_232中的checkURL(): -------------------------------------------------------------------------- static URL checkURL(URL u) throws IllegalArgumentException { if (u != null && u.toExternalForm().indexOf(10) > -1) { MalformedURLException mfue = new MalformedURLException("Illegal character in URL"); throw new IllegalArgumentException(mfue.getMessage(), mfue); } String s = IPAddressUtil.checkAuthority(u); if (s != null) { MalformedURLException mfue = new MalformedURLException(s); throw new IllegalArgumentException(mfue.getMessage(), mfue); } return u; } FtpURLConnection(URL url, Proxy p) { super(FtpURLConnection.checkURL(url)); this.instProxy = p; this.host = url.getHost(); this.port = url.getPort(); String userInfo = url.getUserInfo(); if (userInfo != null) { int delimiter = userInfo.indexOf(58); if (delimiter == -1) { this.user = ParseUtil.decode(userInfo); this.password = null; } else { this.user = ParseUtil.decode(userInfo.substring(0, delimiter++)); this.password = ParseUtil.decode(userInfo.substring(delimiter)); } } } public FtpURLConnection(URL url) { this(url, null); } -------------------------------------------------------------------------- 看一下老版本源码: http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/jdk8-b132/src/share/classes/sun/net/www/protocol/ftp/FtpURLConnection.java -------------------------------------------------------------------------- /** * Creates an FtpURLConnection from a URL. * * @param url The URL to retrieve or store. */ public FtpURLConnection(URL url) { this(url, null); } /** * Same as FtpURLconnection(URL) with a per connection proxy specified */ FtpURLConnection(URL url, Proxy p) { super(url); instProxy = p; host = url.getHost(); port = url.getPort(); String userInfo = url.getUserInfo(); if (userInfo != null) { // get the user and password int delimiter = userInfo.indexOf(':'); if (delimiter == -1) { user = ParseUtil.decode(userInfo); password = null; } else { user = ParseUtil.decode(userInfo.substring(0, delimiter++)); password = ParseUtil.decode(userInfo.substring(delimiter)); } } } -------------------------------------------------------------------------- 老版本中FtpURLConnection()没有调用checkURL(),也没有checkURL()。 如果XXE利用FTP通道失败,最好反编译rt.jar确认。 Zimbra 8.6.0用了自带的Java 8,前述checkURL()、issueCommand()对\n的检查都不 存在,从而可以利用FTP命令向外传递被窃取的数据。 ☆ 参考资源 [1] A Saga of Code Executions on Zimbra - [2019-03-13] https://blog.tint0.com/2019/03/a-saga-of-code-executions-on-zimbra.html [2] xxer https://github.com/TheTwitchy/xxer [3] vulnd_xxe https://github.com/TheTwitchy/vulnd_xxe [4] 9102年Java里的XXE - [2019-07-17] https://www.leadroyal.cn/?p=914 https://github.com/LeadroyaL/java_xxe_2019