Require an HTTP Referer header in the server. Send one in ipa tools.

This is to prevent a Cross-Site Request Forgery (CSRF) attack where
a rogue server tricks a user who was logged into the FreeIPA
management interface into visiting a specially-crafted URL where
the attacker could perform FreeIPA oonfiguration changes with the
privileges of the logged-in user.

https://bugzilla.redhat.com/show_bug.cgi?id=747710
This commit is contained in:
Rob Crittenden 2011-10-20 11:29:26 -04:00
parent da4b4fc4d9
commit 2d6eeb205e
5 changed files with 67 additions and 5 deletions

View File

@ -264,6 +264,9 @@ def uninstall(options, env, quiet=False):
if not options.on_master and os.path.exists('/etc/ipa/default.conf'):
emit_quiet(quiet, "Unenrolling client from IPA server")
join_args = ["/usr/sbin/ipa-join", "--unenroll", "-h", hostname]
if options.debug:
join_args.append("-d")
env['XMLRPC_TRACE_CURL'] = 'yes'
(stdout, stderr, returncode) = run(join_args, raiseonerr=False, env=env)
if returncode != 0:
emit_quiet(quiet, "Unenrolling host failed: %s" % stderr)
@ -1037,6 +1040,7 @@ def install(options, env, fstore, statestore):
join_args = ["/usr/sbin/ipa-join", "-s", cli_server, "-b", realm_to_suffix(cli_realm)]
if options.debug:
join_args.append("-d")
env['XMLRPC_TRACE_CURL'] = 'yes'
if options.hostname:
join_args.append("-h")
join_args.append(options.hostname)

View File

@ -19,6 +19,7 @@
#define _GNU_SOURCE
#include "config.h"
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
@ -40,7 +41,6 @@
#include "ipa-client-common.h"
#define NAME "ipa-join"
#define VERSION "1.0"
#define JOIN_OID "2.16.840.1.113730.3.8.10.3"
@ -118,13 +118,33 @@ static int check_perms(const char *keytab)
return 0;
}
/*
* There is no API in xmlrpc-c to set arbitrary headers but we can fake it
* by using a specially-crafted User-Agent string.
*
* The caller is responsible for freeing the return value.
*/
char *
set_user_agent(const char *ipaserver) {
int ret;
char *user_agent = NULL;
ret = asprintf(&user_agent, "%s/%s\r\nReferer: https://%s/ipa/xml\r\nX-Original-User-Agent:", NAME, VERSION, ipaserver);
if (ret == -1) {
fprintf(stderr, _("Out of memory!"));
return NULL;
}
return user_agent;
}
/*
* Make an XML-RPC call to methodName. This uses the curl client to make
* a connection over SSL using the CA cert that should have been installed
* by ipa-client-install.
*/
static void
callRPC(xmlrpc_env * const envP,
callRPC(char * user_agent,
xmlrpc_env * const envP,
xmlrpc_server_info * const serverInfoP,
const char * const methodName,
xmlrpc_value * const paramArrayP,
@ -149,6 +169,7 @@ callRPC(xmlrpc_env * const envP,
curlXportParmsP->no_ssl_verifypeer = 1;
curlXportParmsP->no_ssl_verifyhost = 1;
curlXportParmsP->cainfo = "/etc/ipa/ca.crt";
curlXportParmsP->user_agent = user_agent;
/* Enable GSSAPI credentials delegation */
curlXportParmsP->gssapi_delegation = 1;
@ -523,6 +544,7 @@ join_krb5(const char *ipaserver, char *hostname, char **hostdn, const char **pri
xmlrpc_value *hostdnP = NULL;
const char *krblastpwdchange = NULL;
char * url = NULL;
char * user_agent = NULL;
int rval = 0;
int ret;
@ -575,7 +597,11 @@ join_krb5(const char *ipaserver, char *hostname, char **hostdn, const char **pri
xmlrpc_array_append_item(&env, paramArrayP, optionsP);
xmlrpc_DECREF(optionsP);
callRPC(&env, serverInfoP, "join", paramArrayP, &resultP);
if ((user_agent = set_user_agent(ipaserver)) == NULL) {
rval = 3;
goto cleanup;
}
callRPC(user_agent, &env, serverInfoP, "join", paramArrayP, &resultP);
if (handle_fault(&env)) {
rval = 17;
goto cleanup_xmlrpc;
@ -640,6 +666,7 @@ cleanup:
if (resultP) xmlrpc_DECREF(resultP);
cleanup_xmlrpc:
free(user_agent);
free(url);
free((char *)krblastpwdchange);
xmlrpc_env_clean(&env);
@ -676,6 +703,7 @@ unenroll_host(const char *server, const char *hostname, const char *ktname, int
xmlrpc_server_info * serverInfoP = NULL;
xmlrpc_value *princP = NULL;
char * url = NULL;
char * user_agent = NULL;
if (server) {
ipaserver = strdup(server);
@ -817,7 +845,11 @@ unenroll_host(const char *server, const char *hostname, const char *ktname, int
xmlrpc_array_append_item(&env, paramArrayP, argArrayP);
xmlrpc_DECREF(paramP);
callRPC(&env, serverInfoP, "host_disable", paramArrayP, &resultP);
if ((user_agent = set_user_agent(ipaserver)) == NULL) {
rval = 3;
goto cleanup;
}
callRPC(user_agent, &env, serverInfoP, "host_disable", paramArrayP, &resultP);
if (handle_fault(&env)) {
rval = 17;
goto cleanup;
@ -845,6 +877,7 @@ unenroll_host(const char *server, const char *hostname, const char *ktname, int
cleanup:
free(user_agent);
if (keytab) krb5_kt_close(krbctx, keytab);
free((char *)principal);
free((char *)ipaserver);

View File

@ -441,6 +441,23 @@ class XMLRPCMarshallError(PublicError):
errno = 910
format = _('error marshalling data for XML-RPC transport: %(error)s')
class RefererError(PublicError):
"""
**911** Raised when the the request does not contain an HTTP referer
For example:
>>> raise RefererError()
Traceback (most recent call last):
...
RefererError: Missing or invalid HTTP Referer
"""
errno = 911
format = _('Missing or invalid HTTP Referer, %(referer)s')
##############################################################################
# 1000 - 1999: Authentication errors
class AuthenticationError(PublicError):

View File

@ -208,6 +208,9 @@ class LanguageAwareTransport(Transport):
extra_headers.append(
('Accept-Language', lang.replace('_', '-'))
)
extra_headers.append(
('Referer', 'https://%s/ipa/xml' % str(host))
)
return (host, extra_headers, x509)

View File

@ -27,7 +27,7 @@ from cgi import parse_qs
from xml.sax.saxutils import escape
from xmlrpclib import Fault
from ipalib.backend import Executioner
from ipalib.errors import PublicError, InternalError, CommandError, JSONError, ConversionError, CCacheError
from ipalib.errors import PublicError, InternalError, CommandError, JSONError, ConversionError, CCacheError, RefererError
from ipalib.request import context, Connection, destroy_context
from ipalib.rpc import xml_dumps, xml_loads
from ipalib.util import make_repr
@ -200,6 +200,11 @@ class WSGIExecutioner(Executioner):
options = {}
if not 'KRB5CCNAME' in environ:
return self.marshal(result, CCacheError(), _id)
self.debug('Request environment: %s' % environ)
if not 'HTTP_REFERER' in environ:
return self.marshal(result, RefererError(referer='missing'), _id)
if not environ['HTTP_REFERER'].startswith('https://%s/ipa' % self.api.env.host) and not self.env.in_tree:
return self.marshal(result, RefererError(referer=environ['HTTP_REFERER']), _id)
try:
if ('HTTP_ACCEPT_LANGUAGE' in environ):
lang_reg_w_q = environ['HTTP_ACCEPT_LANGUAGE'].split(',')[0]