From d0ac8d7962ea82574cf78318b1dcd57a6b62338f Mon Sep 17 00:00:00 2001 From: ggrandes Date: Sun, 28 Jun 2020 18:45:28 +0200 Subject: [PATCH] limit Diffie-Hellman Group Exchange prime sizes --- .../javastack/sftpserver/ModuliFilter.java | 82 +++++++++++++++++++ .../java/org/javastack/sftpserver/Server.java | 73 +++++++++++++++++ src/main/resources/sftpd.policy | 1 + 3 files changed, 156 insertions(+) create mode 100644 src/main/java/org/javastack/sftpserver/ModuliFilter.java diff --git a/src/main/java/org/javastack/sftpserver/ModuliFilter.java b/src/main/java/org/javastack/sftpserver/ModuliFilter.java new file mode 100644 index 0000000..f2b5b71 --- /dev/null +++ b/src/main/java/org/javastack/sftpserver/ModuliFilter.java @@ -0,0 +1,82 @@ +package org.javastack.sftpserver; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; + +/** + * @see org.apache.sshd.server.kex.Moduli + */ +public class ModuliFilter { + public static final int MODULI_TYPE_SAFE = 2; + public static final int MODULI_TESTS_COMPOSITE = 0x01; + + public static ArrayList filterModuli(final URL url, final int minSize, final int maxSize) + throws IOException { + final ArrayList parsed = new ArrayList<>(); + try (BufferedReader r = new BufferedReader(new InputStreamReader(url.openStream(), StandardCharsets.UTF_8))) { + for (String line = r.readLine(); line != null; line = r.readLine()) { + line = line.trim(); + if (line.isEmpty()) { + continue; + } + + if (line.startsWith("#")) { + continue; + } + + String[] parts = line.split("\\s+"); + // Ensure valid line + if (parts.length != 7) { + continue; + } + + // Discard moduli types which are not safe + int type = Integer.parseInt(parts[1]); + if (type != MODULI_TYPE_SAFE) { + continue; + } + + // Discard untested moduli + int tests = Integer.parseInt(parts[2]); + if (((tests & MODULI_TESTS_COMPOSITE) != 0) || ((tests & ~MODULI_TESTS_COMPOSITE) == 0)) { + continue; + } + + // Discard untried + int tries = Integer.parseInt(parts[3]); + if (tries == 0) { + continue; + } + + // Discard unwanted sizes + int size = Integer.parseInt(parts[4]); + if ((size < minSize) || (size > maxSize)) { + continue; + } + + parsed.add(line); + } + + return parsed; + } + } + + public static void writeModuli(final File file, final List lines) throws IOException { + try (BufferedWriter r = new BufferedWriter( + new OutputStreamWriter(new FileOutputStream(file), StandardCharsets.UTF_8))) { + for (final String str : lines) { + r.write(str); + r.newLine(); + } + } + } +} diff --git a/src/main/java/org/javastack/sftpserver/Server.java b/src/main/java/org/javastack/sftpserver/Server.java index 290af5f..037b229 100644 --- a/src/main/java/org/javastack/sftpserver/Server.java +++ b/src/main/java/org/javastack/sftpserver/Server.java @@ -21,6 +21,7 @@ import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; +import java.net.URL; import java.nio.file.FileSystem; import java.nio.file.LinkOption; import java.nio.file.Path; @@ -64,6 +65,7 @@ import org.apache.sshd.server.channel.ChannelSession; import org.apache.sshd.server.channel.ChannelSessionFactory; import org.apache.sshd.server.command.Command; +import org.apache.sshd.server.kex.Moduli; import org.apache.sshd.server.keyprovider.AbstractGeneratorHostKeyProvider; import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider; import org.apache.sshd.server.scp.ScpCommandFactory; @@ -244,6 +246,56 @@ private void hackVersion() { PropertyResolverUtils.updateProperty(sshd, ServerFactoryManager.SERVER_IDENTIFICATION, "SSHD"); } + /** + * Filter the moduli file contains prime numbers and generators used by + * Diffie-Hellman Group Exchange. + * + * @see org.apache.sshd.common.kex.BuiltinDHFactories#dhgex256 + * diffie-hellman-group-exchange-sha256 + * @see org.apache.sshd.common.kex.BuiltinDHFactories#dhgex + * diffie-hellman-group-exchange-sha1 + * @see org.apache.sshd.server.kex.DHGEXServer + * @see org.apache.sshd.server.kex.Moduli + * @see http://manpages.ubuntu.com/manpages/focal/man5/moduli.5.html + */ + private void hackModuliDHGEX() { + URL srcModuli = null; + final File sysLinuxModuli = new File("/etc/ssh/moduli"); + if (sysLinuxModuli.canRead()) { + try { + srcModuli = sysLinuxModuli.toURI().toURL(); + LOG.info("Linux moduli file: " + sysLinuxModuli); + } catch (IOException e) { + } + } else { + final String moduliPath = Moduli.INTERNAL_MODULI_RESPATH; + srcModuli = Moduli.class.getResource(moduliPath); + if (srcModuli == null) { + LOG.warn("Missing internal moduli file: " + moduliPath); + } + } + if (srcModuli != null) { + final File newModuli = new File(System.getProperty("java.io.tmpdir", "/tmp/"), "moduli.sftpd"); + if (!newModuli.exists() // create + || (newModuli.length() <= 0) // empty + || (System.currentTimeMillis() - newModuli.lastModified() > TimeUnit.DAYS.toMillis(1))) { // 1day + try { + LOG.info("Filtering moduli file:" + srcModuli.toExternalForm()); + final List data = ModuliFilter.filterModuli(srcModuli, // + db.getMinSizeDHGEX(), db.getMaxSizeDHGEX()); + ModuliFilter.writeModuli(newModuli, data); + } catch (IOException e) { + LOG.error("Error filtering moduli: " + e, e); + } + } + if ((newModuli != null) && newModuli.canRead() && (newModuli.length() > 0)) { + LOG.warn("Using moduli file: " + newModuli); + PropertyResolverUtils.updateProperty(sshd, ServerFactoryManager.MODULI_URL, + newModuli.toURI().toString()); + } + } + } + public void start() { LOG.info("Starting"); logger = new ServiceLogger(); @@ -252,6 +304,7 @@ public void start() { sshd = SshServer.setUpDefaultServer(); LOG.info("SSHD " + sshd.getVersion()); hackVersion(); + hackModuliDHGEX(); setupFactories(); setupKeyPair(); setupScp(); @@ -335,6 +388,8 @@ public boolean authenticate(final String username, final PublicKey key, final Se static class Config { // @see https://stribika.github.io/2015/01/04/secure-secure-shell.html // @see http://manpages.ubuntu.com/manpages/focal/man5/sshd_config.5.html + public static final int DEFAULT_DHGEX_MIN = 2000; + public static final int DEFAULT_DHGEX_MAX = 8200; /** * man 5 sshd_config : KexAlgorithms * @@ -383,6 +438,8 @@ static class Config { public static final String PROP_KEX_ALGORITHMS = "kexalgorithms"; public static final String PROP_CIPHERS = "ciphers"; public static final String PROP_MACS = "macs"; + public static final String PROP_DHGEX_MIN = "dhgex-min"; + public static final String PROP_DHGEX_MAX = "dhgex-max"; // HtPasswd config public static final String PROP_HTPASSWD = BASE + "." + "htpasswd"; public static final String PROP_HT_HOME = "homedirectory"; @@ -477,6 +534,22 @@ public String getMacs() { return value; } + public int getMinSizeDHGEX() { + final String value = getValue(PROP_DHGEX_MIN); + if (value == null) { + return DEFAULT_DHGEX_MIN; + } + return Integer.parseInt(value); + } + + public int getMaxSizeDHGEX() { + final String value = getValue(PROP_DHGEX_MAX); + if (value == null) { + return DEFAULT_DHGEX_MAX; + } + return Integer.parseInt(value); + } + // User config private final String getValue(final String user, final String key) { if ((user == null) || (key == null)) diff --git a/src/main/resources/sftpd.policy b/src/main/resources/sftpd.policy index f0c9ab2..4f01c10 100644 --- a/src/main/resources/sftpd.policy +++ b/src/main/resources/sftpd.policy @@ -52,6 +52,7 @@ grant { permission java.io.FilePermission "${java.io.tmpdir}${/}-", "read, write, delete"; // permission java.io.FilePermission "/etc/resolv.conf", "read"; + permission java.io.FilePermission "/etc/ssh/moduli", "read"; permission java.io.FilePermission "${sftp.home}/-", "read"; permission java.io.FilePermission "${sftp.home}/keys/hostkey.ser", "read, write"; permission java.io.FilePermission "${sftp.home}/keys/hostkey.pem", "read, write";