Как избежать хранения паролей в открытом виде для определения ресурса tomcat server.xml источника данных?

Определение ресурса в server.xml tomcat выглядит примерно так...

<Resource
    name="jdbc/tox"
    scope="Shareable"
    type="javax.sql.DataSource"
    url="jdbc:oracle:thin:@yourDBserver.yourCompany.com:1521:yourDBsid"
    driverClassName="oracle.jdbc.pool.OracleDataSource"
    username="tox"
    password="toxbaby"
    maxIdle="3"
    maxActive="10"
    removeAbandoned="true"
    removeAbandonedTimeout="60"
    testOnBorrow="true"
    validationQuery="select * from dual"
    logAbandoned="true"
    debug="99"/>

Пароль в открытом виде. Как этого избежать?


person dacracot    schedule 24.09.2008    source источник


Ответы (9)


Как было сказано ранее, шифрование паролей просто перемещает проблему в другое место.

Во всяком случае, это довольно просто. Просто напишите класс со статическими полями для вашего секретного ключа и так далее, и статические методы для шифрования, расшифровки ваших паролей. Зашифруйте свой пароль в файле конфигурации Tomcat (server.xml или yourapp.xml...), используя этот класс.

А чтобы расшифровать пароль "на лету" в Tomcat, расширьте BasicDataSourceFactory DBCP и используйте эту фабрику в своем ресурсе.

Это будет выглядеть так:

    <Resource
        name="jdbc/myDataSource"
        auth="Container"
        type="javax.sql.DataSource"
        username="user"
        password="encryptedpassword"
        driverClassName="driverClass"
        factory="mypackage.MyCustomBasicDataSourceFactory"
        url="jdbc:blabla://..."/>

И для пользовательской фабрики:

package mypackage;

....

public class MyCustomBasicDataSourceFactory extends org.apache.tomcat.dbcp.dbcp.BasicDataSourceFactory {

@Override
public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable environment) throws Exception {
    Object o = super.getObjectInstance(obj, name, nameCtx, environment);
    if (o != null) {
        BasicDataSource ds = (BasicDataSource) o;
        if (ds.getPassword() != null && ds.getPassword().length() > 0) {
            String pwd = MyPasswordUtilClass.unscramblePassword(ds.getPassword());
            ds.setPassword(pwd);
        }
        return ds;
    } else {
        return null;
    }
}

Надеюсь это поможет.

person Jerome Delattre    schedule 10.12.2008
comment
Для меня это не добавляет никакой безопасности, потому что злоумышленник всегда может декомпилировать вашу банку, чтобы получить пароль, который вы жестко закодировали в файле класса... - person dmansfield; 26.06.2012
comment
ИМО, это все же лучше, чем иметь пароль в открытом виде, где каждый тупой ребенок со сценарием будет знать, как его искать. Это несколько поднимает планку, не так ли? - person Pierre Henry; 28.02.2014
comment
@dmansfield - Я что-то пропустил? Я не вижу, где пароль будет жестко закодирован в любом файле класса в описанном выше подходе? - person Ajoy Bhatia; 20.12.2014
comment
@AjoyBhatia - Как MyPasswordUtilClass.unscramblePassword(String s) выполняет расшифровку? Он должен иметь доступный секрет для расшифровки или, что еще хуже, использовать алгоритм без ключей (например, rot13) для расшифровки. Так что на самом деле это просто подталкивает проблему к MyPasswordUtilClass — откуда берется секрет? - person dmansfield; 05.01.2015
comment
вы можете прочитать пароль из внешнего файла, который доступен для чтения только пользователю, под которым работает tomcat (при этом сохраняя его отдельно от остальной части конфигурации) - person Andre Holzner; 20.08.2018
comment
@AndreHolzner Наличие отдельного файла, доступного только для чтения tomcat, который содержит удобочитаемый пароль для расшифровки учетных данных в context.xml, совсем не более безопасно, чем наличие удобочитаемых учетных данных в context.xml, если последний также доступен для чтения только tomcat. Оба будут скомпрометированы точно такими же условиями. Однако это намного более обременительно для администраторов и разработчиков. То, что вы описываете, рассматривается в первом предложении этого ответа. - person Ricardo van den Broek; 22.07.2020

У Tomcat есть Часто задаваемые вопросы о паролях, в которых конкретно рассматривается ваш вопрос. Короче говоря: держите пароль в чистоте и правильно заблокируйте свой сервер.

На этой странице также предлагаются некоторые предложения о том, как безопасность за счет неясности может быть использована для прохождения контрольного списка аудитора.

person Ryan    schedule 15.06.2015
comment
Тем не менее, вы можете захотеть сохранить файл, содержащий пароль, отдельно от остальной части конфигурации. В некоторых случаях хочется поместить конфигурационные файлы в какую-нибудь систему контроля версий, которая будет доступна для чтения всем вовлеченным разработчикам, а не только системным администраторам. - person Andre Holzner; 20.08.2018
comment
Это почти ничего не решает. Я не могу передать файл конфигурации с паролями в систему управления версиями, но context.xml должен быть одинаковым для всех развертываний, поэтому их аудитор — подставное лицо. - person wmakley; 26.03.2019
comment
к вашему сведению: я взял на себя смелость обновить ссылку FAQ на новое место - person Olaf Kock; 09.10.2019

Tomcat должен знать, как подключиться к базе данных, поэтому ему нужен доступ к текстовому паролю. Если пароль зашифрован, Tomcat должен знать, как его расшифровать, поэтому вы только перемещаете проблему в другое место.

Настоящая проблема заключается в следующем: кто может получить доступ к server.xml, кроме Tomcat? Решение состоит в том, чтобы предоставить доступ для чтения к server.xml только пользователю root, требуя, чтобы Tomcat был запущен с привилегиями root: если злонамеренный пользователь получает привилегии root в системе, потеря пароля базы данных, вероятно, не будет проблемой.

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

person gameame    schedule 26.09.2008
comment
Я далеко не эксперт по безопасности, но мне кажется, что сам Tomcat является одной из вероятных входных дверей для атаки (используя уязвимость самого Tomcat или одной из библиотек, используемых приложением, например, недавние проблемы со Struts 2) и поэтому не следует давать привилегии суперпользователя!? - person Pierre Henry; 06.03.2014
comment
Я согласен. Я больше не использую Tomcat, но я бы посоветовал запустить его без привилегий root. В любом случае, кто может читать каталог tomcat, может прочитать пароль БД. Насколько я знаю, это неизбежная проблема с любым используемым вами языком/фреймворком. - person gameame; 14.03.2014
comment
Это такой плохой ответ, почему меня смущают положительные голоса. Держите вещи в безопасности. Да, шифруйте пароли, если можете, и НЕТ, вам не нужно запускать tomcat от имени пользователя root (это не только плохо, но и утверждение делает ложное предположение, что вы должны это сделать). И получение доступа к системе, потеря пароля или его легкое обнаружение - это ОСНОВНАЯ проблема, поскольку взлом может не быть обнаружен в течение некоторого времени, а ваша БД может содержать конфиденциальные данные. База данных — это GEM в системе и главная причина, по которой кто-то попытается взломать ваш компьютер. - person ; 13.10.2014
comment
Предоставление доступа для чтения к server.xml только пользователю root — плохая практика. Вместо этого следует создать нового пользователя с минимальными привилегиями, необходимыми для запуска Tomcat. Этот пользователь должен ТОЛЬКО иметь возможность запускать/останавливать сервер. Дайте права на чтение всех файлов конфигурации только этому пользователю. Затем отключите прямой вход в систему от имени этого пользователя. - person Ajoy Bhatia; 20.12.2014

Как упоминал @Ryan, перед внедрением этого решения прочитайте Tomcat Password FAQ. Вы только добавляете неясности, а не безопасности.

Ответ @Jerome Delattre будет работать для простых источников данных JDBC, но не для более сложных, которые подключаются как часть конструкции источника данных (например, oracle.jdbc.xa.client.OracleXADataSource).

Это альтернативный подход, который изменяет пароль перед вызовом существующей фабрики. Ниже приведен пример фабрики для базового источника данных и фабрики для источника данных XA, совместимого с Atomikos JTA.

Основной пример:

public class MyEncryptedPasswordFactory extends BasicDataSourceFactory {

    @Override
    public Object getObjectInstance(Object obj, Name name, Context context, Hashtable<?, ?> environment)
            throws Exception {
        if (obj instanceof Reference) {
            Reference ref = (Reference) obj;
            DecryptPasswordUtil.replacePasswordWithDecrypted(ref, "password");
            return super.getObjectInstance(obj, name, context, environment);
        } else {
            throw new IllegalArgumentException(
                    "Expecting javax.naming.Reference as object type not " + obj.getClass().getName());
        }
    }
}

Пример атомикоса:

public class MyEncryptedAtomikosPasswordFactory extends EnhancedTomcatAtomikosBeanFactory {
    @Override
    public Object getObjectInstance(Object obj, Name name, Context context, Hashtable<?, ?> environment)
            throws NamingException {
        if (obj instanceof Reference) {
            Reference ref = (Reference) obj;
            DecryptPasswordUtil.replacePasswordWithDecrypted(ref, "xaProperties.password");
            return super.getObjectInstance(obj, name, context, environment);
        } else {
            throw new IllegalArgumentException(
                    "Expecting javax.naming.Reference as object type not " + obj.getClass().getName());
        }
    }
}

Обновление значения пароля в справочнике:

public class DecryptPasswordUtil {

    public static void replacePasswordWithDecrypted(Reference reference, String passwordKey) {
        if(reference == null) {
            throw new IllegalArgumentException("Reference object must not be null");
        }

        // Search for password addr and replace with decrypted
        for (int i = 0; i < reference.size(); i++) {
            RefAddr addr = reference.get(i);
            if (passwordKey.equals(addr.getType())) {
                if (addr.getContent() == null) {
                    throw new IllegalArgumentException("Password must not be null for key " + passwordKey);
                }
                String decrypted = yourDecryptionMethod(addr.getContent().toString());
                reference.remove(i);
                reference.add(i, new StringRefAddr(passwordKey, decrypted));
                break;
            }
        }
    }
}

Как только файл .jar, содержащий эти классы, окажется в пути к классам Tomcat, вы можете обновить свой server.xml, чтобы использовать их.

<Resource factory="com.mycompany.MyEncryptedPasswordFactory" username="user" password="encryptedPassword" ...other options... />

<Resource factory="com.mycompany.MyEncryptedAtomikosPasswordFactory" type="com.atomikos.jdbc.AtomikosDataSourceBean" xaProperties.user="user" xaProperties.password="encryptedPassword" ...other options... />
person JustinKSU    schedule 02.09.2016
comment
Спасибо, это сработало для меня, в то время как ранее предложенный подход Джерома не сработал (он вызвал исключение для super.getObjectInstance, который, похоже, пытался получить доступ к источнику данных с использованием незашифрованного пароля). В качестве небольшого дополнения, мы используем сеансовую БД, а также БД приложений; Я обнаружил, что, чтобы разрешить шифрование паролей для источника данных БД сеанса, нам пришлось скопировать jar, содержащий пользовательские классы, в каталог Tomcat /lib, а также включить их в веб-приложения, иначе они были бы недоступны для сеанса Tomcat. управляющий делами. - person David Ellis; 31.01.2017
comment
Правильный. Вы можете поместить файлы драйвера в каталог, который мы открыли для Tomcat, используя файл catalina.properties. - person JustinKSU; 01.02.2017
comment
Нужно ли для этого добавлять какой-либо файл Jars в папку Lib? - person greencheese; 16.09.2020
comment
@greencheese Вам не нужно, если вы сделаете предоставленную зависимость. - person JustinKSU; 17.09.2020

После 4 часов работы, поиска вопросов и ответов я получил решение. Основываясь на ответе @Jerome Delattre, вот полный код (с конфигурацией источника данных JNDI).

Контекст.xml

<Resource
    name="jdbc/myDataSource"
    auth="Container"
    type="javax.sql.DataSource"
    username="user"
    password="encryptedpassword"
    driverClassName="driverClass"
    factory="mypackage.MyCustomBasicDataSourceFactory"
    url="jdbc:blabla://..."/>

Фабрика пользовательских источников данных:

package mypackage;

public class MyCustomBasicDataSourceFactory extends org.apache.tomcat.dbcp.dbcp.BasicDataSourceFactory {
    @Override
    public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable environment) throws Exception {
        Object o = super.getObjectInstance(obj, name, nameCtx, environment);
        if (o != null) {
            BasicDataSource ds = (BasicDataSource) o;
            if (ds.getPassword() != null && ds.getPassword().length() > 0) {
                String pwd = MyPasswordUtilClass.unscramblePassword(ds.getPassword());
                ds.setPassword(pwd);
            }
            return ds;
        } else {
            return null;
        }
    }
}

Компонент источника данных:

@Bean
public DataSource dataSource() {
    DataSource ds = null;
    JndiTemplate jndi = new JndiTemplate();
    try {
        ds = jndi.lookup("java:comp/env/jdbc/myDataSource", DataSource.class);
    } catch (NamingException e) {
        log.error("NamingException for java:comp/env/jdbc/myDataSource", e);
    }
    return ds;
}
person HolloW    schedule 10.01.2018
comment
Это единственный способ? Возможно ли, что это может зависеть от поставщика БД? - person David Brossard; 01.03.2018
comment
@DavidBrossard нет, я использую один и тот же код для серверов postgresql и sql и работает отлично. - person HolloW; 20.03.2018

Примечание.

Вы можете использовать WinDPAPI для шифрования и расшифровки данных.

public class MyDataSourceFactory extends DataSourceFactory{

private static WinDPAPI winDPAPI;

protected static final String DATA_SOURCE_FACTORY_PROP_PASSWORD = "password";

@Override
public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable environment) throws Exception{

    Reference ref = (Reference) obj;
    for (int i = 0; i < ref.size(); i++) {
        RefAddr ra = ref.get(i);
        if (ra.getType().equals(DATA_SOURCE_FACTORY_PROP_PASSWORD)) {

            if (ra.getContent() != null && ra.getContent().toString().length() > 0) {
                String pwd = getUnprotectedData(ra.getContent().toString());
                ref.remove(i);
                ref.add(i, new StringRefAddr(DATA_SOURCE_FACTORY_PROP_PASSWORD, pwd));
            }

            break;
        }
    }

    return super.getObjectInstance(obj, name, nameCtx, environment);
  }
}
person Gawri Edussuriya    schedule 04.05.2018

Проблема. Как уже отмечалось, шифрование учетных данных в context.xml при сохранении ключа дешифрования в следующем файле буквально только устраняет проблему. Поскольку пользователю, обращающемуся к context.xml, также потребуется доступ к ключу дешифрования, все учетные данные по-прежнему будут скомпрометированы, если скомпрометировано приложение или пользователь ОС.

Решение. Единственное решение, которое повысит безопасность, — это полное удаление ключа дешифрования из всей установки. Этого можно добиться, попросив кого-то ввести пароль в вашем приложении при запуске, который затем используется для расшифровки всех учетных данных.

Дополнительная отсрочка решения. В большинстве случаев такой пароль, скорее всего, должен быть известен нескольким администраторам и/или разработчикам. Используя решение для совместного использования паролей, которое позволяет совместно использовать пароли (например, 1Password), безопасность затем откладывается до индивидуального мастер-пароля каждого администратора/разработчика, используемого для разблокировки его личного хранилища паролей.

Возможная деградация решения/сарказм. В худшем случае при такой настройке кто-то просто сохранит свой мастер-пароль на стикере, прикрепленном к монитору. Является ли это более безопасным, чем наличие ключа дешифрования в файле рядом с зашифрованными значениями, вероятно, должно быть отдельным вопросом SO или, возможно, будущим исследованием.

person Ricardo van den Broek    schedule 22.07.2020

С учетом всего вышесказанного, если вы все же хотите избежать простых текстовых паролей, вы можете использовать алгоритм хеширования, такой как SHA-256 или (предпочтительно) SHA-512. При создании пароля получите хешированное значение и сохраните его, а не пароль. Когда пользователь входит в систему, хешируйте пароль и убедитесь, что он соответствует сохраненному хешированному паролю. Алгоритмы хеширования переносят строку символов (или число) из небольшого пространства строки (или числа) в пространство гораздо большего размера таким способом, который требует больших затрат для реверсирования.

person stuartw    schedule 03.09.2016
comment
это совершенно бесполезно для подключения к базе данных - person spy; 16.01.2017
comment
Ваше решение описывает общее решение для хранения пароля в любой системе, требующей аутентификации, например в базе данных. Сама база данных, вероятно, уже делает это внутри. То, что запрашивает OP, - это способ сохранить пароль в виде неясного текста в конфигурации tomcat, который позволяет Tomcat аутентифицировать соединение с базой данных с использованием этого пароля. - person Volksman; 06.04.2018

Мы используем SHA1CryptoServiceProvider C#.

print(SHA1CryptoServiceProvider sHA1Hasher = new SHA1CryptoServiceProvider();
        ASCIIEncoding enc = new ASCIIEncoding();

        byte[] arrbytHashValue = sHA1Hasher.ComputeHash(enc.GetBytes(clearTextPW));
        string HashData = System.BitConverter.ToString(arrbytHashValue);
        HashData = HashData.Replace("-", "");
        if (HashData == databaseHashedPassWO)
        {
            return true;
        }
        else
        {
            return false;
        });

)

person Brad8118    schedule 24.09.2008
comment
Не уверен, что это связано с конфигурационными файлами tomcat. - person dacracot; 24.09.2008