security::validated_host_header (public)
security::validated_host_header
Defined in packages/acs-tcl/tcl/security-procs.tcl
- Returns:
- validated host header field or empty
- Author:
- Gustaf Neumann Protect against faked or invalid host header fields. Host header attacks can lead to web-cache poisoning and password reset attacks (for more details, see e.g. http://www.skeletonscribe.net/2013/05/practical-http-host-header-attacks.html) or to unintended redirects to different sites. The validated host header most be syntactically correct, and it must be either configured/white-listed or it must be from a non-routable IP address. White-listed hosts are taken from the alternate host names specified in the "ns/module/DRIVER/servers" section, or via the configuration variable "hostname" (e.g., "openacs.org www.openacs.org") which is added the the "/server" section during startup.
- Partial Call Graph (max 5 caller/called nodes):
- Testcases:
- No testcase defined.
Source code: # # Check, if we have a host header field # set hostHeaderValue [ns_set iget [ns_conn headers] Host] if {$hostHeaderValue eq ""} { return "" } # # Domain names are case insensitive. So convert it to lower to # avoid surprises. # set hostHeaderValue [string tolower $hostHeaderValue] # # Check, if we have validated it before, or it belongs to the # predefined accepted host header fields. # set key ::acs::validated_host_header($hostHeaderValue) if {[info exists $key]} { return $hostHeaderValue } set hostHeaderDict [ns_parsehostport $hostHeaderValue] # # Remove trailing dot, as this is allowed in fully qualified DNS # names (see e.g. §3.2.2 of RFC 3976). # set hostName [string trimright [dict get $hostHeaderDict host] .] set hostPort [expr {[dict exists $hostHeaderDict port] ? [dict get $hostHeaderDict port] : ""}] set normalizedHostHeaderValue [util::join_location -host $hostName -port $hostPort] set validationOk 0 # # Check if the value in "hostName" can be regarded as safe. # # The host header value is one of the names registered for # this server. # if {[acs::icanuse "ns_server hosts"]} { if {$normalizedHostHeaderValue in [ns_server hosts]} { # # set validationOk 1 } } elseif {[ns_info name] eq "NaviServer"} { # # As a replacement for "ns_server hosts" check against the # virtual server configuration of NaviServer. # set s [ns_info server] set driverInfo [security::configured_driver_info] set drivers [lmap d $driverInfo {dict get $d driver}] foreach driver $drivers { # # Check global "servers" configuration for virtual servers for the driver # set ns [ns_configsection ns/module/$driver/servers] if {$ns ne ""} { # # We have a global "servers" configuration for the driver # set names [lmap {key value} [ns_set array $ns] { if {$key ne $s} continue set value }] if {$normalizedHostHeaderValue in $names} { ns_log notice "security::validated_host_header: found $hostHeaderValue" "in global virtual server configuration for $driver" return 1 } } } } if {$validationOk == 0} { set validationOk [security::secure_hostname_p $hostName] } if {$validationOk == 0} { # # Check against the white-listed hosts from # # ns_section ns/server/$server/acs { # ns_param whitelistedHosts {...} # } # # of the configuration file. # if {$hostHeaderValue in [ns_config "ns/server/[ns_info server]/acs" whitelistedHosts {}]} { set validationOk 1 } } if {$validationOk == 0} { # # Check against host node map. Here we need as well protection # against invalid utf-8 characters. # if {![security::provided_host_valid $hostName]} { return "" } set validationOk [db_0or1row host_header_field_mapped { select 1 from host_node_map where host = :hostName }] } if {$validationOk == 0} { # # Validation is OK, when the hostName is either the same as # configured hostname. This is a legacy branch for very old # versions of NaviServer or AOLserver. # set driverInfo [util_driver_info] set driverHostName [dict get $driverInfo hostname] if {$hostName eq $driverHostName} { set validationOk 1 } } if {$validationOk == 0 && [info exists driverHostName]} { # # Validation is OK, when the hostName is one of the IP # addresses of the configured host name. # try { ns_addrbyhost -all $driverHostName } on error {errorMsg} { # # Name resolution of the hostname configured for this # driver failed, we cannot validate incoming IP addresses. # ns_log error "security::validated_host_header: configuration error:" "name resolution for configured hostname '$driverHostName'" "of driver '[ad_conn driver]' failed" } on ok {result} { set validationOk [expr {$hostName in $result}] } } # # Check, if the provided host is the same in [ns_conn location] # (will be used as default, but we do not want a warning in such # cases). This is also a legacy case. # if {$validationOk == 0 && [util::split_location [ns_conn location] proto locationHost locationPort]} { set validationOk [expr {$hostName eq $locationHost}] } # # Check, if the provided host is the same as in the configured # SystemURL. Legacy case. # if {$validationOk == 0 && [util::split_location [ad_url] .proto systemHost systemPort]} { set validationOk [expr {$hostName eq $systemHost && ($hostPort eq $systemPort || $hostPort eq "") }] } # # When any of the validation attempts above were successful, we # are done. We keep the logic for successful lookups # centralized. Performance of the individual tests are not # critical, since the lookups are cache per thread. # if {$validationOk} { set $key 1 return $hostHeaderValue } # # Now we give up # ns_log warning "ignore untrusted host header field: '$hostHeaderValue'." "Consider adding this value to 'whitelistedHosts' in the" "section 'ns/server/\$server/acs' of your configuration file" return ""Generic XQL file: packages/acs-tcl/tcl/security-procs.xql
PostgreSQL XQL file: packages/acs-tcl/tcl/security-procs-postgresql.xql
Oracle XQL file: packages/acs-tcl/tcl/security-procs-oracle.xql