From 3572f6a27072a9ebfc7a0296ec79d67e657072e0 Mon Sep 17 00:00:00 2001 From: Sergey Linev Date: Fri, 8 May 2026 14:27:16 +0200 Subject: [PATCH] [http] introduce fAllowPostObject flag It allows to deserialize post data as ROOT object when processing exe.json request. While this can create leads to arbitrary code loading and injection, disable this feature by default. Can be enabled back with: ``` serv->SetAllowPostObject(kTRUE); ``` --- net/http/inc/THttpServer.h | 4 ++++ net/http/inc/TRootSniffer.h | 7 +++++++ net/http/src/THttpServer.cxx | 25 +++++++++++++++++++++++++ net/httpsniff/src/TRootSnifferFull.cxx | 6 +++--- 4 files changed, 39 insertions(+), 3 deletions(-) diff --git a/net/http/inc/THttpServer.h b/net/http/inc/THttpServer.h index c612773362be6..74f69239db425 100644 --- a/net/http/inc/THttpServer.h +++ b/net/http/inc/THttpServer.h @@ -94,6 +94,10 @@ class THttpServer : public TNamed { void SetReadOnly(Bool_t readonly = kTRUE); + Bool_t IsAllowPostObject() const; + + void SetAllowPostObject(Bool_t allow_post_obj); + Bool_t IsWSOnly() const; void SetWSOnly(Bool_t on = kTRUE); diff --git a/net/http/inc/TRootSniffer.h b/net/http/inc/TRootSniffer.h index 4351e66ffc9ed..558473ec4ce84 100644 --- a/net/http/inc/TRootSniffer.h +++ b/net/http/inc/TRootSniffer.h @@ -120,6 +120,7 @@ class TRootSniffer : public TNamed { protected: TString fObjectsPath; /// fTopFolder; ///SetReadOnly(readonly); } +//////////////////////////////////////////////////////////////////////////////// +/// Returns true if server accept object content in POST reequests + +Bool_t THttpServer::IsAllowPostObject() const +{ + return fSniffer ? fSniffer->IsAllowPostObject() : kFALSE; +} + +//////////////////////////////////////////////////////////////////////////////// +/// Set flag to allow receive and desereilize objects in POST requests +/// +/// When object methods are executed via exe.json request, +/// one can supply object as binary/json/xml in the body of POST request +/// To allow creation of such object, one need to enable this flag +/// Use of exe.json only possible in not-readonly mode +/// +/// CAUTION! This is sensitive functionality and therefore should be +/// used only when server not exposed to publicaly-accessed netowork. + +void THttpServer::SetAllowPostObject(Bool_t allow_post_obj) +{ + if (fSniffer) + fSniffer->SetAllowPostObject(allow_post_obj); +} + //////////////////////////////////////////////////////////////////////////////// /// returns true if only websockets are handled by the server /// diff --git a/net/httpsniff/src/TRootSnifferFull.cxx b/net/httpsniff/src/TRootSnifferFull.cxx index c9f265fb2f43e..8c62face7a41a 100644 --- a/net/httpsniff/src/TRootSnifferFull.cxx +++ b/net/httpsniff/src/TRootSnifferFull.cxx @@ -644,7 +644,7 @@ Bool_t TRootSnifferFull::ProduceExe(const std::string &path, const std::string & val = sval.Data(); } else if ((val != nullptr) && (fCurrentArg != nullptr) && (fCurrentArg->GetPostData() != nullptr)) { // process several arguments which are specific for post requests - if (strcmp(val, "_post_object_xml_") == 0) { + if (fAllowPostObject && strcmp(val, "_post_object_xml_") == 0) { // post data has extra 0 at the end and can be used as null-terminated string post_obj = TBufferXML::ConvertFromXML((const char *)fCurrentArg->GetPostData()); if (!post_obj) { @@ -655,7 +655,7 @@ Bool_t TRootSnifferFull::ProduceExe(const std::string &path, const std::string & garbage.Add(post_obj); } val = sval.Data(); - } else if (strcmp(val, "_post_object_json_") == 0) { + } else if (fAllowPostObject && strcmp(val, "_post_object_json_") == 0) { // post data has extra 0 at the end and can be used as null-terminated string post_obj = TBufferJSON::ConvertFromJSON((const char *)fCurrentArg->GetPostData()); if (!post_obj) { @@ -666,7 +666,7 @@ Bool_t TRootSnifferFull::ProduceExe(const std::string &path, const std::string & garbage.Add(post_obj); } val = sval.Data(); - } else if ((strcmp(val, "_post_object_") == 0) && url.HasOption("_post_class_")) { + } else if (fAllowPostObject && (strcmp(val, "_post_object_") == 0) && url.HasOption("_post_class_")) { TString clname = url.GetValueFromOptions("_post_class_"); TClass *arg_cl = gROOT->GetClass(clname, kTRUE, kTRUE); if ((arg_cl != nullptr) && (arg_cl->GetBaseClassOffset(TObject::Class()) == 0) && (post_obj == nullptr)) {