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},

Attachment: signature.asc
Description: Digital signature

Reply via email to