summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRob Crittenden <rcritten@redhat.com>2011-10-20 11:29:26 -0400
committerRob Crittenden <rcritten@redhat.com>2011-12-05 16:02:24 -0500
commit9f10fb20e918e867c44932b478275d9754265ee1 (patch)
treed39ff2e127666d00916ec4a9342b11fd9018f0c6
parent89d9ad428cf48a3aac55173ecf074e0a234a5ee5 (diff)
downloadfreeipa.git-9f10fb20e918e867c44932b478275d9754265ee1.tar.gz
freeipa.git-9f10fb20e918e867c44932b478275d9754265ee1.tar.xz
freeipa.git-9f10fb20e918e867c44932b478275d9754265ee1.zip
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
-rwxr-xr-xipa-client/ipa-install/ipa-client-install4
-rw-r--r--ipa-client/ipa-join.c41
-rw-r--r--ipalib/errors.py17
-rw-r--r--ipalib/rpc.py3
-rw-r--r--ipaserver/rpcserver.py7
5 files changed, 67 insertions, 5 deletions
diff --git a/ipa-client/ipa-install/ipa-client-install b/ipa-client/ipa-install/ipa-client-install
index 77418f72..dc0c6a1d 100755
--- a/ipa-client/ipa-install/ipa-client-install
+++ b/ipa-client/ipa-install/ipa-client-install
@@ -262,6 +262,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)
@@ -1030,6 +1033,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)
diff --git a/ipa-client/ipa-join.c b/ipa-client/ipa-join.c
index 6a852313..c174e2c1 100644
--- a/ipa-client/ipa-join.c
+++ b/ipa-client/ipa-join.c
@@ -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"
@@ -119,12 +119,32 @@ static int check_perms(const char *keytab)
}
/*
+ * 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);
diff --git a/ipalib/errors.py b/ipalib/errors.py
index 3434c26b..2bfb0692 100644
--- a/ipalib/errors.py
+++ b/ipalib/errors.py
@@ -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):
diff --git a/ipalib/rpc.py b/ipalib/rpc.py
index f8e4d9e6..8ec3a2f2 100644
--- a/ipalib/rpc.py
+++ b/ipalib/rpc.py
@@ -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)
diff --git a/ipaserver/rpcserver.py b/ipaserver/rpcserver.py
index 35a10926..77e5bdab 100644
--- a/ipaserver/rpcserver.py
+++ b/ipaserver/rpcserver.py
@@ -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]