こんにちは、もうすっかり秋ですね。エンジニアの三宅です。
秋といえばnginxですね。
今回は、nginxのバージョンがHTTPのレスポンス・ヘッダフィールドにdefaultで設定されるという噂を耳にしたので確認します。
nginxのバージョン設定
Note: Revealing the specific software version of the server mightallow the server machine to become more vulnerable to attacksagainst software that is known to contain security holes. Serverimplementors are encouraged to make this field a configurableoption.注意: サーバーの特定のソフトウェアバージョンを明らかにすると、以下のようになる場合があります。サーバマシンが攻撃に対してより脆弱になることを可能にします。セキュリティホールが含まれていることが知られているソフトウェアに対してサーバ実装者は、このフィールドを設定可能なオプションを使用します。
特定のバージョンで動いている事を公開していることは、セキュリティホールが含まれたバージョンを使っていた場合、攻撃対象になってしまいます…ということですね。
まぁ、見せて得することはあまり無さそうですね。
nginxの方ではconfig設定で以下を定義する事で消すことが出来るそうです。
Syntax: server_tokens on | off | build | string;
Default: server_tokens on;
Context: http, server, location
Enables or disables emitting nginx version on error pages and in the “Server” response header field.
nginxのコードを読む
main関数のループ
https://github.com/nginx/nginx/blob/9c3ac44de268f0cf057bc5dd67929e74c9bbc3e3/src/core/nginx.c#L195
int ngx_cdecl
main(int argc, char *const *argv)
{
…
}
if (ngx_process == NGX_PROCESS_SINGLE) {
ngx_single_process_cycle(cycle);
} else {
ngx_master_process_cycle(cycle);
}
ngx_master_process_cycle
関数を見てみます。C言語では関数を呼び出すためにはheaderファイルで定義したものをincludeすることで出来ます。
ngx_master_process_cycle
が定義されているのはngx_process_cycle.h
で、これはngx_core.h
でincludeされており、nginx.c
ではngx_core.h
をincludeしています。ngx_process_cycle.h
が2つあるな…ngx_process_cycle.h
ファイルはOSがunix系かwindows系かで分かれるようですね。OSによって中の実装が変わるシステムコール系の処理をラッピングする為に、同名でOS毎に分けて書かれているんですね
UNIX_DEPS="$CORE_DEPS $EVENT_DEPS \
src/os/unix/ngx_time.h \
src/os/unix/ngx_errno.h \
src/os/unix/ngx_alloc.h \
src/os/unix/ngx_files.h \
src/os/unix/ngx_channel.h \
src/os/unix/ngx_shmem.h \
src/os/unix/ngx_process.h \
src/os/unix/ngx_setaffinity.h \
src/os/unix/ngx_setproctitle.h \
src/os/unix/ngx_atomic.h \
src/os/unix/ngx_gcc_atomic_x86.h \
src/os/unix/ngx_thread.h \
src/os/unix/ngx_socket.h \
src/os/unix/ngx_os.h \
src/os/unix/ngx_user.h \
src/os/unix/ngx_dlopen.h \
src/os/unix/ngx_process_cycle.h"
WIN32_DEPS="$CORE_DEPS $EVENT_DEPS \
src/os/win32/ngx_win32_config.h \
src/os/win32/ngx_time.h \
src/os/win32/ngx_errno.h \
src/os/win32/ngx_alloc.h \
src/os/win32/ngx_files.h \
src/os/win32/ngx_shmem.h \
src/os/win32/ngx_process.h \
src/os/win32/ngx_atomic.h \
src/os/win32/ngx_thread.h \
src/os/win32/ngx_socket.h \
src/os/win32/ngx_os.h \
src/os/win32/ngx_user.h \
src/os/win32/ngx_dlopen.h \
src/os/win32/ngx_process_cycle.h"
この設定はmakeファイルにあるはずです。
windowsはここでWIN32_DEPSを設定
https://github.com/nginx/nginx/blob/c85d6fec217d1b17291779542de20ad77ae68661/auto/os/win32#L9
CORE_DEPS="$WIN32_DEPS"
https://github.com/nginx/nginx/blob/c85d6fec217d1b17291779542de20ad77ae68661/auto/os/linux#L9
Solarisだったらここ(LinuxとSolarisはUNIX系なのでUNIX_DEPSは共通ですね)
https://github.com/nginx/nginx/blob/c85d6fec217d1b17291779542de20ad77ae68661/auto/os/solaris#L9
makeファイルでCORE_DEPSを利用
https://github.com/nginx/nginx/blob/c85d6fec217d1b17291779542de20ad77ae68661/auto/make#L56
| sed -e "s/ *\([^ ][^ ]*\)/$ngx_regex_cont\1/g" \
-e "s/\//$ngx_regex_dirsep/g"`
…ということで、ngx_master_process_cycleはUNIX系かWindows系かで別れていますよ…と。
差分を見ると関数の中身、初っ端から全然違いますね。。
windows(左側)はHANDLEとか使ってますね。
windowsでスレッドやイベント生成した時に返ってくるポインタの型ですね。
windowsプログラムの基本ですね、懐かしい
(そして、今はweb上にこんなドキュメントがあるのか...羨ましい)
そうそうmain関数の中にある無限ループを探すのが目的でした…
ありました!このfor文の中をグルグル回ってるんですね、nginxは..
for ( ;; ) {
・・・
}
本筋を見失ってしまいましたが、この無限ループ内でworkerプロセスのイベント監視して非同期にhttp通信を管理しているんですね。。。
アーキテクチャはこの図がわかりやすいです!
responseに設定しているのは何処
では今回のレスポンス・ヘッダーにサーバーアプリケーションのバージョンを設定している部分はソフトウェアではどうしているのでしょうか。
まずは、NGINXのバージョンを定義している部分を見つけました
https://github.com/nginx/nginx/blob/554916301c424f02b1cabc073845b64f8681099b/src/core/nginx.h#L14
#define NGINX_VERSION "1.19.3"
#define NGINX_VER "nginx/" NGINX_VERSION
恐らく、この定義を元に設定している部分があるのでしょう
ありますね!ngx_http_header_filter_module.c
ファイルですね。ヘッダーを組み立てる処理が書かれていそうです。
static u_char ngx_http_server_string[] = "Server: nginx" CRLF;
static u_char ngx_http_server_full_string[] = "Server: " NGINX_VER CRLF;
static u_char ngx_http_server_build_string[] = "Server: " NGINX_VER_BUILD CRLF;
ngx_http_server_full_string
がversion付きの文字列ですね
ここで使ってますね
if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_ON) {
p = ngx_http_server_full_string;
len = sizeof(ngx_http_server_full_string) - 1;
} else if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_BUILD) {
p = ngx_http_server_build_string;
len = sizeof(ngx_http_server_build_string) - 1;
} else {
p = ngx_http_server_string;
len = sizeof(ngx_http_server_string) - 1;
}
b->last = ngx_cpymem(b->last, p, len);
if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_ON) {
で判定されてますね。b->last
にcopyされてます。
ちなみにngx_cpymem
はmemcpy
をラッピングしたマクロになります。
https://github.com/nginx/nginx/blob/6c3838f9ed45f5c2aa6a971a0da3cb6ffe45b61e/src/core/ngx_string.h#L107
#define ngx_cpymem(dst, src, n) (((u_char *) memcpy(dst, src, n)) + (n))
clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
関数名を見る限り、ローカルの設定のようです。
構造体の定義struct ngx_http_core_loc_conf_s
はここにあります。
設定を集約した定義のようです。https://github.com/nginx/nginx/blob/b82c08f6102d65a5e5902e6fa85082e184a75003/src/http/ngx_http_core_module.h#L397
struct ngx_http_core_loc_conf_s {
...
ngx_uint_t server_tokens; /* server_tokens */
...
}
初期値はONなのか?
確かにdefaultはONで設定されていました!
offをconfigに設定するとNGX_HTTP_SERVER_TOKENS_OFF
が設定されることも確認できます。
static ngx_conf_enum_t ngx_http_core_server_tokens[] = {
{ ngx_string("off"), NGX_HTTP_SERVER_TOKENS_OFF },
{ ngx_string("on"), NGX_HTTP_SERVER_TOKENS_ON },
{ ngx_string("build"), NGX_HTTP_SERVER_TOKENS_BUILD },
{ ngx_null_string, 0 }
};
ということで...
を設定すると、condig設定でoff→NGX_HTTP_SERVER_TOKENS_OFF
に置き換わり、header_filter_module
の分岐にてどの文字列をcopyするかが決まります。
response送信
最後、送信するところですね
ngx_int_t
ngx_http_send_response(ngx_http_request_t *r, ngx_uint_t status,
ngx_str_t *ct, ngx_http_complex_value_t *cv)
{
...
rc = ngx_http_send_header(r);
...
}
// ヘッダー生成
ngx_int_t
ngx_http_send_header(ngx_http_request_t *r)
{
if (r->post_action) {
return NGX_OK;
}
if (r->header_sent) {
ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
"header already sent");
return NGX_ERROR;
}
if (r->err_status) {
r->headers_out.status = r->err_status;
r->headers_out.status_line.len = 0;
}
return ngx_http_top_header_filter(r);
}
ngx_http_send_response
でresponseを生成しています。ngx_http_send_header
でヘッダー部分を作ります。
その中でngx_http_top_header_filter
を呼び出しています。
ngx_http_top_header_filter
は以下で設定されます。
static ngx_int_t
ngx_http_header_filter_init(ngx_conf_t *cf)
{
ngx_http_top_header_filter = ngx_http_header_filter;
return NGX_OK;
}
ここで ngx_http_top_header_filter
に ngx_http_header_filter
を設定しています。ngx_http_header_filter
は上記のngx_cpymem
で設定してる関数になります。
ここでヘッダーの設定部分と実際にresponseを生成している関数が繋がります。
おわりに
という事でserver_tokens off;
の設定から、nginxが実際に設定する所を読んでみました。オープンソースは気になったら実際にコードを読み、どんな挙動をしているか明確にわかるのが良いですね。また、そのアーキテクチャ含め、コードから色んな事を学べることが大きな利点だと思います。
また機会があればこういう記事を書いていこうと思います。