Ir al contenido

Interacción manual con HTTP y TLS

Por

Madurezreviewed
Última revisión
Publicado
Última actualización

Una observación web no es solo «hacer un GET». Antes de que la aplicación responda intervienen la resolución del nombre, la ruta de red, la conexión TCP o QUIC, la negociación TLS, la autoridad HTTP y el recurso solicitado. Un navegador resuelve todas esas capas de forma cómoda, pero también oculta cuál de ellas produjo un fallo. La interacción manual permite fijar cada variable y conservar la petición exacta.

Esta página forma parte del workflow de reconocimiento web. Vuelve al mapa canónico cuando una observación de HTTP o TLS descubra un nombre, origen o aplicación nuevos.

Los ejemplos utilizan FQDN para el nombre que selecciona la aplicación, TARGET_IP para una dirección ya atribuida, PORT para el puerto y BASE_URL para el origen completo.

Registra estos valores antes de comparar respuestas:

Capa Variable que debe permanecer visible
Resolución Nombre consultado, resolutor, A, AAAA, CNAME y timestamp
Red IP contactada, interfaz de salida, ruta, TCP o QUIC
TLS SNI, versión, ALPN, certificado, cipher y resultado de validación
HTTP Versión, método, autoridad, destino, cabeceras y cuerpo
Aplicación Código, redirección, cookies, contenido, longitud y tiempo

Dos peticiones solo son comparables cuando se conoce qué cambió entre ellas. Modificar a la vez la IP, el hostname, la sesión y el seguimiento de redirecciones puede producir una respuesta distinta sin revelar la causa.

Ncat permite comprobar si existe una conversación TCP y leer servicios HTTP/1.x sin TLS. -n evita una resolución inversa que añadiría tráfico y demora. -v muestra el estado de la conexión. -C convierte los saltos de línea enviados en CRLF, que es el terminador utilizado por HTTP/1.1.

Una petición HTTP/1.1 contiene una línea de petición, la cabecera Host y una línea vacía que termina las cabeceras:

Enviar una petición HTTP/1.1 mínima
printf 'GET / HTTP/1.1\nHost: FQDN\nConnection: close\n\n' |
ncat -nvC TARGET_IP PORT

GET / HTTP/1.1 solicita el recurso /. Host: FQDN identifica la autoridad que debe seleccionar el servidor. Connection: close facilita la captura porque pide cerrar la conexión tras la respuesta. La salida conserva la línea de estado, las cabeceras y el cuerpo sin interpretación del navegador.

Si la conexión se abre y no aparece una respuesta, revisa primero la línea vacía final, el uso de CRLF, el puerto y si el servicio espera TLS. Un cierre inmediato puede indicar protocolo incorrecto, un proxy que rechaza la petición o una política aplicada antes de llegar a la aplicación.

Ncat no es el cliente apropiado para HTTP/2 ni HTTP/3. Ambos utilizan representaciones binarias y negociación adicional. En esos casos se emplea curl o un cliente específico.

openssl s_client muestra la negociación TLS sin ocultarla detrás del cliente HTTP. -connect fija el destino de red. -servername envía el SNI que selecciona el certificado y, con frecuencia, el virtual host TLS. -showcerts presenta la cadena enviada por el servidor. Esa lista no equivale a una cadena verificada.

Inspeccionar TLS con SNI
openssl s_client \
-connect TARGET_IP:PORT \
-servername FQDN \
-showcerts \
-verify_return_error

-verify_return_error detiene el handshake cuando falla la validación. Sin esa opción, s_client continúa para facilitar el diagnóstico. Registra por separado:

  • versión TLS y protocolo negociado mediante ALPN
  • subject, SAN, issuer, serial y periodo de validez
  • fingerprint del certificado observado
  • cadena enviada y código de verificación
  • dirección y SNI utilizados

Un certificado válido para FQDN demuestra que el peer presentó una identidad aceptable para ese nombre bajo la confianza local. No demuestra que la aplicación pertenezca al objetivo ni que todas las IP asociadas entreguen la misma aplicación.

Para enviar HTTP/1.1 dentro de la sesión TLS, añade -crlf y escribe la petición completa:

Enviar HTTP/1.1 dentro de TLS
openssl s_client \
-connect TARGET_IP:PORT \
-servername FQDN \
-quiet \
-crlf
GET / HTTP/1.1
Host: FQDN
Connection: close

La línea vacía final forma parte de la petición.

curl es el cliente principal para observaciones repetibles porque separa cabeceras, cuerpo, redirecciones, resolución y tiempos. Antes de usar filtros, conserva una respuesta completa. --verbose muestra resolución, conexión, TLS y cabeceras intercambiadas. --include incorpora las cabeceras de respuesta al output. --output guarda el cuerpo sin volcar contenido binario en la terminal.

Conservar una observación HTTP completa
curl --verbose \
--include \
--output response.body \
--dump-header response.headers \
BASE_URL/

La información de --verbose se escribe en stderr. Si forma parte de la evidencia, redirígela a un fichero distinto:

Separar trazas, cabeceras y cuerpo
curl --verbose \
--dump-header response.headers \
--output response.body \
BASE_URL/ \
2>connection.trace

No uses --insecure como valor por defecto. Esa opción evita el error de validación y permite continuar, pero también elimina una señal importante. Si necesitas comparar el contenido pese a un certificado inválido, conserva primero el fallo y documenta la segunda petición como una observación sin validación del peer.

--connect-timeout SECONDS limita DNS y establecimiento de la conexión, incluido el handshake TLS. --max-time SECONDS limita la operación completa. La diferencia permite separar un endpoint inalcanzable de una descarga o respuesta que se prolonga después de conectar.

--retry COUNT reintenta errores transitorios que cURL reconoce. --retry-all-errors amplía ese conjunto. Un reintento vuelve a enviar la petición. Solo se aplica automáticamente a métodos y cuerpos cuya repetición no pueda crear dos objetos, dos mensajes o dos cambios de estado. --retry-delay fija la espera entre intentos y --retry-max-time limita el tiempo dedicado a todos ellos:

Observar un GET con límites y reintentos acotados
curl --connect-timeout 5 \
--max-time 20 \
--retry 2 \
--retry-all-errors \
--retry-delay 1 \
--retry-max-time 30 \
--dump-header response.headers \
--output response.body \
BASE_URL/

El comando puede producir hasta tres intentos. Conserva stderr para atribuir qué intento falló. Si el servicio devuelve 429, lee Retry-After y compara la política del servidor con la espera configurada. Repetir inmediatamente puede prolongar el bloqueo.

--write-out imprime métricas después de la transferencia. Las variables %{remote_ip}, %{http_code}, %{time_connect}, %{time_appconnect}, %{time_starttransfer} y %{time_total} separan conexión, TLS, primer byte y duración total:

Conservar métricas de una observación
curl --silent \
--show-error \
--connect-timeout 5 \
--max-time 20 \
--output response.body \
--write-out '%{json}\n' \
BASE_URL/ > response.metrics.json

En versiones que admiten %{json}, la salida contiene las variables de --write-out como JSON. --silent oculta el progreso y --show-error mantiene los errores. El cuerpo continúa en response.body, de modo que el fichero de métricas no mezcla datos de aplicación.

Editar /etc/hosts cambia la resolución del sistema y puede afectar a otras herramientas. curl --resolve aplica una asociación host:port:address solo a esa ejecución. La URL mantiene FQDN, por lo que curl usa ese nombre para SNI y para la autoridad HTTP mientras conecta con TARGET_IP:

Validar un origen concreto sin modificar hosts
curl --verbose \
--resolve FQDN:443:TARGET_IP \
https://FQDN/

Esta prueba responde a una pregunta precisa: «¿qué entrega esta IP cuando TLS y HTTP reciben este nombre?». No es equivalente a solicitar https://TARGET_IP/ y añadir únicamente Host: FQDN. Esa segunda variante puede enviar la IP como SNI y seleccionar otro certificado o terminar el handshake antes de que exista HTTP.

Para comparar IP distintas conserva el mismo nombre, puerto, ruta, cabeceras y sesión. Registra el comando y el timestamp de cada ejecución porque una CDN o un balanceador pueden cambiar por salud, región o tiempo.

--proxy URL envía la petición mediante un proxy HTTP, HTTPS o SOCKS compatible con cURL. El proxy puede cambiar resolución, dirección de origen, TLS o versión HTTP. Registra si la resolución ocurre en el cliente o en el proxy. Para SOCKS5, socks5h:// delega también la resolución del hostname.

--trace-ascii FILE conserva una traza legible de los datos enviados y recibidos. --trace-time antepone timestamps. La traza puede contener Authorization, cookies y cuerpos completos:

Reproducir una petición a través de un proxy
curl --proxy http://127.0.0.1:8080 \
--connect-timeout 5 \
--max-time 20 \
--trace-ascii evidence/curl.trace \
--trace-time \
--output evidence/curl.body \
BASE_URL/

Si el resultado directo y el obtenido por proxy difieren, compara primero resolución, SNI, certificados, cabeceras añadidas, negociación HTTP y redirecciones. El proxy deja de ser transparente en cuanto modifica una de esas variables.

Sin opciones adicionales, curl muestra la primera respuesta y no sigue Location. Ese es el punto de partida adecuado porque conserva la transición propuesta por el servidor. --location sigue la cadena. --max-redirs limita el número de saltos:

Seguir una cadena acotada
curl --location \
--max-redirs 5 \
--dump-header redirect-chain.headers \
--output final.body \
BASE_URL/

Anota en cada salto el código, la URL de origen, Location, cookies y cualquier cambio de esquema, hostname o puerto. Una redirección hacia un proveedor externo amplía relaciones, pero no incorpora automáticamente ese proveedor al mismo scope técnico.

--request METHOD cambia el método enviado. No convierte por sí solo la semántica del cuerpo o de otras opciones. Para comprobar los métodos anunciados por el recurso completo puede enviarse OPTIONS, aunque Allow no siempre refleja todos los caminos reales:

Consultar métodos anunciados
curl --request OPTIONS \
--include \
--output /dev/null \
BASE_URL/

Una respuesta a HEAD puede ahorrar transferencia cuando solo interesan cabeceras, pero no sustituye a GET. Algunas aplicaciones implementan rutas diferentes o calculan cabeceras de otra forma.

Las cookies y cabeceras modifican la vista de la aplicación. --cookie-jar guarda las cookies recibidas y --cookie las reutiliza. El fichero puede contener material de sesión y debe tratarse como un secreto:

Conservar y reutilizar una sesión
curl --cookie-jar session.cookies \
--output login-response.body \
BASE_URL/login
curl --cookie session.cookies \
--dump-header account.headers \
--output account.body \
BASE_URL/account

--header 'Name: value' añade una cabecera. Utilízalo para reproducir una petición observada, no para acumular cabeceras sin saber qué seleccionan. Un Authorization, una cookie, un Origin, un Accept-Language o una cabecera introducida por un proxy inverso pueden cambiar el resultado.

No coloques tokens, contraseñas o cookies directamente en comandos que quedarán en el historial. Usa ficheros con permisos restrictivos, variables inyectadas por el entorno de ejecución o la función de secretos de la herramienta de trabajo. Sanea esos valores antes de publicar el output.

Señal Capa que debe investigarse primero
NXDOMAIN o timeout del resolutor DNS, vista, resolutor y nombre
Connection refused puerto, dirección, listener y política de rechazo
timeout de conexión ruta, filtrado, pérdida y dirección
alerta TLS o fallo de validación SNI, cadena, hostname, fecha y trust store
400 antes de llegar a la aplicación destino de la petición, Host, sintaxis y proxy frontal
401 autenticación necesaria o credencial rechazada
403 autorización, política, WAF, origen o recurso
404 con cuerpo variable aplicación, soft 404, sesión, idioma o ruta normalizada
429 rate limit, ventana temporal e identidad usada para contar peticiones
respuesta distinta por herramienta DNS, SNI, protocolo, redirecciones, proxy, cookies, cabeceras y HTTP/2

Un código HTTP confirma que algún componente de la cadena interpretó la petición. No identifica por sí solo qué componente lo generó.

Una observación defendible conserva:

  • versión del cliente y comando exacto
  • timestamp y posición de origen
  • URL, IP, nombre, SNI y resolución
  • petición enviada y cadena de redirecciones
  • cabeceras, cuerpo o hash del cuerpo
  • errores de DNS, TCP y TLS por separado
  • cookies o tokens saneados
  • interpretación y siguiente pregunta

La interacción de lectura normalmente no deja cambios persistentes en la aplicación, pero puede crear sesiones, entradas de log, cachés o contadores. Elimina ficheros locales con secretos cuando ya no sean necesarios y registra cualquier acción que haya creado estado remoto, como una sesión, un objeto o una subida.