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:
#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.
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:
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.
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.
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
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.
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)] = '