Android - Повторное использование сеанса FTPS - Без поля sessionHostPortCache

Я разрабатываю приложение для Android с Android Studio, и я хочу использовать FTP для отправки файлов на сервер. Мне нужно поддерживать повторное использование сеанса, поскольку сервер размещен у поставщика услуг хостинга, и у них, очевидно, включено повторное использование сеанса.


В этом посте я обнаружил, что этот прием отражения используется многими, чтобы сделать это возможным:

// adapted from:
// https://trac.cyberduck.io/browser/trunk/ftp/src/main/java/ch/cyberduck/core/ftp/FTPClient.java
@Override
protected void _prepareDataSocket_(final Socket socket) throws IOException {
    if (socket instanceof SSLSocket) {
        // Control socket is SSL
        final SSLSession session = ((SSLSocket) _socket_).getSession();
        if (session.isValid()) {
            final SSLSessionContext context = session.getSessionContext();
            try {
                final Field sessionHostPortCache = context.getClass().getDeclaredField("sessionHostPortCache");
                sessionHostPortCache.setAccessible(true);
                final Object cache = sessionHostPortCache.get(context);
                final Method method = cache.getClass().getDeclaredMethod("put", Object.class, Object.class);
                method.setAccessible(true);
                method.invoke(cache, String
                        .format("%s:%s", socket.getInetAddress().getHostName(), String.valueOf(socket.getPort()))
                        .toLowerCase(Locale.ROOT), session);
                method.invoke(cache, String
                        .format("%s:%s", socket.getInetAddress().getHostAddress(), String.valueOf(socket.getPort()))
                        .toLowerCase(Locale.ROOT), session);
            } catch (NoSuchFieldException e) {
                throw new IOException(e);
            } catch (Exception e) {
                throw new IOException(e);
            }
        } else {
            throw new IOException("Invalid SSL Session");
        }
    }
}

Вот код, который использует SSLSessionReuseFTPSClient:

System.setProperty("jdk.tls.useExtendedMasterSecret", "false");

String host = "xxxxxxxx";
String user = "xxxxxxxx";
String password = "xxxxxxxx";
String directory = "xxxxxxxx";

ProtocolCommandListener listener = new MyProtocolCommandListener(host);

SSLSessionReuseFTPSClient client = new SSLSessionReuseFTPSClient("TLS", false);
client.addProtocolCommandListener(listener);

try {
    client.connect(host);
    client.execPBSZ(0);
    client.execPROT("P");

    if (client.login(user, password)) {
        Log.w("myApp", "Logged in as " + user + " on " + host + ".");
    }

    if (client.changeWorkingDirectory(directory)) {
        Log.w("myApp", "Working directory changed to " + directory + ".");
    }

    client.enterLocalPassiveMode();

    InputStream input = new FileInputStream(file);

    if (client.storeFile(file.getName(), input)) {
        Log.w("myApp", "File " + file.getName() + " sent to " + host + ".");
    } else {
        Log.w("myApp", "Couldn't send file " + file.getName() + " to " + host + ".");
        Log.w("myApp", "Reply: " + client.getReplyString());
    }

    client.logout();
    client.disconnect();
} catch (Exception e) {
    e.printStackTrace();
}

Я сначала попробовал в Eclipse, и это сработало. Затем я попытался реализовать это в своем приложении для Android, но получаю такую ​​ошибку:

java.io.IOException: java.lang.NoSuchFieldException: No field sessionHostPortCache in class Lcom/android/org/conscrypt/ClientSessionContext; (declaration of 'com.android.org.conscrypt.ClientSessionContext' appears in /system/framework/conscrypt.jar)

Я заметил, что когда я выполняю код в Eclipse и распечатываю context имя класса, я получаю это: sun.security.ssl.SSLSessionContextImpl, но в Android Studio я получаю: com.android.org.conscrypt.ClientSessionContext.


Я искал почти два дня подряд, и у меня просто недостаточно опыта, чтобы знать, что происходит. Почемуcom.android.org.conscrypt.ClientSessionContext использовать вместо sun.security.ssl.SSLSessionContextImpl? Я проверяю файл java.security и из того, что вижу,sun.security.ssl.SSLSessionContextImpl должен быть использован.

Если бы кто-то мог мне с этим помочь, я был бы безумно благодарен.

Наконец, вот некоторая информация, которая может быть полезна:

Android Studio 3.6.2
commons-net-3.6
openjdk version "1.8.0_212-release"
OpenJDK Runtime Environment (build 1.8.0_212-release-1586-b04)
OpenJDK 64-Bit Server VM (build 25.212-b04, mixed mode)

Спасибо!

2 ответа

Основываясь на ту же идею решения на Java Cyberduck, я перекрытый в "prepareDataSocket метод" из FTPSClient сделать это работает на Android. Я тестирую его в Android 9.0 и Android 5.1.1, и он отлично работает. Код был:

import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSessionContext;
import javax.net.ssl.SSLSocket;

import org.apache.commons.net.ftp.FTPSClient;

public class TLSAndroidFTPSClient extends FTPSClient
{
    @Override
    protected void _prepareDataSocket_(final Socket socket) throws IOException
    {
        if (socket instanceof SSLSocket)
        {
            final SSLSession sessionAux = ((SSLSocket) _socket_).getSession();
            if(sessionAux.isValid())
            {
                final SSLSessionContext sessionsContext = sessionAux.getSessionContext();
                try
                {
                    // lets find the sessions in the context' cache
                    final Field fieldSessionsInContext =sessionsContext.getClass().getDeclaredField("sessionsByHostAndPort");
                    fieldSessionsInContext.setAccessible(true);
                    final Object sessionsInContext = fieldSessionsInContext.get(sessionsContext);

                    // lets find the session of our conexion
                    int portNumb=sessionAux.getPeerPort();
                    Set keys=((HashMap)sessionsInContext).keySet();
                    if(keys.size()==0)
                        throw new IOException("Invalid SSL Session");
                    final Field fieldPort=((keys.toArray())[0]).getClass().getDeclaredField("port");
                    fieldPort.setAccessible(true);
                    int i=0;
                    while(i<keys.size() && ((int)fieldPort.get((keys.toArray())[i]))!=portNumb)
                        i++;

                    if(i<keys.size())   // it was found
                    {
                        Object ourKey=(keys.toArray())[i];
                        // building two objects like our key but with the new port and the host Name and host address
                        final Constructor construc =ourKey.getClass().getDeclaredConstructor(String.class, int.class);
                        construc.setAccessible(true);
                        Object copy1Key=construc.newInstance(socket.getInetAddress().getHostName(),socket.getPort());
                        Object copy2Key=construc.newInstance(socket.getInetAddress().getHostAddress(),socket.getPort());

                        // getting our session
                        Object ourSession=((HashMap)sessionsInContext).get(ourKey);

                        // Lets add the pairs copy1Key-ourSession & copy2Key-ourSession to the context'cache
                        final Method method = sessionsInContext.getClass().getDeclaredMethod("put", Object.class, Object.class);
                        method.setAccessible(true);
                        method.invoke(sessionsInContext,copy1Key,ourSession);
                        method.invoke(sessionsInContext,copy2Key,ourSession);
                    }
                    else
                        throw new IOException("Invalid SSL Session");

                } catch (NoSuchFieldException e) {
                    throw new IOException(e);
                } catch (Exception e) {
                    throw new IOException(e);
                }
            } else {
                throw new IOException("Invalid SSL Session");
            }
        }
    }
}

Я столкнулся с той же проблемой в проекте Android с API 33. Попытка получить поле: sessionContext.getClass().getDeclaredField("sessionsByHostAndPort") приводит к:

      Accessing hidden field (blocked, reflection, denied)
W/Accessing hidden field Lcom/android/org/conscrypt/ClientSessionContext;
->sessionsByHostAndPort:Ljava/util/Map; (blocked, reflection, denied)
W/System.err: java.lang.NoSuchFieldException: 
No field sessionsByHostAndPort in class Lcom/android/org/conscrypt/ClientSessionContext; 
(declaration of 'com.android.org.conscrypt.ClientSessionContext' 
appears in /apex/com.android.conscrypt/javalib/conscrypt.jar)

W/System.err:     at java.lang.Class.getDeclaredField(Native Method)
W/System.err:     at org.apache.commons.net.ftp.FTPSClient._prepareDataSocket_(FTPSClient.java:409)
W/System.err:     at org.apache.commons.net.ftp.FTPSClient._openDataConnection_(FTPSClient.java:310)
W/System.err:     at org.apache.commons.net.ftp.FTPClient._openDataConnection_(FTPClient.java:638)
W/System.err:     at org.apache.commons.net.ftp.FTPClient.initiateListParsing(FTPClient.java:1988)
W/System.err:     at org.apache.commons.net.ftp.FTPClient.initiateListParsing(FTPClient.java:2084)
W/System.err:     at org.apache.commons.net.ftp.FTPClient.listFiles(FTPClient.java:2282)
W/System.err:     at org.apache.commons.net.ftp.FTPClient.listFiles(FTPClient.java:2248)
W/System.err:     at de.medialux.powerftp.activities.RemoteExplorerActivityFTP$23.doInBackground(RemoteExplorerActivityFTP.java:2186)
W/System.err:     at de.medialux.powerftp.activities.RemoteExplorerActivityFTP$23.doInBackground(RemoteExplorerActivityFTP.java:1978)
W/System.err:     at android.os.AsyncTask$3.call(AsyncTask.java:394)
W/System.err:     at java.util.concurrent.FutureTask.run(FutureTask.java:264)
W/System.err:     at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:305)
W/System.err:     at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1137)
W/System.err:     at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:637)
W/System.err:     at java.lang.Thread.run(Thread.java:1012)

Начиная с уровня API 28 Android запрещает доступ к некоторым скрытым функциям API (см.: https://developer.android.com/distribute/best-practices/develop/restrictions-non-sdk-interfaces). Многие необходимые функции, используемые PoC, занесены в черный список и вызывают исключения при попытке доступа к ним через API Reflection.

После некоторого времени исследований я нашел библиотеку com.github.ChickenHook:RestrictionBypass.

https://github.com/ChickenHook/RestrictionBypass

После добавления библиотеки в проект в файле приложения gradle предоставляется доступ к полям в ClientSessionContext.

Другие вопросы по тегам