Ocultando los endpoints de la API REST de WordPress v2 de la vista pública

2 jun 2016, 18:39:44
Vistas: 36.5K
Votos: 24

Me gustaría empezar a usar la API REST de WordPress v2 para consultar información de mi sitio. He notado que cuando visito una URL de endpoint directamente, puedo ver todos los datos públicamente. También he visto que muchos tutoriales mencionan el uso de servidores de prueba o locales en lugar de sitios en producción.

Mis preguntas son:

  • ¿Está diseñado para ser usado en sitios en producción?
  • ¿Existe algún riesgo de seguridad al permitir que cualquiera pueda ver los endpoints, como por ejemplo /wp-json/wp/v2/users/ que muestra todos los usuarios registrados en el sitio?
  • ¿Es posible permitir que solo los usuarios autorizados accedan a un endpoint?

Quiero asegurarme de seguir las mejores prácticas con respecto a la seguridad, así que cualquier consejo sería útil. La documentación de la API menciona la autenticación, pero no estoy seguro de cómo evitar que se acceda directamente a la URL. ¿Cómo suelen configurar otros desarrolladores este acceso a datos para aplicaciones externas sin exponer demasiada información?

2
Comentarios

La pregunta real es, ¿estás usando los endpoints del lado del cliente (es decir, en llamadas AJAX), o del lado del servidor (quizás desde otra aplicación)?

TheDeadMedic TheDeadMedic
2 jun 2016 21:32:50

Nota: La versión más reciente del plugin WordFence tiene una opción para "Evitar el descubrimiento de nombres de usuario a través de escaneos '/?author=N', la API oEmbed y la API REST de WordPress"

squarecandy squarecandy
11 ene 2018 05:37:42
Todas las respuestas a la pregunta 5
3
22

¿Está diseñado para usarse en sitios en producción?

Sí. Muchos sitios ya lo están utilizando.

¿Existe un riesgo de seguridad al permitir que cualquiera vea los endpoints, como /wp-json/wp/v2/users/ que muestra todos los usuarios registrados en el sitio?

No. Las respuestas del servidor no tienen nada que ver con la seguridad, no hay nada que puedas hacer contra una pantalla en blanco o una respuesta de solo lectura.

Sin embargo, si tus sitios permiten contraseñas débiles, hay algunos problemas. Pero es política de tu sitio, la REST API no sabe nada sobre eso.

¿Es posible permitir que solo usuarios autorizados accedan a un endpoint?

Sí. Puedes hacerlo usando permission callback.

Por ejemplo:

if ( 'edit' === $request['context'] && ! current_user_can( 'list_users' ) ) {
    return new WP_Error( 'rest_forbidden_context', __( 'Lo siento, no puedes ver este recurso con contexto de edición.' ), array( 'status' => rest_authorization_required_code() ) );
}

¿Cómo suelen configurar otros estos datos para que sean accesibles por aplicaciones externas sin exponer demasiada información?

Esta pregunta es difícil de responder porque no sabemos qué/cuándo es demasiada información. Pero podemos seguir estrictamente las referencias de la API y las hojas de trucos de seguridad para evitar situaciones no deseadas.

3 jun 2016 08:08:10
Comentarios

Es importante destacar: "La exposición se limita a los usuarios que han creado tipos de publicaciones que están configurados para exponerse a través de la API REST". Por lo tanto, si tienes, por ejemplo, una tienda en línea donde cada cliente es un usuario, estos usuarios no se exponen a través de /wp-json/wp/v2/users/. (Referencia https://wordpress.stackexchange.com/q/252328/41488 comentario de @JHoffmann)

squarecandy squarecandy
10 nov 2017 18:48:18

Debe tenerse en cuenta que necesitas tener un nonce basado en REST wp_create_nonce('wp_rest') en el encabezado 'X-WP-Nonce', o nada de esto funcionará y siempre devolverá un error 403.

Andrew Killen Andrew Killen
12 dic 2018 11:17:10

Exactamente. Acabo de recibir un informe de Open Bug Bounty sobre esto en mi sitio WP en la versión 5.x. Incluso los script kiddies (openbugbounty.org/researchers/Cyber_World) malinterpretan el CVE para esto (cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-5487). Esto fue corregido para quienes estén preocupados. No copies y pegues todo esto sin entender qué es realmente lo que lo está utilizando y si lo necesitas.

Tony-Caffe Tony-Caffe
19 nov 2021 22:00:16
1

¿Es posible permitir que solo usuarios autorizados accedan a un endpoint?

Sí es posible agregar un callback de permisos personalizado a tu endpoint de API que requiera autenticación para ver el contenido. Los usuarios no autorizados recibirán una respuesta de error "code": "rest_forbidden"

La forma más sencilla de hacer esto es extendiendo el WP_REST_Posts_Controller. Aquí tienes un ejemplo muy simple de cómo hacerlo:

class My_Private_Posts_Controller extends WP_REST_Posts_Controller {

   /**
   * El namespace.
   *
   * @var string
   */
   protected $namespace;

   /**
   * El tipo de post para el objeto actual.
   *
   * @var string
   */
   protected $post_type;

   /**
   * Base REST para el objeto actual.
   *
   * @var string
   */
   protected $rest_base;

  /**
   * Registrar las rutas para los objetos del controlador.
   * Casi igual que WP_REST_Posts_Controller::register_routes(), pero con un 
   * callback de permisos personalizado.
   */
  public function register_routes() {
    register_rest_route( $this->namespace, '/' . $this->rest_base, array(
        array(
            'methods'             => WP_REST_Server::READABLE,
            'callback'            => array( $this, 'get_items' ),
            'permission_callback' => array( $this, 'get_items_permissions_check' ),
            'args'                => $this->get_collection_params(),
            'show_in_index'       => true,
        ),
        array(
            'methods'             => WP_REST_Server::CREATABLE,
            'callback'            => array( $this, 'create_item' ),
            'permission_callback' => array( $this, 'create_item_permissions_check' ),
            'args'                => $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ),
            'show_in_index'       => true,
        ),
        'schema' => array( $this, 'get_public_item_schema' ),
    ) );

    register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P<id>[\d]+)', array(
        array(
            'methods'             => WP_REST_Server::READABLE,
            'callback'            => array( $this, 'get_item' ),
            'permission_callback' => array( $this, 'get_item_permissions_check' ),
            'args'                => array(
                'context' => $this->get_context_param( array( 'default' => 'view' ) ),
            ),
            'show_in_index'       => true,
        ),
        array(
            'methods'             => WP_REST_Server::EDITABLE,
            'callback'            => array( $this, 'update_item' ),
            'permission_callback' => array( $this, 'update_item_permissions_check' ),
            'args'                => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
            'show_in_index'       => true,
        ),
        array(
            'methods'             => WP_REST_Server::DELETABLE,
            'callback'            => array( $this, 'delete_item' ),
            'permission_callback' => array( $this, 'delete_item_permissions_check' ),
            'args'                => array(
                'force' => array(
                    'default'     => true,
                    'description' => __( 'Si se debe omitir la papelera y forzar la eliminación.' ),
                ),
            ),
            'show_in_index'       => false,
        ),
        'schema' => array( $this, 'get_public_item_schema' ),
    ) );     
  }

  /**
   * Verificar si una solicitud dada tiene acceso para obtener items
   *
   * @param WP_REST_Request $request Datos completos sobre la solicitud.
   * @return WP_Error|bool
   */
  public function get_items_permissions_check( $request ) {
    return current_user_can( 'edit_posts' );
  }

}

Notarás que el callback de permisos function get_items_permissions_check usa current_user_can para determinar si permite el acceso. Dependiendo de cómo estés usando la API, quizás necesites aprender más sobre autenticación de clientes.

Luego puedes registrar tu tipo de post personalizado con soporte para REST API agregando los siguientes argumentos en register_post_type

  /**
   * Registrar un tipo de post para libros, con soporte para REST API
   *
   * Basado en el ejemplo de: http://codex.wordpress.org/Function_Reference/register_post_type
   */
  add_action( 'init', 'my_book_cpt' );
  function my_book_cpt() {
    $labels = array(
        'name'               => _x( 'Libros', 'nombre general del tipo de post', 'your-plugin-textdomain' ),
        'singular_name'      => _x( 'Libro', 'nombre singular del tipo de post', 'your-plugin-textdomain' ),
        'menu_name'          => _x( 'Libros', 'menú admin', 'your-plugin-textdomain' ),
        'name_admin_bar'     => _x( 'Libro', 'añadir nuevo en la barra admin', 'your-plugin-textdomain' ),
        'add_new'            => _x( 'Añadir Nuevo', 'libro', 'your-plugin-textdomain' ),
        'add_new_item'       => __( 'Añadir Nuevo Libro', 'your-plugin-textdomain' ),
        'new_item'           => __( 'Nuevo Libro', 'your-plugin-textdomain' ),
        'edit_item'          => __( 'Editar Libro', 'your-plugin-textdomain' ),
        'view_item'          => __( 'Ver Libro', 'your-plugin-textdomain' ),
        'all_items'          => __( 'Todos los Libros', 'your-plugin-textdomain' ),
        'search_items'       => __( 'Buscar Libros', 'your-plugin-textdomain' ),
        'parent_item_colon'  => __( 'Libros Padre:', 'your-plugin-textdomain' ),
        'not_found'          => __( 'No se encontraron libros.', 'your-plugin-textdomain' ),
        'not_found_in_trash' => __( 'No se encontraron libros en la Papelera.', 'your-plugin-textdomain' )
    );

    $args = array(
        'labels'             => $labels,
        'description'        => __( 'Descripción.', 'your-plugin-textdomain' ),
        'public'             => true,
        'publicly_queryable' => true,
        'show_ui'            => true,
        'show_in_menu'       => true,
        'query_var'          => true,
        'rewrite'            => array( 'slug' => 'libro' ),
        'capability_type'    => 'post',
        'has_archive'        => true,
        'hierarchical'       => false,
        'menu_position'      => null,
        'show_in_rest'       => true,
        'rest_base'          => 'books-api',
        'rest_controller_class' => 'My_Private_Posts_Controller',
        'supports'           => array( 'title', 'editor', 'author', 'thumbnail', 'excerpt', 'comments' )
    );

    register_post_type( 'book', $args );
}

Verás que rest_controller_class usa My_Private_Posts_Controller en lugar del controlador por defecto.

He tenido dificultades para encontrar buenos ejemplos y explicaciones sobre el uso de REST API fuera de la documentación. Sin embargo, encontré esta excelente explicación sobre cómo extender el controlador por defecto, y aquí hay una guía muy completa sobre cómo agregar endpoints.

19 jul 2016 20:49:33
Comentarios

Gracias, funcionó bien para mis necesidades - aunque no olvides incluir un namespace en la clase controlador si estás usando namespaces - ya que no hay error si no se encuentra la clase controlador (sí, ¡yo me olvidé!)

Alan Fuller Alan Fuller
4 jun 2020 17:06:01
2

Esto es lo que he utilizado para bloquear a todos los usuarios no logueados de usar la API REST por completo:

add_filter( 'rest_api_init', 'rest_only_for_authorized_users', 99 );
function rest_only_for_authorized_users($wp_rest_server){
    if ( !is_user_logged_in() ) {
        wp_die('Lo siento, no tienes permiso para acceder a estos datos','¿haciendo trampa?',403);
    }
}
1 dic 2017 04:48:27
Comentarios

A medida que el uso del punto final REST se expanda, este tipo de estrategia se volverá problemática. Al final, el punto final wp-json reemplazará al de admin-ajax, lo que significa que también habrá todo tipo de solicitudes legítimas desde el frontend. De todos modos, es mejor terminar con un 403 que con algo que podría interpretarse como contenido.

Mark Kaplun Mark Kaplun
1 dic 2017 07:22:41

@MarkKaplun - sí, tienes razón sobre eso. Estoy usando esto en el contexto de un sitio que básicamente no ofrece datos públicos y los datos que almacenamos, incluyendo usuarios, metadatos de usuarios, datos de tipos de posts personalizados, etc., son datos propietarios que nunca deberían ser accesibles por el público. Es frustrante cuando haces mucho trabajo dentro de la estructura clásica de plantillas de WP para asegurarte de que ciertos datos sean privados y luego te das cuenta de repente de que son accesibles públicamente a través de la API REST. En fin, buen punto sobre servir un 403...

squarecandy squarecandy
1 dic 2017 16:29:53
1

La mejor opción es desactivar el nuevo editor V5 y luego deshabilitar la API json, como se explica aquí.

https://codber.com/2020/05/01/how-to-disable-wordpress-rest-api-to-not-logged-in-user-without-a-plugin/

9 may 2020 18:52:07
Comentarios

Las respuestas que solo contienen enlaces se volverán obsoletas cuando los enlaces expiren. Puedes [editar] tu respuesta con la esencia de la misma y luego incluir un enlace para más detalles. :)

Mayeenul Islam Mayeenul Islam
10 may 2020 21:56:48
2
add_filter( 'rest_api_init', 'rest_only_for_authorized_users', 99 );
function rest_only_for_authorized_users($wp_rest_server)
{
if( !is_user_logged_in() ) 

    wp_die('Lo sentimos, no tienes permiso para acceder a estos datos','Autenticación Requerida',403);
} } 
function json_authenticate_handler( $user ) {

global $wp_json_basic_auth_error;

$wp_json_basic_auth_error = null;

// No autenticar dos veces
if ( ! empty( $user ) ) {
    return $user;
}

if ( !isset( $_SERVER['PHP_AUTH_USER'] ) ) {
    return $user;
}

$username = $_SERVER['PHP_AUTH_USER'];
$password = $_SERVER['PHP_AUTH_PW'];


remove_filter( 'determine_current_user', 'json_authenticate_handler', 20 );

$user = wp_authenticate( $username, $password );

add_filter( 'determine_current_user', 'json_authenticate_handler', 20 );

if ( is_wp_error( $user ) ) {
    $wp_json_basic_auth_error = $user;
    return null;
}

$wp_json_basic_auth_error = true;

return $user->ID;}add_filter( 'determine_current_user', 'json_authenticate_handler', 20 );
8 ene 2018 13:56:56
Comentarios

¿Podrías elaborar en texto por qué y cómo esto responde a las preguntas del OP?

kero kero
8 ene 2018 14:32:59

Esta no es la respuesta del OP y solo proporcioné código para mostrar cómo funciona en la práctica. He intentado que lo entiendas fácilmente de manera programática, si es que lo has entendido

dipen patel dipen patel
9 ene 2018 15:17:02