99
99
valid_path , valid_ascii , valid_job_id , valid_base_url , valid_url , \
100
100
valid_complex_url , InputException
101
101
from mig .shared .tlsserver import hardened_ssl_context
102
- from mig .shared .url import urlparse , urlencode
102
+ from mig .shared .url import urlparse , urlencode , check_local_site_url
103
103
from mig .shared .useradm import get_openid_user_dn , check_password_scramble , \
104
104
check_hash
105
105
from mig .shared .userdb import default_db_path
@@ -189,9 +189,16 @@ def filter_why_pw(configuration, why):
189
189
if isinstance (why , server .EncodingError ):
190
190
text = why .response .encodeToKVForm ()
191
191
return strip_password (configuration , text )
192
+ elif isinstance (why , server .ProtocolError ):
193
+ text = why .message
194
+ return strip_password (configuration , text )
192
195
else :
193
- _logger .warning ("can't filter password in unknown 'why': %s" % why )
194
- return why
196
+ # IMPORTANT: do NOT log or show raw why as it may contain credentials
197
+ # NOTE: we should not get here, but return generic safe msg if we do.
198
+ why_type = type (why )
199
+ _logger .warning ("can't filter unknown 'why' (%s) - show generic err" %
200
+ why_type )
201
+ return 'Unexpected %s to filter for safe log and output.' % why_type
195
202
196
203
197
204
def lookup_full_user (username ):
@@ -468,7 +475,7 @@ def do_GET(self):
468
475
# Do not disclose internal details
469
476
filtered_exc = cgitb .text (sys .exc_info (), context = 10 )
470
477
# IMPORTANT: do NOT ever print or log raw password
471
- pw = self .password
478
+ pw = self .password or ''
472
479
filtered_exc = filtered_exc .replace (pw , '*' * len (pw ))
473
480
logger .debug ("Traceback %s: %s" % (error_ref , filtered_exc ))
474
481
err_msg = """<p class='leftpad'>
@@ -581,9 +588,20 @@ def handleAllow(self, query):
581
588
except server .ProtocolError as why :
582
589
# IMPORTANT: NEVER log or show raw why or query with password!
583
590
safe_query = strip_password (configuration , query )
584
- logger .error ("handleAllow got broken request: %s" % safe_query )
585
- # NOTE: let displayResponse filter pw
586
- self .displayResponse (why )
591
+ safe_why = filter_why_pw (configuration , why )
592
+ logger .error ("handleAllow got broken request %s: %s" %
593
+ (safe_query , safe_why ))
594
+ # IMPORTANT: do NOT use displayResponse here! It would a.o.
595
+ # uncritically forward user any unverified return_to
596
+ # value in query.
597
+ self .showErrorPage ('''<h2>Error in Communication</h2>
598
+ You may have discovered a bug in the %s OpenID 2.0 service. Please report it
599
+ to the site admins if you keep getting here. If you arrived here using the
600
+ browser "back" button, however, that is expected since it results in
601
+ inconsistent session state.
602
+ <h3>Error details:</h3>
603
+ <pre>%s</pre>
604
+ ''' % (configuration .short_title , cgi .escape (safe_why )))
587
605
return
588
606
589
607
logger .debug ("handleAllow with last request %s from user %s" %
@@ -743,9 +761,20 @@ def serverEndPoint(self, query):
743
761
except server .ProtocolError as why :
744
762
# IMPORTANT: NEVER log or show raw why or query with password!
745
763
safe_query = strip_password (configuration , query )
746
- logger .error ("serverEndPoint got broken request: %s" % safe_query )
747
- # NOTE: let displayResponse filter pw
748
- self .displayResponse (why )
764
+ safe_why = filter_why_pw (configuration , why )
765
+ logger .error ("serverEndPoint got broken request %s: %s" %
766
+ (safe_query , safe_why ))
767
+ # IMPORTANT: do NOT use displayResponse here! It would a.o.
768
+ # uncritically forward user any unverified return_to
769
+ # value in query.
770
+ self .showErrorPage ('''<h2>Error in Communication</h2>
771
+ You may have discovered a bug in the %s OpenID 2.0 service. Please report it
772
+ to the site admins if you keep getting here. If you arrived here using the
773
+ browser "back" button, however, that is expected since it results in
774
+ inconsistent session state.
775
+ <h3>Error details:</h3>
776
+ <pre>%s</pre>
777
+ ''' % (configuration .short_title , cgi .escape (safe_why )))
749
778
return
750
779
751
780
if request is None :
@@ -825,18 +854,17 @@ def displayResponse(self, response):
825
854
try :
826
855
webresponse = self .server .openid .encodeResponse (response )
827
856
except server .EncodingError as why :
828
- # IMPORTANT: always mask passwords in output for security
829
- text = filter_why_pw (configuration , why )
857
+ # IMPORTANT: NEVER log or show raw why or query with password!
858
+ safe_why = filter_why_pw (configuration , why )
859
+ logger .error ("displayResponse failed encode: %s" % safe_why )
830
860
self .showErrorPage ('''<h2>Error in Communication</h2>
831
- <p>
832
- You may have discovered a bug in the OpenID service. Please report it to the
833
- site admins if you keep getting here. If you arrived here using the browser
834
- "back" button, however, that is expected since it results in inconsistent
835
- session state.
836
- </p>
861
+ You may have discovered a bug in the %s OpenID 2.0 service. Please report it
862
+ to the site admins if you keep getting here. If you arrived here using the
863
+ browser "back" button, however, that is expected since it results in
864
+ inconsistent session state.
837
865
<h3>Error details:</h3>
838
866
<pre>%s</pre>
839
- ''' % cgi .escape (text ))
867
+ ''' % ( configuration . short_title , cgi .escape (safe_why ) ))
840
868
return
841
869
842
870
self .send_response (webresponse .code )
@@ -938,7 +966,8 @@ def doLogin(self):
938
966
self .user = self .query ['user' ]
939
967
else :
940
968
self .clearUser ()
941
- self .redirect (self .query ['success_to' ])
969
+ success_to_url = self .query .get ('success_to' , None )
970
+ self .redirect (success_to_url )
942
971
return
943
972
944
973
if hit_rate_limit (configuration , "openid" ,
@@ -992,7 +1021,8 @@ def doLogin(self):
992
1021
)
993
1022
994
1023
if authorized :
995
- self .redirect (self .query ['success_to' ])
1024
+ success_to_url = self .query .get ('success_to' , None )
1025
+ self .redirect (success_to_url )
996
1026
else :
997
1027
logger .warning ("login failed for %s" % self .user )
998
1028
logger .debug ("full query: %s" % self .query )
@@ -1012,23 +1042,30 @@ def doLogin(self):
1012
1042
retry_url = self .server .base_url
1013
1043
self .redirect (retry_url )
1014
1044
elif 'cancel' in self .query :
1015
- self .redirect (self .query ['fail_to' ])
1045
+ fail_to_url = self .query .get ('fail_to' , None )
1046
+ self .redirect (fail_to_url )
1016
1047
else :
1017
1048
assert 0 , 'strange login %r' % (self .query ,)
1018
1049
1019
1050
def doLogout (self ):
1020
1051
"""Logout handler"""
1021
1052
logger .debug ("logout clearing user %s" % self .user )
1022
1053
self .clearUser ()
1023
- if 'return_to' in self .query :
1024
- # print "logout redirecting to %(return_to)s" % self.query
1025
- self .redirect (self . query [ 'return_to' ] )
1054
+ return_to_url = self .query . get ( 'return_to' , None )
1055
+ if return_to_url :
1056
+ self .redirect (return_to_url )
1026
1057
1027
1058
def redirect (self , url ):
1028
- """Redirect helper"""
1029
- self .send_response (302 )
1030
- self .send_header ('Location' , url )
1031
- self .writeUserHeader ()
1059
+ """Redirect helper with built-in check for safe destination URL"""
1060
+
1061
+ if url and not check_local_site_url (configuration , url ):
1062
+ logger .error ("reject redirect to external URL %r" % url )
1063
+ self .send_response (400 )
1064
+ else :
1065
+ logger .debug ("redirect to local site URL %r" % url )
1066
+ self .send_response (302 )
1067
+ self .send_header ('Location' , url )
1068
+ self .writeUserHeader ()
1032
1069
1033
1070
self .end_headers ()
1034
1071
@@ -1152,7 +1189,7 @@ def showErrorPage(self, error_message, error_code=400):
1152
1189
1153
1190
def showDecidePage (self , request ):
1154
1191
"""Decide page provider"""
1155
- id_url_base = self .server .base_url + 'id/'
1192
+ id_url_base = self .server .base_url + 'id/'
1156
1193
# XXX: This may break if there are any synonyms for id_url_base,
1157
1194
# such as referring to it by IP address or a CNAME.
1158
1195
assert (request .identity .startswith (id_url_base ) or
@@ -1297,7 +1334,7 @@ def showIdPage(self, path):
1297
1334
link_tag = '<link rel="openid.server" href="%sopenidserver">' % \
1298
1335
self .server .base_url
1299
1336
yadis_loc_tag = '<meta http-equiv="x-xrds-location" content="%s"/>' % \
1300
- (self .server .base_url + 'yadis/' + path [4 :])
1337
+ (self .server .base_url + 'yadis/' + path [4 :])
1301
1338
disco_tags = link_tag + yadis_loc_tag
1302
1339
ident = self .server .base_url + path [1 :]
1303
1340
@@ -1689,7 +1726,7 @@ def start_service(configuration):
1689
1726
'root_dir' : os .path .abspath (configuration .user_home ),
1690
1727
'db_path' : os .path .abspath (default_db_path (configuration )),
1691
1728
'session_store' : os .path .abspath (configuration .openid_store ),
1692
- 'session_ttl' : 24 * 3600 ,
1729
+ 'session_ttl' : 24 * 3600 ,
1693
1730
'allow_password' : 'password' in configuration .user_openid_auth ,
1694
1731
'allow_digest' : 'digest' in configuration .user_openid_auth ,
1695
1732
'allow_publickey' : 'publickey' in configuration .user_openid_auth ,
0 commit comments