Commit message: Check for presence of crsf in admin scripts
CVE: CVE-2017-5473
CWE: CWE-284
Vulnerability description: Cross-site request forgery (CSRF) vulnerability in ntopng through 2.4 allows remote attackers to hijack the authentication of arbitrary users, as demonstrated by admin/add_user.lua, admin/change_user_prefs.lua, admin/delete_user.lua, and admin/password_reset.lua.

Project: ntopng
File: Lua.cpp

static int ntop_reload_l7_rules(lua_State *vm) {
  NetworkInterface *ntop_interface = getCurrentInterface(vm);

  ntop->getTrace()->traceEvent(TRACE_DEBUG, "%s() called", __FUNCTION__);

  if(ntop_interface) {
    char *net;
    AddressTree ptree;

    if(ntop_lua_check(vm, __FUNCTION__, 1, LUA_TSTRING)) return(CONST_LUA_PARAM_ERROR);
    if((net = (char*)lua_tostring(vm, 1)) == NULL)       return(CONST_LUA_PARAM_ERROR);

#ifdef SHAPER_DEBUG
    ntop->getTrace()->traceEvent(TRACE_NORMAL, "%s(%s)", __FUNCTION__, net);
#endif

    ptree.addAddresses(net);

#ifdef NTOPNG_PRO
    ntop_interface->refreshL7Rules(&ptree);
#endif

    return(CONST_LUA_OK);
  } else
    return(CONST_LUA_ERROR);
}

Project: ntopng
File: Lua.cpp

static int ntop_get_interface_hosts(lua_State* vm, LocationPolicy location) {
  NetworkInterface *ntop_interface = getCurrentInterface(vm);
  bool show_details = true;
  char *sortColumn = (char*)"column_ip", *country = NULL, *os_filter = NULL, *mac_filter = NULL;
  bool a2zSortOrder = true;
  u_int16_t vlan_filter,  *vlan_filter_ptr    = NULL;
  u_int32_t asn_filter,   *asn_filter_ptr     = NULL;
  int16_t network_filter, *network_filter_ptr = NULL;
  u_int32_t toSkip = 0, maxHits = CONST_MAX_NUM_HITS;

  ntop->getTrace()->traceEvent(TRACE_DEBUG, "%s() called", __FUNCTION__);

  if(lua_type(vm, 1) == LUA_TBOOLEAN) show_details   = lua_toboolean(vm, 1) ? true : false;
  if(lua_type(vm, 2) == LUA_TSTRING)  sortColumn     = (char*)lua_tostring(vm, 2);
  if(lua_type(vm, 3) == LUA_TNUMBER)  maxHits        = (u_int16_t)lua_tonumber(vm, 3);
  if(lua_type(vm, 4) == LUA_TNUMBER)  toSkip         = (u_int16_t)lua_tonumber(vm, 4);
  if(lua_type(vm, 5) == LUA_TBOOLEAN) a2zSortOrder   = lua_toboolean(vm, 5) ? true : false;
  if(lua_type(vm, 6) == LUA_TSTRING)  country        = (char*)lua_tostring(vm, 6);
  if(lua_type(vm, 7) == LUA_TSTRING)  os_filter      = (char*)lua_tostring(vm, 7);
  if(lua_type(vm, 8) == LUA_TNUMBER)  vlan_filter    = (u_int16_t)lua_tonumber(vm, 8), vlan_filter_ptr = &vlan_filter;
  if(lua_type(vm, 9) == LUA_TNUMBER)  asn_filter     = (u_int32_t)lua_tonumber(vm, 9), asn_filter_ptr = &asn_filter;
  if(lua_type(vm,10) == LUA_TNUMBER)  network_filter = (int16_t)lua_tonumber(vm, 10),  network_filter_ptr = &network_filter;
  if(lua_type(vm,11) == LUA_TSTRING)  mac_filter     = (char*)lua_tostring(vm, 11);

  if(!ntop_interface ||
    ntop_interface->getActiveHostsList(vm, get_allowed_nets(vm),
                                       show_details, location,
                                       country, mac_filter,
				       vlan_filter_ptr, os_filter, asn_filter_ptr, network_filter_ptr,
				       sortColumn, maxHits,
				       toSkip, a2zSortOrder) < 0)
    return(CONST_LUA_ERROR);

  return(CONST_LUA_OK);
}

Project: ntopng
File: Lua.cpp

static int ntop_get_grouped_interface_hosts(lua_State* vm) {
  NetworkInterface *ntop_interface = getCurrentInterface(vm);
  bool show_details = true, hostsOnly = true;
  char *country = NULL, *os_filter = NULL;
  char *groupBy = (char*)"column_ip";
  u_int16_t vlan_filter,  *vlan_filter_ptr    = NULL;
  u_int32_t asn_filter,   *asn_filter_ptr     = NULL;
  int16_t network_filter, *network_filter_ptr = NULL;

  ntop->getTrace()->traceEvent(TRACE_DEBUG, "%s() called", __FUNCTION__);

  if(lua_type(vm, 1) == LUA_TBOOLEAN) show_details = lua_toboolean(vm, 1) ? true : false;
  if(lua_type(vm, 2) == LUA_TSTRING)  groupBy    = (char*)lua_tostring(vm, 2);
  if(lua_type(vm, 3) == LUA_TSTRING)  country = (char*)lua_tostring(vm, 3);
  if(lua_type(vm, 4) == LUA_TSTRING)  os_filter      = (char*)lua_tostring(vm, 4);
  if(lua_type(vm, 5) == LUA_TNUMBER)  vlan_filter    = (u_int16_t)lua_tonumber(vm, 5), vlan_filter_ptr = &vlan_filter;
  if(lua_type(vm, 6) == LUA_TNUMBER)  asn_filter     = (u_int32_t)lua_tonumber(vm, 6), asn_filter_ptr = &asn_filter;
  if(lua_type(vm, 7) == LUA_TNUMBER)  network_filter = (int16_t)lua_tonumber(vm, 7),  network_filter_ptr = &network_filter;
  if(lua_type(vm, 8) == LUA_TBOOLEAN) hostsOnly      = lua_toboolean(vm, 8) ? true : false;

  if((!ntop_interface)
     || ntop_interface->getActiveHostsGroup(vm, get_allowed_nets(vm),
					    show_details, location_all,
					    country,
					    vlan_filter_ptr, os_filter,
					    asn_filter_ptr, network_filter_ptr,
					    hostsOnly, groupBy) < 0)
    return(CONST_LUA_ERROR);

  return(CONST_LUA_OK);
}

Project: ntopng
File: Lua.cpp

static char from_hex(char ch) {
  return isdigit(ch) ? ch - '0' : tolower(ch) - 'a' + 10;
}

Project: ntopng
File: Lua.cpp

static int ntop_get_interface_flows_peers(lua_State* vm) {
  NetworkInterface *ntop_interface = getCurrentInterface(vm);
  char *host_name;
  u_int16_t vlan_id = 0;

  ntop->getTrace()->traceEvent(TRACE_DEBUG, "%s() called", __FUNCTION__);

  if(lua_type(vm, 1) == LUA_TSTRING) {
    char buf[64];

    get_host_vlan_info((char*)lua_tostring(vm, 1), &host_name, &vlan_id, buf, sizeof(buf));
  } else
    host_name = NULL;

  /* Optional VLAN id */
  if(lua_type(vm, 2) == LUA_TNUMBER) vlan_id = (u_int16_t)lua_tonumber(vm, 2);

  if(ntop_interface)
    ntop_interface->getFlowPeersList(vm, get_allowed_nets(vm), host_name, vlan_id);

  return(CONST_LUA_OK);
}

Project: ntopng
File: Lua.cpp

int Lua::handle_script_request(struct mg_connection *conn,
			       const struct mg_request_info *request_info,
			       char *script_path) {
  char buf[64], key[64], ifname[MAX_INTERFACE_NAME_LEN];
  char *_cookies, user[64] = { '\0' }, outbuf[FILENAME_MAX];
  AddressTree ptree;
  int rc;
  bool csrf_found = false;
  
  if(!L) return(-1);

  luaL_openlibs(L); /* Load base libraries */
  lua_register_classes(L, true); /* Load custom classes */

  lua_pushlightuserdata(L, (char*)conn);
  lua_setglobal(L, CONST_HTTP_CONN);

  /* Put the GET params into the environment */
  lua_newtable(L);
  if(request_info->query_string != NULL) {
    char *query_string = strdup(request_info->query_string);
    
    if(query_string) {
      char *where;
      char *tok;
      
      // ntop->getTrace()->traceEvent(TRACE_WARNING, "[HTTP] %s", query_string);

      tok = strtok_r(query_string, "&", &where);

      while(tok != NULL) {
	/* key=val */
	char *_equal = strchr(tok, '=');

	if(_equal) {
	  char *equal;
	  int len;

	  _equal[0] = '\0';
	  _equal = &_equal[1];
	  len = strlen(_equal);

	  purifyHTTPParameter(tok), purifyHTTPParameter(_equal);

	  // ntop->getTrace()->traceEvent(TRACE_WARNING, "%s = %s", tok, _equal);

	  if((equal = (char*)malloc(len+1)) != NULL) {
	    char *decoded_buf;

	    Utils::urlDecode(_equal, equal, len+1);

	    if((decoded_buf = http_decode(equal)) != NULL) {
	      FILE *fd;

	      Utils::purifyHTTPparam(tok, true, false);
	      Utils::purifyHTTPparam(decoded_buf, false, false);

	      /* Now make sure that decoded_buf is not a file path */
	      if((decoded_buf[0] == '.')
		 && ((fd = fopen(decoded_buf, "r"))
		     || (fd = fopen(realpath(decoded_buf, outbuf), "r")))) {
		ntop->getTrace()->traceEvent(TRACE_WARNING, "Discarded '%s'='%s' as argument is a valid file path",
					     tok, decoded_buf);

		decoded_buf[0] = '\0';
		fclose(fd);
	      }

	      /* ntop->getTrace()->traceEvent(TRACE_WARNING, "'%s'='%s'", tok, decoded_buf); */

	      if(strcmp(tok, "csrf") == 0) {
		char rsp[32], user[64] = { '\0' };

		mg_get_cookie(conn, "user", user, sizeof(user));

		if((ntop->getRedis()->get(decoded_buf, rsp, sizeof(rsp)) == -1)
		   || (strcmp(rsp, user) != 0)) {
		  const char *msg = "The submitted form is expired. Please reload the page and try again";

		  ntop->getTrace()->traceEvent(TRACE_WARNING,
					       "Invalid CSRF parameter specified [%s][%s][%s][%s]: page expired?",
					       decoded_buf, rsp, user, tok);
		  free(equal);
		  return(send_error(conn, 500 /* Internal server error */,
				    msg, PAGE_ERROR, query_string, msg));
		} else
		  ntop->getRedis()->delKey(decoded_buf);

		csrf_found = true;
	      }

	      lua_push_str_table_entry(L, tok, decoded_buf);
	      free(decoded_buf);
	    }

	    free(equal);
	  } else
	    ntop->getTrace()->traceEvent(TRACE_WARNING, "Not enough memory");
	}

	tok = strtok_r(NULL, "&", &where);
      } /* while */

      free(query_string);
    } else
      ntop->getTrace()->traceEvent(TRACE_WARNING, "Not enough memory");
  }

  if(strstr(request_info->uri, "/admin/") && (!csrf_found)) {
    const char *msg = "Missing CSRF parameter";
    
    return(send_error(conn, 500 /* Internal server error */, msg, PAGE_ERROR, request_info->uri, msg));
  }
  
  lua_setglobal(L, "_GET"); /* Like in php */

  /* _SERVER */
  lua_newtable(L);
  lua_push_str_table_entry(L, "HTTP_REFERER", (char*)mg_get_header(conn, "Referer"));
  lua_push_str_table_entry(L, "HTTP_USER_AGENT", (char*)mg_get_header(conn, "User-Agent"));
  lua_push_str_table_entry(L, "SERVER_NAME", (char*)mg_get_header(conn, "Host"));
  lua_setglobal(L, "_SERVER"); /* Like in php */

  /* Cookies */
  lua_newtable(L);
  if((_cookies = (char*)mg_get_header(conn, "Cookie")) != NULL) {
    char *cookies = strdup(_cookies);
    char *tok, *where;

    // ntop->getTrace()->traceEvent(TRACE_WARNING, "=> '%s'", cookies);
    tok = strtok_r(cookies, "=", &where);
    while(tok != NULL) {
      char *val;

      while(tok[0] == ' ') tok++;

      if((val = strtok_r(NULL, ";", &where)) != NULL) {
	lua_push_str_table_entry(L, tok, val);
	// ntop->getTrace()->traceEvent(TRACE_WARNING, "'%s'='%s'", tok, val);
      } else
	break;

      tok = strtok_r(NULL, "=", &where);
    }

    free(cookies);
  }
  lua_setglobal(L, "_COOKIE"); /* Like in php */

  /* Put the _SESSION params into the environment */
  lua_newtable(L);

  mg_get_cookie(conn, "user", user, sizeof(user));
  lua_push_str_table_entry(L, "user", user);
  mg_get_cookie(conn, "session", buf, sizeof(buf));
  lua_push_str_table_entry(L, "session", buf);

  // now it's time to set the interface.
  setInterface(user);

  lua_setglobal(L, "_SESSION"); /* Like in php */

  if(user[0] != '\0') {
    char val[255];

    lua_pushlightuserdata(L, user);
    lua_setglobal(L, "user");

    snprintf(key, sizeof(key), "ntopng.user.%s.allowed_nets", user);
    if((ntop->getRedis()->get(key, val, sizeof(val)) != -1)
       && (val[0] != '\0')) {
      ptree.addAddresses(val);
      lua_pushlightuserdata(L, &ptree);
      lua_setglobal(L, CONST_ALLOWED_NETS);
      // ntop->getTrace()->traceEvent(TRACE_WARNING, "SET %p", ptree);
    }

    snprintf(key, sizeof(key), CONST_STR_USER_ALLOWED_IFNAME, user);
    if(snprintf(key, sizeof(key), CONST_STR_USER_ALLOWED_IFNAME, user)
       && !ntop->getRedis()->get(key, ifname, sizeof(ifname))) {
      lua_pushlightuserdata(L, ifname);
      lua_setglobal(L, CONST_ALLOWED_IFNAME);
    }
  }

#ifndef NTOPNG_PRO
  rc = luaL_dofile(L, script_path);
#else
  if(ntop->getPro()->has_valid_license())
    rc = __ntop_lua_handlefile(L, script_path, true);
  else
    rc = luaL_dofile(L, script_path);
#endif

  if(rc != 0) {
    const char *err = lua_tostring(L, -1);

    ntop->getTrace()->traceEvent(TRACE_WARNING, "Script failure [%s][%s]", script_path, err);
    return(send_error(conn, 500 /* Internal server error */,
		      "Internal server error", PAGE_ERROR, script_path, err));
  }

  return(CONST_LUA_OK);
}

Project: ntopng
File: Lua.cpp

static int ntop_get_ndpi_interface_stats(lua_State* vm) {
  NetworkInterface *ntop_interface = getCurrentInterface(vm);
  nDPIStats stats;

  ntop->getTrace()->traceEvent(TRACE_DEBUG, "%s() called", __FUNCTION__);

  if(ntop_interface) {
    ntop_interface->getnDPIStats(&stats);

    lua_newtable(vm);
    stats.lua(ntop_interface, vm);
  }

  return(CONST_LUA_OK);
}

Project: ntopng
File: Lua.cpp

void Lua::purifyHTTPParameter(char *param) {
  char *ampercent;

  if((ampercent = strchr(param, '%')) != NULL) {
    /* We allow only a few chars, removing all the others */

    if((ampercent[1] != 0) && (ampercent[2] != 0)) {
      char c;
      char b = ampercent[3];

      ampercent[3] = '\0';
      c = (char)strtol(&ampercent[1], NULL, 16);
      ampercent[3] = b;

      switch(c) {
      case '/':
      case ':':
      case '(':
      case ')':
      case '{':
      case '}':
      case '[':
      case ']':
      case '?':
      case '!':
      case '$':
      case ',':
      case '^':
      case '*':
      case '_':
      case '&':
      case ' ':
      case '=':
      case '<':
      case '>':
      case '@':
      case '#':
	break;

      default:
	if(!Utils::isPrintableChar(c)) {
	  ntop->getTrace()->traceEvent(TRACE_WARNING, "Discarded char '%c' in URI [%s]", c, param);
	  ampercent[0] = '\0';
	  return;
	}
      }

      purifyHTTPParameter(&ampercent[3]);
    } else
      ampercent[0] = '\0';
  }
}

Project: ntopng
File: Lua.cpp

static int ntop_delete_redis_key(lua_State* vm) {
  char *key;

  ntop->getTrace()->traceEvent(TRACE_DEBUG, "%s() called", __FUNCTION__);

  if(ntop_lua_check(vm, __FUNCTION__, 1, LUA_TSTRING)) return(CONST_LUA_PARAM_ERROR);
  if((key = (char*)lua_tostring(vm, 1)) == NULL)  return(CONST_LUA_PARAM_ERROR);
  ntop->getRedis()->delKey(key);
  return(CONST_LUA_OK);
}

Project: ntopng
File: Lua.cpp

static char* http_decode(char *str) {
  char *pstr = str, *buf = (char*)malloc(strlen(str) + 1), *pbuf = buf;
  while (*pstr) {
    if(*pstr == '%') {
      if(pstr[1] && pstr[2]) {
        *pbuf++ = from_hex(pstr[1]) << 4 | from_hex(pstr[2]);
        pstr += 2;
      }
    } else if(*pstr == '+') {
      *pbuf++ = ' ';
    } else {
      *pbuf++ = *pstr;
    }
    pstr++;
  }
  *pbuf = '\0';
  return buf;
}

Project: ntopng
File: Lua.cpp

static int ntop_add_user(lua_State* vm) {
  char *username, *full_name, *password, *host_role, *allowed_networks, *allowed_interface;

  ntop->getTrace()->traceEvent(TRACE_DEBUG, "%s() called", __FUNCTION__);

  if(!Utils::isUserAdministrator(vm)) return(CONST_LUA_ERROR);

  if(ntop_lua_check(vm, __FUNCTION__, 1, LUA_TSTRING)) return(CONST_LUA_PARAM_ERROR);
  if((username = (char*)lua_tostring(vm, 1)) == NULL) return(CONST_LUA_PARAM_ERROR);

  if(ntop_lua_check(vm, __FUNCTION__, 2, LUA_TSTRING)) return(CONST_LUA_PARAM_ERROR);
  if((full_name = (char*)lua_tostring(vm, 2)) == NULL) return(CONST_LUA_PARAM_ERROR);

  if(ntop_lua_check(vm, __FUNCTION__, 3, LUA_TSTRING)) return(CONST_LUA_PARAM_ERROR);
  if((password = (char*)lua_tostring(vm, 3)) == NULL) return(CONST_LUA_PARAM_ERROR);

  if(ntop_lua_check(vm, __FUNCTION__, 4, LUA_TSTRING)) return(CONST_LUA_PARAM_ERROR);
  if((host_role = (char*)lua_tostring(vm, 4)) == NULL) return(CONST_LUA_PARAM_ERROR);

  if(ntop_lua_check(vm, __FUNCTION__, 5, LUA_TSTRING)) return(CONST_LUA_PARAM_ERROR);
  if((allowed_networks = (char*)lua_tostring(vm, 5)) == NULL) return(CONST_LUA_PARAM_ERROR);

  if(ntop_lua_check(vm, __FUNCTION__, 6, LUA_TSTRING)) return(CONST_LUA_PARAM_ERROR);
  if((allowed_interface = (char*)lua_tostring(vm, 6)) == NULL) return(CONST_LUA_PARAM_ERROR);

  return ntop->addUser(username, full_name, password, host_role,
		       allowed_networks, allowed_interface);
}