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

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

......@@ -264,6 +264,8 @@ public abstract class CLICommand implements ExtensionPoint, Cloneable {
sc.setAuthentication(getTransportAuthentication());
new ClassParser().parse(authenticator,p);
if (!(this instanceof LoginCommand || this instanceof LogoutCommand || this instanceof HelpCommand))
Jenkins.getActiveInstance().checkPermission(Jenkins.READ);
p.parseArgument(args.toArray(new String[args.size()]));
Authentication auth = authenticator.authenticate();
if (auth==Jenkins.ANONYMOUS)
......
......@@ -103,12 +103,13 @@ public class ViewOptionHandler extends OptionHandler<View> {
String viewName = tok.nextToken();
view = group.getView(viewName);
if (view == null)
if (view == null) {
group.checkPermission(View.READ);
throw new IllegalArgumentException(String.format(
"No view named %s inside view %s",
viewName, group.getDisplayName()
));
}
view.checkPermission(View.READ);
if (view instanceof ViewGroup) {
group = (ViewGroup) view;
......
......@@ -75,7 +75,7 @@ THE SOFTWARE.
<j:mute>${customizedFields.add(name)}</j:mute>
</j:if>
</td><td width="1">
<input type="button" value="&#x25BC;" onclick="expandTextArea(this,'textarea.${name}')"
<input type="button" value="&#x25BC;" onclick="expandTextArea(this,'textarea.${h.jsStringEscape(name)}')"
tooltip="${%tooltip}"/>
</td>
</tr>
......
......@@ -48,7 +48,7 @@ THE SOFTWARE.
</st:documentation>
<f:entry>
<div style="float:right">
<input type="button" value="${title}" class="yui-button validate-button" onclick="validateButton('${descriptor.descriptorFullUrl}/${method}','${with}',this)" />
<input type="button" value="${title}" class="yui-button validate-button" onclick="validateButton('${descriptor.descriptorFullUrl}/${h.jsStringEscape(method)}','${h.jsStringEscape(with)}',this)" />
</div>
<div style="display:none;">
<img src="${imagesURL}/spinner.gif" /> ${attrs.progress}
......
......@@ -31,7 +31,7 @@ THE SOFTWARE.
<st:attribute name="href" use="required">
The URL to go to.
</st:attribute>
<st:attribute name="post">
<st:attribute name="post" type="boolean">
Use POST rather than GET (recommended).
</st:attribute>
<st:attribute name="message" use="required">
......@@ -42,7 +42,7 @@ THE SOFTWARE.
</st:attribute>
</st:documentation>
<j:set var="id" value="${h.generateId()}"/>
<a href="#" class="${class}" onclick="confirmPOST_${id}(${post ?: 'false'}, '${attrs.href}', '${h.jsStringEscape(message)}')"><d:invokeBody/></a>
<a href="#" class="${class}" onclick="confirmPOST_${id}(${post ? 'true' : 'false'}, '${h.jsStringEscape(attrs.href)}', '${h.jsStringEscape(message)}')"><d:invokeBody/></a>
<script>
function confirmPOST_${id}(post, href, message) {
if (confirm(message)) {
......
......@@ -39,13 +39,13 @@ THE SOFTWARE.
</st:documentation>
<j:choose>
<j:when test="${confirm == null}">
<a class="stop-button-link" href="${href}" onclick='new Ajax.Request("${href}"); return false;'>
<l:icon class="icon-stop icon-sm"/>
<a class="stop-button-link" href="${href}" onclick='new Ajax.Request("${h.jsStringEscape(href)}"); return false;'>
<l:icon class="icon-stop icon-sm" alt="${alt}"/>
</a>
</j:when>
<j:otherwise>
<a class="stop-button-link" href="${href}" onclick='if(confirm("${confirm}"))new Ajax.Request("${href}"); return false;'>
<l:icon class="icon-stop icon-sm"/>
<a class="stop-button-link" href="${href}" onclick='if(confirm("${h.jsStringEscape(confirm)}"))new Ajax.Request("${h.jsStringEscape(href)}"); return false;'>
<l:icon class="icon-stop icon-sm" alt="${alt}"/>
</a>
</j:otherwise>
</j:choose>
......
......@@ -31,10 +31,12 @@ import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
import hudson.model.ViewGroup;
import hudson.model.View;
import hudson.security.ACL;
import hudson.security.Permission;
import jenkins.model.Jenkins;
import org.acegisecurity.AccessDeniedException;
import org.acegisecurity.Authentication;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
......@@ -83,6 +85,12 @@ public class ViewOptionHandlerTest {
PowerMockito.when(Jenkins.getActiveInstance()).thenReturn(jenkins);
when(jenkins.getView("outer")).thenReturn(outer);
when(jenkins.getDisplayName()).thenReturn("Jenkins");
when(jenkins.getACL()).thenReturn(new ACL() {
@Override
public boolean hasPermission(Authentication a, Permission p) {
return true;
}
});
}
@Test public void resolveTopLevelView() throws Exception {
......
......@@ -4,6 +4,7 @@ import com.google.common.collect.Lists;
import hudson.Functions;
import hudson.Launcher;
import hudson.Proc;
import hudson.model.AllView;
import hudson.model.Item;
import hudson.model.User;
import hudson.remoting.Channel;
......@@ -134,17 +135,17 @@ public class CLIActionTest {
j.jenkins.setAuthorizationStrategy(new MockAuthorizationStrategy().grant(Jenkins.ADMINISTER).everywhere().to(ADMIN));
j.createFreeStyleProject("p");
// CLICommand with @Argument:
assertExitCode(3, false, jar, "-remoting", "get-job", "p"); // IllegalArgumentException from GenericItemOptionHandler
assertExitCode(3, false, jar, "get-job", "p"); // ditto under new protocol
assertExitCode(3, false, jar, "-remoting", "get-job", "--username", ADMIN, "--password", ADMIN, "p"); // JENKINS-12543: too late
assertExitCode(3, false, jar, "get-job", "--username", ADMIN, "--password", ADMIN, "p"); // same
assertExitCode(6, false, jar, "-remoting", "get-job", "p"); // SECURITY-754 requires Overall/Read for nearly all CLICommands.
assertExitCode(6, false, jar, "get-job", "p"); // ditto under new protocol
assertExitCode(6, false, jar, "-remoting", "get-job", "--username", ADMIN, "--password", ADMIN, "p"); // SECURITY-754 and JENKINS-12543: too late
assertExitCode(6, false, jar, "get-job", "--username", ADMIN, "--password", ADMIN, "p"); // same
assertExitCode(0, false, jar, "-remoting", "login", "--username", ADMIN, "--password", ADMIN);
try {
assertExitCode(3, false, jar, "-remoting", "get-job", "p"); // ClientAuthenticationCache also used too late
assertExitCode(6, false, jar, "-remoting", "get-job", "p"); // SECURITY-754: ClientAuthenticationCache also used too late
} finally {
assertExitCode(0, false, jar, "-remoting", "logout");
}
assertExitCode(3, true, jar, "-remoting", "get-job", "p"); // does not work with API tokens
assertExitCode(6, true, jar, "-remoting", "get-job", "p"); // SECURITY-754: does not work with API tokens
assertExitCode(0, true, jar, "get-job", "p"); // but does under new protocol
// @CLIMethod:
assertExitCode(6, false, jar, "-remoting", "disable-job", "p"); // AccessDeniedException from CLIRegisterer?
......@@ -269,6 +270,31 @@ public class CLIActionTest {
assertEquals(0, proc.join());
}
@Issue("SECURITY-754")
@Test
public void noPreAuthOptionHandlerInfoLeak() throws Exception {
File jar = tmp.newFile("jenkins-cli.jar");
FileUtils.copyURLToFile(j.jenkins.getJnlpJars("jenkins-cli.jar").getURL(), jar);
j.jenkins.setSecurityRealm(j.createDummySecurityRealm());
j.jenkins.addView(new AllView("v1"));
j.jenkins.addNode(j.createSlave("n1", null, null));
j.jenkins.setAuthorizationStrategy(new MockAuthorizationStrategy().grant(Jenkins.ADMINISTER).everywhere().to(ADMIN));
// No anonymous read access
assertExitCode(6, false, jar, "get-view", "v1");
assertExitCode(6, false, jar, "get-view", "v2"); // Error code 3 before SECURITY-754
assertExitCode(6, false, jar, "get-node", "n1");
assertExitCode(6, false, jar, "get-node", "n2"); // Error code 3 before SECURITY-754
// Authenticated with no read access
assertExitCode(6, false, jar, "-auth", "user:user", "get-view", "v1");
assertExitCode(6, false, jar, "-auth", "user:user", "get-view", "v2"); // Error code 3 before SECURITY-754
assertExitCode(6, false, jar, "-auth", "user:user", "get-node", "n1");
assertExitCode(6, false, jar, "-auth", "user:user", "get-node", "n2"); // Error code 3 before SECURITY-754
// Anonymous read access
j.jenkins.setAuthorizationStrategy(new MockAuthorizationStrategy().grant(Jenkins.ADMINISTER).everywhere().to(ADMIN).grant(Jenkins.READ, Item.READ).everywhere().toEveryone());
assertExitCode(6, false, jar, "get-view", "v1");
assertExitCode(6, false, jar, "get-view", "v2"); // Error code 3 before SECURITY-754
}
@TestExtension("encodingAndLocale")
public static class TestDiagnosticCommand extends CLICommand {
......
......@@ -24,39 +24,130 @@
package lib.form;
import static com.gargoylesoftware.htmlunit.HttpMethod.POST;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import com.gargoylesoftware.htmlunit.Page;
import com.gargoylesoftware.htmlunit.WebRequest;
import com.gargoylesoftware.htmlunit.html.DomNodeList;
import com.gargoylesoftware.htmlunit.html.HtmlAnchor;
import com.gargoylesoftware.htmlunit.html.HtmlButton;
import com.gargoylesoftware.htmlunit.html.HtmlButtonInput;
import com.gargoylesoftware.htmlunit.html.HtmlElement;
import com.gargoylesoftware.htmlunit.html.HtmlElementUtil;
import com.gargoylesoftware.htmlunit.html.HtmlPage;
import hudson.model.UnprotectedRootAction;
import hudson.util.HttpResponses;
import lib.layout.ConfirmationLinkTest;
import org.junit.Assert;
import org.junit.Rule;
import org.junit.Test;
import org.jvnet.hudson.test.Issue;
import org.jvnet.hudson.test.HudsonTestCase;
import org.jvnet.hudson.test.JenkinsRule;
import org.jvnet.hudson.test.TestExtension;
import org.kohsuke.stapler.HttpResponse;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.WebMethod;
import org.w3c.dom.NodeList;
import javax.annotation.CheckForNull;
/**
* @author Kohsuke Kawaguchi
*/
public class ExpandableTextboxTest extends HudsonTestCase {
public class ExpandableTextboxTest {
@Rule
public JenkinsRule j = new JenkinsRule();
@Issue("JENKINS-2816")
@Test
public void testMultiline() throws Exception {
// because attribute values are normalized, it's not very easy to encode multi-line string as @value. So let's use the system message here.
jenkins.setSystemMessage("foo\nbar\nzot");
j.jenkins.setSystemMessage("foo\nbar\nzot");
HtmlPage page = evaluateAsHtml("<l:layout><l:main-panel><table><j:set var='instance' value='${it}'/><f:expandableTextbox field='systemMessage' /></table></l:main-panel></l:layout>");
// System.out.println(page.getWebResponse().getContentAsString());
NodeList textareas = page.getElementsByTagName("textarea");
assertEquals(1, textareas.getLength());
assertEquals(jenkins.getSystemMessage(),textareas.item(0).getTextContent());
assertEquals(j.jenkins.getSystemMessage(),textareas.item(0).getTextContent());
}
/**
* Evaluates the literal Jelly script passed as a parameter as HTML and returns the page.
*/
protected HtmlPage evaluateAsHtml(String jellyScript) throws Exception {
HudsonTestCase.WebClient wc = new WebClient();
JenkinsRule.WebClient wc = j.createWebClient();
WebRequest req = new WebRequest(wc.createCrumbedUrl("eval"), POST);
req.setEncodingType(null);
req.setRequestBody("<j:jelly xmlns:j='jelly:core' xmlns:st='jelly:stapler' xmlns:l='/lib/layout' xmlns:f='/lib/form'>"+jellyScript+"</j:jelly>");
Page page = wc.getPage(req);
return (HtmlPage) page;
}
@Test
public void noInjectionArePossible() throws Exception {
TestRootAction testParams = j.jenkins.getExtensionList(UnprotectedRootAction.class).get(TestRootAction.class);
assertNotNull(testParams);
checkRegularCase(testParams);
checkInjectionInName(testParams);
}
private void checkRegularCase(TestRootAction testParams) throws Exception {
testParams.paramName = "testName";
JenkinsRule.WebClient wc = j.createWebClient();
wc.getOptions().setThrowExceptionOnFailingStatusCode(false);
HtmlPage p = wc.goTo("test");
HtmlElementUtil.click(getExpandButton(p));
assertNotEquals("hacked", p.getTitleText());
}
private void checkInjectionInName(TestRootAction testParams) throws Exception {
testParams.paramName = "testName',document.title='hacked'+'";
JenkinsRule.WebClient wc = j.createWebClient();
wc.getOptions().setThrowExceptionOnFailingStatusCode(false);
HtmlPage p = wc.goTo("test");
HtmlElementUtil.click(getExpandButton(p));
assertNotEquals("hacked", p.getTitleText());
}
private HtmlButtonInput getExpandButton(HtmlPage page){
DomNodeList<HtmlElement> buttons = page.getElementById("test-panel").getElementsByTagName("input");
// the first one is the text input
assertEquals(2, buttons.size());
return (HtmlButtonInput) buttons.get(1);
}
@TestExtension("noInjectionArePossible")
public static final class TestRootAction implements UnprotectedRootAction {
public String paramName;
@Override
public @CheckForNull String getIconFileName() {
return null;
}
@Override
public @CheckForNull String getDisplayName() {
return null;
}
@Override
public String getUrlName() {
return "test";
}
@WebMethod(name = "submit")
public HttpResponse doSubmit(StaplerRequest request) {
return HttpResponses.plainText("method:" + request.getMethod());
}
}
}
......@@ -23,53 +23,185 @@
*/
package lib.form;
import com.gargoylesoftware.htmlunit.html.DomNodeList;
import com.gargoylesoftware.htmlunit.html.HtmlButton;
import com.gargoylesoftware.htmlunit.html.HtmlElement;
import com.gargoylesoftware.htmlunit.html.HtmlElementUtil;
import com.gargoylesoftware.htmlunit.html.HtmlFormUtil;
import org.jvnet.hudson.test.HudsonTestCase;
import hudson.model.UnprotectedRootAction;
import jenkins.model.Jenkins;
import org.junit.Rule;
import org.junit.Test;
import org.jvnet.hudson.test.JenkinsRule;
import org.jvnet.hudson.test.TestExtension;
import org.kohsuke.stapler.QueryParameter;
import hudson.model.Describable;
import hudson.model.Descriptor;
import hudson.Extension;
import com.gargoylesoftware.htmlunit.html.HtmlPage;
import org.kohsuke.stapler.StaplerRequest;
import javax.annotation.CheckForNull;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.assertFalse;
/**
*
*
* @author Kohsuke Kawaguchi
*/
public class ValidateButtonTest extends HudsonTestCase implements Describable<ValidateButtonTest> {
public class ValidateButtonTest {
@Rule
public JenkinsRule j = new JenkinsRule();
public void test1() throws Exception {
DescriptorImpl d = getDescriptor();
@Test
public void testValidateIsCalled() throws Exception {
TestValidateIsCalled.DescriptorImpl d = j.jenkins.getDescriptorByType(TestValidateIsCalled.DescriptorImpl.class);
assertNotNull(d);
d.test1Outcome = new Exception(); // if doValidateTest1() doesn't get invoked, we want to know.
HtmlPage p = createWebClient().goTo("self/test1");
HtmlPage p = j.createWebClient().goTo("test");
HtmlButton button = HtmlFormUtil.getButtonByCaption(p.getFormByName("config"), "test");
HtmlElementUtil.click(button);
if (d.test1Outcome!=null)
throw d.test1Outcome;
}
public DescriptorImpl getDescriptor() {
return jenkins.getDescriptorByType(DescriptorImpl.class);
@TestExtension("testValidateIsCalled")
public static final class TestValidateIsCalled implements Describable<TestValidateIsCalled>, UnprotectedRootAction {
@Override
public @CheckForNull String getIconFileName() {
return null;
}
@Override
public @CheckForNull String getDisplayName() {
return null;
}
@Override
public String getUrlName() {
return "test";
}
public DescriptorImpl getDescriptor() {
return Jenkins.getInstance().getDescriptorByType(DescriptorImpl.class);
}
@Extension
public static final class DescriptorImpl extends Descriptor<TestValidateIsCalled> {
private Exception test1Outcome;
public void doValidateTest1(@QueryParameter("a") String a, @QueryParameter("b") boolean b,
@QueryParameter("c") boolean c, @QueryParameter("d") String d,
@QueryParameter("e") String e) {
try {
assertEquals("avalue",a);
assertTrue(b);
assertFalse(c);
assertEquals("dvalue",d);
assertEquals("e2",e);
test1Outcome = null;
} catch (Exception t) {
test1Outcome = t;
}
}
}
}
@Extension
public static final class DescriptorImpl extends Descriptor<ValidateButtonTest> {
private Exception test1Outcome;
public void doValidateTest1(@QueryParameter("a") String a, @QueryParameter("b") boolean b,
@QueryParameter("c") boolean c, @QueryParameter("d") String d,
@QueryParameter("e") String e) {
try {
assertEquals("avalue",a);
assertTrue(b);
assertFalse(c);
assertEquals("dvalue",d);
assertEquals("e2",e);
test1Outcome = null;
} catch (Exception t) {
test1Outcome = t;
@Test
public void noInjectionArePossible() throws Exception {
NoInjectionArePossible.DescriptorImpl d = j.jenkins.getDescriptorByType(NoInjectionArePossible.DescriptorImpl.class);
assertNotNull(d);
checkRegularCase(d);
checkInjectionInMethod(d);
checkInjectionInWith(d);
}
private void checkRegularCase(NoInjectionArePossible.DescriptorImpl descriptor) throws Exception {
descriptor.paramMethod = "validateInjection";
descriptor.paramWith = "a,b";
JenkinsRule.WebClient wc = j.createWebClient();
wc.getOptions().setThrowExceptionOnFailingStatusCode(false);
HtmlPage p = wc.goTo("test");
descriptor.wasCalled = false;
HtmlElementUtil.click(getValidateButton(p));
assertNotEquals("hacked", p.getTitleText());
assertTrue(descriptor.wasCalled);
}
private void checkInjectionInMethod(NoInjectionArePossible.DescriptorImpl descriptor) throws Exception {
descriptor.paramMethod = "validateInjection',document.title='hacked'+'";
descriptor.paramWith = "a,b";
JenkinsRule.WebClient wc = j.createWebClient();
wc.getOptions().setThrowExceptionOnFailingStatusCode(false);
HtmlPage p = wc.goTo("test");
// no check on wasCalled because the button that is expected by the method is not passed (arguments are shifted due to the injection)
HtmlElementUtil.click(getValidateButton(p));
assertNotEquals("hacked", p.getTitleText());
}
private void checkInjectionInWith(NoInjectionArePossible.DescriptorImpl descriptor) throws Exception {
descriptor.paramMethod = "validateInjection";
descriptor.paramWith = "a,b',document.title='hacked'+'";
JenkinsRule.WebClient wc = j.createWebClient();
wc.getOptions().setThrowExceptionOnFailingStatusCode(false);
HtmlPage p = wc.goTo("test");
descriptor.wasCalled = false;
HtmlElementUtil.click(getValidateButton(p));
assertNotEquals("hacked", p.getTitleText());
assertTrue(descriptor.wasCalled);
}
private HtmlButton getValidateButton(HtmlPage page){
DomNodeList<HtmlElement> buttons = page.getElementById("test-panel").getElementsByTagName("button");
assertEquals(1, buttons.size());
return (HtmlButton) buttons.get(0);
}
@TestExtension("noInjectionArePossible")
public static final class NoInjectionArePossible implements Describable<NoInjectionArePossible>, UnprotectedRootAction {
@Override
public @CheckForNull String getIconFileName() {
return null;
}
@Override
public @CheckForNull String getDisplayName() {
return null;
}
@Override
public String getUrlName() {
return "test";
}
public DescriptorImpl getDescriptor() {
return Jenkins.getInstance().getDescriptorByType(DescriptorImpl.class);
}
@Extension
public static final class DescriptorImpl extends Descriptor<NoInjectionArePossible> {
private boolean wasCalled = false;
public String paramMethod = "validateInjection";
public String paramWith = null;
public void doValidateInjection(StaplerRequest request) {
wasCalled = true;
}
}
}
......
/*
* 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 lib.layout;
import com.gargoylesoftware.htmlunit.Page;
import com.gargoylesoftware.htmlunit.html.DomNodeList;
import com.gargoylesoftware.htmlunit.html.HtmlAnchor;
import com.gargoylesoftware.htmlunit.html.HtmlElement;
import com.gargoylesoftware.htmlunit.html.HtmlElementUtil;
import com.gargoylesoftware.htmlunit.html.HtmlPage;
import hudson.model.UnprotectedRootAction;
import hudson.util.HttpResponses;
import org.junit.Rule;
import org.junit.Test;
import org.jvnet.hudson.test.JenkinsRule;
import org.jvnet.hudson.test.TestExtension;
import org.kohsuke.stapler.HttpResponse;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.WebMethod;
import javax.annotation.CheckForNull;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
public class ConfirmationLinkTest {
@Rule
public JenkinsRule j = new JenkinsRule();
private static final String hrefPayload = "',document.title='hacked'+'";
private static final String messagePayload = "',document.title='hacked'+'";
private static final String postPayload = "document.title='hacked'";
@Test
public void noInjectionArePossible() throws Exception {
TestRootAction testParams = j.jenkins.getExtensionList(UnprotectedRootAction.class).get(TestRootAction.class);
assertNotNull(testParams);
checkRegularCase(testParams);
checkRegularCasePost(testParams);
checkInjectionInHref(testParams);
checkInjectionInMessage(testParams);
checkInjectionInPost(testParams);
}
private void checkRegularCase(TestRootAction testParams) throws Exception {
testParams.paramHref = "#";
testParams.paramMessage = "Message to confirm the click";
testParams.paramClass = null;
testParams.paramPost = null;
HtmlPage p = j.createWebClient().goTo("test");
assertTrue(p.getWebResponse().getContentAsString().contains("Message to confirm the click"));
}
private void checkRegularCasePost(TestRootAction testParams) throws Exception {
testParams.paramHref = "submit";
testParams.paramMessage = "Message to confirm the click";
testParams.paramClass = null;
testParams.paramPost = true;
assertMethodPostAfterClick();
testParams.paramPost = "true";
assertMethodPostAfterClick();
testParams.paramPost = "TruE";
assertMethodPostAfterClick();
testParams.paramPost = false;
assertMethodGetAfterClick();
testParams.paramPost = "false";
assertMethodGetAfterClick();
testParams.paramPost = "any other string";
assertMethodGetAfterClick();
}
private void assertMethodGetAfterClick() throws Exception {
Page pageAfterClick = getPageAfterClick();
assertTrue(pageAfterClick.getWebResponse().getContentAsString().contains("method:GET"));
}
private void assertMethodPostAfterClick() throws Exception {
Page pageAfterClick = getPageAfterClick();
assertTrue(pageAfterClick.getWebResponse().getContentAsString().contains("method:POST"));
}
private Page getPageAfterClick() throws Exception {
JenkinsRule.WebClient wc = j.createWebClient();
wc.getOptions().setThrowExceptionOnFailingStatusCode(false);
HtmlPage p = wc.goTo("test");
return HtmlElementUtil.click(getClickableLink(p));
}
private void checkInjectionInHref(TestRootAction testParams) throws Exception {
testParams.paramHref = hrefPayload;
testParams.paramMessage = "Message to confirm the click";
testParams.paramClass = null;
testParams.paramPost = null;
JenkinsRule.WebClient wc = j.createWebClient();
wc.getOptions().setThrowExceptionOnFailingStatusCode(false);
HtmlPage p = wc.goTo("test");
Page pageAfterClick = HtmlElementUtil.click(getClickableLink(p));
assertNotEquals("hacked", p.getTitleText());
assertTrue(p.getWebResponse().getContentAsString().contains("Message to confirm the click"));
// the url it clicks on is escaped and so does not exist
assertEquals(404, pageAfterClick.getWebResponse().getStatusCode());
}
private void checkInjectionInMessage(TestRootAction testParams) throws Exception {
testParams.paramHref = "#";
testParams.paramMessage = messagePayload;
testParams.paramClass = null;
testParams.paramPost = null;
JenkinsRule.WebClient wc = j.createWebClient();
wc.getOptions().setThrowExceptionOnFailingStatusCode(false);
HtmlPage p = wc.goTo("test");
Page pageAfterClick = HtmlElementUtil.click(getClickableLink(p));
assertNotEquals("hacked", p.getTitleText());
// the url is normally the same page so it's ok
assertEquals(200, pageAfterClick.getWebResponse().getStatusCode());
}
private void checkInjectionInPost(TestRootAction testParams) throws Exception {
testParams.paramHref = "#";
testParams.paramMessage = "Message to confirm the click";
testParams.paramClass = null;
testParams.paramPost = postPayload;
JenkinsRule.WebClient wc = j.createWebClient();
wc.getOptions().setThrowExceptionOnFailingStatusCode(false);
HtmlPage p = wc.goTo("test");
Page pageAfterClick = HtmlElementUtil.click(getClickableLink(p));
assertNotEquals("hacked", p.getTitleText());
assertTrue(p.getWebResponse().getContentAsString().contains("Message to confirm the click"));
// the url is normally the same page so it's ok
assertEquals(200, pageAfterClick.getWebResponse().getStatusCode());
}
private HtmlAnchor getClickableLink(HtmlPage page){
DomNodeList<HtmlElement> anchors = page.getElementById("test-panel").getElementsByTagName("a");
assertEquals(1, anchors.size());
return (HtmlAnchor) anchors.get(0);
}
@TestExtension("noInjectionArePossible")
public static final class TestRootAction implements UnprotectedRootAction {
public String paramHref = "";
public String paramMessage = "";
public String paramClass;
public Object paramPost;
@Override
public @CheckForNull String getIconFileName() {
return null;
}
@Override
public @CheckForNull String getDisplayName() {
return null;
}
@Override
public String getUrlName() {
return "test";
}
@WebMethod(name = "submit")
public HttpResponse doSubmit(StaplerRequest request) {
return HttpResponses.plainText("method:" + request.getMethod());
}
}
}
/*
* 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 lib.layout;
import com.gargoylesoftware.htmlunit.html.DomNodeList;
import com.gargoylesoftware.htmlunit.html.HtmlAnchor;
import com.gargoylesoftware.htmlunit.html.HtmlElement;
import com.gargoylesoftware.htmlunit.html.HtmlElementUtil;
import com.gargoylesoftware.htmlunit.html.HtmlPage;
import hudson.model.UnprotectedRootAction;
import hudson.util.HttpResponses;
import org.junit.Rule;
import org.junit.Test;
import org.jvnet.hudson.test.JenkinsRule;
import org.jvnet.hudson.test.TestExtension;
import org.kohsuke.stapler.HttpResponse;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.WebMethod;
import javax.annotation.CheckForNull;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
public class StopButtonTest {
@Rule
public JenkinsRule j = new JenkinsRule();
private static final String hrefPayload = "\",document.title='hacked',\"";
private static final String postPayload = "\",document.title='hacked',\"";
@Test
public void noInjectionArePossible() throws Exception {
TestRootAction testParams = j.jenkins.getExtensionList(UnprotectedRootAction.class).get(TestRootAction.class);
assertNotNull(testParams);
checkRegularCase(testParams);
checkInjectionInHref(testParams);
checkInjectionInHrefWithConfirm(testParams);
checkInjectionInConfirm(testParams);
}
private void checkRegularCase(TestRootAction testParams) throws Exception {
testParams.paramHref = "#";
testParams.paramAlt = "Message to confirm the click";
testParams.paramConfirm = null;
HtmlPage p = j.createWebClient().goTo("test");
assertTrue(p.getWebResponse().getContentAsString().contains("Message to confirm the click"));
}
private void checkInjectionInHref(TestRootAction testParams) throws Exception {
testParams.paramHref = hrefPayload;
testParams.paramAlt = "Alternative text for icon";
testParams.paramConfirm = null;
JenkinsRule.WebClient wc = j.createWebClient();
wc.getOptions().setThrowExceptionOnFailingStatusCode(false);
HtmlPage p = wc.goTo("test");
HtmlElementUtil.click(getStopLink(p));
assertNotEquals("hacked", p.getTitleText());
assertTrue(p.getWebResponse().getContentAsString().contains("Alternative text for icon"));
}
private void checkInjectionInHrefWithConfirm(TestRootAction testParams) throws Exception {
testParams.paramHref = hrefPayload;
testParams.paramAlt = "Alternative text for icon";
testParams.paramConfirm = "Confirm message";
JenkinsRule.WebClient wc = j.createWebClient();
wc.getOptions().setThrowExceptionOnFailingStatusCode(false);
HtmlPage p = wc.goTo("test");
HtmlElementUtil.click(getStopLink(p));
assertNotEquals("hacked", p.getTitleText());
assertTrue(p.getWebResponse().getContentAsString().contains("Alternative text for icon"));
}
private void checkInjectionInConfirm(TestRootAction testParams) throws Exception {
testParams.paramHref = "#";
testParams.paramAlt = "Alternative text for icon";
testParams.paramConfirm = postPayload;
JenkinsRule.WebClient wc = j.createWebClient();
wc.getOptions().setThrowExceptionOnFailingStatusCode(false);
HtmlPage p = wc.goTo("test");
HtmlElementUtil.click(getStopLink(p));
assertNotEquals("hacked", p.getTitleText());
assertTrue(p.getWebResponse().getContentAsString().contains("Alternative text for icon"));
}
private HtmlAnchor getStopLink(HtmlPage page){
DomNodeList<HtmlElement> anchors = page.getElementById("test-panel").getElementsByTagName("a");
assertEquals(1, anchors.size());
return (HtmlAnchor) anchors.get(0);
}
@TestExtension("noInjectionArePossible")
public static class TestRootAction implements UnprotectedRootAction {
public String paramHref = "";
public String paramAlt = "";
public String paramConfirm;
@Override
public @CheckForNull String getIconFileName() {
return null;
}
@Override
public @CheckForNull String getDisplayName() {
return null;
}
@Override
public @CheckForNull String getUrlName() {
return "test";
}
@WebMethod(name = "submit")
public HttpResponse doSubmit(StaplerRequest request) {
return HttpResponses.plainText("method:" + request.getMethod());
}
}
}
<!--
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.
-->
<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core" xmlns:f="/lib/form" xmlns:l="/lib/layout">
<l:layout title="Try to inject javascript inside confirmationLink">
<l:main-panel>
<div id="test-panel">
<f:expandableTextbox name="${it.paramName}" />
</div>
</l:main-panel>
</l:layout>
</j:jelly>
\ No newline at end of file
<!--
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.
-->
<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core" xmlns:l="/lib/layout" xmlns:f="/lib/form">
<l:layout title="Try to inject javascript inside validateButton">
<l:main-panel>
<div id="test-panel">
<f:form method="post" name="config" action="thisFormWillNotBeSubmitted">
<!--<j:set var="instance" value="${it}" />-->
<j:set var="descriptor" value="${it.descriptor}" />
<f:entry title="a title">
<f:textbox name="a" value="avalue" />
</f:entry>
<f:entry title="b title">
<f:textbox name="b" value="bvalue" />
</f:entry>
<f:validateButton title="test" method="${descriptor.paramMethod}" with="${descriptor.paramWith}" />
</f:form>
</div>
</l:main-panel>
</l:layout>
</j:jelly>
\ No newline at end of file
......@@ -26,7 +26,7 @@ THE SOFTWARE.
Config page
-->
<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form">
<j:jelly xmlns:j="jelly:core" xmlns:l="/lib/layout" xmlns:f="/lib/form">
<l:layout title="Testing the effect of validateButton">
<l:main-panel>
<f:form method="post" name="config" action="thisFormWillNotBeSubmitted">
......
<!--
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.
-->
<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core" xmlns:l="/lib/layout">
<l:layout title="Try to inject javascript inside confirmationLink">
<l:main-panel>
<div id="test-panel">
<l:confirmationLink href="${it.paramHref}" message="${it.paramMessage}" class="${it.paramClass}" post="${it.paramPost}">
link name
</l:confirmationLink>
</div>
</l:main-panel>
</l:layout>
</j:jelly>
\ No newline at end of file
<!--
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.
-->
<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core" xmlns:l="/lib/layout">
<l:layout title="Try to inject javascript inside stopButton">
<l:main-panel>
<div id="test-panel">
<l:stopButton href="${it.paramHref}" alt="${it.paramAlt}" confirm="${it.paramConfirm}" />
</div>
</l:main-panel>
</l:layout>
</j:jelly>
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册