44
55namespace Icinga \Util ;
66
7+ use Icinga \Application \Icinga ;
8+ use Icinga \Application \Logger ;
9+ use Icinga \Security \SecurityException ;
710use Icinga \Web \Response ;
811use Icinga \Web \Window ;
12+ use ipl \I18n \StaticTranslator ;
913use RuntimeException ;
1014
1115use function ipl \Stdlib \get_php_type ;
@@ -51,9 +55,13 @@ public static function addHeader(Response $response): void
5155 throw new RuntimeException ('No nonce set for CSS ' );
5256 }
5357
58+ $ header = "default-src 'self'; style-src 'self' 'nonce- {$ csp ->styleNonce }'; " ;
59+ $ imageSourceWhitelist = Icinga::app ()->getConfig ()->get ("security " , "image_source_whitelist " , "" );
60+ $ header = $ header . Csp::getImageSourceDirective ($ imageSourceWhitelist );
61+
5462 $ response ->setHeader (
5563 'Content-Security-Policy ' ,
56- " script-src 'self'; style-src 'self' 'nonce- $ csp -> styleNonce '; " ,
64+ $ header ,
5765 true
5866 );
5967 }
@@ -79,6 +87,9 @@ public static function createNonce(): void
7987 */
8088 public static function getStyleNonce (): ?string
8189 {
90+ if (Icinga::app ()->isCli ()) {
91+ return null ;
92+ }
8293 return static ::getInstance ()->styleNonce ;
8394 }
8495
@@ -108,4 +119,46 @@ protected static function getInstance(): self
108119
109120 return static ::$ instance ;
110121 }
122+
123+ public static function getImageSourceDirective (string $ whitelist ): string {
124+ $ directives = ["img-src " , "'self' " , "data: " ];
125+ foreach (explode (", " , $ whitelist ) as $ domain ) {
126+ try {
127+ $ directives [] = Csp::validateImageSourceWhitelistItem ($ domain );
128+ } catch (SecurityException $ e ) {
129+ Logger::error ("Ignoring domain ' $ domain' as it is not valid. $ e " );
130+ }
131+ }
132+
133+ return implode (' ' , $ directives ) . "; " ;
134+ }
135+
136+ /**
137+ * Validates and trims an item that is used as a domain in the img-src directive.
138+ * Will throw an error, if the user tries to whitelist everything (*) or tries
139+ * to inject special characters that might allow to escape and edit the whole csp.
140+ *
141+ * @throws SecurityException
142+ */
143+ public static function validateImageSourceWhitelistItem (string $ item ): string {
144+ $ item = trim ($ item );
145+ // Don't allow general whitelisting of all domains
146+ if ($ item == '* ' ) {
147+ throw new SecurityException (
148+ StaticTranslator::$ instance ->translate ("Whitelisting all domains is not allowed. " )
149+ );
150+ }
151+
152+ // Don't allow special characters that might allow to escape and edit the whole csp.
153+ // Otherwise, e.g. "example.com; script-src *" will allow the user to change other directives.
154+ $ csp_config_regex = "|^\*?[a-zA-Z0-9+._\-:]*$| " ;
155+ if (!preg_match ($ csp_config_regex , $ item )) {
156+ throw new SecurityException (
157+ StaticTranslator::$ instance ->translate ("The following domain is invalid: " )
158+ . $ item
159+ );
160+ }
161+
162+ return $ item ;
163+ }
111164}
0 commit comments