Haciendo un levantamiento de seguridad rutinario, me preocupó que el formulario para acceder a un dashboard fuese visible para cualquier visitante, de modo que un robot o un usuario malicioso pudiese intentar adivinar la combinación de usuario y contraseña.
Usualmente no se le da mucha prioridad a la protección del formulario de login. Al revés, la prioridad está en que dado que accedemos al formulario sea muy difícil autenticarse para acceder al dashboard. Pero prevenir un paso antes me suena como una buena práctica. ¿Qué hacen las empresas que manejan datos confidenciales y sensibles?
- Acceso al formulario de login sólo desde la intranet corporativa, o desde una lista blanca de IPs. Esto dificulta o de plano impide operar desde el móvil, y lo malo de las mantenciones de emergencia es que no esperan al horario laboral. Un downtime puede pillarte de vacaciones en Varsovia.
- El formulario de login se bloquea luego de N intentos, dificultando la entrada por fuerza bruta
- El formulario de login está en una ruta menos obvia que /admin o /login, lo cual importa muy poco habiendo bots que prueban todas las combinaciones posibles en segundos.
Todas estas medidas tienen sus pro y sus contra y pueden usarse por separado o en conjunto. La idea de este post es proporcionar una medida adicional: la autentificación via Github.
Qué tiene de especial?
De especial, como proveedor de autenticación, muy poco. Es un proveedor Oauth2 como lo son Google, Twitter, Facebook, Linkedin y una larga lista. La diferencia puntual es que cuando inicias sesión con GitHub el response autenticado incluye las organizaciones a las cuales pertenece el usuario.
Al principio pensé en usar Google, pero tener una cuenta válida de correo de una organización no necesariamente significa que tengas permiso de ver el panel de administración. Por el contrario, suena natural que si alguien tiene acceso al repositorio privado de una aplicación esté en un círculo de privilegios más alto. La idea es comprobar que, dado que estás intentando ver el formulario de login, sepamos quién eres en GitHub y comprobemos que eres miembro de la organización.
Empezamos por crear una aplicación en Github
Para efectos del ejemplo, la aplicación que vamos a crear tiene por objeto darle un poco más de seguridad al login de wordpress de este humilde blog.
Y a cambio, Github te dará un CLIENT_ID y un CLIENT_SECRET
En wordpress, la manera de usar esta app es es crear un plugin y gatillarlo en el hook ‘login_init’. Lo siguiente es el contenido del archivo /wp-content/plugins/login_checks_github.php. Obviamente hay que reemplazar los valores de CLIENT_ID, CLIENT_SECRET y ORGANIZATION con lo que corresponda y activar el plugin.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
<?php /* Plugin Name: FFF Login Checks Github Plugin URI: http://ffflabs.com Description: Check user belongs to an organization before showing the login form Author: Felipe Figueroa Version: 0.1 Author URI: http://ffflabs.com/ */ add_action( 'login_init', 'login_with_github' ); function login_with_github() { $redirect_url = get_bloginfo('url'). '/wp-login.php'; $client_id = CLIENT_ID; $client_secret=CLIENT_SECRET; $organizacion=ORGANIZATION; if(empty($_GET['code'])) { $url = "https://github.com/login/oauth/authorize?client_id=$client_id&redirect_uri=$redirect_url&scope=user"; header("Location: $url"); } else { $post = http_build_query(array( 'client_id' => $client_id , 'redirect_uri' => $redirect_url , 'client_secret' => $client_secret, 'code' => $_GET['code'] )); $context = stream_context_create(array("http" => array( "method" => "POST", "header" => "Content-Type: application/x-www-form-urlencoded\r\n" . "Content-Length: ". strlen($post) . "\r\n". "Accept: application/json" , "content" => $post, ))); if($json_data = file_get_contents("https://github.com/login/oauth/access_token", false, $context)) { $r = json_decode($json_data , true); $access_token = $r['access_token']; if(isset($access_token)) { $organizations=file_get_contents("https://api.github.com/user/orgs?access_token=$access_token"); $organizations = json_decode($organizations , true); $orglogin=array(); foreach($organizations as $organization) { $orglogin[]=$organization['login']; } if(in_array($organizacion,$orglogin)) { setcookie($client_id, $access_token,time()+3600,"/","",1,1); setcookie($organizacion, sha1($_COOKIE[$client_id]),time()+3600,"/","",1,1); } else { die ('No autorizado'); } } } } } |
Al tratar de ingresar al login, habrá un paso intermedio por Github para autorizar la aplicación y proveer un código
Autorizar la aplicación te devuelve a tu URI original, pero ahora contendrá el parametro code en el query string. Si es un código válido, permitirá comprobar que el usuario pertenece a la organización que hemos definido. El flujo vuelve al curso natural y esta vez sí permite ingresar usuario y password.
Se puede usar para algo que no sea wordpress? Claro que sí. Yo lo uso en un phpmyadmin. Sólo usé wordpress para el ejemplo, claro que en cada app hay que buscar un lugar apto para insertar el código.
El código se puede modificar para comprobar que el nombre de usuario pertenezca a un array de usuarios habilitados, el cual habría que crear y mantener manualmente al principio del plugin. También es trivial proveer un GUI para ingresar las variables en el dashboard de wordpress, pero el propósito de este post no apunta a una solución particular para wordpress. Este mismo código yo lo usé, por ejemplo, en un PHPMyAdmin.