tags 570013 + patch thanks Dear maintainer,
I've prepared an NMU for couchdb (versioned as 0.11.0-2.1). The diff is attached to this message. Regards. Giuseppe.
diff -u couchdb-0.11.0/debian/changelog couchdb-0.11.0/debian/changelog --- couchdb-0.11.0/debian/changelog +++ couchdb-0.11.0/debian/changelog @@ -1,3 +1,11 @@ +couchdb (0.11.0-2.1) unstable; urgency=high + + * Non-maintainer upload by the Security Team. + * CVE-2010-2234: fix Cross-site request forgery vulnerability + (Closes: #570013) + + -- Giuseppe Iuculano <iucul...@debian.org> Sun, 26 Sep 2010 11:09:53 +0200 + couchdb (0.11.0-2) unstable; urgency=low * Added patch from upstream which fixes compatibiluty with Erlang R14A only in patch2: unchanged: --- couchdb-0.11.0.orig/debian/patches/CVE-2010-2234.patch +++ couchdb-0.11.0/debian/patches/CVE-2010-2234.patch @@ -0,0 +1,351 @@ +diff --git a/share/www/script/couch.js b/share/www/script/couch.js +index c549542..fc4b80d 100644 +--- a/share/www/script/couch.js ++++ b/share/www/script/couch.js +@@ -411,6 +411,8 @@ CouchDB.newXhr = function() { + + CouchDB.request = function(method, uri, options) { + options = options || {}; ++ options.headers = options.headers || {}; ++ options.headers["Content-Type"] = options.headers["Content-Type"] || "application/json"; + var req = CouchDB.newXhr(); + if(uri.substr(0, "http://".length) != "http://") { + uri = CouchDB.urlPrefix + uri +diff --git a/share/www/script/test/basics.js b/share/www/script/test/basics.js +index 0f9ac44..6a3ae47 100644 +--- a/share/www/script/test/basics.js ++++ b/share/www/script/test/basics.js +@@ -152,7 +152,8 @@ couchTests.basics = function(debug) { + + // test that the POST response has a Location header + var xhr = CouchDB.request("POST", "/test_suite_db", { +- body: JSON.stringify({"foo":"bar"}) ++ body: JSON.stringify({"foo":"bar"}), ++ headers: {"Content-Type": "application/json"} + }); + var resp = JSON.parse(xhr.responseText); + T(resp.ok); +@@ -164,6 +165,7 @@ couchTests.basics = function(debug) { + + // test that that POST's with an _id aren't overriden with a UUID. + var xhr = CouchDB.request("POST", "/test_suite_db", { ++ headers: {"Content-Type": "application/json"}, + body: JSON.stringify({"_id": "oppossum", "yar": "matey"}) + }); + var resp = JSON.parse(xhr.responseText); +@@ -202,7 +204,10 @@ couchTests.basics = function(debug) { + result = JSON.parse(xhr.responseText); + T(result.error == "doc_validation"); + +- xhr = CouchDB.request("POST", "/test_suite_db/", {body: data}); ++ xhr = CouchDB.request("POST", "/test_suite_db/", { ++ headers: {"Content-Type": "application/json"}, ++ body: data ++ }); + T(xhr.status == 500); + result = JSON.parse(xhr.responseText); + T(result.error == "doc_validation"); +diff --git a/share/www/script/test/batch_save.js b/share/www/script/test/batch_save.js +index 1c8a2be..a1b0019 100644 +--- a/share/www/script/test/batch_save.js ++++ b/share/www/script/test/batch_save.js +@@ -36,7 +36,10 @@ couchTests.batch_save = function(debug) { + + // repeat the tests for POST + for(i=0; i < 100; i++) { +- var resp = db.request("POST", db.uri + "?batch=ok", {body: JSON.stringify({a:1})}); ++ var resp = db.request("POST", db.uri + "?batch=ok", { ++ headers: {"Content-Type": "application/json"}, ++ body: JSON.stringify({a:1}) ++ }); + T(JSON.parse(resp.responseText).ok); + } + +diff --git a/share/www/script/test/stats.js b/share/www/script/test/stats.js +index 793b390..c605f27 100644 +--- a/share/www/script/test/stats.js ++++ b/share/www/script/test/stats.js +@@ -162,7 +162,10 @@ couchTests.stats = function(debug) { + + runTest("couchdb", "database_writes", { + run: function(db) { +- CouchDB.request("POST", "/test_suite_db", {body: '{"a": "1"}'}) ++ CouchDB.request("POST", "/test_suite_db", { ++ headers: {"Content-Type": "application/json"}, ++ body: '{"a": "1"}' ++ }) + }, + test: function(before, after) { + TEquals(before+1, after, "POST'ing new docs increments doc writes."); +diff --git a/src/couchdb/couch_httpd.erl b/src/couchdb/couch_httpd.erl +index b25242f..4e13b6c 100644 +--- a/src/couchdb/couch_httpd.erl ++++ b/src/couchdb/couch_httpd.erl +@@ -25,7 +25,7 @@ + -export([start_json_response/2, start_json_response/3, end_json_response/1]). + -export([send_response/4,send_method_not_allowed/2,send_error/4, send_redirect/2,send_chunked_error/2]). + -export([send_json/2,send_json/3,send_json/4,last_chunk/1,parse_multipart_request/3]). +--export([accepted_encodings/1,handle_request_int/5]). ++-export([accepted_encodings/1,handle_request_int/5,validate_referer/1,validate_ctype/2]). + + start_link() -> + % read config and register for configuration changes +@@ -294,6 +294,34 @@ increment_method_stats(Method) -> + couch_stats_collector:increment({httpd_request_methods, Method}). + + ++validate_referer(Req) -> ++ Host = host_for_request(Req), ++ Referer = header_value(Req, "Referer", fail), ++ case Referer of ++ fail -> ++ throw({bad_request, <<"Referer header required.">>}); ++ Referer -> ++ {_,RefererHost,_,_,_} = mochiweb_util:urlsplit(Referer), ++ if ++ RefererHost =:= Host -> ok; ++ true -> throw({bad_request, <<"Referer header must match host.">>}) ++ end ++ end. ++ ++validate_ctype(Req, Ctype) -> ++ case couch_httpd:header_value(Req, "Content-Type") of ++ undefined -> ++ throw({bad_ctype, "Content-Type must be "++Ctype}); ++ ReqCtype -> ++ % ?LOG_ERROR("Ctype ~p ReqCtype ~p",[Ctype,ReqCtype]), ++ case re:split(ReqCtype, ";", [{return, list}]) of ++ [Ctype] -> ok; ++ [Ctype, _Rest] -> ok; ++ _Else -> ++ throw({bad_ctype, "Content-Type must be "++Ctype}) ++ end ++ end. ++ + % Utilities + + partition(Path) -> +@@ -340,9 +368,9 @@ qs(#httpd{mochi_req=MochiReq}) -> + path(#httpd{mochi_req=MochiReq}) -> + MochiReq:get(path). + +-absolute_uri(#httpd{mochi_req=MochiReq}, Path) -> ++host_for_request(#httpd{mochi_req=MochiReq}) -> + XHost = couch_config:get("httpd", "x_forwarded_host", "X-Forwarded-Host"), +- Host = case MochiReq:get_header_value(XHost) of ++ case MochiReq:get_header_value(XHost) of + undefined -> + case MochiReq:get_header_value("Host") of + undefined -> +@@ -352,7 +380,10 @@ absolute_uri(#httpd{mochi_req=MochiReq}, Path) -> + Value1 + end; + Value -> Value +- end, ++ end. ++ ++absolute_uri(#httpd{mochi_req=MochiReq}=Req, Path) -> ++ Host = host_for_request(Req), + XSsl = couch_config:get("httpd", "x_forwarded_ssl", "X-Forwarded-Ssl"), + Scheme = case MochiReq:get_header_value(XSsl) of + "on" -> "https"; +diff --git a/src/couchdb/couch_httpd_auth.erl b/src/couchdb/couch_httpd_auth.erl +index 3e2981a..54b578c 100644 +--- a/src/couchdb/couch_httpd_auth.erl ++++ b/src/couchdb/couch_httpd_auth.erl +@@ -395,6 +395,7 @@ ensure_cookie_auth_secret() -> + handle_session_req(#httpd{method='POST', mochi_req=MochiReq}=Req) -> + ReqBody = MochiReq:recv_body(), + Form = case MochiReq:get_primary_header_value("content-type") of ++ % content type should be json + "application/x-www-form-urlencoded" ++ _ -> + mochiweb_util:parse_qs(ReqBody); + _ -> +diff --git a/src/couchdb/couch_httpd_db.erl b/src/couchdb/couch_httpd_db.erl +index 666ecb2..b7e0e8f 100644 +--- a/src/couchdb/couch_httpd_db.erl ++++ b/src/couchdb/couch_httpd_db.erl +@@ -112,6 +112,7 @@ handle_changes_req(#httpd{path_parts=[_,<<"_changes">>]}=Req, _Db) -> + send_method_not_allowed(Req, "GET,HEAD"). + + handle_compact_req(#httpd{method='POST',path_parts=[DbName,_,Id|_]}=Req, _Db) -> ++ couch_httpd:validate_ctype(Req, "application/json"), + ok = couch_view_compactor:start_compact(DbName, Id), + send_json(Req, 202, {[{ok, true}]}); + +@@ -214,6 +215,7 @@ db_req(#httpd{method='GET',path_parts=[_DbName]}=Req, Db) -> + send_json(Req, {DbInfo}); + + db_req(#httpd{method='POST',path_parts=[DbName]}=Req, Db) -> ++ couch_httpd:validate_ctype(Req, "application/json"), + Doc = couch_doc:from_json_obj(couch_httpd:json_body(Req)), + Doc2 = case Doc#doc.id of + <<"">> -> +@@ -282,6 +284,7 @@ db_req(#httpd{path_parts=[_,<<"_ensure_full_commit">>]}=Req, _Db) -> + + db_req(#httpd{method='POST',path_parts=[_,<<"_bulk_docs">>]}=Req, Db) -> + couch_stats_collector:increment({httpd, bulk_requests}), ++ couch_httpd:validate_ctype(Req, "application/json"), + {JsonProps} = couch_httpd:json_body_obj(Req), + DocsArray = proplists:get_value(<<"docs">>, JsonProps), + case couch_httpd:header_value(Req, "X-Couch-Full-Commit") of +@@ -343,6 +346,7 @@ db_req(#httpd{path_parts=[_,<<"_bulk_docs">>]}=Req, _Db) -> + send_method_not_allowed(Req, "POST"); + + db_req(#httpd{method='POST',path_parts=[_,<<"_purge">>]}=Req, Db) -> ++ couch_httpd:validate_ctype(Req, "application/json"), + {IdsRevs} = couch_httpd:json_body_obj(Req), + IdsRevs2 = [{Id, couch_doc:parse_revs(Revs)} || {Id, Revs} <- IdsRevs], + +@@ -387,7 +391,6 @@ db_req(#httpd{method='POST',path_parts=[_,<<"_missing_revs">>]}=Req, Db) -> + db_req(#httpd{path_parts=[_,<<"_missing_revs">>]}=Req, _Db) -> + send_method_not_allowed(Req, "POST"); + +- + db_req(#httpd{method='POST',path_parts=[_,<<"_revs_diff">>]}=Req, Db) -> + {JsonDocIdRevs} = couch_httpd:json_body_obj(Req), + JsonDocIdRevs2 = +@@ -598,14 +601,11 @@ db_doc_req(#httpd{method='GET'}=Req, Db, DocId) -> + end_json_response(Resp) + end; + ++ + db_doc_req(#httpd{method='POST'}=Req, Db, DocId) -> ++ couch_httpd:validate_referer(Req), + couch_doc:validate_docid(DocId), +- case couch_httpd:header_value(Req, "Content-Type") of +- "multipart/form-data" ++ _Rest -> +- ok; +- _Else -> +- throw({bad_ctype, <<"Invalid Content-Type header for form upload">>}) +- end, ++ couch_httpd:validate_ctype(Req, "multipart/form-data"), + Form = couch_httpd:parse_form(Req), + case proplists:is_defined("_doc", Form) of + true -> +diff --git a/src/couchdb/couch_httpd_show.erl b/src/couchdb/couch_httpd_show.erl +index 72c6bae..5ff97b4 100644 +--- a/src/couchdb/couch_httpd_show.erl ++++ b/src/couchdb/couch_httpd_show.erl +@@ -140,7 +140,7 @@ send_doc_update_response(Req, Db, DDoc, UpdateName, Doc, DocId) -> + Code = 200, + ok + end, +- JsonResp2 = json_apply_field({<<"code">>, Code}, JsonResp), ++ JsonResp2 = couch_util:json_apply_field({<<"code">>, Code}, JsonResp), + % todo set location field + couch_httpd_external:send_external_response(Req, JsonResp2). + +@@ -368,21 +368,6 @@ render_head_for_empty_list(StartListRespFun, Req, Etag, null) -> + render_head_for_empty_list(StartListRespFun, Req, Etag, TotalRows) -> + StartListRespFun(Req, Etag, TotalRows, null, []). + +- +-% Maybe this is in the proplists API +-% todo move to couch_util +-json_apply_field(H, {L}) -> +- json_apply_field(H, L, []). +-json_apply_field({Key, NewValue}, [{Key, _OldVal} | Headers], Acc) -> +- % drop matching keys +- json_apply_field({Key, NewValue}, Headers, Acc); +-json_apply_field({Key, NewValue}, [{OtherKey, OtherVal} | Headers], Acc) -> +- % something else is next, leave it alone. +- json_apply_field({Key, NewValue}, Headers, [{OtherKey, OtherVal} | Acc]); +-json_apply_field({Key, NewValue}, [], Acc) -> +- % end of list, add ours +- {[{Key, NewValue}|Acc]}. +- + apply_etag({ExternalResponse}, CurrentEtag) -> + % Here we embark on the delicate task of replacing or creating the + % headers on the JsonResponse object. We need to control the Etag and +@@ -396,8 +381,8 @@ apply_etag({ExternalResponse}, CurrentEtag) -> + JsonHeaders -> + {[case Field of + {<<"headers">>, JsonHeaders} -> % add our headers +- JsonHeadersEtagged = json_apply_field({<<"Etag">>, CurrentEtag}, JsonHeaders), +- JsonHeadersVaried = json_apply_field({<<"Vary">>, <<"Accept">>}, JsonHeadersEtagged), ++ JsonHeadersEtagged = couch_util:json_apply_field({<<"Etag">>, CurrentEtag}, JsonHeaders), ++ JsonHeadersVaried = couch_util:json_apply_field({<<"Vary">>, <<"Accept">>}, JsonHeadersEtagged), + {<<"headers">>, JsonHeadersVaried}; + _ -> % skip non-header fields + Field +diff --git a/src/couchdb/couch_rep.erl b/src/couchdb/couch_rep.erl +index b7f8790..ca27747 100644 +--- a/src/couchdb/couch_rep.erl ++++ b/src/couchdb/couch_rep.erl +@@ -647,10 +647,11 @@ commit_to_both(Source, Target, RequiredSeq) -> + end, + {SourceStartTime, TargetStartTime}. + +-ensure_full_commit(#http_db{} = Target) -> ++ensure_full_commit(#http_db{headers = Headers} = Target) -> + Req = Target#http_db{ + resource = "_ensure_full_commit", + method = post, ++ headers = [{"content-type", "application/json"} | Headers], + body = true + }, + {ResultProps} = couch_rep_httpc:request(Req), +@@ -672,12 +673,13 @@ ensure_full_commit(Target) -> + InstanceStartTime + end. + +-ensure_full_commit(#http_db{} = Source, RequiredSeq) -> ++ensure_full_commit(#http_db{headers = Headers} = Source, RequiredSeq) -> + Req = Source#http_db{ + resource = "_ensure_full_commit", + method = post, + body = true, +- qs = [{seq, RequiredSeq}] ++ qs = [{seq, RequiredSeq}], ++ headers = [{"content-type", "application/json"} | Headers] + }, + {ResultProps} = couch_rep_httpc:request(Req), + case proplists:get_value(<<"ok">>, ResultProps) of +diff --git a/src/couchdb/couch_rep_writer.erl b/src/couchdb/couch_rep_writer.erl +index 269b979..8b7e4fc 100644 +--- a/src/couchdb/couch_rep_writer.erl ++++ b/src/couchdb/couch_rep_writer.erl +@@ -57,8 +57,9 @@ write_docs(#http_db{headers = Headers} = Db, Docs) -> + resource = "_bulk_docs", + method = post, + body = {[{new_edits, false}, {docs, JsonDocs}]}, +- headers = [{"x-couch-full-commit", "false"} | Headers] ++ headers = couch_util:proplist_apply_field({"Content-Type", "application/json"}, [{"X-Couch-Full-Commit", "false"} | Headers]) + }, ++ ?LOG_ERROR("headers ~p",[Request#http_db.headers]), + ErrorsJson = case couch_rep_httpc:request(Request) of + {FailProps} -> + exit({target_error, proplists:get_value(<<"error">>, FailProps)}); +diff --git a/src/couchdb/couch_util.erl b/src/couchdb/couch_util.erl +index 311bc88..7738d72 100644 +--- a/src/couchdb/couch_util.erl ++++ b/src/couchdb/couch_util.erl +@@ -19,6 +19,7 @@ + -export([encodeBase64/1, decodeBase64/1, encodeBase64Url/1, decodeBase64Url/1, + to_hex/1,parse_term/1, dict_find/3]). + -export([file_read_size/1, get_nested_json_value/2, json_user_ctx/1]). ++-export([proplist_apply_field/2, json_apply_field/2]). + -export([to_binary/1, to_integer/1, to_list/1, url_encode/1]). + -export([json_encode/1, json_decode/1]). + -export([verify/2]). +@@ -109,6 +110,19 @@ get_nested_json_value(Value, []) -> + get_nested_json_value(_NotJSONObj, _) -> + throw({not_found, json_mismatch}). + ++proplist_apply_field(H, L) -> ++ {R} = json_apply_field(H, {L}), ++ R. ++ ++json_apply_field(H, {L}) -> ++ json_apply_field(H, L, []). ++json_apply_field({Key, NewValue}, [{Key, _OldVal} | Headers], Acc) -> ++ json_apply_field({Key, NewValue}, Headers, Acc); ++json_apply_field({Key, NewValue}, [{OtherKey, OtherVal} | Headers], Acc) -> ++ json_apply_field({Key, NewValue}, Headers, [{OtherKey, OtherVal} | Acc]); ++json_apply_field({Key, NewValue}, [], Acc) -> ++ {[{Key, NewValue}|Acc]}. ++ + json_user_ctx(#db{name=DbName, user_ctx=Ctx}) -> + {[{<<"db">>, DbName}, + {<<"name">>,Ctx#user_ctx.name},
signature.asc
Description: Digital signature