Skip to content

Commit 068ac7c

Browse files
committed
SECURITY-3319
1 parent 263f264 commit 068ac7c

File tree

2 files changed

+100
-1
lines changed

2 files changed

+100
-1
lines changed

src/main/java/org/jenkinsci/plugins/gitserver/ssh/AbstractGitCommand.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import org.kohsuke.args4j.Argument;
88
import org.kohsuke.args4j.CmdLineException;
99
import org.kohsuke.args4j.CmdLineParser;
10+
import org.kohsuke.args4j.ParserProperties;
1011

1112
/**
1213
* Implements the SSH {@link Command} for the server side git command.
@@ -24,7 +25,8 @@ abstract class AbstractGitCommand extends AsynchronousCommand {
2425
@Override
2526
protected final int runCommand() throws Exception {
2627
try {
27-
new CmdLineParser(this).parseArgument(getCmdLine().subList(1,getCmdLine().size()));
28+
ParserProperties properties = ParserProperties.defaults().withAtSyntax(false);
29+
new CmdLineParser(this, properties).parseArgument(getCmdLine().subList(1,getCmdLine().size()));
2830
} catch (CmdLineException e) {
2931
throw new AbortException(e.getMessage());
3032
}
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
package org.jenkinsci.plugins.gitserver.ssh;
2+
3+
import hudson.Functions;
4+
import java.io.ByteArrayOutputStream;
5+
import java.nio.file.Files;
6+
import java.nio.file.Path;
7+
import java.util.EnumSet;
8+
import org.apache.sshd.client.SshClient;
9+
import org.apache.sshd.client.channel.ClientChannel;
10+
import org.apache.sshd.client.channel.ClientChannelEvent;
11+
import org.apache.sshd.client.future.ConnectFuture;
12+
import org.apache.sshd.client.keyverifier.AcceptAllServerKeyVerifier;
13+
import org.apache.sshd.client.session.ClientSession;
14+
import org.jenkinsci.main.modules.cli.auth.ssh.PublicKeySignatureWriter;
15+
import org.jenkinsci.main.modules.cli.auth.ssh.UserPropertyImpl;
16+
import org.jenkinsci.main.modules.sshd.SSHD;
17+
import org.junit.Before;
18+
import org.junit.Rule;
19+
import org.junit.Test;
20+
import org.jvnet.hudson.test.Issue;
21+
import org.jvnet.hudson.test.JenkinsRule;
22+
23+
import java.io.IOException;
24+
import java.net.InetSocketAddress;
25+
import java.security.KeyPair;
26+
import java.security.KeyPairGenerator;
27+
import java.security.NoSuchAlgorithmException;
28+
import java.util.concurrent.TimeUnit;
29+
30+
import static org.hamcrest.MatcherAssert.assertThat;
31+
import static org.hamcrest.Matchers.containsString;
32+
import static org.hamcrest.Matchers.not;
33+
import static org.junit.Assert.assertTrue;
34+
import static org.junit.Assume.assumeFalse;
35+
36+
/**
37+
* This class contains code borrowed and adapted from the Jenkins SSHD plugin.
38+
* Original source: org.jenkinsci.main.modules.sshd.SSHDTest.java
39+
*/
40+
public class ReceivePackCommandTest {
41+
42+
@Rule
43+
public JenkinsRule j = new JenkinsRule();
44+
45+
@Before
46+
public void setUp() {
47+
assumeFalse(Functions.isWindows());
48+
}
49+
50+
@Test
51+
@Issue("SECURITY-3319")
52+
public void shouldNotParseAtChar() throws Exception {
53+
hudson.model.User enabled = hudson.model.User.getOrCreateByIdOrFullName("enabled");
54+
KeyPair keyPair = generateKeys(enabled);
55+
SSHD server = SSHD.get();
56+
server.setPort(0);
57+
server.start();
58+
59+
Path tempPath = Files.createTempFile("tempFile", ".txt");
60+
tempPath.toFile().deleteOnExit();
61+
String content = "AtGotParsed";
62+
Files.write(tempPath, content.getBytes());
63+
64+
try (SshClient client = SshClient.setUpDefaultClient()) {
65+
client.setServerKeyVerifier(AcceptAllServerKeyVerifier.INSTANCE);
66+
client.start();
67+
ConnectFuture future = client.connect("enabled", new InetSocketAddress(server.getActualPort()));
68+
try (ClientSession session = future.verify(10, TimeUnit.SECONDS).getSession()) {
69+
session.addPublicKeyIdentity(keyPair);
70+
assertTrue(session.auth().await(10, TimeUnit.SECONDS));
71+
72+
String command = "git-receive-pack @" + tempPath;
73+
try (ClientChannel channel = session.createExecChannel(command)) {
74+
ByteArrayOutputStream errorStream = new ByteArrayOutputStream();
75+
channel.setErr(errorStream);
76+
channel.open().verify(5, TimeUnit.SECONDS);
77+
channel.waitFor(EnumSet.of(ClientChannelEvent.CLOSED), TimeUnit.SECONDS.toMillis(5));
78+
79+
String errorMessage = errorStream.toString();
80+
assertThat(errorMessage, containsString("@" + tempPath));
81+
assertThat(errorMessage, not(containsString(content)));
82+
}
83+
}
84+
}
85+
}
86+
87+
private static KeyPair generateKeys(hudson.model.User user) throws NoSuchAlgorithmException, IOException {
88+
// I'd prefer to generate Ed25519 keys here, but the API is too awkward currently
89+
// ECDSA keys would be even more awkward as we'd need a copy of the curve parameters
90+
KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
91+
generator.initialize(2048);
92+
KeyPair keyPair = generator.generateKeyPair();
93+
String encodedPublicKey = "ssh-rsa " + new PublicKeySignatureWriter().asString(keyPair.getPublic());
94+
user.addProperty(new UserPropertyImpl(encodedPublicKey));
95+
return keyPair;
96+
}
97+
}

0 commit comments

Comments
 (0)