def test_parse_sources(self): """ Only testing the flag for this method, rest is handled in following the following tests :return: """ script = u""" <style> h1 {color:red;} p {color:blue;} </style> <script> alert('test'); </script> """ soup = BeautifulSoup(script, 'html.parser') script_tag = soup.find_all('script') style_tag = soup.find_all('style') # Setting up res_sorter generators and falgs report_generator = ReportGenerator() report_generator.flags.append(Flag('inline_script', script_tag[0])) report_generator.flags.append(Flag('inline_style', style_tag[0])) self.res_sorter.report_generator = report_generator # Setting report generator for test sorter self.test_sorter.report_generator = ReportGenerator() self.test_sorter.parse_sources(script) assert (self.test_sorter.report_generator.flags == self.res_sorter.report_generator.flags)
def parse_sources(self, html): """ Function called to parse html Parses resources and prepare the resource analysis. Retrieves the script tags that have text (inline script) and calls another method to handle script includes and parses the html page (makes a soup). :param html: the html body of the page to parse :return: none """ soup = BeautifulSoup(html, 'html.parser') scripts = soup.find_all("script") styles = soup.find_all("style") for script in scripts: # If it is an inline script, format it and adds it to tree list if script.text: # Submitting a flag for each script encountered self.submit_flag(Flag('inline_script', script)) try: self.trees.append(es5(script.text)) except ECMASyntaxError: pass # Else it is an include script, just extract the source directly else: self.extract_inline_script_source(script) # Extract the sources in inline style (fonts, style dynamic stylesheets) self.styles = styles for style in styles: self.submit_flag(Flag('inline_style', style)) self.extract_inline_style_source(style)
def extract_new_expr_eval_instruction(self, node): # TO DO : make the adding condition more flexible for legit use case # setTimeout not evil if used with built-in function for example directive = self.eval_instructions[str(node.identifier)] self.directives_sources[directive].add("'unsafe-eval'") # Submitting the flag according to the directive if directive == 'script-src': self.submit_flag(Flag('eval-script', node)) elif directive == 'style-src': self.submit_flag(Flag('eval-style', node))
def raise_unsafe_protocol(self, csp_dict, url): if 'block-all-mixed-content' not in csp_dict.keys() and urlparse( url).scheme == 'https': for directive in csp_dict: for source in csp_dict[directive][1:]: if source == 'http': return Flag('possible_mixed_content') elif not self.lower_case_in('upgrade-insecure-requests', csp_dict): return Flag('no_upgrade_insecure_requests') return None
def extract_func_call_eval_instruction(self, node, instruction): try: directive = self.eval_instructions[instruction] self.directives_sources[directive].add("'unsafe-eval'") if directive == 'script-src': # print('SUBMITTING THIS FOR FLAG VALUE ', node.identifier) self.submit_flag(Flag('eval_script', node)) elif directive == 'style-src': # print('SUBMITTING THIS FOR FLAG VALUE ', node.identifier) self.submit_flag(Flag('eval_style', node)) except KeyError: pass
def test_extract_func_call_eval_instruction(self): """ Aims to test if eval instruction properly generate unsafe-eval directive and raise correct flags :return: """ # ------------------------------- # # ----------- ARRANGE ----------- # # ------------------------------- # self.res_sorter.report_generator = ReportGenerator() self.test_sorter.report_generator = ReportGenerator() script = """ eval('2*3;') var m = 3; var f = new Function('a', 'return a'); document.getElementsByTagName("body").style.cssText = "background-color:pink;font-size:55px;border:2px dashed green;color:white;" myStyle.insertRule('#blanc { color: white }', 0); """ # Getting the node from the script nodes = [] walker = Walker() for node in walker.filter( es5(script), lambda node: (isinstance(node, FunctionCall))): nodes.append(node) # Adding unsafe-eval directives for each relevant directive self.res_sorter.directives_sources['script-src'].add("'unsafe-eval'") self.res_sorter.directives_sources['style-src'].add("'unsafe-eval'") # Adding flag into test report generator flag_eval = Flag('eval_script', nodes[0]) flag_insert_rule = Flag('eval_style', nodes[2]) self.res_sorter.report_generator.flags.append(flag_eval) self.res_sorter.report_generator.flags.append(flag_insert_rule) # ------------------------------- # # ------------- ACT ------------- # # ------------------------------- # for node in nodes: instruction = self.test_sorter.get_node_instruction(node) self.test_sorter.extract_func_call_eval_instruction( node, instruction) # ------------------------------- # # ----------- ASSERT ------------ # # ------------------------------- # assert (self.res_sorter.directives_sources == self.test_sorter.directives_sources) assert (set(self.res_sorter.report_generator.flags) == set( self.test_sorter.report_generator.flags))
def sort_link_tag_info(self): """ Sorts link tag into the right directives Parses the rel attribute of each link and retrieves the right directive for the tag :return: None """ # Parsing all links link_list = self.soup.find_all('link') print(link_list) for tag in link_list: # Checking if link tag got 'rel' attribute if tag.attrs['rel'][0]: rel = tag.attrs['rel'][0] try: directive = self.link_tag[rel][1] if self.is_javascript_protocol( tag.attrs[self.link_tag[rel][0]]): self.directives_sources[directive].add( "'unsafe-inline'") self.submit_flag(Flag('inline_javascript', tag)) # Retrieving the right directive for the source of the tag # Building the source else: source = self.build_source( self.url, tag.attrs[self.link_tag[rel][0]]) # Adding the source to a directive self.directives_sources[directive].add(source) except KeyError: pass
def add_inline_directives(self): """ Adds unsafe-inline directives for script-src and style-src Checks if a tag has a style attribute and if a script tags have no inte- grity attribute. In these cases, add unsafe-inline into the right direc- tives to keep integrity of the page :return: None """ # Checking if a tag got a style attribute, if it does, add unsafe-inline # into the style directive if self.soup.find_all(style=True): self.directives_sources['style-src'].add("'unsafe-inline'") # Retrieving all the script tag inline_script = self.soup.find_all(lambda tag: any( attr in self.inline_event for attr in tag.attrs.keys())) # Checking if script tag are safe by checking presence of integrity a- # ttribute if inline_script: for script in inline_script: if not script.has_attr('integrity') or not script.has_attr( 'nonce'): self.submit_flag(Flag('inline_event', script)) self.directives_sources['script-src'].add( "'unsafe-inline'")
def raise_frame_option(self, csp_dict, header): try: if csp_dict['frame-ancestor'].lower() not in ['none', 'self']: flag_id = 'permissive_frame_rule' return Flag(flag_id) except KeyError: pass if not self.lower_case_in('X-Frame-Options', csp_dict): flag_id = 'no_frame_rule' elif header['X-Frame-Options'].lower().startswith('allowall'): flag_id = 'permissive_frame_rule' elif header['X-Frame-Options'].lower().startswith('allow-from'): flag_id = 'permissive_frame_rule' else: flag_id = 'missing_frame_ancestors' return Flag(flag_id)
def test_extract_new_expr(self): script = """ // Raises unsafe-eval for script and raise flag var func = new Function("console.log('a');"); // We suppose that a variable named original_link has already // been encountered to test variable nesting // adds original_link: connect-src to nested_sources nested = new Worker(original_link, test, fake, bait); // adds <http://worker.com> to worker-src n = new Worker("http://worker.com"); // adds <http://shared-worker.com> to worker-src var share = new SharedWorker('http://shared-worker.com'); // adds <wss://socket.com> to connect-src var socket = new WebSocket("wss://socket.com"); // adds <http://test.php> to connect-src var event_source = new EventSource('http://test.php'); // Let and const are not parsed by the library // let test = 1; """ nodes = self.gen_js_script_nodes(NewExpr, [script]) print(nodes) self.res_sorter.report_generator = ReportGenerator() self.res_sorter.nested_source['original_link'] = 'worker-src' self.res_sorter.report_generator.flags.append( Flag('eval_style', nodes[0])) self.res_sorter.directives_sources['script-src'].add("'unsafe-eval'") self.res_sorter.directives_sources['worker-src'].add( 'http://worker.com') self.res_sorter.directives_sources['worker-src'].add( 'http://shared-worker.com') self.res_sorter.directives_sources['connect-src'].add( 'wss://socket.com') self.res_sorter.directives_sources['connect-src'].add( 'http://test.php') self.test_sorter.report_generator = ReportGenerator() self.test_sorter.variable['original_link'] = 'http://test.lu' for node in nodes: self.test_sorter.extract_new_expr(node) print(self.test_sorter.nested_source) print(self.res_sorter.nested_source) assert (self.res_sorter.directives_sources == self.test_sorter.directives_sources) assert ( self.res_sorter.nested_source == self.test_sorter.nested_source)
def extract_assign(self, node): """ Sort assign statement into directive. Only cssText is concerned :param node: the Assign node :return: None """ try: directive = self.eval_instructions[str(node.left.identifier)] print(directive) self.directives_sources[directive].add("'unsafe-eval'") self.submit_flag(Flag('eval_style', node)) except (AttributeError, KeyError) as e: print(e)
def extract_simple_tag_source(self, tag, tag_type): for attr in tag.attrs: if "javascript:" in tag.attrs[attr]: self.directives_sources['script-src'].add("'unsafe-inline'") self.submit_flag(Flag('inline_javascript', tag)) try: # Retrieving the source source = tag[self.simple_tag[tag_type][0]] if source: # Determining the directive the source belongs to directive = self.simple_tag[tag_type][1] # Building the source before adding it to directive source = self.build_source(self.url, source) print('The source of the resource is : ', source) # Adding the source self.directives_sources[directive].add(source) except KeyError: pass
def test_extract_assign(self): script = """ document.getElementById("myP").style.cssText = "background-color:pink;font-size:55px;border:2px dashed green;color:white;" """ self.test_sorter.report_generator = ReportGenerator() self.res_sorter.report_generator = ReportGenerator() nodes = self.gen_js_script_nodes(Assign, [script]) self.res_sorter.directives_sources['style-src'].add("'unsafe-eval'") self.res_sorter.report_generator.flags.append( Flag('eval_style', nodes[0])) for node in nodes: self.test_sorter.extract_assign(node) print(self.test_sorter.directives_sources) print(self.res_sorter.directives_sources) assert (self.res_sorter.directives_sources == self.test_sorter.directives_sources) assert (self.res_sorter.report_generator.flags == self.test_sorter.report_generator.flags)
def raise_missing_object(self, csp_dict): if not self.lower_case_in('object-src', csp_dict) and \ csp_dict['default-src'] != 'none': return Flag('missing_obj_src') return None
def raise_trusted_types(self, csp_dict): if not self.lower_case_in('trusted_types', csp_dict): return Flag('no_trusted_types') return None