提交 8c4e97d1 编写于 作者: D dty

Merge in changes that implement the cross-site request forgery crumb feature.



git-svn-id: https://hudson.dev.java.net/svn/hudson/trunk/hudson/main@18738 71c3de6d-444a-0410-be80-ed276b4c234a
上级 fa48bd22
package hudson.cli;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.UUID;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Creates a capacity-unlimited bi-directional {@link InputStream}/{@link OutputStream} pair over
......@@ -34,12 +40,17 @@ public class FullDuplexHttpStream {
public FullDuplexHttpStream(URL target) throws IOException {
this.target = target;
CrumbData crumbData = new CrumbData();
// server->client
HttpURLConnection con = (HttpURLConnection) target.openConnection();
con.setDoOutput(true); // request POST to avoid caching
con.setRequestMethod("POST");
con.addRequestProperty("Session",uuid.toString());
con.addRequestProperty("Side","download");
if(crumbData.isValid) {
con.addRequestProperty(crumbData.crumbName, crumbData.crumb);
}
con.getOutputStream().close();
input = con.getInputStream();
// make sure we hit the right URL
......@@ -53,8 +64,54 @@ public class FullDuplexHttpStream {
con.setChunkedStreamingMode(0);
con.addRequestProperty("Session",uuid.toString());
con.addRequestProperty("Side","upload");
if(crumbData.isValid) {
con.addRequestProperty(crumbData.crumbName, crumbData.crumb);
}
output = con.getOutputStream();
}
static final int BLOCK_SIZE = 1024;
static final Logger LOGGER = Logger.getLogger(FullDuplexHttpStream.class.getName());
private final class CrumbData {
String crumbName;
String crumb;
boolean isValid;
private CrumbData() {
this.crumbName = "";
this.crumb = "";
this.isValid = false;
getData();
}
private void getData() {
try {
String base = createCrumbUrlBase();
crumbName = readData(base+"?xpath=/*/crumbRequestField/text()");
crumb = readData(base+"?xpath=/*/crumb/text()");
isValid = true;
LOGGER.fine("Crumb data: "+crumbName+"="+crumb);
}
catch (IOException e) {
LOGGER.log(Level.WARNING,"Failed to get crumb data",e);
}
}
private String createCrumbUrlBase() {
String url = target.toExternalForm();
return new StringBuilder(url.substring(0, url.lastIndexOf("/cli"))).append("/crumbIssuer/api/xml/").toString();
}
private String readData(String dest) throws IOException {
HttpURLConnection con = (HttpURLConnection) new URL(dest).openConnection();
try {
BufferedReader reader = new BufferedReader(new InputStreamReader(con.getInputStream()));
return reader.readLine();
}
finally {
con.disconnect();
}
}
}
}
......@@ -702,6 +702,17 @@ THE SOFTWARE.
<artifactId>jinterop-wmi</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>org.jvnet.hudson</groupId>
<artifactId>htmlunit</artifactId>
<version>2.2-hudson-9</version>
<exclusions>
<exclusion>
<groupId>xalan</groupId>
<artifactId>xalan</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- offline profiler API to put in the classpath if we need it -->
<!--dependency>
......
......@@ -47,6 +47,7 @@ import hudson.security.AccessControlled;
import hudson.security.AuthorizationStrategy;
import hudson.security.Permission;
import hudson.security.SecurityRealm;
import hudson.security.csrf.CrumbIssuer;
import hudson.slaves.Cloud;
import hudson.slaves.ComputerLauncher;
import hudson.slaves.NodeProperty;
......@@ -1132,6 +1133,56 @@ public class Functions {
return body;
}
public static List<Descriptor<CrumbIssuer>> getCrumbIssuerDescriptors() {
return CrumbIssuer.all();
}
public static String getCrumb(StaplerRequest req) {
CrumbIssuer issuer = Hudson.getInstance().getCrumbIssuer();
if (issuer != null) {
return issuer.getCrumb(req);
}
return "";
}
public static String getCrumbRequestField() {
CrumbIssuer issuer = Hudson.getInstance().getCrumbIssuer();
if (issuer != null) {
return issuer.getDescriptor().getCrumbRequestField();
}
return "";
}
public static String getCrumbAsJSONParameterBlock(StaplerRequest req) {
StringBuilder builder = new StringBuilder();
if (Hudson.getInstance().isUseCrumbs()) {
builder.append("parameters:{\"");
builder.append(getCrumbRequestField());
builder.append("\":\"");
builder.append(getCrumb(req));
builder.append("\"}");
}
return builder.toString();
}
public static String getReplaceDescriptionInvoker(StaplerRequest req) {
StringBuilder builder = new StringBuilder();
if (isAutoRefresh(req)) {
builder.append("null");
} else {
builder.append("'return replaceDescription(");
builder.append("\\'");
builder.append(getCrumbRequestField());
builder.append("\\',\\'");
builder.append(getCrumb(req));
builder.append("\\'");
builder.append(");'");
}
return builder.toString();
}
private static final Pattern SCHEME = Pattern.compile("[a-z]+://.+");
/**
......
......@@ -3,6 +3,7 @@ package hudson.model;
import hudson.Extension;
import hudson.ExtensionList;
import hudson.ExtensionPoint;
import hudson.security.csrf.CrumbIssuer;
import hudson.util.QuotedStringTokenizer;
import hudson.util.TextFile;
import hudson.util.TimeUnit2;
......@@ -38,9 +39,22 @@ public class DownloadService extends PageDecorator {
StringBuilder buf = new StringBuilder();
if(Hudson.getInstance().hasPermission(Hudson.READ)) {
long now = System.currentTimeMillis();
CrumbIssuer ci = Hudson.getInstance().getCrumbIssuer();
String crumbName = null;
String crumb = null;
if(ci!=null) {
crumbName = ci.getCrumbRequestField();
crumb = ci.getCrumb();
}
for (Downloadable d : Downloadable.all()) {
if(d.getDue()<now) {
buf.append("<script>downloadService.download(")
buf.append("<script>");
if(ci!=null) {
buf.append("downloadService.crumbName="+QuotedStringTokenizer.quote(crumbName))
.append(";downloadService.crumb="+QuotedStringTokenizer.quote(crumb))
.append(';');
}
buf.append("downloadService.download(")
.append(QuotedStringTokenizer.quote(d.getId()))
.append(',')
.append(QuotedStringTokenizer.quote(d.getUrl()))
......@@ -197,3 +211,4 @@ public class DownloadService extends PageDecorator {
private static final Logger LOGGER = Logger.getLogger(Downloadable.class.getName());
}
}
/*
* The MIT License
*
* Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Erik Ramfelt, Koichi Fujikawa, Red Hat, Inc., Seiji Sogabe, Stephen Connolly, Tom Huybrechts
* Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Erik Ramfelt, Koichi Fujikawa, Red Hat, Inc., Seiji Sogabe, Stephen Connolly, Tom Huybrechts, Yahoo! 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
......@@ -79,6 +79,9 @@ import hudson.security.Permission;
import hudson.security.PermissionGroup;
import hudson.security.SecurityMode;
import hudson.security.SecurityRealm;
import hudson.security.csrf.CrumbFilter;
import hudson.security.csrf.CrumbIssuer;
import hudson.security.csrf.CrumbIssuerDescriptor;
import hudson.slaves.ComputerListener;
import hudson.slaves.NodeProperty;
import hudson.slaves.NodePropertyDescriptor;
......@@ -416,6 +419,13 @@ public final class Hudson extends Node implements ItemGroup<TopLevelItem>, Stapl
*/
private String label="";
private Boolean useCrumbs;
/**
* {@link hudson.security.csrf.CrumbIssuer}
*/
private volatile CrumbIssuer crumbIssuer;
/**
* All labels known to Hudson. This allows us to reuse the same label instances
* as much as possible, even though that's not a strict requirement.
......@@ -1582,6 +1592,10 @@ public final class Hudson extends Node implements ItemGroup<TopLevelItem>, Stapl
return securityRealm!=SecurityRealm.NO_AUTHENTICATION || authorizationStrategy!=AuthorizationStrategy.UNSECURED;
}
public boolean isUseCrumbs() {
return (useCrumbs != null) && useCrumbs;
}
/**
* Returns the constant that captures the three basic security modes
* in Hudson.
......@@ -2047,6 +2061,9 @@ public final class Hudson extends Node implements ItemGroup<TopLevelItem>, Stapl
setSecurityRealm(SecurityRealm.NO_AUTHENTICATION);
}
// Initialize the filter with the crumb issuer
setCrumbIssuer(crumbIssuer);
LOGGER.info(String.format("Took %s ms to load",System.currentTimeMillis()-startTime));
if(KILL_AFTER_LOAD)
System.exit(0);
......@@ -2149,6 +2166,15 @@ public final class Hudson extends Node implements ItemGroup<TopLevelItem>, Stapl
authorizationStrategy = AuthorizationStrategy.UNSECURED;
}
if (json.has("csrf")) {
useCrumbs = true;
JSONObject csrf = json.getJSONObject("csrf");
setCrumbIssuer(CrumbIssuer.all().newInstanceFromRadioList(csrf, "issuer"));
} else {
useCrumbs = null;
setCrumbIssuer(null);
}
noUsageStatistics = json.has("usageStatisticsCollected") ? null : true;
{
......@@ -2219,6 +2245,9 @@ public final class Hudson extends Node implements ItemGroup<TopLevelItem>, Stapl
for( PageDecorator d : PageDecorator.all() )
result &= configureDescriptor(req,json,d);
for( Descriptor<CrumbIssuer> d : CrumbIssuer.all() )
result &= configureDescriptor(req,json, d);
for( ToolDescriptor d : ToolInstallation.all() )
result &= configureDescriptor(req,json,d);
......@@ -2247,6 +2276,19 @@ public final class Hudson extends Node implements ItemGroup<TopLevelItem>, Stapl
}
}
public CrumbIssuer getCrumbIssuer() {
return crumbIssuer;
}
public void setCrumbIssuer(CrumbIssuer issuer) {
crumbIssuer = issuer;
CrumbFilter.get(servletContext).setCrumbIssuer(issuer);
}
public void setUseCrumbs(Boolean use) {
useCrumbs = use;
}
public synchronized void doTestPost( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException {
JSONObject form = req.getSubmittedForm();
rsp.sendRedirect("foo");
......@@ -2608,6 +2650,9 @@ public final class Hudson extends Node implements ItemGroup<TopLevelItem>, Stapl
public void doDoFingerprintCheck( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException {
// Parse the request
MultipartFormDataParser p = new MultipartFormDataParser(req);
if(Hudson.getInstance().isUseCrumbs() && !Hudson.getInstance().getCrumbIssuer().validateCrumb(req, p)) {
rsp.sendError(HttpServletResponse.SC_FORBIDDEN,"No crumb found");
}
try {
rsp.sendRedirect2(req.getContextPath()+"/fingerprint/"+
Util.getDigestOf(p.getFileItem("name").getInputStream())+'/');
......
......@@ -96,6 +96,7 @@ import org.tmatesoft.svn.core.wc.SVNWCUtil;
import org.tmatesoft.svn.core.wc.SVNLogClient;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletResponse;
import javax.xml.transform.stream.StreamResult;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
......@@ -1286,6 +1287,10 @@ public class SubversionSCM extends SCM implements Serializable {
MultipartFormDataParser parser = new MultipartFormDataParser(req);
if(Hudson.getInstance().isUseCrumbs() && !Hudson.getInstance().getCrumbIssuer().validateCrumb(req, parser)) {
rsp.sendError(HttpServletResponse.SC_FORBIDDEN,"No crumb found");
}
String url = parser.get("url");
String kind = parser.get("kind");
......
/**
* Copyright (c) 2008-2009 Yahoo! Inc.
* All rights reserved.
* The copyrights to the contents of this file are licensed under the MIT License (http://www.opensource.org/licenses/mit-license.php)
*/
package hudson.security.csrf;
import java.io.IOException;
import java.util.Enumeration;
import java.util.logging.Logger;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Checks for and validates crumbs on requests that cause state changes, to
* protect against cross site request forgeries.
*
* @author dty
*
*/
public class CrumbFilter implements Filter {
private volatile CrumbIssuer crumbIssuer;
public CrumbIssuer getCrumbIssuer() {
return crumbIssuer;
}
public void setCrumbIssuer(CrumbIssuer issuer) {
crumbIssuer = issuer;
}
/**
* Gets the {@link CrumbFilter} created for the given {@link ServletContext}.
*/
public static CrumbFilter get(ServletContext context) {
return (CrumbFilter) context.getAttribute(CrumbFilter.class.getName());
}
/**
* {@inheritDoc}
*/
public void init(FilterConfig filterConfig) throws ServletException {
// this is how we make us available to the rest of Hudson.
filterConfig.getServletContext().setAttribute(CrumbFilter.class.getName(), this);
}
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
if (crumbIssuer == null) {
chain.doFilter(request, response);
return;
}
if (!(request instanceof HttpServletRequest)) {
chain.doFilter(request, response);
return;
}
HttpServletRequest httpRequest = (HttpServletRequest) request;
String crumbFieldName = crumbIssuer.getDescriptor().getCrumbRequestField();
String crumbSalt = crumbIssuer.getDescriptor().getCrumbSalt();
if ("POST".equals(httpRequest.getMethod())) {
String crumb = httpRequest.getHeader(crumbFieldName);
boolean valid = false;
if (crumb == null) {
Enumeration<?> paramNames = request.getParameterNames();
while (paramNames.hasMoreElements()) {
String paramName = (String) paramNames.nextElement();
if (crumbFieldName.equals(paramName)) {
crumb = request.getParameter(paramName);
break;
}
}
}
if (crumb != null) {
if (crumbIssuer.validateCrumb(httpRequest, crumbSalt, crumb)) {
valid = true;
} else {
LOGGER.warning("Found invalid crumb " + crumb +
". Will check remaining parameters for a valid one...");
}
}
// Multipart requests need to be handled by each handler.
if (valid || isMultipart(httpRequest)) {
chain.doFilter(request, response);
} else {
LOGGER.warning("No valid crumb was included in request for " + httpRequest.getRequestURI() + ". Returning " + HttpServletResponse.SC_FORBIDDEN + ".");
((HttpServletResponse) response).sendError(HttpServletResponse.SC_FORBIDDEN);
}
} else {
chain.doFilter(request, response);
}
}
protected static boolean isMultipart(HttpServletRequest request) {
if (request == null) {
return false;
}
String contentType = request.getContentType();
if (contentType == null) {
return false;
}
String[] parts = contentType.split(";");
if (parts.length == 0) {
return false;
}
for (int i = 0; i < parts.length; i++) {
if ("multipart/form-data".equals(parts[i])) {
return true;
}
}
return false;
}
/**
* {@inheritDoc}
*/
public void destroy() {
}
private static final Logger LOGGER = Logger.getLogger(CrumbFilter.class.getName());
}
/**
* Copyright (c) 2008-2009 Yahoo! Inc.
* All rights reserved.
* The copyrights to the contents of this file are licensed under the MIT License (http://www.opensource.org/licenses/mit-license.php)
*/
package hudson.security.csrf;
import javax.servlet.ServletRequest;
import org.kohsuke.stapler.Stapler;
import org.kohsuke.stapler.export.Exported;
import org.kohsuke.stapler.export.ExportedBean;
import hudson.DescriptorExtensionList;
import hudson.ExtensionPoint;
import hudson.model.Api;
import hudson.model.Describable;
import hudson.model.Descriptor;
import hudson.model.Hudson;
import hudson.util.MultipartFormDataParser;
/**
* A CrumbIssuer represents an algorithm to generate a nonce value, known as a
* crumb, to counter cross site request forgery exploits. Crumbs are typically
* hashes incorporating information that uniquely identifies an agent that sends
* a request, along with a guarded secret so that the crumb value cannot be
* forged by a third party.
*
* @author dty
* @see http://en.wikipedia.org/wiki/XSRF
*/
@ExportedBean
public abstract class CrumbIssuer implements Describable<CrumbIssuer>, ExtensionPoint {
private static final String CRUMB_ATTRIBUTE = CrumbIssuer.class.getName() + "_crumb";
/**
* Get the name of the request parameter the crumb will be stored in. Exposed
* here for the remote API.
*/
@Exported
public String getCrumbRequestField() {
return getDescriptor().getCrumbRequestField();
}
/**
* Get a crumb value based on user specific information in the current request.
* Intended for use only by the remote API.
* @return
*/
@Exported
public String getCrumb() {
return getCrumb(Stapler.getCurrentRequest());
}
/**
* Get a crumb value based on user specific information in the request.
* @param request
* @return
*/
public String getCrumb(ServletRequest request) {
String crumb = null;
if (request != null) {
crumb = (String) request.getAttribute(CRUMB_ATTRIBUTE);
}
if (crumb == null) {
crumb = issueCrumb(request, getDescriptor().getCrumbSalt());
if (request != null) {
if ((crumb != null) && !crumb.isEmpty()) {
request.setAttribute(CRUMB_ATTRIBUTE, crumb);
} else {
request.removeAttribute(CRUMB_ATTRIBUTE);
}
}
}
return crumb;
}
/**
* Create a crumb value based on user specific information in the request.
* The crumb should be generated by building a cryptographic hash of:
* <ul>
* <li>relevant information in the request that can uniquely identify the client
* <li>the salt value
* <li>an implementation specific guarded secret.
* </ul>
*
* @param request
* @param salt
* @return
*/
protected abstract String issueCrumb(ServletRequest request, String salt);
/**
* Get a crumb from a request parameter and validate it against other data
* in the current request. The salt and request parameter that is used is
* defined by the current configuration.
*
* @param request
* @return
*/
public boolean validateCrumb(ServletRequest request) {
CrumbIssuerDescriptor<CrumbIssuer> desc = getDescriptor();
String crumbField = desc.getCrumbRequestField();
String crumbSalt = desc.getCrumbSalt();
return validateCrumb(request, crumbSalt, request.getParameter(crumbField));
}
/**
* Get a crumb from multipart form data and validate it against other data
* in the current request. The salt and request parameter that is used is
* defined by the current configuration.
*
* @param request
* @param parser
* @return
*/
public boolean validateCrumb(ServletRequest request, MultipartFormDataParser parser) {
CrumbIssuerDescriptor<CrumbIssuer> desc = getDescriptor();
String crumbField = desc.getCrumbRequestField();
String crumbSalt = desc.getCrumbSalt();
return validateCrumb(request, crumbSalt, parser.get(crumbField));
}
/**
* Validate a previously created crumb against information in the current request.
*
* @param request
* @param salt
* @param crumb The previously generated crumb to validate against information in the current request
* @return
*/
public abstract boolean validateCrumb(ServletRequest request, String salt, String crumb);
/**
* Access global configuration for the crumb issuer.
*/
public CrumbIssuerDescriptor<CrumbIssuer> getDescriptor() {
return (CrumbIssuerDescriptor<CrumbIssuer>) Hudson.getInstance().getDescriptor(getClass());
}
/**
* Returns all the registered {@link CrumbIssuer} descriptors.
*/
public static DescriptorExtensionList<CrumbIssuer, Descriptor<CrumbIssuer>> all() {
return Hudson.getInstance().getDescriptorList(CrumbIssuer.class);
}
public Api getApi() {
return new Api(this);
}
}
/**
* Copyright (c) 2008-2009 Yahoo! Inc.
* All rights reserved.
* The copyrights to the contents of this file are licensed under the MIT License (http://www.opensource.org/licenses/mit-license.php)
*/
package hudson.security.csrf;
import org.kohsuke.stapler.StaplerRequest;
import hudson.Util;
import hudson.model.Descriptor;
/**
* Describes global configuration for crumb issuers. Create subclasses to specify
* additional global configuration for custom crumb issuers.
*
* @author dty
*
*/
public abstract class CrumbIssuerDescriptor<T extends CrumbIssuer> extends Descriptor<CrumbIssuer> {
private String crumbSalt;
private String crumbRequestField;
/**
* Crumb issuers always take a salt and a request field name.
*
* @param salt Salt value
* @param crumbRequestField Request parameter name containing crumb from previous response
*/
protected CrumbIssuerDescriptor(String salt, String crumbRequestField) {
setCrumbSalt(salt);
setCrumbRequestField(crumbRequestField);
}
/**
* Get the salt value.
* @return
*/
public String getCrumbSalt() {
return crumbSalt;
}
/**
* Set the salt value. Must not be null.
* @param salt
*/
public void setCrumbSalt(String salt) {
if (Util.fixEmptyAndTrim(salt) == null) {
crumbSalt = "hudson.crumb";
} else {
crumbSalt = salt;
}
}
/**
* Gets the request parameter name that contains the crumb generated from a
* previous response.
*
* @return
*/
public String getCrumbRequestField() {
return crumbRequestField;
}
/**
* Set the request parameter name. Must not be null.
*
* @param requestField
*/
public void setCrumbRequestField(String requestField) {
if (Util.fixEmptyAndTrim(requestField) == null) {
crumbRequestField = ".crumb";
} else {
crumbRequestField = requestField;
}
}
@Override
public boolean configure(StaplerRequest request) {
setCrumbSalt(request.getParameter("csrf_crumbSalt"));
setCrumbRequestField(request.getParameter("csrf_crumbRequestField"));
save();
return true;
}
/**
* Returns the Jelly script that contains common configuration.
*/
@Override
public final String getConfigPage() {
return getViewPage(CrumbIssuer.class, "config.jelly");
}
/**
* Returns a subclass specific configuration page. The base CrumbIssuerDescriptor
* class provides configuration options that are common to all crumb issuers.
* Implementations may provide additional configuration options which are
* kept in Jelly script file tied to the subclass.
* <p>
* By default, an empty string is returned, which signifies no additional
* configuration is needed for a crumb issuer. Override this method if your
* crumb issuer has additional configuration options.
* <p>
* A typical implementation of this method would look like:
* <p>
* <code>
* return getViewPage(clazz, "config.jelly");
* </code>
*
* @return An empty string, signifying no additional configuration.
*/
public String getSubConfigPage() {
return "";
}
}
/**
* Copyright (c) 2008-2009 Yahoo! Inc.
* All rights reserved.
* The copyrights to the contents of this file are licensed under the MIT License (http://www.opensource.org/licenses/mit-license.php)
*/
package hudson.security.csrf;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.logging.Level;
import java.util.logging.Logger;
import hudson.Extension;
import hudson.model.Hudson;
import hudson.model.ModelObject;
import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import net.sf.json.JSONObject;
import org.acegisecurity.Authentication;
import org.kohsuke.stapler.StaplerRequest;
/**
* A crumb issuing algorithm based on the request principal and the remote address.
*
* @author dty
*
*/
public class DefaultCrumbIssuer extends CrumbIssuer {
private MessageDigest md;
DefaultCrumbIssuer() {
try {
this.md = MessageDigest.getInstance("MD5");
} catch (NoSuchAlgorithmException e) {
this.md = null;
LOGGER.log(Level.SEVERE, "Can't find MD5", e);
}
}
/**
* {@inheritDoc}
*/
@Override
protected String issueCrumb(ServletRequest request, String salt) {
if (request instanceof HttpServletRequest) {
if (md != null) {
HttpServletRequest req = (HttpServletRequest) request;
StringBuilder buffer = new StringBuilder();
Authentication a = Hudson.getAuthentication();
if (a != null) {
buffer.append(a.getName());
}
buffer.append(';');
buffer.append(req.getRemoteAddr());
md.update(buffer.toString().getBytes());
md.update(salt.getBytes());
byte[] crumbBytes = md.digest(Hudson.getInstance().getSecretKey().getBytes());
StringBuilder hexString = new StringBuilder();
for (int i = 0; i < crumbBytes.length; i++) {
String hex = Integer.toHexString(0xFF & crumbBytes[i]);
if (hex.length() == 1) {
hexString.append('0');
}
hexString.append(hex);
}
return hexString.toString();
}
}
return null;
}
/**
* {@inheritDoc}
*/
@Override
public boolean validateCrumb(ServletRequest request, String salt, String crumb) {
if (request instanceof HttpServletRequest) {
String newCrumb = issueCrumb(request, salt);
if ((newCrumb != null) && (crumb != null)) {
return newCrumb.equals(crumb);
}
}
return false;
}
@Extension
public static final class DescriptorImpl extends CrumbIssuerDescriptor<DefaultCrumbIssuer> implements ModelObject {
public DescriptorImpl() {
super(null, null);
load();
}
@Override
public String getDisplayName() {
return "Default Crumb Issuer";
}
public DefaultCrumbIssuer newInstance(StaplerRequest req, JSONObject formData) throws FormException {
return new DefaultCrumbIssuer();
}
}
private static final Logger LOGGER = Logger.getLogger(DefaultCrumbIssuer.class.getName());
}
<!--
The MIT License
Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Seiji Sogabe
Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Seiji Sogabe, Yahoo! 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
......@@ -110,6 +110,9 @@ THE SOFTWARE.
<!-- trigger -->
new Ajax.Request(btn.getAttribute('url')+"/make"+(btn.checked?"Enable":"Disable")+"d", {
<j:if test="${app.useCrumbs}">
parameters : { "${h.getCrumbRequestField()}": "${h.getCrumb(request)}" },
</j:if>
onFailure : function(req,o) {
$('needRestart').innerHTML = req.responseText;
}
......
<!--
The MIT License
Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Erik Ramfelt, Tom Huybrechts, id:cactusman
Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Erik Ramfelt, Tom Huybrechts, id:cactusman, Yahoo! 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
......@@ -46,7 +46,8 @@ THE SOFTWARE.
onclick="${it.parameterized?null:'return build(this)'}" permission="${it.BUILD}" />
<script>
function build(a) {
new Ajax.Request(a.href,{method:"post"});
new Ajax.Request(a.href,{method:"post",${h.getCrumbAsJSONParameterBlock(request)} });
hoverNotification('${%Build scheduled}',a.parentNode);
return false;
}
......
<!--
The MIT License
Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Erik Ramfelt, Jean-Baptiste Quenot, Stephen Connolly, Tom Huybrechts
Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Erik Ramfelt, Jean-Baptiste Quenot, Stephen Connolly, Tom Huybrechts, Yahoo! 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
......@@ -88,6 +88,19 @@ THE SOFTWARE.
</f:entry>
</f:optionalBlock>
<j:if test="${!empty(h.crumbIssuerDescriptors)}">
<f:optionalBlock name="csrf" title="${%Prevent Cross Site Request Forgery exploits}"
checked="${it.useCrumbs}" help="/help/system-config/csrf.html">
<f:entry title="${Crumbs}">
<table style="width:100%">
<f:descriptorRadioList title="${%Crumb Algorithm}" varName="issuer"
instance="${it.crumbIssuer}"
descriptors="${h.crumbIssuerDescriptors}"/>
</table>
</f:entry>
</f:optionalBlock>
</j:if>
<f:optionalBlock name="usageStatisticsCollected" checked="${it.usageStatisticsCollected}"
title="${%statsBlurb}"
help="/help/system-config/usage-statistics.html" />
......
<!--
The MIT License
Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, id:cactusman
Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, id:cactusman, Yahoo! 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
......@@ -55,6 +55,7 @@ THE SOFTWARE.
</j:forEach>
<f:block>
<f:crumb />
<input type="submit" name="Submit" value="OK" id="ok" style="margin-left:5em" />
</f:block>
</f:form>
......
......@@ -35,6 +35,8 @@ THE SOFTWARE.
updateCenter.postBackURL = "${rootURL}/updateCenter/postBack";
updateCenter.info = { version:"${h.version}" };
updateCenter.url = "${h.updateCenterUrl}";
updateCenter.crumbName = "${h.getCrumbRequestField()}";
updateCenter.crumb = "${h.getCrumb(request)}";
Behaviour.addLoadEvent(updateCenter.checkUpdates);
</script>
</j:if>
......
<!--
The MIT License
Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Seiji Sogabe
Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Seiji Sogabe, Yahoo! 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
......@@ -47,6 +47,7 @@ THE SOFTWARE.
window.setTimeout(function() {
new Ajax.Request("./body", {
method: "post",
${h.getCrumbAsJSONParameterBlock(request)},
onSuccess: function(rsp) {
var div = document.createElement('div');
div.innerHTML = rsp.responseText;
......
<!--
The MIT License
Copyright (c) 2008-2009, Yahoo! 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.
-->
<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">
<f:entry title="${%Salt}" help="/help/security/csrf/salt.html">
<f:textbox name="csrf_crumbSalt" value="${descriptor.crumbSalt}" />
</f:entry>
<f:entry title="${%Request Field}" help="/help/security/csrf/field.html">
<f:textbox name="csrf_crumbField" value="${descriptor.crumbRequestField}" />
</f:entry>
<j:if test="${!empty(descriptor.subConfigPage)}">
<st:include from="${descriptor}" page="${descriptor.subConfigPage}" optional="true" />
</j:if>
</j:jelly>
\ No newline at end of file
<!--
The MIT License
Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Seiji Sogabe, id:cactusman
Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Seiji Sogabe, id:cactusman, Yahoo! 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
......@@ -83,6 +83,6 @@ THE SOFTWARE.
</tr>
</l:pane>
<script defer="true">
updateBuildHistory("${it.baseUrl}/buildHistory/ajax",${it.nextBuildNumberToFetch});
updateBuildHistory("${it.baseUrl}/buildHistory/ajax",${it.nextBuildNumberToFetch},"${h.getCrumbRequestField()}","${h.getCrumb(request)}");
</script>
</j:jelly>
\ No newline at end of file
<!--
Copyright (c) 2008-2009 Yahoo! Inc.
All rights reserved.
The copyrights to the contents of this file are licensed under the MIT License (http://www.opensource.org/licenses/mit-license.php)
-->
<j:jelly xmlns:j="jelly:core" xmlns:s="jelly:stapler" xmlns:d="jelly:define" xmlns:f="/lib/form">
<j:if test="${app.useCrumbs}">
<input type="hidden" name="${h.getCrumbRequestField()}" value="${h.getCrumb(request)}" />
</j:if>
</j:jelly>
\ No newline at end of file
<!--
The MIT License
Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi
Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Yahoo! 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
......@@ -22,7 +22,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
-->
<j:jelly xmlns:j="jelly:core" xmlns:s="jelly:stapler" xmlns:d="jelly:define">
<j:jelly xmlns:j="jelly:core" xmlns:s="jelly:stapler" xmlns:d="jelly:define" xmlns:f="/lib/form">
<s:documentation>
Submit button themed by YUI. This should be always
used instead of the plain &lt;input tag.
......@@ -36,5 +36,6 @@ THE SOFTWARE.
The text of the submit button. Something like "submit", "OK", etc.
</s:attribute>
</s:documentation>
<f:crumb/>
<input type="submit" name="${attrs.name ?: 'Submit'}" value="${attrs.value}" class="submit-button" />
</j:jelly>
\ No newline at end of file
<!--
The MIT License
Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Tom Huybrechts
Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Tom Huybrechts, Yahoo! 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
......@@ -39,7 +39,7 @@ THE SOFTWARE.
</div>
<l:hasPermission permission="${permission}">
<div align="right"><a href="editDescription" onclick="${h.isAutoRefresh(request) ? null : 'return replaceDescription();'}">
<div align="right"><a href="editDescription" onclick="${h.getReplaceDescriptionInvoker(request)}">
<img src="${imagesURL}/16x16/notepad.gif" alt="" />
<j:choose>
<j:when test="${empty(it.description)}">
......
<!--
The MIT License
Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Stephen Connolly, id:cactusman
Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Stephen Connolly, id:cactusman, Yahoo! 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
......@@ -120,7 +120,7 @@ THE SOFTWARE.
<!-- schedule updates only for the full page reload -->
<j:if test="${ajax==null and !h.isAutoRefresh(request) and h.hasPermission(app.READ)}">
<script defer="defer">
refreshPart('executors',"${h.hasView(it,'ajaxExecutors')?'.':rootURL}/ajaxExecutors");
refreshPart('executors',"${h.hasView(it,'ajaxExecutors')?'.':rootURL}/ajaxExecutors", "${h.getCrumbRequestField()}", "${h.getCrumb(request)}");
</script>
</j:if>
</l:pane>
......
<!--
The MIT License
Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi
Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Yahoo! 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
......@@ -69,6 +69,7 @@ THE SOFTWARE.
-->
<input type="radio" name="mode" value="dummy1" style="display:none" />
<input type="radio" name="mode" value="dummy2" style="display:none" />
<s:crumb />
<input type="submit" name="Submit" value="OK" id="ok" style="margin-left:5em" />
</s:block>
</s:form>
......
<!--
The MIT License
Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi
Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Yahoo! 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
......@@ -44,7 +44,7 @@ THE SOFTWARE.
function fetchNext(e,href) {
new Ajax.Request(href,{
method: "post",
parameters: "start="+e.fetchedBytes,
parameters: {"start":e.fetchedBytes,"${h.getCrumbRequestField()}":"${h.getCrumb(request)}"},
onComplete: function(rsp,_) {
<!-- append text and do autoscroll if applicable-->
var stickToBottom = scroller.isSticking();
......
<!--
The MIT License
Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi
Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Yahoo! 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
......@@ -94,7 +94,7 @@ THE SOFTWARE.
</j:choose>
<j:if test="${ajax==null and attrs.autoRefresh and !h.isAutoRefresh(request)}">
<script defer="defer">
refreshPart('matrix',"./ajaxMatrix");
refreshPart('matrix',"./ajaxMatrix","${h.getCrumbRequestField()}","${h.getCrumb(request)}");
</script>
</j:if>
</div>
......
<!--
The MIT License
Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Tom Huybrechts
Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Tom Huybrechts, Yahoo! 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
......@@ -82,7 +82,7 @@ THE SOFTWARE.
<!-- schedule updates only for the full page reload -->
<j:if test="${ajax==null and !h.isAutoRefresh(request) and h.hasPermission(app.READ)}">
<script defer="defer">
refreshPart('buildQueue',"${h.hasView(it,'ajaxBuildQueue')?'.':rootURL}/ajaxBuildQueue");
refreshPart('buildQueue',"${h.hasView(it,'ajaxBuildQueue')?'.':rootURL}/ajaxBuildQueue", "${h.getCrumbRequestField()}", "${h.getCrumb(request)}");
</script>
</j:if>
</l:pane>
......
......@@ -54,6 +54,8 @@ import hudson.model.AbstractProject;
import hudson.model.UpdateCenter.UpdateCenterConfiguration;
import hudson.model.Node.Mode;
import hudson.scm.SubversionSCM;
import hudson.security.csrf.CrumbIssuer;
import hudson.security.csrf.CrumbIssuerDescriptor;
import hudson.slaves.CommandLauncher;
import hudson.slaves.DumbSlave;
import hudson.slaves.RetentionStrategy;
......@@ -98,6 +100,7 @@ import javax.servlet.ServletContextEvent;
import junit.framework.TestCase;
import org.apache.commons.httpclient.NameValuePair;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.beanutils.PropertyUtils;
......@@ -128,8 +131,10 @@ import org.xml.sax.SAXException;
import com.gargoylesoftware.htmlunit.AjaxController;
import com.gargoylesoftware.htmlunit.BrowserVersion;
import com.gargoylesoftware.htmlunit.FailingHttpStatusCodeException;
import com.gargoylesoftware.htmlunit.HttpMethod;
import com.gargoylesoftware.htmlunit.Page;
import com.gargoylesoftware.htmlunit.WebRequestSettings;
import com.gargoylesoftware.htmlunit.html.DomNode;
import com.gargoylesoftware.htmlunit.html.HtmlButton;
import com.gargoylesoftware.htmlunit.html.HtmlForm;
import com.gargoylesoftware.htmlunit.html.HtmlPage;
......@@ -199,6 +204,10 @@ public abstract class HudsonTestCase extends TestCase {
hudson = newHudson();
hudson.setNoUsageStatistics(true); // collecting usage stats from tests are pointless.
hudson.setUseCrumbs(true);
hudson.setCrumbIssuer(((CrumbIssuerDescriptor<CrumbIssuer>)Hudson.getInstance().getDescriptor(TestCrumbIssuer.class)).newInstance(null,null));
hudson.servletContext.setAttribute("app",hudson);
hudson.servletContext.setAttribute("version","?");
WebAppMain.installExpressionFactory(new ServletContextEvent(hudson.servletContext));
......@@ -918,6 +927,30 @@ public abstract class HudsonTestCase extends TestCase {
public String getContextPath() {
return "http://localhost:"+localPort+contextPath;
}
/**
* Adds a security crumb to the quest
*/
public WebRequestSettings addCrumb(WebRequestSettings req) {
NameValuePair crumb[] = { new NameValuePair() };
crumb[0].setName(hudson.getCrumbIssuer().getDescriptor().getCrumbRequestField());
crumb[0].setValue(hudson.getCrumbIssuer().getCrumb( null ));
req.setRequestParameters(Arrays.asList( crumb ));
return req;
}
/**
* Creates a URL with crumb parameters relative to {{@link #getContextPath()}
*/
public URL createCrumbedUrl(String relativePath) throws MalformedURLException {
CrumbIssuer issuer = hudson.getCrumbIssuer();
String crumbName = issuer.getDescriptor().getCrumbRequestField();
String crumb = issuer.getCrumb(null);
return new URL(getContextPath()+relativePath+"?"+crumbName+"="+crumb);
}
}
// needs to keep reference, or it gets GC-ed.
......
/**
* Copyright (c) 2008-2009 Yahoo! Inc.
* All rights reserved.
* The copyrights to the contents of this file are licensed under the MIT License (http://www.opensource.org/licenses/mit-license.php)
*/
package org.jvnet.hudson.test;
import javax.servlet.ServletRequest;
import net.sf.json.JSONObject;
import org.kohsuke.stapler.StaplerRequest;
import hudson.Extension;
import hudson.model.ModelObject;
import hudson.security.csrf.CrumbIssuer;
import hudson.security.csrf.CrumbIssuerDescriptor;
/**
* A crumb issuer that issues a constant crumb value. Used for unit testing.
* @author dty
*/
public class TestCrumbIssuer extends CrumbIssuer
{
@Override
protected String issueCrumb( ServletRequest request, String salt )
{
return "test";
}
@Override
public boolean validateCrumb( ServletRequest request, String salt, String crumb )
{
return "test".equals(crumb);
}
@Extension
public static final class DescriptorImpl extends CrumbIssuerDescriptor<TestCrumbIssuer> implements ModelObject {
public DescriptorImpl()
{
super(null, null);
load();
}
@Override
public String getDisplayName() {
return "Test Crumb";
}
public TestCrumbIssuer newInstance(StaplerRequest req, JSONObject formData) throws FormException {
return new TestCrumbIssuer();
}
}
}
/*
* The MIT License
*
* Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi
* Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Yahoo! 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
......@@ -43,7 +43,7 @@ import org.jvnet.hudson.test.Email;
import org.jvnet.hudson.test.HudsonTestCase;
import org.jvnet.hudson.test.recipes.LocalData;
import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
import static javax.servlet.http.HttpServletResponse.SC_FORBIDDEN;
import java.net.URL;
import java.util.List;
......@@ -140,7 +140,7 @@ public class HudsonTest extends HudsonTestCase {
wc.getPage(req);
fail("Error code expected");
} catch (FailingHttpStatusCodeException e) {
assertEquals(SC_BAD_REQUEST,e.getStatusCode());
assertEquals(SC_FORBIDDEN,e.getStatusCode());
}
// the master computer object should be still here
......
/*
* The MIT License
*
* Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi
* Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Yahoo! 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
......@@ -54,8 +54,8 @@ public class ExpandableTextboxTest extends HudsonTestCase {
*/
protected HtmlPage evaluateAsHtml(String jellyScript) throws Exception {
HudsonTestCase.WebClient wc = new WebClient();
WebRequestSettings req = new WebRequestSettings(new URL(wc.getContextPath()+"eval"), POST);
WebRequestSettings req = new WebRequestSettings(wc.createCrumbedUrl("eval"), POST);
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;
......
......@@ -2,7 +2,7 @@
<!--
The MIT License
Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Tom Huybrechts, id:digerata
Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Tom Huybrechts, id:digerata, Yahoo! 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
......@@ -49,6 +49,11 @@ THE SOFTWARE.
<filter-class>hudson.security.HudsonFilter</filter-class>
</filter>
<filter>
<filter-name>csrf-filter</filter-name>
<filter-class>hudson.security.csrf.CrumbFilter</filter-class>
</filter>
<!--
The Headers filter allows us to to override headers sent by the container
that may be in conflict with what we want. For example, Tomcat will set
......@@ -82,7 +87,11 @@ THE SOFTWARE.
<filter-name>authentication-filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>csrf-filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter>
<filter-name>plugins-filter</filter-name>
<filter-class>hudson.util.PluginServletFilter</filter-class>
......
<div>
This is name of the request parameter Hudson will look in for a crumb
value.
</div>
\ No newline at end of file
<div>
The salt value is taken as an input to the crumb generation algorithm. It acts
as further randomization to complicate dictionary style attacks against the
algorithm. In the context of CSRF exploits against Hudson servers, each Hudson
server should use a different salt value. If multiple Hudson servers all use
a crumb generation algorithm that gets broken, the salt prevents an attacker
from running CSRF exploits against all these servers.
</div>
<div>
A cross site request forgery (or CSRF/XSRF) is an exploit that enables
an unauthorized third party to take actions on a web site as you. In Hudson,
this could allow someone to delete jobs, builds or change Hudson's configuration.
<p>
When this option is enabled, Hudson will check for a generated nonce value, or
"crumb", on any request that may cause a change on the Hudson server. This
includes any form submission and calls to the remote API.
<p>
More information about CSRF exploits can be found <a href="http://www.owasp.org/index.php/Cross-Site_Request_Forgery">here</a>.
</div>
......@@ -623,13 +623,16 @@ function xor(a,b) {
}
// used by editableDescription.jelly to replace the description field with a form
function replaceDescription() {
function replaceDescription(crumbName,crumb) {
var d = document.getElementById("description");
d.firstChild.nextSibling.innerHTML = "<div class='spinner-right'>loading...</div>";
var params = new Array(1);
params[crumbName] = crumb;
new Ajax.Request(
"./descriptionForm",
{
method : 'post',
parameters : params,
onComplete : function(x) {
d.innerHTML = x.responseText;
Behaviour.applySubtree(d);
......@@ -803,10 +806,13 @@ function expandTextArea(button,id) {
// refresh a part of the HTML specified by the given ID,
// by using the contents fetched from the given URL.
function refreshPart(id,url) {
function refreshPart(id,url,crumbName,crumb) {
var f = function() {
var params = new Array(1);
params[crumbName] = crumb;
new Ajax.Request(url, {
method: "post",
parameters: params,
onSuccess: function(rsp) {
var hist = $(id);
var p = hist.parentNode;
......@@ -822,7 +828,7 @@ function refreshPart(id,url) {
Behaviour.applySubtree(node);
if(isRunAsTest) return;
refreshPart(id,url);
refreshPart(id,url,crumbName,crumb);
}
});
};
......@@ -1074,14 +1080,18 @@ function addRadioBlock(id) {
}
function updateBuildHistory(ajaxUrl,nBuild) {
function updateBuildHistory(ajaxUrl,nBuild,crumbName,crumb) {
if(isRunAsTest) return;
$('buildHistory').headers = ["n",nBuild];
function updateBuilds() {
var bh = $('buildHistory');
var params = new Array(1);
params[crumbName] = crumb;
new Ajax.Request(ajaxUrl, {
requestHeaders: bh.headers,
method: "post",
parameters: params,
onSuccess: function(rsp) {
var rows = bh.rows;
......@@ -1111,9 +1121,12 @@ function updateBuildHistory(ajaxUrl,nBuild) {
// send async request to the given URL (which will send back serialized ListBoxModel object),
// then use the result to fill the list box.
function updateListBox(listBox,url) {
function updateListBox(listBox,url,crumbName,crumb) {
var params = new Array(1);
params[crumbName] = crumb;
new Ajax.Request(url, {
method: "post",
parameters: params,
onSuccess: function(rsp) {
var l = $(listBox);
while(l.length>0) l.options[0] = null;
......@@ -1500,6 +1513,8 @@ function loadScript(href) {
var downloadService = {
continuations: {},
crumbName: null,
crumb: null,
download : function(id,url,info, postBack,completionHandler) {
this.continuations[id] = {postBack:postBack,completionHandler:completionHandler};
......@@ -1508,9 +1523,12 @@ var downloadService = {
post : function(id,data) {
var o = this.continuations[id];
var params = new Array(2);
params["json"] = Object.toJSON(data);
params[downloadService.crumbName] = downloadService.crumb;
new Ajax.Request(o.postBack, {
method:"post",
parameters:{json:Object.toJSON(data)},
parameters:params,
onSuccess: function() {
if(o.completionHandler!=null)
o.completionHandler();
......@@ -1525,6 +1543,8 @@ var updateCenter = {
postBackURL : null,
info: {},
completionHandler: null,
crumbName: null,
crumb: null,
url: "https://hudson.dev.java.net/",
checkUpdates : function() {
......@@ -1532,9 +1552,12 @@ var updateCenter = {
},
post : function(data) {
var params = new Array(2);
params["json"] = Object.toJSON(data);
params[updateCenter.crumbName] = updateCenter.crumb;
new Ajax.Request(updateCenter.postBackURL, {
method:"post",
parameters:{json:Object.toJSON(data)},
parameters:params,
onSuccess: function() {
if(updateCenter.completionHandler!=null)
updateCenter.completionHandler();
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册