root/platform/apache/mod_checksum_filter/mod_checksum_filter.c @ 15679

Revision 15679, 10.0 kB (checked in by yappo, 5 years ago)

app server から受け取ったコンテンツbodyのサイズをerror_logに吐き出す

Line 
1/*
2$ perldoc mod_checksum_filter.c
3
4=encoding utf8
5
6=head1 NAME
7
8checksum_filter_module - アプリケーションとリバースプロキシ間での改竄検出フィルタ
9
10=head1 SYNOPSIS
11
12  FilterDeclare  checksum CONTENT_SET
13  FilterProvider checksum CHECKSUM Content-Type /^text\x2F/
14  FilterChain    checksum
15  RewriteEngine on
16  RewriteRule   ^/proxy http://localhost:1978 [P]
17
18特定のPATHにだけフィルタをかけたい場合
19
20  <Location /proxy>
21      FilterDeclare  checksum CONTENT_SET
22      FilterProvider checksum CHECKSUM Content-Type /^text\x2F/
23      FilterChain    checksum
24  </Location>
25  RewriteEngine on
26  RewriteRule   ^/proxy http://localhost:1978 [P]
27
28=head1 INSTALL
29
30  shell# apxs -a -c -i mod_checksum_filter.c
31
32=head1 DESCRIPTION
33
34アプリケーションとリバースプロキシ間でのコンテンツ改竄を検出する用途として作成されています。
35このモジュールの影響下に有るURIではstatic fileへのリクエストだろうが何であろうが
36X-CheckSum-ResponseとX-CheckSum-Requestがレスポンスヘッダに含まれていなければ
37502 Bad Gateway エラーを出力します。
38
39アプリケーション側で特定のレスポンスヘッダを出力する事により検出を可能としていて、以下の二つのヘッダを出力する必要があります。
40
41=head2 X-CheckSum-Response
42
43コンテンツ本体の改竄を検出出来ます。
44
45Perl + HTTP::Engine 環境下では以下のようなコードで作成可能です。
46
47  my $body = '<html>.....</html>';
48  $c->res->header( 'X-CheckSum-Response' => '{SHA}'.sha1_base64($body) );
49
50=head2 X-CheckSum-Request
51
52意図しないクライアントに間違ったレスポンスを送ってしまう事を検出出来ます。
53
54Perl + HTTP::Engine 環境下では以下のようなコードで作成可能です。
55
56  my $request_data = sprintf '%s/%s', $c->req->address, $c->req->header('user-agent');
57  $c->res->header( 'X-CheckSum-Request'  => '{SHA}'.sha1_base64($request_data) );
58
59=head1 ERROR DETECTION
60
61チェックサムに異常が見られた場合には error_log に以下のメッセージを出力します
62
63  mod_checksum_filter: not checksum request
64  mod_checksum_filter: not checksum response
65  mod_checksum_filter: bad checksum request [sha1] != [sha1]
66  mod_checksum_filter: bad checksum response [sha1] != [sha1]
67
68=head1 TESTING
69
70以下の Perl コードを立ち上げて mod_rewrite かなんかで飛ばしてテストすると良いよ
71
72  use strict;
73  use warnings;
74  use Digest::SHA1 qw( sha1_base64 );
75  use String::TT qw( tt strip );
76  use HTTPEx::Declare;
77 
78  interface ServerSimple => {};
79  run {
80      my $c = shift;
81 
82      my $body = tt strip q{
83          <html>
84            <head>
85              <title>test</title>
86            </head>
87            <body>
88              test test
89            </body>
90          </html>
91      };
92 
93      my $remote_ip    = $c->req->param('ip') ? '192.168.192.168' : $c->req->address;
94      my $request_data = sprintf '%s/%s', $remote_ip, $c->req->header('user-agent');
95      $c->res->header( 'X-CheckSum-Request'  => '{SHA}'.sha1_base64($request_data) );
96      $c->res->header( 'X-CheckSum-Response' => '{SHA}'.sha1_base64($body) );
97 
98      $body .= 'bug' if $c->req->param('error');
99      $c->res->body($body);
100  };
101
102URI に ?error=1 や ?ip=1 をくっ付けると意図的にエラーを作り出せるよ
103
104=head1 SEE ALSO
105
106L<http://d.hatena.ne.jp/dayflower/20070416/1176705134>
107
108=head1 AUTHOR
109
110Kazuhiro Osawa
111
112=head1 LICENSE
113
114Licensed to the Apache Software Foundation (ASF) under one or more
115contributor license agreements.  See the NOTICE file distributed with
116this work for additional information regarding copyright ownership.
117The ASF licenses this file to You under the Apache License, Version 2.0
118(the "License"); you may not use this file except in compliance with
119the License.  You may obtain a copy of the License at
120
121    http://www.apache.org/licenses/LICENSE-2.0
122
123Unless required by applicable law or agreed to in writing, software
124distributed under the License is distributed on an "AS IS" BASIS,
125WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
126See the License for the specific language governing permissions and
127limitations under the License.
128
129=cut
130
131*/
132
133#include "httpd.h"
134#include "http_config.h"
135#include "http_log.h"
136#include "util_filter.h"
137
138#include "apr_strings.h"
139#include "apr_sha1.h"
140
141static const char checksumFilterName[] = "CHECKSUM";
142module AP_MODULE_DECLARE_DATA checksum_filter_module;
143
144typedef struct {
145    int                 eos;
146    apr_bucket_brigade *bb;
147} checksum_ctx_t;
148
149/*
150  なんかエラーがあったら Bad Gateway レスポンスを全部リセットしちゃうよ
151 */
152static apr_status_t create_error_brigade(ap_filter_t        *f,
153                                         apr_bucket_brigade *bb)
154{
155    request_rec        *r = f->r;
156    conn_rec           *c = r->connection;
157    apr_bucket_brigade *out_bb;
158    apr_bucket         *b;
159
160    apr_brigade_destroy(bb);
161
162    out_bb = apr_brigade_create(c->pool, c->bucket_alloc);
163
164    b = ap_bucket_error_create(HTTP_BAD_GATEWAY, NULL, c->pool, c->bucket_alloc);
165    APR_BRIGADE_INSERT_TAIL(out_bb, b);
166
167    b = apr_bucket_eos_create(c->bucket_alloc);
168    APR_BRIGADE_INSERT_TAIL(out_bb, b);
169
170    r->no_cache    = 1;
171    r->status      = HTTP_BAD_GATEWAY;
172    r->status_line = apr_psprintf(r->pool, "502 Bad Gateway");
173
174    return ap_pass_brigade(f->next, out_bb);
175}
176
177/*
178  apr_sha1_base64 で作った文字列の末尾にある = を取り除くよ
179 */
180static void strip_last_equal(char *s)
181{
182    apr_size_t  len;
183    char       *sp;
184    if (!s) return;
185
186    len = strlen(s);
187    if (len < 1) return;
188
189    for (sp = s + len - 1;s != sp;sp--) {
190      if (*sp != '=') break;
191      *sp = '\0';
192    }
193}
194
195/*
196  reverse proxy 経由だろうが local file だろうが何でもチェックするよ
197 */
198static apr_status_t checksum_out_filter(ap_filter_t        *f,
199                                        apr_bucket_brigade *bb)
200{
201    request_rec        *r = f->r;
202    checksum_ctx_t     *ctx = f->ctx;
203    apr_bucket         *b;
204    apr_bucket_brigade *out_bb;
205    char               *content_body;
206    apr_size_t          content_length;
207    apr_status_t        rv;
208    char               *request_data;
209    const char         *checksum_request;
210    const char         *checksum_response;
211    unsigned char       request_sha1[120];
212    unsigned char       response_sha1[120];
213
214    /* Do nothing if asked to filter nothing. */
215    if (APR_BRIGADE_EMPTY(bb)) {
216        return ap_pass_brigade(f->next, bb);
217    }
218
219    /* already done */
220    if (ctx && ctx->eos) {
221        return ap_pass_brigade(f->next, bb);
222    }
223
224    if (!ctx) {
225        ctx      = apr_palloc(r->pool, sizeof(checksum_ctx_t));
226        f->ctx   = ctx;
227        ctx->eos = 0;
228        ctx->bb  = apr_brigade_create(r->pool, f->c->bucket_alloc);
229    }
230
231    /* check the all brigades */
232    b = APR_BRIGADE_FIRST(bb);
233    while (b != APR_BRIGADE_SENTINEL(bb)) {
234        apr_bucket *next_b = APR_BUCKET_NEXT(b);
235
236        APR_BUCKET_REMOVE(b);
237        APR_BRIGADE_INSERT_TAIL(ctx->bb, b);
238
239        if (APR_BUCKET_IS_EOS(b)) {
240            ctx->eos = 1;
241        }
242
243        b = next_b;
244    }
245
246    /* given brigade to trash */
247    apr_brigade_destroy(bb);
248
249    if (!ctx->eos) {
250        return APR_SUCCESS;
251    }
252
253    /* reading stream completed */
254
255    rv = apr_brigade_pflatten(ctx->bb, &content_body, &content_length, r->pool);
256    if (rv != APR_SUCCESS) {
257        ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
258                      "mod_checksum_filter: apr_brigade_pflatten() failed");
259        return create_error_brigade(f, ctx->bb);
260    }
261
262    /* check request env */
263    checksum_request = apr_table_get(r->headers_out, "X-CheckSum-Request");
264    if (!checksum_request) {
265        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
266                      "mod_checksum_filter: not checksum request (%dbytes)", content_length);
267        return create_error_brigade(f, ctx->bb);
268    }
269    apr_table_unset(r->headers_out, "X-CheckSum-Request");
270
271    request_data = apr_pstrcat(r->pool,
272                               r->connection->remote_ip, "/",
273                               apr_table_get(r->headers_in, "User-Agent"), NULL);
274    if (!request_data) {
275        ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
276                      "mod_checksum_filter: apr_pstrcat() failed");
277        return create_error_brigade(f, ctx->bb);
278    }
279    apr_sha1_base64(request_data, strlen(request_data), request_sha1);
280    strip_last_equal(request_sha1);
281
282    if (apr_strnatcmp(checksum_request, request_sha1) != 0) {
283        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
284                      "mod_checksum_filter: bad checksum request %s != %s (%dbytes)",
285                      checksum_request, request_sha1, content_length);
286        return create_error_brigade(f, ctx->bb);
287    }
288
289    /* check response body */
290    checksum_response = apr_table_get(r->headers_out, "X-CheckSum-Response");
291    if (!checksum_response) {
292        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
293                      "mod_checksum_filter: not checksum response (%dbytes)", content_length);
294        return create_error_brigade(f, ctx->bb);
295    }
296    apr_table_unset(r->headers_out, "X-CheckSum-Response");
297
298    apr_sha1_base64(content_body, content_length, response_sha1);
299    strip_last_equal(response_sha1);
300
301    if (apr_strnatcmp(checksum_response, response_sha1) != 0) {
302        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
303                      "mod_checksum_filter: bad checksum response %s != %s (%dbytes)",
304                      checksum_response, response_sha1, content_length);
305        return create_error_brigade(f, ctx->bb);
306    }
307
308    return ap_pass_brigade(f->next, ctx->bb);
309}
310
311static const command_rec checksum_filter_cmds[] =
312{
313    {NULL}
314};
315
316static void register_hooks(apr_pool_t *p)
317{
318    ap_register_output_filter(checksumFilterName, checksum_out_filter, NULL,
319                              AP_FTYPE_CONTENT_SET);
320}
321
322module AP_MODULE_DECLARE_DATA checksum_filter_module =
323{
324    STANDARD20_MODULE_STUFF,
325    NULL,
326    NULL,
327    NULL,
328    NULL,
329    checksum_filter_cmds,
330    register_hooks
331};
Note: See TracBrowser for help on using the browser.