Skip to content

Commit 374642b

Browse files
Boris Yaoraul-arabaolaza
authored andcommitted
SECURITY-3301
1 parent e792f5a commit 374642b

File tree

8 files changed

+387
-69
lines changed

8 files changed

+387
-69
lines changed

src/main/java/htmlpublisher/HtmlPublisherTarget.java

Lines changed: 27 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,22 @@
11
package htmlpublisher;
22

3-
import java.io.File;
4-
import java.io.IOException;
5-
import java.nio.charset.StandardCharsets;
6-
import java.util.Objects;
7-
import java.util.regex.Matcher;
8-
import java.util.regex.Pattern;
9-
3+
import com.infradna.tool.bridge_method_injector.WithBridgeMethods;
104
import edu.umd.cs.findbugs.annotations.NonNull;
11-
import javax.servlet.ServletException;
12-
5+
import hudson.Extension;
6+
import hudson.FilePath;
7+
import hudson.Util;
8+
import hudson.model.AbstractDescribableImpl;
9+
import hudson.model.Action;
10+
import hudson.model.AbstractItem;
11+
import hudson.model.Run;
12+
import hudson.model.DirectoryBrowserSupport;
13+
import hudson.model.Job;
14+
import hudson.model.ProminentProjectAction;
15+
import hudson.model.AbstractBuild;
16+
import hudson.model.InvisibleAction;
17+
import hudson.model.Descriptor;
18+
import hudson.util.HttpResponses;
19+
import jenkins.model.RunAction2;
1320
import org.apache.commons.codec.binary.Hex;
1421
import org.apache.commons.lang.StringUtils;
1522
import org.kohsuke.accmod.Restricted;
@@ -20,23 +27,15 @@
2027
import org.kohsuke.stapler.StaplerResponse;
2128
import org.owasp.encoder.Encode;
2229

23-
import com.infradna.tool.bridge_method_injector.WithBridgeMethods;
30+
import javax.servlet.ServletException;
31+
import java.io.File;
32+
import java.io.IOException;
33+
import java.nio.charset.StandardCharsets;
34+
import java.util.Objects;
35+
import java.util.regex.Matcher;
36+
import java.util.regex.Pattern;
2437

25-
import hudson.Extension;
26-
import hudson.FilePath;
27-
import hudson.Util;
28-
import hudson.model.AbstractBuild;
29-
import hudson.model.AbstractDescribableImpl;
30-
import hudson.model.AbstractItem;
31-
import hudson.model.Action;
32-
import hudson.model.Descriptor;
33-
import hudson.model.DirectoryBrowserSupport;
34-
import hudson.model.InvisibleAction;
35-
import hudson.model.Job;
36-
import hudson.model.ProminentProjectAction;
37-
import hudson.model.Run;
38-
import hudson.util.HttpResponses;
39-
import jenkins.model.RunAction2;
38+
import static hudson.Functions.htmlAttributeEscape;
4039

4140
/**
4241
* A representation of an HTML directory to archive and publish.
@@ -48,7 +47,7 @@ public class HtmlPublisherTarget extends AbstractDescribableImpl<HtmlPublisherTa
4847
/**
4948
* The name of the report to display for the build/project, such as "Code Coverage"
5049
*/
51-
private final String reportName;
50+
private String reportName;
5251

5352
/**
5453
* The path to the HTML report directory relative to the workspace.
@@ -183,15 +182,8 @@ public void setReportTitles(String reportTitles) {
183182
this.reportTitles = StringUtils.trim(reportTitles);
184183
}
185184

186-
/**
187-
* Actually not safe, this allowed directory traversal (SECURITY-784).
188-
* @return Returns a string with replaced whitespaces by underscores.
189-
*/
190-
private String getLegacySanitizedName() {
191-
String safeName = this.reportName;
192-
safeName = safeName.replace(" ", "_");
193-
return safeName;
194-
}
185+
//Add this for testing purposes
186+
public void setReportName(String reportName) {this.reportName = StringUtils.trim(reportName);}
195187

196188
public String getSanitizedName() {
197189
return sanitizeReportName(this.reportName, getEscapeUnderscores());
@@ -313,11 +305,6 @@ protected File dir() {
313305
if (run != null) {
314306
File javadocDir = getBuildArchiveDir(run);
315307

316-
if (!javadocDir.exists()) {
317-
javadocDir = getBuildArchiveDir(run, getLegacySanitizedName());
318-
}
319-
// TODO not sure about this change
320-
321308
if (javadocDir.exists()) {
322309
for (HTMLBuildAction a : run.getActions(HTMLBuildAction.class)) {
323310
if (a.getHTMLTarget().getReportName().equals(getHTMLTarget().getReportName())) {
@@ -329,15 +316,7 @@ protected File dir() {
329316
}
330317
}
331318

332-
// SECURITY-784: prefer safe over legacy, but if neither exists, return safe dir
333319
File projectArchiveDir = getProjectArchiveDir(this.project);
334-
if (projectArchiveDir.exists()) {
335-
return projectArchiveDir;
336-
}
337-
File legacyProjectArchiveDir = getProjectArchiveDir(this.project, getLegacySanitizedName());
338-
if (legacyProjectArchiveDir.exists()) {
339-
return legacyProjectArchiveDir;
340-
}
341320
return projectArchiveDir;
342321
}
343322

@@ -440,15 +419,7 @@ public String getBackToUrl() {
440419

441420
@Override
442421
protected File dir() {
443-
// SECURITY-784: prefer safe over legacy, but if neither exists, return safe dir
444422
File buildArchiveDir = getBuildArchiveDir(this.build);
445-
if (buildArchiveDir.exists()) {
446-
return buildArchiveDir;
447-
}
448-
File legacyBuildArchiveDir = getBuildArchiveDir(this.build, getLegacySanitizedName());
449-
if (legacyBuildArchiveDir.exists()) {
450-
return legacyBuildArchiveDir;
451-
}
452423
return buildArchiveDir;
453424
}
454425

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
package htmlpublisher;
2+
3+
import hudson.model.FreeStyleProject;
4+
import org.junit.Assert;
5+
import org.junit.Rule;
6+
import org.junit.Test;
7+
import org.jvnet.hudson.test.JenkinsRule;
8+
import org.jvnet.hudson.test.recipes.LocalData;
9+
10+
import java.io.File;
11+
12+
import static hudson.Functions.isWindows;
13+
import static org.junit.Assume.assumeFalse;
14+
15+
public class Security3301Test {
16+
17+
@Rule
18+
public JenkinsRule j = new JenkinsRule();
19+
20+
@Test
21+
@LocalData
22+
public void security3301sanitizeTest() throws Exception {
23+
24+
25+
// Skip on windows
26+
assumeFalse(isWindows());
27+
28+
FreeStyleProject job = j.jenkins.getItemByFullName("testJob", FreeStyleProject.class);
29+
30+
Assert.assertTrue(new File(job.getRootDir(), "htmlreports/HTML_20Report").exists());
31+
32+
j.buildAndAssertSuccess(job);
33+
34+
changeJobReportName(job,"HTML_20Report/javascript:alert(1)");
35+
36+
job.save();
37+
38+
j.buildAndAssertSuccess(job);
39+
40+
HtmlPublisherTarget.HTMLAction action = job.getAction(HtmlPublisherTarget.HTMLAction.class);
41+
Assert.assertNotNull(action);
42+
43+
//Check that the report name is escaped for the Url
44+
Assert.assertEquals("HTML_20Report/javascript:alert(1)", action.getHTMLTarget().getReportName());
45+
Assert.assertEquals("HTML_5f20Report_2fjavascript_3aalert_281_29", action.getUrlName());
46+
47+
Assert.assertTrue(new File(job.getRootDir(), "htmlreports/HTML_5f20Report_2fjavascript_3aalert_281_29").exists());
48+
49+
FreeStyleProject anotherJob = j.jenkins.getItemByFullName("anotherJob", FreeStyleProject.class);
50+
51+
Assert.assertTrue(new File(anotherJob.getRootDir(), "htmlreports/HTML_20Report").exists());
52+
53+
j.buildAndAssertSuccess(anotherJob);
54+
55+
changeJobReportName(job,"../../anotherJob/htmlreports/HTML_20Report");
56+
57+
job.save();
58+
59+
//Check that the build reports is not from the new job (anotherJob)
60+
Assert.assertEquals("../../anotherJob/htmlreports/HTML_20Report", action.getHTMLTarget().getReportName());
61+
Assert.assertEquals("_2e_2e_2f_2e_2e_2fanotherJob_2fhtmlreports_2fHTML_5f20Report", action.getUrlName());
62+
Assert.assertFalse(new File(job.getRootDir(), "htmlreports/_2e_2e_2f_2e_2e_2fanotherJob_2fhtmlreports_2fHTML_5f20Report/test.txt").exists());
63+
64+
}
65+
66+
public void changeJobReportName(FreeStyleProject job, String newName) {
67+
for (Object publisher : job.getPublishersList()) {
68+
if (publisher instanceof HtmlPublisher) {
69+
HtmlPublisher existingPublishHTML = (HtmlPublisher) publisher;
70+
existingPublishHTML.getReportTargets().get(0).setReportName(newName);
71+
}
72+
}
73+
}
74+
}

src/test/java/htmlpublisher/Security784Test.java

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -24,15 +24,7 @@ public void security784upgradeTest() throws Exception {
2424

2525
Assert.assertTrue(new File(job.getRootDir(), "htmlreports/foo!!!!bar/index.html").exists());
2626

27-
HtmlPublisherTarget.HTMLAction action = job.getAction(HtmlPublisherTarget.HTMLAction.class);
28-
Assert.assertNotNull(action);
29-
Assert.assertEquals("foo!!!!bar", action.getHTMLTarget().getReportName());
30-
Assert.assertEquals("foo!!!!bar", action.getUrlName()); // legacy
31-
3227
JenkinsRule.WebClient client = j.createWebClient();
33-
HtmlPage page = client.getPage(job, "foo!!!!bar/index.html");
34-
String text = page.getWebResponse().getContentAsString();
35-
Assert.assertEquals("Sun Mar 25 15:42:10 CEST 2018", text.trim());
3628

3729
job.getBuildersList().clear();
3830
String newDate = new Date().toString();
@@ -44,16 +36,13 @@ public void security784upgradeTest() throws Exception {
4436

4537
Assert.assertTrue(new File(job.getRootDir(), "htmlreports/foo_21_21_21_21bar/index.html").exists());
4638

47-
action = job.getAction(HtmlPublisherTarget.HTMLAction.class);
39+
HtmlPublisherTarget.HTMLAction action = job.getAction(HtmlPublisherTarget.HTMLAction.class);
4840
Assert.assertNotNull(action);
4941
Assert.assertEquals("foo!!!!bar", action.getHTMLTarget().getReportName());
5042
Assert.assertEquals("foo_21_21_21_21bar", action.getUrlName()); // new
5143

52-
text = client.goTo("job/thejob/foo_21_21_21_21bar/index.html").getWebResponse().getContentAsString();
44+
String text = client.goTo("job/thejob/foo_21_21_21_21bar/index.html").getWebResponse().getContentAsString();
5345
Assert.assertEquals(newDate, text.trim());
54-
55-
// leftovers from legacy naming
56-
Assert.assertTrue(new File(job.getRootDir(), "htmlreports/foo!!!!bar/index.html").exists());
5746
}
5847

5948
@Test
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<?xml version='1.1' encoding='UTF-8'?>
2+
<project>
3+
<actions/>
4+
<description></description>
5+
<keepDependencies>false</keepDependencies>
6+
<properties/>
7+
<scm class="hudson.scm.NullSCM"/>
8+
<canRoam>true</canRoam>
9+
<disabled>false</disabled>
10+
<blockBuildWhenDownstreamBuilding>false</blockBuildWhenDownstreamBuilding>
11+
<blockBuildWhenUpstreamBuilding>false</blockBuildWhenUpstreamBuilding>
12+
<triggers/>
13+
<concurrentBuild>false</concurrentBuild>
14+
<builders>
15+
<hudson.tasks.Shell>
16+
<command>echo &quot;Test999&quot; &gt; test.txt</command>
17+
<configuredLocalRules/>
18+
</hudson.tasks.Shell>
19+
</builders>
20+
<publishers>
21+
<htmlpublisher.HtmlPublisher plugin="htmlpublisher@1.33-SNAPSHOT">
22+
<reportTargets>
23+
<htmlpublisher.HtmlPublisherTarget>
24+
<reportName>HTML Report</reportName>
25+
<reportDir></reportDir>
26+
<reportFiles>test.txt</reportFiles>
27+
<alwaysLinkToLastBuild>false</alwaysLinkToLastBuild>
28+
<reportTitles></reportTitles>
29+
<keepAll>false</keepAll>
30+
<allowMissing>false</allowMissing>
31+
<includes>**/*</includes>
32+
<escapeUnderscores>true</escapeUnderscores>
33+
<useWrapperFileDirectly>true</useWrapperFileDirectly>
34+
</htmlpublisher.HtmlPublisherTarget>
35+
</reportTargets>
36+
</htmlpublisher.HtmlPublisher>
37+
</publishers>
38+
<buildWrappers/>
39+
</project>

0 commit comments

Comments
 (0)