CVE-2014-9773: Improper Access Control (CWE-284)
value: modules/chanserv/flags.c in Atheme before 7.2.7 allows remote attackers to modify the Anope FLAGS behavior by registering and dropping the (1) LIST, (2) CLEAR, or (3) MODIFY keyword nicks.
CWE description: The product does not restrict or incorrectly restricts access to a resource from an unauthorized actor.
CVSS v3 base score: 7.5
Published: 2016-06-13T19:59Z
Repository: https://github.com/atheme/atheme
Language: C
Files changed: modules/chanserv/flags.c
Commit date: 2016-03-06 15:33:00 +0100
Commit message: chanserv/flags: make Anope FLAGS compatibility an option

Previously, ChanServ FLAGS behavior could be modified by registering or
dropping the keyword nicks "LIST", "CLEAR", and "MODIFY".
Now, a configuration option is available that when turned on (default),
disables registration of these keyword nicks and enables this
compatibility feature. When turned off, registration of these keyword
nicks is possible, and compatibility to Anope's FLAGS command is
disabled.

Fixes atheme/atheme#397

Diff: diff --git a/modules/chanserv/flags.c b/modules/chanserv/flags.c
index 289345d85..72d8fcfbd 100644
--- a/modules/chanserv/flags.c
+++ b/modules/chanserv/flags.c
@@ -15,22 +15,39 @@ DECLARE_MODULE_V1
 	PACKAGE_STRING,
 	"Atheme Development Group <http://www.atheme.org>"
 );
 
 static void cs_cmd_flags(sourceinfo_t *si, int parc, char *parv[]);
+static void check_registration_keywords(hook_user_register_check_t *hdata);
 
 command_t cs_flags = { "FLAGS", N_("Manipulates specific permissions on a channel."),
                         AC_NONE, 3, cs_cmd_flags, { .path = "cservice/flags" } };
 
+static bool anope_flags_compat = true;
+
 void _modinit(module_t *m)
 {
 	service_named_bind_command("chanserv", &cs_flags);
+
+	add_bool_conf_item("ANOPE_FLAGS_COMPAT", &chansvs.me->conf_table, 0, &anope_flags_compat, true);
+
+	hook_add_event("nick_can_register");
+	hook_add_nick_can_register(check_registration_keywords);
+
+	hook_add_event("user_can_register");
+	hook_add_user_can_register(check_registration_keywords);
 }
 
 void _moddeinit(module_unload_intent_t intent)
 {
 	service_named_unbind_command("chanserv", &cs_flags);
+
+	hook_del_nick_can_register(check_registration_keywords);
+
+	hook_del_user_can_register(check_registration_keywords);
+
+	del_conf_item("ANOPE_FLAGS_COMPAT", &chansvs.me->conf_table);
 }
 
 typedef struct {
 	const char *res;
 	unsigned int level;
@@ -148,10 +165,24 @@ static void do_list(sourceinfo_t *si, mychan_t *mc, unsigned int flags)
 		logcommand(si, CMDLOG_ADMIN, "FLAGS: \2%s\2 (oper override)", mc->name);
 	else
 		logcommand(si, CMDLOG_GET, "FLAGS: \2%s\2", mc->name);
 }
 
+static void check_registration_keywords(hook_user_register_check_t *hdata)
+{
+	if (hdata->approved || !anope_flags_compat)
+	{
+		return;
+	}
+
+	if (!strcasecmp(hdata->account, "LIST") || !strcasecmp(hdata->account, "CLEAR") || !strcasecmp(hdata->account, "MODIFY"))
+	{
+		command_fail(hdata->si, fault_badparams, "The nick \2%s\2 is reserved and cannot be registered.", hdata->account);
+		hdata->approved = 1;
+	}
+}
+
 /* FLAGS <channel> [user] [flags] */
 static void cs_cmd_flags(sourceinfo_t *si, int parc, char *parv[])
 {
 	chanacs_t *ca;
 	mowgli_node_t *n;
@@ -216,18 +247,18 @@ static void cs_cmd_flags(sourceinfo_t *si, int parc, char *parv[])
 	 * most of the anope flags tie up to atheme flags in some weird way anyway (probably because,
 	 * i don't know, they copied the entire design and then fucked it up?  yeah.  probably that.)
 	 *
 	 *   --nenolod
 	 */
-	else if (!strcasecmp(target, "LIST") && myentity_find_ext(target) == NULL)
+	else if (anope_flags_compat && !strcasecmp(target, "LIST") && myentity_find_ext(target) == NULL)
 	{
 		do_list(si, mc, 0);
 		free(target);
 
 		return;
 	}
-	else if (!strcasecmp(target, "CLEAR") && myentity_find_ext(target) == NULL)
+	else if (anope_flags_compat && !strcasecmp(target, "CLEAR") && myentity_find_ext(target) == NULL)
 	{
 		free(target);
 
 		if (!chanacs_source_has_flag(mc, si, CA_FOUNDER))
 		{
@@ -249,11 +280,11 @@ static void cs_cmd_flags(sourceinfo_t *si, int parc, char *parv[])
 
 		logcommand(si, CMDLOG_DO, "CLEAR:FLAGS: \2%s\2", mc->name);
 		command_success_nodata(si, _("Cleared flags in \2%s\2."), mc->name);
 		return;
 	}
-	else if (!strcasecmp(target, "MODIFY") && myentity_find_ext(target) == NULL)
+	else if (anope_flags_compat && !strcasecmp(target, "MODIFY") && myentity_find_ext(target) == NULL)
 	{
 		free(target);
 
 		if (parc < 3)
 		{

Vulnerable code: 	else if (!strcasecmp(target, "LIST") && myentity_find_ext(target) == NULL)
	else if (!strcasecmp(target, "CLEAR") && myentity_find_ext(target) == NULL)
	else if (!strcasecmp(target, "MODIFY") && myentity_find_ext(target) == NULL)
Fixed code: static void check_registration_keywords(hook_user_register_check_t *hdata);
static bool anope_flags_compat = true;


	add_bool_conf_item("ANOPE_FLAGS_COMPAT", &chansvs.me->conf_table, 0, &anope_flags_compat, true);

	hook_add_event("nick_can_register");
	hook_add_nick_can_register(check_registration_keywords);

	hook_add_event("user_can_register");
	hook_add_user_can_register(check_registration_keywords);

	hook_del_nick_can_register(check_registration_keywords);

	hook_del_user_can_register(check_registration_keywords);

	del_conf_item("ANOPE_FLAGS_COMPAT", &chansvs.me->conf_table);
static void check_registration_keywords(hook_user_register_check_t *hdata)
{
	if (hdata->approved || !anope_flags_compat)
	{
		return;
	}

	if (!strcasecmp(hdata->account, "LIST") || !strcasecmp(hdata->account, "CLEAR") || !strcasecmp(hdata->account, "MODIFY"))
	{
		command_fail(hdata->si, fault_badparams, "The nick \2%s\2 is reserved and cannot be registered.", hdata->account);
		hdata->approved = 1;
	}
}

	else if (anope_flags_compat && !strcasecmp(target, "LIST") && myentity_find_ext(target) == NULL)
	else if (anope_flags_compat && !strcasecmp(target, "CLEAR") && myentity_find_ext(target) == NULL)
	else if (anope_flags_compat && !strcasecmp(target, "MODIFY") && myentity_find_ext(target) == NULL)