提交 b000130a 编写于 作者: D Daniel Beck

Merge branch 'security-stable-2.89' into security-stable-2.107

......@@ -39,7 +39,7 @@ THE SOFTWARE.
<properties>
<staplerFork>true</staplerFork>
<stapler.version>1.254</stapler.version>
<stapler.version>1.254.1</stapler.version>
<spring.version>2.5.6.SEC03</spring.version>
<groovy.version>2.4.11</groovy.version>
<!-- TODO: Actually many issues are being filtered by src/findbugs/findbugs-excludes.xml -->
......
......@@ -228,7 +228,11 @@ public abstract class Plugin implements Saveable {
String path = req.getRestOfPath();
String pathUC = path.toUpperCase(Locale.ENGLISH);
if (path.isEmpty() || path.contains("..") || path.startsWith(".") || path.contains("%") || pathUC.contains("META-INF") || pathUC.contains("WEB-INF")) {
if (path.isEmpty() || path.contains("..") || path.startsWith(".") || path.contains("%")
|| pathUC.contains("META-INF") || pathUC.contains("WEB-INF")
// ClassicPluginStrategy#explode produce that file to know if a new explosion is required or not
|| pathUC.equals("/.TIMESTAMP2")
) {
LOGGER.warning("rejecting possibly malicious " + req.getRequestURIWithQueryString());
rsp.sendError(HttpServletResponse.SC_BAD_REQUEST);
return;
......
......@@ -23,6 +23,7 @@
*/
package hudson.model;
import hudson.Util;
import hudson.util.RunList;
import org.kohsuke.stapler.QueryParameter;
import org.kohsuke.stapler.StaplerRequest;
......@@ -64,7 +65,9 @@ public class BuildTimelineWidget {
Event e = new Event();
e.start = new Date(r.getStartTimeInMillis());
e.end = new Date(r.getStartTimeInMillis()+r.getDuration());
e.title = r.getFullDisplayName();
// due to SimileAjax.HTML.deEntify (in simile-ajax-bundle.js), "&lt;" are transformed back to "<", but not the "&#60";
// to protect against XSS
e.title = Util.escape(r.getFullDisplayName()).replace("&lt;", "&#60;");
// what to put in the description?
// e.description = "Longish description of event "+r.getFullDisplayName();
// e.durationEvent = true;
......
......@@ -757,7 +757,9 @@ public class Queue extends ResourceController implements Saveable {
public HttpResponse doCancelItem(@QueryParameter long id) throws IOException, ServletException {
Item item = getItem(id);
if (item != null) {
cancel(item);
if(item.hasCancelPermission()){
cancel(item);
}
} // else too late, ignore (JENKINS-14813)
return HttpResponses.forwardToPreviousPage();
}
......@@ -2256,7 +2258,9 @@ public class Queue extends ResourceController implements Saveable {
@Deprecated
@RequirePOST
public HttpResponse doCancelQueue() throws IOException, ServletException {
Jenkins.getInstance().getQueue().cancel(this);
if(hasCancelPermission()){
Jenkins.getInstance().getQueue().cancel(this);
}
return HttpResponses.forwardToPreviousPage();
}
......
......@@ -490,7 +490,8 @@ public class User extends AbstractModelObject implements AccessControlled, Descr
byNameLock.readLock().unlock();
}
final File configFile = getConfigFileFor(id);
if (unsanitizedLegacyConfigFile.exists() && !unsanitizedLegacyConfigFile.equals(configFile)) {
boolean mustMigrateLegacyConfig = isMigrationRequiredForLegacyConfigFile(unsanitizedLegacyConfigFile, configFile);
if (mustMigrateLegacyConfig) {
File ancestor = unsanitizedLegacyConfigFile.getParentFile();
if (!configFile.exists()) {
try {
......@@ -552,6 +553,37 @@ public class User extends AbstractModelObject implements AccessControlled, Descr
}
return u;
}
private static boolean isMigrationRequiredForLegacyConfigFile(@Nonnull File legacyConfigFile, @Nonnull File newConfigFile){
boolean mustMigrateLegacyConfig = legacyConfigFile.exists() && !legacyConfigFile.equals(newConfigFile);
if(mustMigrateLegacyConfig){
try{
// TODO Could be replace by Util.isDescendant(getRootDir(), legacyConfigFile) in 2.80+
String canonicalLegacy = legacyConfigFile.getCanonicalPath();
String canonicalUserDir = getRootDir().getCanonicalPath();
if(!canonicalLegacy.startsWith(canonicalUserDir + File.separator)){
// without that check, the application config.xml could be moved (i.e. erased from application PoV)
mustMigrateLegacyConfig = false;
LOGGER.log(Level.WARNING, String.format(
"Attempt to escape from users directory with %s, migration aborted, see SECURITY-897 for more information",
legacyConfigFile.getAbsolutePath()
));
}
}
catch (IOException e){
mustMigrateLegacyConfig = false;
LOGGER.log(
Level.WARNING,
String.format(
"Failed to determine the canonical path of %s, migration aborted, see SECURITY-897 for more information",
legacyConfigFile.getAbsolutePath()
),
e
);
}
}
return mustMigrateLegacyConfig;
}
/**
* Gets the {@link User} object by its id or full name.
......
......@@ -669,6 +669,8 @@ public class SlaveComputer extends Computer {
@RequirePOST
public void doLaunchSlaveAgent(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException {
checkPermission(CONNECT);
if(channel!=null) {
req.getView(this,"already-launched.jelly").forward(req, rsp);
return;
......
......@@ -56,7 +56,7 @@ THE SOFTWARE.
onSuccess: function(t) {
if (t.status != 0) {
try {
eventSource1.loadJSON(eval('('+t.responseText+')'),'.');
eventSource1.loadJSON(JSON.parse(t.responseText),'.');
getData(eventSource1, current-1, min, max);
} catch (e) {
alert(e);
......
package hudson;
import hudson.model.UpdateCenter;
import jenkins.model.Jenkins;
import org.junit.Rule;
import org.junit.Test;
import org.jvnet.hudson.test.Issue;
import org.jvnet.hudson.test.JenkinsRule;
import javax.servlet.http.HttpServletResponse;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.Future;
//TODO merge it within PluginTest after the security release
public class PluginTest_SEC925 {
@Rule
public JenkinsRule r = new JenkinsRule();
@Test
@Issue("SECURITY-925")
public void preventTimestamp2_toBeServed() throws Exception {
// impossible to use installDetachedPlugin("credentials") since we want to have it exploded like with WAR
Jenkins.getInstance().getUpdateCenter().getSites().get(0).updateDirectlyNow(false);
List<Future<UpdateCenter.UpdateCenterJob>> pluginInstalled = r.jenkins.pluginManager.install(Arrays.asList("credentials"), true);
for (Future<UpdateCenter.UpdateCenterJob> job : pluginInstalled) {
job.get();
}
r.createWebClient().assertFails("plugin/credentials/.timestamp2", HttpServletResponse.SC_BAD_REQUEST);
}
}
package hudson.model;
import com.gargoylesoftware.htmlunit.HttpMethod;
import com.gargoylesoftware.htmlunit.Page;
import com.gargoylesoftware.htmlunit.WebRequest;
import hudson.model.Cause.UserIdCause;
import hudson.slaves.NodeProvisionerRule;
import jenkins.model.Jenkins;
import org.junit.Rule;
import org.junit.Test;
import org.jvnet.hudson.test.JenkinsRule;
import org.jvnet.hudson.test.MockAuthorizationStrategy;
import java.net.URL;
import java.util.function.Function;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.lessThan;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
//TODO merge into QueueTest after security patch
public class QueueTest_SEC891 {
@Rule
public JenkinsRule r = new NodeProvisionerRule(-1, 0, 10);
@Test public void doCancelItem_PermissionIsChecked() throws Exception {
checkCancelOperationUsingUrl(item -> "queue/cancelItem?id=" + item.getId());
}
@Test public void doCancelQueue_PermissionIsChecked() throws Exception {
checkCancelOperationUsingUrl(item -> "queue/item/" + item.getId() + "/cancelQueue");
}
private void checkCancelOperationUsingUrl(Function<Queue.Item, String> urlProvider) throws Exception {
Queue q = r.jenkins.getQueue();
r.jenkins.setCrumbIssuer(null);
r.jenkins.setSecurityRealm(r.createDummySecurityRealm());
r.jenkins.setAuthorizationStrategy(new MockAuthorizationStrategy()
.grant(Jenkins.READ, Item.CANCEL).everywhere().to("admin")
.grant(Jenkins.READ).everywhere().to("user")
);
// prevent execution to push stuff into the queue
r.jenkins.setNumExecutors(0);
assertThat(q.getItems().length, equalTo(0));
FreeStyleProject testProject = r.createFreeStyleProject("test");
testProject.scheduleBuild(new UserIdCause());
Queue.Item[] items = q.getItems();
assertThat(items.length, equalTo(1));
Queue.Item currentOne = items[0];
assertFalse(currentOne.getFuture().isCancelled());
WebRequest request = new WebRequest(new URL(r.getURL() + urlProvider.apply(currentOne)), HttpMethod.POST);
{ // user without right cannot cancel
JenkinsRule.WebClient wc = r.createWebClient();
wc.getOptions().setThrowExceptionOnFailingStatusCode(false);
wc.getOptions().setRedirectEnabled(false);
wc.login("user");
Page p = wc.getPage(request);
// currently the endpoint return a redirection to the previously visited page, none in our case
// (so force no redirect to avoid false positive error)
assertThat(p.getWebResponse().getStatusCode(), lessThan(400));
assertFalse(currentOne.getFuture().isCancelled());
}
{ // user with right can
JenkinsRule.WebClient wc = r.createWebClient();
wc.getOptions().setThrowExceptionOnFailingStatusCode(false);
wc.getOptions().setRedirectEnabled(false);
wc.login("admin");
Page p = wc.getPage(request);
assertThat(p.getWebResponse().getStatusCode(), lessThan(400));
assertTrue(currentOne.getFuture().isCancelled());
}
}
}
package hudson.model;
import com.gargoylesoftware.htmlunit.WebRequest;
import hudson.FilePath;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runners.model.Statement;
import org.jvnet.hudson.test.JenkinsRule;
import org.jvnet.hudson.test.RestartableJenkinsRule;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import static org.hamcrest.core.IsEqual.equalTo;
import static org.junit.Assert.assertThat;
//TODO after the security fix, it could be merged inside UserRestartTest
public class UserRestartTest_SEC897 {
@Rule
public RestartableJenkinsRule rr = new RestartableJenkinsRule();
@Test public void legacyConfigMoveCannotEscapeUserFolder() {
rr.addStep(new Statement() {
@Override
public void evaluate() throws Throwable {
rr.j.jenkins.setSecurityRealm(rr.j.createDummySecurityRealm());
assertThat(rr.j.jenkins.isUseSecurity(), equalTo(true));
// in order to create the folder "users"
User.getById("admin", true).save();
{ // attempt with ".."
JenkinsRule.WebClient wc = rr.j.createWebClient();
wc.getOptions().setThrowExceptionOnFailingStatusCode(false);
WebRequest request = new WebRequest(new URL(rr.j.jenkins.getRootUrl() + "whoAmI/api/xml"));
request.setAdditionalHeader("Authorization", base64("..", "any-password"));
wc.getPage(request);
}
{ // attempt with "../users/.."
JenkinsRule.WebClient wc = rr.j.createWebClient();
wc.getOptions().setThrowExceptionOnFailingStatusCode(false);
WebRequest request = new WebRequest(new URL(rr.j.jenkins.getRootUrl() + "whoAmI/api/xml"));
request.setAdditionalHeader("Authorization", base64("../users/..", "any-password"));
wc.getPage(request);
}
// security is still active
assertThat(rr.j.jenkins.isUseSecurity(), equalTo(true));
// but, the config file was moved
FilePath rootPath = rr.j.jenkins.getRootPath();
assertThat(rootPath.child("config.xml").exists(), equalTo(true));
}
});
rr.addStep(new Statement() {
@Override
public void evaluate() throws Throwable {
assertThat(rr.j.jenkins.isUseSecurity(), equalTo(true));
FilePath rootPath = rr.j.jenkins.getRootPath();
assertThat(rootPath.child("config.xml").exists(), equalTo(true));
}
});
}
private String base64(String login, String password) {
return "Basic " + Base64.getEncoder().encodeToString((login + ":" + password).getBytes(StandardCharsets.UTF_8));
}
}
/*
* The MIT License
*
* Copyright (c) 2018, CloudBees, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package jenkins.security.stapler;
import com.gargoylesoftware.htmlunit.Page;
import com.gargoylesoftware.htmlunit.WebRequest;
import hudson.Functions;
import org.junit.Assume;
import org.junit.Rule;
import org.junit.Test;
import org.jvnet.hudson.test.Issue;
import org.jvnet.hudson.test.JenkinsRule;
import org.jvnet.hudson.test.TestPluginManager;
import java.net.URL;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
@Issue("SECURITY-914")
public class Security914Test {
@Rule
public JenkinsRule j = new JenkinsRule();
@Test
public void cannotUseInvalidLocale_toTraverseFolder() throws Exception {
Assume.assumeTrue(Functions.isWindows());
if (j.jenkins.pluginManager.getPlugin("credentials") == null) {
((TestPluginManager) j.jenkins.pluginManager).installDetachedPlugin("credentials");
}
j.createWebClient().goTo("plugin/credentials/images/24x24/credentials.png", "image/png");
JenkinsRule.WebClient wc = j.createWebClient();
wc.getOptions().setThrowExceptionOnFailingStatusCode(false);
WebRequest request = new WebRequest(new URL(j.getURL() + "plugin/credentials/.xml"));
// plugin deployed in: test\target\jenkins7375296945862059919tmp
// rootDir is in : test\target\jenkinsTests.tmp\jenkins1274934531848159942test
// j.jenkins.getRootDir().getName() = jenkins1274934531848159942test
request.setAdditionalHeader("Accept-Language", "../../../../jenkinsTests.tmp/" + j.jenkins.getRootDir().getName() + "/config");
Page p = wc.getPage(request);
assertEquals(p.getWebResponse().getStatusCode(), 404);
assertNotEquals(p.getWebResponse().getContentType(), "application/xml");
}
@Test
public void cannotUseInvalidLocale_toAnyFileInSystem() throws Exception {
Assume.assumeTrue(Functions.isWindows());
if (j.jenkins.pluginManager.getPlugin("credentials") == null) {
((TestPluginManager) j.jenkins.pluginManager).installDetachedPlugin("credentials");
}
j.createWebClient().goTo("plugin/credentials/images/24x24/credentials.png", "image/png");
JenkinsRule.WebClient wc = j.createWebClient();
wc.getOptions().setThrowExceptionOnFailingStatusCode(false);
WebRequest request = new WebRequest(new URL(j.getURL() + "plugin/credentials/.ini"));
// ../ can be multiply to infinity, no impact, we just need to have enough to reach the root
request.setAdditionalHeader("Accept-Language", "../../../../../../../../../../../../windows/win");
Page p = wc.getPage(request);
assertEquals(p.getWebResponse().getStatusCode(), 404);
assertEquals(p.getWebResponse().getContentType(), "text/html");
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册