From 3ea5b63fd99233bc4aaff07397c5fec840e5a750 Mon Sep 17 00:00:00 2001 From: Scott Cantor Date: Wed, 4 Nov 2009 15:15:35 +0000 Subject: [PATCH] https://issues.shibboleth.net/jira/browse/SSPCPP-255 --- apache/mod_apache.cpp | 38 +++++++- doc/NEWS.txt | 5 ++ doc/README.txt | 4 +- fastcgi/shibauthorizer.cpp | 44 ++++++++- fastcgi/shibresponder.cpp | 42 ++++++++- isapi_shib/isapi_shib.cpp | 59 ++++++++++-- nsapi_shib/nsapi_shib.cpp | 41 ++++++++- schemas/shibboleth-targetconfig-1.0.xsd | 153 ++++++++++++++++---------------- 8 files changed, 293 insertions(+), 93 deletions(-) diff --git a/apache/mod_apache.cpp b/apache/mod_apache.cpp index 65066d9..e6ca506 100644 --- a/apache/mod_apache.cpp +++ b/apache/mod_apache.cpp @@ -69,6 +69,7 @@ namespace { char* g_szSchemaDir = NULL; ShibTargetConfig* g_Config = NULL; string g_unsetHeaderValue,g_spoofKey; + set g_allowedSchemes; bool g_checkSpoofing = true; bool g_catchAll = true; static const char* g_UserDataKey = "_shib_check_user_"; @@ -459,9 +460,12 @@ public: const string& content_type="text/html", const Iterator& headers=EMPTY(header_t) ) { + checkString(content_type, "Detected control character in a response header."); m_req->content_type = ap_psprintf(m_req->pool, content_type.c_str()); while (headers.hasNext()) { const header_t& h=headers.next(); + checkString(h.first, "Detected control character in a response header."); + checkString(h.second, "Detected control character in a response header."); ap_table_set(m_req->headers_out, h.first.c_str(), h.second.c_str()); } ap_send_http_header(m_req); @@ -469,6 +473,9 @@ public: return (void*)((code==200) ? DONE : code); } virtual void* sendRedirect(const string& url) { + checkString(url, "Detected control character in an attempted redirect."); + if (g_allowedSchemes.find(url.substr(0, url.find(':'))) == g_allowedSchemes.end()) + throw FatalProfileException("Invalid scheme in attempted redirect."); ap_table_set(m_req->headers_out, "Location", url.c_str()); return (void*)REDIRECT; } @@ -481,6 +488,15 @@ public: shib_server_config* m_sc; shib_request_config* m_rc; set m_allhttp; + +private: + void checkString(const string& s, const char* msg) { + string::const_iterator e = s.end(); + for (string::const_iterator i=s.begin(); i!=e; ++i) { + if (iscntrl(*i)) + throw FatalProfileException(msg); + } + } }; /********************************************************************************/ @@ -1137,14 +1153,30 @@ extern "C" void shib_child_init(apr_pool_t* p, server_rec* s) saml::Locker locker(conf); const IPropertySet* props=conf->getPropertySet("Local"); if (props) { - pair unsetValue=props->getString("unsetHeaderValue"); - if (unsetValue.first) - g_unsetHeaderValue = unsetValue.second; + pair str=props->getString("unsetHeaderValue"); + if (str.first) + g_unsetHeaderValue = str.second; + str=props->getString("allowedSchemes"); + if (str.first) { + string schemes=str.second; + unsigned int j=0; + for (unsigned int i=0; i < schemes.length(); i++) { + if (schemes.at(i)==' ') { + g_allowedSchemes.insert(schemes.substr(j, i-j)); + j = i+1; + } + } + g_allowedSchemes.insert(schemes.substr(j, schemes.length()-j)); + } pair flag=props->getBool("checkSpoofing"); g_checkSpoofing = !flag.first || flag.second; flag=props->getBool("catchAll"); g_catchAll = !flag.first || flag.second; } + if (g_allowedSchemes.empty()) { + g_allowedSchemes.insert("https"); + g_allowedSchemes.insert("http"); + } } catch (exception&) { ap_log_error(APLOG_MARK,APLOG_CRIT|APLOG_NOERRNO,SH_AP_R(s),"shib_child_init() failed to initialize system"); diff --git a/doc/NEWS.txt b/doc/NEWS.txt index 93db4d5..07e2033 100644 --- a/doc/NEWS.txt +++ b/doc/NEWS.txt @@ -1,3 +1,8 @@ +Nov 4, 2009 +Version 1.3.5 + +Fix for secadv 20091104 + Aug 26, 2009 Version 1.3.4 diff --git a/doc/README.txt b/doc/README.txt index 91e4793..6b13763 100644 --- a/doc/README.txt +++ b/doc/README.txt @@ -1,5 +1,5 @@ -Aug 26, 2009 -Version 1.3.4 +Nov 4, 2009 +Version 1.3.5 Welcome to Internet2's Shibboleth diff --git a/fastcgi/shibauthorizer.cpp b/fastcgi/shibauthorizer.cpp index db811b4..745855d 100644 --- a/fastcgi/shibauthorizer.cpp +++ b/fastcgi/shibauthorizer.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2001-2007 Internet2 + * Copyright 2001-2009 Internet2 * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -32,6 +32,7 @@ #include using namespace shibtarget; +using namespace saml; using namespace std; typedef enum { @@ -40,10 +41,21 @@ typedef enum { SHIB_RETURN_DONE } shib_return_t; +set g_allowedSchemes; + class ShibTargetFCGIAuth : public ShibTarget { FCGX_Request* m_req; string m_cookie; + + void checkString(const string& s, const char* msg) { + string::const_iterator e = s.end(); + for (string::const_iterator i=s.begin(); i!=e; ++i) { + if (iscntrl(*i)) + throw runtime_error(msg); + } + } + public: map m_headers; @@ -143,9 +155,12 @@ public: const string& content_type="text/html", const saml::Iterator& headers=EMPTY(header_t)) { + checkString(content_type, "Detected control character in a response header."); string hdr = m_cookie + "Connection: close\r\nContent-type: " + content_type + "\r\n"; while (headers.hasNext()) { const header_t& h=headers.next(); + checkString(h.first, "Detected control character in a response header."); + checkString(h.second, "Detected control character in a response header."); hdr += h.first + ": " + h.second + "\r\n"; } @@ -162,6 +177,9 @@ public: } virtual void* sendRedirect(const string& url) { + checkString(url, "Detected control character in an attempted redirect."); + if (g_allowedSchemes.find(url.substr(0, url.find(':'))) == g_allowedSchemes.end()) + throw runtime_error("Invalid scheme in attempted redirect."); cout << "Status: 302 Please Wait" << "\r\n" << "Location: " << url << "\r\n" << m_cookie << "\r\n" @@ -227,12 +245,36 @@ int main(void) cerr << "failed to load Shibboleth configuration" << endl; exit(1); } + + IConfig* conf=g_Config->getINI(); + Locker locker(conf); + const IPropertySet* props=conf->getPropertySet("Local"); + if (props) { + pair str=props->getString("allowedSchemes"); + if (str.first) { + string schemes=str.second; + unsigned int j=0; + for (unsigned int i=0; i < schemes.length(); i++) { + if (schemes.at(i)==' ') { + g_allowedSchemes.insert(schemes.substr(j, i-j)); + j = i+1; + } + } + g_allowedSchemes.insert(schemes.substr(j, schemes.length()-j)); + } + } + if (g_allowedSchemes.empty()) { + g_allowedSchemes.insert("https"); + g_allowedSchemes.insert("http"); + } } catch (exception& e) { cerr << "exception while initializing Shibboleth configuration: " << e.what() << endl; exit(1); } + + // Load "authoritative" URL fields. char* var = getenv("SHIBSP_SERVER_NAME"); if (var) diff --git a/fastcgi/shibresponder.cpp b/fastcgi/shibresponder.cpp index 86c374c..f1b7dcf 100644 --- a/fastcgi/shibresponder.cpp +++ b/fastcgi/shibresponder.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2001-2007 Internet2 + * Copyright 2001-2009 Internet2 * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -32,6 +32,7 @@ #include using namespace shibtarget; +using namespace saml; using namespace std; typedef enum { @@ -40,6 +41,8 @@ typedef enum { SHIB_RETURN_DONE } shib_return_t; +set g_allowedSchemes; + class ShibTargetFCGI : public ShibTarget { FCGX_Request* m_req; @@ -47,6 +50,14 @@ class ShibTargetFCGI : public ShibTarget string m_cookie; map m_headers; + void checkString(const string& s, const char* msg) { + string::const_iterator e = s.end(); + for (string::const_iterator i=s.begin(); i!=e; ++i) { + if (iscntrl(*i)) + throw runtime_error(msg); + } + } + public: ShibTargetFCGI(FCGX_Request* req, char* post_data, const char* scheme=NULL, const char* hostname=NULL, int port=0) : m_req(req), m_body(post_data) { @@ -146,9 +157,12 @@ public: const string& content_type="text/html", const saml::Iterator& headers=EMPTY(header_t)) { + checkString(content_type, "Detected control character in a response header."); string hdr = string ("Connection: close\r\nContent-type: ") + content_type + "\r\n" + m_cookie; while (headers.hasNext()) { const header_t& h=headers.next(); + checkString(h.first, "Detected control character in a response header."); + checkString(h.second, "Detected control character in a response header."); hdr += h.first + ": " + h.second + "\r\n"; } @@ -164,6 +178,9 @@ public: } virtual void* sendRedirect(const string& url) { + checkString(url, "Detected control character in an attempted redirect."); + if (g_allowedSchemes.find(url.substr(0, url.find(':'))) == g_allowedSchemes.end()) + throw runtime_error("Invalid scheme in attempted redirect."); cout << "Status: 302 Please Wait" << "\r\n" << "Location: " << url << "\r\n" << m_cookie << "\r\n" << "Redirecting..."; return (void*)SHIB_RETURN_DONE; @@ -260,6 +277,29 @@ int main(void) cerr << "failed to load Shibboleth configuration" << endl; exit(1); } + + IConfig* conf=g_Config->getINI(); + Locker locker(conf); + const IPropertySet* props=conf->getPropertySet("Local"); + if (props) { + pair str=props->getString("allowedSchemes"); + if (str.first) { + string schemes=str.second; + unsigned int j=0; + for (unsigned int i=0; i < schemes.length(); i++) { + if (schemes.at(i)==' ') { + g_allowedSchemes.insert(schemes.substr(j, i-j)); + j = i+1; + } + } + g_allowedSchemes.insert(schemes.substr(j, schemes.length()-j)); + } + } + if (g_allowedSchemes.empty()) { + g_allowedSchemes.insert("https"); + g_allowedSchemes.insert("http"); + } + } catch (exception& e) { cerr << "exception while initializing Shibboleth configuration:" << e.what() << endl; diff --git a/isapi_shib/isapi_shib.cpp b/isapi_shib/isapi_shib.cpp index 810db5f..f30cab6 100644 --- a/isapi_shib/isapi_shib.cpp +++ b/isapi_shib/isapi_shib.cpp @@ -91,6 +91,7 @@ namespace { map g_Sites; bool g_bNormalizeRequest = true; string g_unsetHeaderValue,g_spoofKey; + set g_allowedSchemes; bool g_checkSpoofing = true; bool g_catchAll = true; bool g_bSafeHeaderNames = false; @@ -188,13 +189,27 @@ extern "C" BOOL WINAPI GetFilterVersion(PHTTP_FILTER_VERSION pVer) flag=props->getBool("catchAll"); g_catchAll = !flag.first || flag.second; - pair unsetValue=props->getString("unsetHeaderValue"); - if (unsetValue.first) - g_unsetHeaderValue = unsetValue.second; + pair str=props->getString("unsetHeaderValue"); + if (str.first) + g_unsetHeaderValue = str.second; + + str=props->getString("allowedSchemes"); + if (str.first) { + string schemes=str.second; + unsigned int j=0; + for (unsigned int i=0; i < schemes.length(); i++) { + if (schemes.at(i)==' ') { + g_allowedSchemes.insert(schemes.substr(j, i-j)); + j = i+1; + } + } + g_allowedSchemes.insert(schemes.substr(j, schemes.length()-j)); + } + if (g_checkSpoofing) { - unsetValue = props->getString("spoofKey"); - if (unsetValue.first) - g_spoofKey = unsetValue.second; + str = props->getString("spoofKey"); + if (str.first) + g_spoofKey = str.second; else { LogEvent(NULL, EVENTLOG_WARNING_TYPE, 2100, NULL, "Filter generating a pseudorandom anti-spoofing key, consider setting spoofKey yourself."); @@ -222,6 +237,10 @@ extern "C" BOOL WINAPI GetFilterVersion(PHTTP_FILTER_VERSION pVer) } } } + if (g_allowedSchemes.empty()) { + g_allowedSchemes.insert("https"); + g_allowedSchemes.insert("http"); + } } catch (exception&) { LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Filter startup failed with an exception."); @@ -370,6 +389,14 @@ class ShibTargetIsapiF : public ShibTarget dynabuf m_allhttp; bool m_firsttime; + void checkString(const string& s, const char* msg) { + string::const_iterator e = s.end(); + for (string::const_iterator i=s.begin(); i!=e; ++i) { + if (iscntrl(*i)) + throw FatalProfileException(msg); + } + } + public: ShibTargetIsapiF(PHTTP_FILTER_CONTEXT pfc, PHTTP_FILTER_PREPROC_HEADERS pn, const site_t& site) : m_pfc(pfc), m_pn(pn), m_allhttp(4096), m_firsttime(true) { @@ -500,9 +527,12 @@ public: int code=200, const string& content_type="text/html", const Iterator& headers=EMPTY(header_t)) { + checkString(content_type, "Detected control character in a response header."); string hdr = string ("Connection: close\r\nContent-type: ") + content_type + "\r\n"; while (headers.hasNext()) { const header_t& h=headers.next(); + checkString(h.first, "Detected control character in a response header."); + checkString(h.second, "Detected control character in a response header."); hdr += h.first + ": " + h.second + "\r\n"; } hdr += "\r\n"; @@ -518,6 +548,9 @@ public: return (void*)SF_STATUS_REQ_FINISHED; } virtual void* sendRedirect(const string& url) { + checkString(url, "Detected control character in an attempted redirect."); + if (g_allowedSchemes.find(url.substr(0, url.find(':'))) == g_allowedSchemes.end()) + throw FatalProfileException("Invalid scheme in attempted redirect."); // XXX: Don't support the httpRedirect option, yet. string hdrs=m_cookie + string("Location: ") + url + "\r\n" "Content-Type: text/html\r\n" @@ -650,6 +683,14 @@ class ShibTargetIsapiE : public ShibTarget LPEXTENSION_CONTROL_BLOCK m_lpECB; string m_cookie; + void checkString(const string& s, const char* msg) { + string::const_iterator e = s.end(); + for (string::const_iterator i=s.begin(); i!=e; ++i) { + if (iscntrl(*i)) + throw FatalProfileException(msg); + } + } + public: ShibTargetIsapiE(LPEXTENSION_CONTROL_BLOCK lpECB, const site_t& site) { dynabuf ssl(5); @@ -774,8 +815,11 @@ public: int code=200, const string& content_type="text/html", const Iterator& headers=EMPTY(header_t)) { + checkString(content_type, "Detected control character in a response header."); string hdr = m_cookie + "Connection: close\r\nContent-type: " + content_type + "\r\n"; for (int k = 0; k < headers.size(); k++) { + checkString(headers[k].first, "Detected control character in a response header."); + checkString(headers[k].second, "Detected control character in a response header."); hdr += headers[k].first + ": " + headers[k].second + "\r\n"; } hdr += "\r\n"; @@ -792,6 +836,9 @@ public: } virtual void* sendRedirect(const string& url) { // XXX: Don't support the httpRedirect option, yet. + checkString(url, "Detected control character in an attempted redirect."); + if (g_allowedSchemes.find(url.substr(0, url.find(':'))) == g_allowedSchemes.end()) + throw FatalProfileException("Invalid scheme in attempted redirect."); string hdrs = m_cookie + "Location: " + url + "\r\n" "Content-Type: text/html\r\n" "Content-Length: 40\r\n" diff --git a/nsapi_shib/nsapi_shib.cpp b/nsapi_shib/nsapi_shib.cpp index 1925021..ef97932 100644 --- a/nsapi_shib/nsapi_shib.cpp +++ b/nsapi_shib/nsapi_shib.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2001-2005 Internet2 + * Copyright 2001-2009 Internet2 * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -66,6 +66,7 @@ namespace { ShibTargetConfig* g_Config=NULL; string g_ServerName; string g_unsetHeaderValue; + set g_allowedSchemes; bool g_checkSpoofing = false; bool g_catchAll = true; } @@ -146,14 +147,32 @@ extern "C" NSAPI_PUBLIC int nsapi_shib_init(pblock* pb, Session* sn, Request* rq Locker locker(conf); const IPropertySet* props=conf->getPropertySet("Local"); if (props) { - pair unsetValue=props->getString("unsetHeaderValue"); - if (unsetValue.first) - g_unsetHeaderValue = unsetValue.second; + pair str=props->getString("unsetHeaderValue"); + if (str.first) + g_unsetHeaderValue = str.second; + + str=props->getString("allowedSchemes"); + if (str.first) { + string schemes=str.second; + unsigned int j=0; + for (unsigned int i=0; i < schemes.length(); i++) { + if (schemes.at(i)==' ') { + g_allowedSchemes.insert(schemes.substr(j, i-j)); + j = i+1; + } + } + g_allowedSchemes.insert(schemes.substr(j, schemes.length()-j)); + } + pair flag=props->getBool("checkSpoofing"); g_checkSpoofing = !flag.first || flag.second; flag=props->getBool("catchAll"); g_catchAll = !flag.first || flag.second; } + if (g_allowedSchemes.empty()) { + g_allowedSchemes.insert("https"); + g_allowedSchemes.insert("http"); + } } catch (exception&) { g_Config=NULL; @@ -168,6 +187,14 @@ extern "C" NSAPI_PUBLIC int nsapi_shib_init(pblock* pb, Session* sn, Request* rq class ShibTargetNSAPI : public ShibTarget { + void checkString(const string& s, const char* msg) { + string::const_iterator e = s.end(); + for (string::const_iterator i=s.begin(); i!=e; ++i) { + if (iscntrl(*i)) + throw FatalProfileException(msg); + } + } + public: ShibTargetNSAPI(pblock* pb, Session* sn, Request* rq) : m_pb(pb), m_sn(sn), m_rq(rq), m_firsttime(true) { @@ -350,12 +377,15 @@ public: const string& content_type="text/html", const saml::Iterator& headers=EMPTY(header_t) ) { + checkString(content_type, "Detected control character in a response header."); param_free(pblock_remove("content-type", m_rq->srvhdrs)); pblock_nvinsert("content-type", content_type.c_str(), m_rq->srvhdrs); pblock_nninsert("content-length", msg.length(), m_rq->srvhdrs); pblock_nvinsert("connection","close",m_rq->srvhdrs); while (headers.hasNext()) { const header_t& h=headers.next(); + checkString(h.first, "Detected control character in a response header."); + checkString(h.second, "Detected control character in a response header."); pblock_nvinsert(h.first.c_str(), h.second.c_str(), m_rq->srvhdrs); } protocol_status(m_sn, m_rq, code, NULL); @@ -364,6 +394,9 @@ public: return (void*)REQ_EXIT; } virtual void* sendRedirect(const string& url) { + checkString(url, "Detected control character in an attempted redirect."); + if (g_allowedSchemes.find(url.substr(0, url.find(':'))) == g_allowedSchemes.end()) + throw FatalProfileException("Invalid scheme in attempted redirect."); param_free(pblock_remove("content-type", m_rq->srvhdrs)); pblock_nninsert("content-length", 0, m_rq->srvhdrs); pblock_nvinsert("expires", "01-Jan-1997 12:00:00 GMT", m_rq->srvhdrs); diff --git a/schemas/shibboleth-targetconfig-1.0.xsd b/schemas/shibboleth-targetconfig-1.0.xsd index 8bf3002..e1b6e44 100644 --- a/schemas/shibboleth-targetconfig-1.0.xsd +++ b/schemas/shibboleth-targetconfig-1.0.xsd @@ -7,7 +7,7 @@ elementFormDefault="qualified" attributeFormDefault="unqualified" blockDefault="substitution" - version="1.3.2"> + version="1.3.5"> @@ -38,7 +38,7 @@ - + @@ -62,8 +62,8 @@ - - + + @@ -79,7 +79,7 @@ - + @@ -123,8 +123,8 @@ - - + + @@ -151,19 +151,19 @@ - + - - - - - - - - + + + + + + + + @@ -186,12 +186,13 @@ - - - - - - + + + + + + + @@ -205,15 +206,15 @@ - - - + + + - - + + @@ -257,11 +258,11 @@ - - - - - + + + + + @@ -303,7 +304,7 @@ - + @@ -315,8 +316,8 @@ - - + + @@ -355,7 +356,7 @@ - + @@ -397,7 +398,7 @@ - + @@ -421,8 +422,8 @@ - - + + @@ -447,20 +448,20 @@ - - - - - - - - - - - - - - + + + + + + + + + + + + + + @@ -474,11 +475,11 @@ - - - - - + + + + + @@ -491,28 +492,28 @@ - - - + + + - - - - - + + + + + - - - - - - - - + + + + + + + + @@ -522,8 +523,8 @@ - - + + -- 2.1.4