From tateishi.katsuyuki @ oss.ntt.co.jp Wed Feb 10 11:29:39 2010 From: tateishi.katsuyuki @ oss.ntt.co.jp (TATEISHI Katsuyuki) Date: Wed, 10 Feb 2010 11:29:39 +0900 (JST) Subject: [Ultramonkey-l7-develop 572] =?iso-2022-jp?b?bDd2c2QgGyRCJE4bKEIgWC1Gb3J3YXJkZWQtRm9yIA==?= =?iso-2022-jp?b?GyRCSVUyQzUhRz0kTklUNnE5Z0pzOXAbKEI=?= Message-ID: <20100210.112939.1127184445147314642.tateishi.katsuyuki@oss.ntt.co.jp> 立石です。 UltraMonkey-L7 2.1.3-0 の l7vsd における X-Forwarded-For の付 加機能に不具合を見つけましたので報告します。 X-Forwarded-For 付加機能(各モジュールの-F/--forwarded-forオ プション)は使用しないほうがいいです。 ●詳細 UltraMonkey-L7 のバックエンドのリアルサーバへの HTTP リクエ ストに X-Forwarded-For ヘッダを付加するという機能があります。 これは l7directord.cf の virtual セクション内、module の設定 においてmodule名に渡すオプションとして --forwarded-for または -Fオプションをつけると最終的に l7vsd がロードするモジュールに オプションとして渡され、有効になります。 この機能は一見有効に動作しますが、ユーザがPOSTでデータを送る 際、以下の条件を満たすと不正な動作をします。 1. ユーザデータ内に HTTP ヘッダらしき文字列(リクエストライ ン)の並びがある(※) 2. POSTリクエストが複数のパケットに分割されている 3. 1のデータにおけるリクエストラインの開始部が2の分割部の切 れ目と一致する 上記の条件を満たすと、1のリクエストラインのCRLFの後ろ(直感的 に言えば次の行)に X-Forwarded-For: <クライアントのIPアドレス> という文字列を挿入してユーザデータを破壊してしまいます。 ※ 正確には以下の perl 正規表現にマッチする場合にリクエスト ラインであるとみなされます。 ============================================================ (GET|HEAD|POST|PUT|PROP(FIND|PATCH)|OPTIONIS|CONNECT|COPY|TRACE|DELETE|LOCK|UNLOCK|MOVE|MKCOL) [^ \r\n]* HTTP\/1.[01]\r\n ============================================================ 再現コードは添付のとおりです。このコードは ============================================================ !!MARKER!! START POST DATA !!MARKER!! USER_DATA_USER_DATA_USER_DATA_USER_DATA_USER_DA TA_USER_DATA_USER_DATA_USER_DATA_USER_DATA_USER GET / HTTP/1.1 USER_DATA_USER_DATA_USER_DATA_USER_DATA_USER_DA TA_USER_DATA_USER_DATA_USER_DATA_USER_DATA_USER !!MARKER!! END OF POST DATA !!MARKER!! ============================================================ というデータをPOSTするプログラムですが、192.168.0.1というIP アドレスからのクライアントから実行し、UM-L7を通過すると ============================================================ !!MARKER!! START POST DATA !!MARKER!! USER_DATA_USER_DATA_USER_DATA_USER_DATA_USER_DA TA_USER_DATA_USER_DATA_USER_DATA_USER_DATA_USER GET / HTTP/1.1 X-Forwarded-For: 192.168.0.1 USER_DATA_USER_DATA_USER_DATA_USER_DATA_USER_DA TA_USER_DATA_USER_DATA_USER_DATA_USER_DATA_USER !!MARKER!! END OF POST DATA !!MARKER!! ============================================================ というデータになってしまいます。 影響範囲は、UltraMonkey-L7 2.1.3-0 で、かつ X-Forwarded-For 機能を有効にしている場合です。 今のところ回避策は X-Forward-For 付加機能を使わないというもの だけです。 なお、[Ultramonkey-l7-users 240] で報告されているSSLProxyの不 具合と同様の不具合です。 以上です。 -- TATEISHI Katsuyuki -------------- next part -------------- /* * client:c * allmost code are from example in the getaddrinfo(3) * */ #include #include #include #include #include #include #include /* user data for crafted packet */ static char postdata0[] = "!!MARKER!! START POST DATA !!MARKER!!\r\n" "USER_DATA_USER_DATA_USER_DATA_USER_DATA_USER_DA\r\n" "TA_USER_DATA_USER_DATA_USER_DATA_USER_DATA_USER\r\n"; static char postdata1[] = "GET / HTTP/1.1\r\n"; static char postdata2[] = "USER_DATA_USER_DATA_USER_DATA_USER_DATA_USER_DA\r\n" "TA_USER_DATA_USER_DATA_USER_DATA_USER_DATA_USER\r\n" "!!MARKER!! END OF POST DATA !!MARKER!!\r\n"; /* real post request */ static char method[] = "POST / HTTP/1.1\r\n" "User-Agent: crafted-packet client.c\r\n" "Accept: */*\r\n" "Host: 192.168.122.3\r\n" "Content-Type: application/x-www-form-urlencoded\r\n" "Content-Length: %d\r\n" /* content length of postdata* */ "\r\n"; /* hearder-content delimiter */ int main(int argc, char *argv[]) { struct addrinfo hints; struct addrinfo *result, *rp; int sfd, s, j; size_t len; ssize_t nread; char buf[BUFSIZ]; if (argc < 3) { fprintf(stderr, "Usage: %s host port\n", argv[0]); exit(EXIT_FAILURE); } /* Obtain address(es) matching host/port */ memset(&hints, 0, sizeof(struct addrinfo)); hints.ai_family = AF_UNSPEC; /* Allow IPv4 or IPv6 */ hints.ai_socktype = SOCK_STREAM; /* Datagram socket */ hints.ai_flags = 0; hints.ai_protocol = 0; /* Any protocol */ s = getaddrinfo(argv[1], argv[2], &hints, &result); if (s != 0) { fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(s)); exit(EXIT_FAILURE); } /* getaddrinfo() returns a list of address structures. Try each address until we successfully connect(2). */ if (s != 0) { fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(s)); exit(EXIT_FAILURE); } /* getaddrinfo() returns a list of address structures. Try each address until we successfully connect(2). If socket(2) (or connect(2)) fails, we (close the socket and) try the next address. */ for (rp = result; rp != NULL; rp = rp->ai_next) { sfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); if (sfd == -1) continue; if (connect(sfd, rp->ai_addr, rp->ai_addrlen) != -1) break; /* Success */ close(sfd); } if (rp == NULL) { /* No address succeeded */ fprintf(stderr, "Could not connect\n"); exit(EXIT_FAILURE); } freeaddrinfo(result); /* No longer needed */ sprintf(buf, method, strlen(postdata0) + strlen(postdata1) + strlen(postdata2)); len = strlen(buf); if (write(sfd, buf, len) != len) { fprintf(stderr, "partial/failed write\n"); exit(EXIT_FAILURE); } len = strlen(postdata0); if (write(sfd, postdata0, len) != len) { fprintf(stderr, "partial/failed write\n"); exit(EXIT_FAILURE); } sleep(1); len = strlen(postdata1); if (write(sfd, postdata1, len) != len) { fprintf(stderr, "partial/failed write\n"); exit(EXIT_FAILURE); } sleep(1); len = strlen(postdata2); if (write(sfd, postdata2, len) != len) { fprintf(stderr, "partial/failed write\n"); exit(EXIT_FAILURE); } nread = read(sfd, buf, BUFSIZ); if (nread == -1) { perror("read"); exit(EXIT_FAILURE); } printf("Received %ld bytes: %s\n", (long) nread, buf); exit(EXIT_SUCCESS); }