Commit 6619f2ed authored by Sergey Lyubka's avatar Sergey Lyubka

Implemented -x option

parent 33853cd5
...@@ -143,6 +143,9 @@ will serve all URLs that start with "/config" from the "/etc" directory, and ...@@ -143,6 +143,9 @@ will serve all URLs that start with "/config" from the "/etc" directory, and
call handle_doc.cgi script for .doc and .rtf file requests. If some pattern call handle_doc.cgi script for .doc and .rtf file requests. If some pattern
matches, no further matching/substitution is performed matches, no further matching/substitution is performed
(first matching pattern wins). Use full paths in substitutions. Default: "" (first matching pattern wins). Use full paths in substitutions. Default: ""
.It Fl x Ar hide_files_patterns
A prefix pattern for the files to hide. Files that match the pattern will not
show up in directory listing and return 404 Not Found if requested. Default: ""
.El .El
.Pp .Pp
.Sh EMBEDDING .Sh EMBEDDING
......
...@@ -408,14 +408,15 @@ struct socket { ...@@ -408,14 +408,15 @@ struct socket {
int is_ssl; // Is socket SSL-ed int is_ssl; // Is socket SSL-ed
}; };
// NOTE(lsm): this enum shoulds be in sync with the config_options below.
enum { enum {
CGI_EXTENSIONS, CGI_ENVIRONMENT, PUT_DELETE_PASSWORDS_FILE, CGI_INTERPRETER, CGI_EXTENSIONS, CGI_ENVIRONMENT, PUT_DELETE_PASSWORDS_FILE, CGI_INTERPRETER,
PROTECT_URI, AUTHENTICATION_DOMAIN, SSI_EXTENSIONS, ACCESS_LOG_FILE, PROTECT_URI, AUTHENTICATION_DOMAIN, SSI_EXTENSIONS, ACCESS_LOG_FILE,
SSL_CHAIN_FILE, ENABLE_DIRECTORY_LISTING, ERROR_LOG_FILE, SSL_CHAIN_FILE, ENABLE_DIRECTORY_LISTING, ERROR_LOG_FILE,
GLOBAL_PASSWORDS_FILE, INDEX_FILES, GLOBAL_PASSWORDS_FILE, INDEX_FILES,
ENABLE_KEEP_ALIVE, ACCESS_CONTROL_LIST, MAX_REQUEST_SIZE, ENABLE_KEEP_ALIVE, ACCESS_CONTROL_LIST, MAX_REQUEST_SIZE,
EXTRA_MIME_TYPES, LISTENING_PORTS, EXTRA_MIME_TYPES, LISTENING_PORTS, DOCUMENT_ROOT, SSL_CERTIFICATE,
DOCUMENT_ROOT, SSL_CERTIFICATE, NUM_THREADS, RUN_AS_USER, REWRITE, NUM_THREADS, RUN_AS_USER, REWRITE, HIDE_FILES,
NUM_OPTIONS NUM_OPTIONS
}; };
...@@ -443,6 +444,7 @@ static const char *config_options[] = { ...@@ -443,6 +444,7 @@ static const char *config_options[] = {
"t", "num_threads", "10", "t", "num_threads", "10",
"u", "run_as_user", NULL, "u", "run_as_user", NULL,
"w", "url_rewrite_patterns", NULL, "w", "url_rewrite_patterns", NULL,
"x", "hide_files_patterns", NULL,
NULL NULL
}; };
#define ENTRIES_PER_CONFIG_OPTION 3 #define ENTRIES_PER_CONFIG_OPTION 3
...@@ -858,7 +860,6 @@ static void send_http_error(struct mg_connection *conn, int status, ...@@ -858,7 +860,6 @@ static void send_http_error(struct mg_connection *conn, int status,
// Errors 1xx, 204 and 304 MUST NOT send a body // Errors 1xx, 204 and 304 MUST NOT send a body
if (status > 199 && status != 204 && status != 304) { if (status > 199 && status != 204 && status != 304) {
len = mg_snprintf(conn, buf, sizeof(buf), "Error %d: %s", status, reason); len = mg_snprintf(conn, buf, sizeof(buf), "Error %d: %s", status, reason);
cry(conn, "%s", buf);
buf[len++] = '\n'; buf[len++] = '\n';
va_start(ap, fmt); va_start(ap, fmt);
...@@ -2385,6 +2386,13 @@ static int WINCDECL compare_dir_entries(const void *p1, const void *p2) { ...@@ -2385,6 +2386,13 @@ static int WINCDECL compare_dir_entries(const void *p1, const void *p2) {
return query_string[1] == 'd' ? -cmp_result : cmp_result; return query_string[1] == 'd' ? -cmp_result : cmp_result;
} }
static int must_hide_file(struct mg_connection *conn, const char *path) {
const char *pw_pattern = "**" PASSWORDS_FILE_NAME "$";
const char *pattern = conn->ctx->config[HIDE_FILES];
return match_prefix(pw_pattern, strlen(pw_pattern), path) > 0 ||
(pattern != NULL && match_prefix(pattern, strlen(pattern), path) > 0);
}
static int scan_directory(struct mg_connection *conn, const char *dir, static int scan_directory(struct mg_connection *conn, const char *dir,
void *data, void (*cb)(struct de *, void *)) { void *data, void (*cb)(struct de *, void *)) {
char path[PATH_MAX]; char path[PATH_MAX];
...@@ -2398,11 +2406,12 @@ static int scan_directory(struct mg_connection *conn, const char *dir, ...@@ -2398,11 +2406,12 @@ static int scan_directory(struct mg_connection *conn, const char *dir,
de.conn = conn; de.conn = conn;
while ((dp = readdir(dirp)) != NULL) { while ((dp = readdir(dirp)) != NULL) {
// Do not show current dir and passwords file // Do not show current dir and hidden files
if (!strcmp(dp->d_name, ".") || if (!strcmp(dp->d_name, ".") ||
!strcmp(dp->d_name, "..") || !strcmp(dp->d_name, "..") ||
!strcmp(dp->d_name, PASSWORDS_FILE_NAME)) must_hide_file(conn, dp->d_name)) {
continue; continue;
}
mg_snprintf(conn, path, sizeof(path), "%s%c%s", dir, DIRSEP, dp->d_name); mg_snprintf(conn, path, sizeof(path), "%s%c%s", dir, DIRSEP, dp->d_name);
...@@ -3399,9 +3408,6 @@ static void handle_request(struct mg_connection *conn) { ...@@ -3399,9 +3408,6 @@ static void handle_request(struct mg_connection *conn) {
// Do nothing, callback has served the request // Do nothing, callback has served the request
} else if (!strcmp(ri->request_method, "OPTIONS")) { } else if (!strcmp(ri->request_method, "OPTIONS")) {
send_options(conn); send_options(conn);
} else if (strstr(path, PASSWORDS_FILE_NAME)) {
// Do not allow to view passwords files
send_http_error(conn, 403, "Forbidden", "Access Forbidden");
} else if (conn->ctx->config[DOCUMENT_ROOT] == NULL) { } else if (conn->ctx->config[DOCUMENT_ROOT] == NULL) {
send_http_error(conn, 404, "Not Found", "Not Found"); send_http_error(conn, 404, "Not Found", "Not Found");
} else if ((!strcmp(ri->request_method, "PUT") || } else if ((!strcmp(ri->request_method, "PUT") ||
...@@ -3418,12 +3424,11 @@ static void handle_request(struct mg_connection *conn) { ...@@ -3418,12 +3424,11 @@ static void handle_request(struct mg_connection *conn) {
send_http_error(conn, 500, http_500_error, "remove(%s): %s", path, send_http_error(conn, 500, http_500_error, "remove(%s): %s", path,
strerror(ERRNO)); strerror(ERRNO));
} }
} else if (stat_result != 0) { } else if (stat_result != 0 || must_hide_file(conn, path)) {
send_http_error(conn, 404, "Not Found", "%s", "File not found"); send_http_error(conn, 404, "Not Found", "%s", "File not found");
} else if (st.is_directory && ri->uri[uri_len - 1] != '/') { } else if (st.is_directory && ri->uri[uri_len - 1] != '/') {
(void) mg_printf(conn, (void) mg_printf(conn, "HTTP/1.1 301 Moved Permanently\r\n"
"HTTP/1.1 301 Moved Permanently\r\n" "Location: %s/\r\n\r\n", ri->uri);
"Location: %s/\r\n\r\n", ri->uri);
} else if (!strcmp(ri->request_method, "PROPFIND")) { } else if (!strcmp(ri->request_method, "PROPFIND")) {
handle_propfind(conn, path, &st); handle_propfind(conn, path, &st);
} else if (st.is_directory && } else if (st.is_directory &&
...@@ -4322,7 +4327,7 @@ struct mg_context *mg_start(mg_callback_t user_callback, void *user_data, ...@@ -4322,7 +4327,7 @@ struct mg_context *mg_start(mg_callback_t user_callback, void *user_data,
return NULL; return NULL;
} }
if (ctx->config[i] != NULL) { if (ctx->config[i] != NULL) {
cry(fc(ctx), "%s: duplicate option", name); cry(fc(ctx), "warning: %s: duplicate option", name);
} }
ctx->config[i] = mg_strdup(value); ctx->config[i] = mg_strdup(value);
DEBUG_TRACE(("[%s] -> [%s]", name, value)); DEBUG_TRACE(("[%s] -> [%s]", name, value));
......
...@@ -174,14 +174,20 @@ my $cmd = "$exe ". ...@@ -174,14 +174,20 @@ my $cmd = "$exe ".
'-put_delete_passwords_file test/passfile ' . '-put_delete_passwords_file test/passfile ' .
'-access_control_list -0.0.0.0/0,+127.0.0.1 ' . '-access_control_list -0.0.0.0/0,+127.0.0.1 ' .
"-document_root $root ". "-document_root $root ".
"-hide_files_patterns **exploit.pl ".
"-enable_keep_alive yes ".
"-url_rewrite_patterns /aiased=/etc/,/ta=$test_dir"; "-url_rewrite_patterns /aiased=/etc/,/ta=$test_dir";
$cmd .= ' -cgi_interpreter perl' if on_windows(); $cmd .= ' -cgi_interpreter perl' if on_windows();
spawn($cmd); spawn($cmd);
my $x = 'x=' . 'A' x (200 * 1024); o("GET /hello.txt HTTP/1.1\n\n GET /hello.txt HTTP/1.0\n\n",
my $len = length($x); 'HTTP/1.1 200.+keep-alive.+HTTP/1.1 200.+close',
o("POST /env.cgi HTTP/1.0\r\nContent-Length: $len\r\n\r\n$x", 'Request pipelining', 2);
'^HTTP/1.1 200 OK', 'Long POST');
my $x = 'x=' . 'A' x (200 * 1024);
my $len = length($x);
o("POST /env.cgi HTTP/1.0\r\nContent-Length: $len\r\n\r\n$x",
'^HTTP/1.1 200 OK', 'Long POST');
# Try to overflow: Send very long request # Try to overflow: Send very long request
req('POST ' . '/..' x 100 . 'ABCD' x 3000 . "\n\n", 0); # don't log this one req('POST ' . '/..' x 100 . 'ABCD' x 3000 . "\n\n", 0); # don't log this one
...@@ -245,7 +251,8 @@ o("GET /$test_dir_uri/x/ HTTP/1.0\n\n", ...@@ -245,7 +251,8 @@ o("GET /$test_dir_uri/x/ HTTP/1.0\n\n",
"SCRIPT_FILENAME=test/test_dir/x/index.cgi", 'SCRIPT_FILENAME'); "SCRIPT_FILENAME=test/test_dir/x/index.cgi", 'SCRIPT_FILENAME');
o("GET /ta/x/ HTTP/1.0\n\n", "SCRIPT_NAME=/ta/x/index.cgi", o("GET /ta/x/ HTTP/1.0\n\n", "SCRIPT_NAME=/ta/x/index.cgi",
'Aliases SCRIPT_NAME'); 'Aliases SCRIPT_NAME');
o("GET /hello.txt HTTP/1.1\n\n", 'Connection: close', 'No keep-alive'); o("GET /hello.txt HTTP/1.1\nConnection: close\n\n", 'Connection: close',
'No keep-alive');
$path = $test_dir . $dir_separator . 'x' . $dir_separator . 'a.cgi'; $path = $test_dir . $dir_separator . 'x' . $dir_separator . 'a.cgi';
system("ln -s `which perl` $root/myperl") == 0 or fail("Can't symlink perl"); system("ln -s `which perl` $root/myperl") == 0 or fail("Can't symlink perl");
...@@ -253,11 +260,6 @@ write_file($path, "#!../../myperl\n" . ...@@ -253,11 +260,6 @@ write_file($path, "#!../../myperl\n" .
"print \"Content-Type: text/plain\\n\\nhi\";"); "print \"Content-Type: text/plain\\n\\nhi\";");
chmod(0755, $path); chmod(0755, $path);
o("GET /$test_dir_uri/x/a.cgi HTTP/1.0\n\n", "hi", 'Relative CGI interp path'); o("GET /$test_dir_uri/x/a.cgi HTTP/1.0\n\n", "hi", 'Relative CGI interp path');
#o("GET /hello.txt HTTP/1.1\n\n GET /hello.txt HTTP/1.0\n\n",
# 'HTTP/1.1 200.+keep-alive.+HTTP/1.1 200.+close',
# 'Request pipelining', 2);
o("GET * HTTP/1.0\n\n", "^HTTP/1.1 404", '* URI'); o("GET * HTTP/1.0\n\n", "^HTTP/1.1 404", '* URI');
my $mime_types = { my $mime_types = {
...@@ -343,6 +345,14 @@ unless (scalar(@ARGV) > 0 and $ARGV[0] eq "basic_tests") { ...@@ -343,6 +345,14 @@ unless (scalar(@ARGV) > 0 and $ARGV[0] eq "basic_tests") {
"realm=\"mydomain.com\", nonce=\"1291376417\", uri=\"/\",". "realm=\"mydomain.com\", nonce=\"1291376417\", uri=\"/\",".
"response=\"e8dec0c2a1a0c8a7e9a97b4b5ea6a6e6\", qop=auth, nc=00000001, cnonce=\"1a49b53a47a66e82\""; "response=\"e8dec0c2a1a0c8a7e9a97b4b5ea6a6e6\", qop=auth, nc=00000001, cnonce=\"1a49b53a47a66e82\"";
o("GET /hello.txt HTTP/1.0\nAuthorization: $auth_header\n\n", 'HTTP/1.1 200 OK', 'GET regular file with auth'); o("GET /hello.txt HTTP/1.0\nAuthorization: $auth_header\n\n", 'HTTP/1.1 200 OK', 'GET regular file with auth');
o("GET / HTTP/1.0\nAuthorization: $auth_header\n\n", '^(.(?!(.htpasswd)))*$',
'.htpasswd is hidden from the directory list');
o("GET / HTTP/1.0\nAuthorization: $auth_header\n\n", '^(.(?!(exploit.pl)))*$',
'hidden file is hidden from the directory list');
o("GET /.htpasswd HTTP/1.0\nAuthorization: $auth_header\n\n",
'^HTTP/1.1 404 ', '.htpasswd must not be shown');
o("GET /exploit.pl HTTP/1.0\nAuthorization: $auth_header\n\n",
'^HTTP/1.1 404', 'hidden files must not be shown');
unlink "$root/.htpasswd"; unlink "$root/.htpasswd";
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment