diff --git a/lookyloo/lookyloo.py b/lookyloo/lookyloo.py index 9aa7e4b8..ea5d9668 100644 --- a/lookyloo/lookyloo.py +++ b/lookyloo/lookyloo.py @@ -114,21 +114,22 @@ class Indexing(): pipeline = self.redis.pipeline() for urlnode in crawled_tree.root_hartree.url_tree.traverse(): - if not urlnode.empty_response: - pipeline.zincrby('body_hashes', 1, urlnode.body_hash) - pipeline.zincrby(f'bh|{urlnode.body_hash}', 1, urlnode.hostname) - # set of all captures with this hash - pipeline.sadd(f'bh|{urlnode.body_hash}|captures', crawled_tree.uuid) - # ZSet of all urlnode_UUIDs|full_url - pipeline.zincrby(f'bh|{urlnode.body_hash}|captures|{crawled_tree.uuid}', 1, f'{urlnode.uuid}|{urlnode.hostnode_uuid}|{urlnode.name}') - if hasattr(urlnode, 'embedded_ressources') and urlnode.embedded_ressources: - for mimetype, blobs in urlnode.embedded_ressources.items(): - for h, body in blobs: - pipeline.zincrby('body_hashes', 1, h) - pipeline.zincrby(f'bh|{h}', 1, urlnode.hostname) - pipeline.sadd(f'bh|{h}|captures', crawled_tree.uuid) - pipeline.zincrby(f'bh|{h}|captures|{crawled_tree.uuid}', 1, - f'{urlnode.uuid}|{urlnode.hostnode_uuid}|{urlnode.name}') + if urlnode.empty_response: + continue + pipeline.zincrby('body_hashes', 1, urlnode.body_hash) + pipeline.zincrby(f'bh|{urlnode.body_hash}', 1, urlnode.hostname) + # set of all captures with this hash + pipeline.sadd(f'bh|{urlnode.body_hash}|captures', crawled_tree.uuid) + # ZSet of all urlnode_UUIDs|full_url + pipeline.zincrby(f'bh|{urlnode.body_hash}|captures|{crawled_tree.uuid}', 1, f'{urlnode.uuid}|{urlnode.hostnode_uuid}|{urlnode.name}') + if hasattr(urlnode, 'embedded_ressources') and urlnode.embedded_ressources: + for mimetype, blobs in urlnode.embedded_ressources.items(): + for h, body in blobs: + pipeline.zincrby('body_hashes', 1, h) + pipeline.zincrby(f'bh|{h}', 1, urlnode.hostname) + pipeline.sadd(f'bh|{h}|captures', crawled_tree.uuid) + pipeline.zincrby(f'bh|{h}|captures|{crawled_tree.uuid}', 1, + f'{urlnode.uuid}|{urlnode.hostnode_uuid}|{urlnode.name}') pipeline.execute() @@ -146,6 +147,54 @@ class Indexing(): def get_body_hash_domains(self, body_hash: str) -> List[Tuple[str, float]]: return self.redis.zrevrange(f'bh|{body_hash}', 0, -1, withscores=True) + def legitimate_capture(self, crawled_tree: CrawledTree) -> None: + pipeline = self.redis.pipeline() + for urlnode in crawled_tree.root_hartree.url_tree.traverse(): + if urlnode.empty_response: + continue + pipeline.sadd(f'bh|{urlnode.body_hash}|legitimate', urlnode.hostname) + pipeline.execute() + + def legitimate_hostnode(self, hostnode: HostNode) -> None: + pipeline = self.redis.pipeline() + for urlnode in hostnode.urls: + if urlnode.empty_response: + continue + pipeline.sadd(f'bh|{urlnode.body_hash}|legitimate', urlnode.hostname) + pipeline.execute() + + def legitimate_urlnode(self, urlnode: URLNode) -> None: + if urlnode.empty_response: + return + self.redis.sadd(f'bh|{urlnode.body_hash}|legitimate', urlnode.hostname) + + def is_legitimate(self, urlnode: URLNode) -> Optional[bool]: + hostnames = self.redis.smembers(f'bh|{urlnode.body_hash}|legitimate') + if hostnames: + if urlnode.hostname in hostnames: + return True # Legitimate + return False # Malicious + elif self.redis.sismember('bh|malicious', urlnode.body_hash): + return False + return None # Unknown + + def malicious_node(self, urlnode: URLNode) -> None: + if urlnode.empty_response: + return None + self.redis.sadd('bh|malicious', urlnode.body_hash) + + def is_malicious(self, urlnode: URLNode) -> Optional[bool]: + if urlnode.empty_response: + return None + if self.redis.sismember('bh|malicious', urlnode.body_hash): + return True + legitimate = self.is_legitimate(urlnode) + if legitimate is True: + return False + if legitimate is False: + return True + return None + class Lookyloo(): @@ -259,6 +308,31 @@ class Lookyloo(): return ct + def add_to_legitimate(self, capture_uuid: str, hostnode_uuid: Optional[str]=None, urlnode_uuid: Optional[str]=None): + ct = self.get_crawled_tree(capture_uuid) + if not hostnode_uuid and not urlnode_uuid: + self.indexing.legitimate_capture(ct) + return + + if hostnode_uuid: + hostnode = ct.root_hartree.get_host_node_by_uuid(hostnode_uuid) + self.indexing.legitimate_hostnode(hostnode) + if urlnode_uuid: + urlnode = ct.root_hartree.get_url_node_by_uuid(urlnode_uuid) + self.indexing.legitimate_urlnode(urlnode) + + def bodies_legitimacy_check(self, tree: CrawledTree) -> CrawledTree: + hostnodes_with_malicious_content = set() + for urlnode in tree.root_hartree.url_tree.traverse(): + malicious = self.indexing.is_malicious(urlnode) + if malicious is not None: + urlnode.add_feature('malicious', malicious) + hostnodes_with_malicious_content.add(urlnode.hostnode_uuid) + for hostnode_with_malicious_content in hostnodes_with_malicious_content: + hostnode = tree.root_hartree.get_host_node_by_uuid(hostnode_with_malicious_content) + hostnode.add_feature('malicious', malicious) + return tree + def load_tree(self, capture_uuid: str) -> Tuple[str, str, str, str, Dict[str, str]]: capture_dir = self.lookup_capture_dir(capture_uuid) if not capture_dir: @@ -268,6 +342,7 @@ class Lookyloo(): with open((capture_dir / 'meta'), 'r') as f: meta = json.load(f) ct = self.get_crawled_tree(capture_uuid) + ct = self.bodies_legitimacy_check(ct) return ct.to_json(), ct.start_time.isoformat(), ct.user_agent, ct.root_url, meta def remove_pickle(self, capture_uuid: str) -> None: diff --git a/poetry.lock b/poetry.lock index d8f4707f..ff3a1021 100644 --- a/poetry.lock +++ b/poetry.lock @@ -140,7 +140,7 @@ description = "Foreign Function Interface for Python calling C code." name = "cffi" optional = false python-versions = "*" -version = "1.14.1" +version = "1.14.2" [package.dependencies] pycparser = "*" @@ -580,7 +580,7 @@ description = "Library for building powerful interactive command lines in Python name = "prompt-toolkit" optional = false python-versions = ">=3.6.1" -version = "3.0.5" +version = "3.0.6" [package.dependencies] wcwidth = "*" @@ -1186,34 +1186,34 @@ certifi = [ {file = "certifi-2020.6.20.tar.gz", hash = "sha256:5930595817496dd21bb8dc35dad090f1c2cd0adfaf21204bf6732ca5d8ee34d3"}, ] cffi = [ - {file = "cffi-1.14.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:66dd45eb9530e3dde8f7c009f84568bc7cac489b93d04ac86e3111fb46e470c2"}, - {file = "cffi-1.14.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:4f53e4128c81ca3212ff4cf097c797ab44646a40b42ec02a891155cd7a2ba4d8"}, - {file = "cffi-1.14.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:833401b15de1bb92791d7b6fb353d4af60dc688eaa521bd97203dcd2d124a7c1"}, - {file = "cffi-1.14.1-cp27-cp27m-win32.whl", hash = "sha256:26f33e8f6a70c255767e3c3f957ccafc7f1f706b966e110b855bfe944511f1f9"}, - {file = "cffi-1.14.1-cp27-cp27m-win_amd64.whl", hash = "sha256:b87dfa9f10a470eee7f24234a37d1d5f51e5f5fa9eeffda7c282e2b8f5162eb1"}, - {file = "cffi-1.14.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:effd2ba52cee4ceff1a77f20d2a9f9bf8d50353c854a282b8760ac15b9833168"}, - {file = "cffi-1.14.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:bac0d6f7728a9cc3c1e06d4fcbac12aaa70e9379b3025b27ec1226f0e2d404cf"}, - {file = "cffi-1.14.1-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:d6033b4ffa34ef70f0b8086fd4c3df4bf801fee485a8a7d4519399818351aa8e"}, - {file = "cffi-1.14.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:8416ed88ddc057bab0526d4e4e9f3660f614ac2394b5e019a628cdfff3733849"}, - {file = "cffi-1.14.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:892daa86384994fdf4856cb43c93f40cbe80f7f95bb5da94971b39c7f54b3a9c"}, - {file = "cffi-1.14.1-cp35-cp35m-win32.whl", hash = "sha256:c991112622baee0ae4d55c008380c32ecfd0ad417bcd0417ba432e6ba7328caa"}, - {file = "cffi-1.14.1-cp35-cp35m-win_amd64.whl", hash = "sha256:fcf32bf76dc25e30ed793145a57426064520890d7c02866eb93d3e4abe516948"}, - {file = "cffi-1.14.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f960375e9823ae6a07072ff7f8a85954e5a6434f97869f50d0e41649a1c8144f"}, - {file = "cffi-1.14.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:a6d28e7f14ecf3b2ad67c4f106841218c8ab12a0683b1528534a6c87d2307af3"}, - {file = "cffi-1.14.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:cda422d54ee7905bfc53ee6915ab68fe7b230cacf581110df4272ee10462aadc"}, - {file = "cffi-1.14.1-cp36-cp36m-win32.whl", hash = "sha256:4a03416915b82b81af5502459a8a9dd62a3c299b295dcdf470877cb948d655f2"}, - {file = "cffi-1.14.1-cp36-cp36m-win_amd64.whl", hash = "sha256:4ce1e995aeecf7cc32380bc11598bfdfa017d592259d5da00fc7ded11e61d022"}, - {file = "cffi-1.14.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e23cb7f1d8e0f93addf0cae3c5b6f00324cccb4a7949ee558d7b6ca973ab8ae9"}, - {file = "cffi-1.14.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:ddff0b2bd7edcc8c82d1adde6dbbf5e60d57ce985402541cd2985c27f7bec2a0"}, - {file = "cffi-1.14.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:f90c2267101010de42f7273c94a1f026e56cbc043f9330acd8a80e64300aba33"}, - {file = "cffi-1.14.1-cp37-cp37m-win32.whl", hash = "sha256:3cd2c044517f38d1b577f05927fb9729d3396f1d44d0c659a445599e79519792"}, - {file = "cffi-1.14.1-cp37-cp37m-win_amd64.whl", hash = "sha256:4fa72a52a906425416f41738728268072d5acfd48cbe7796af07a923236bcf96"}, - {file = "cffi-1.14.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:267adcf6e68d77ba154334a3e4fc921b8e63cbb38ca00d33d40655d4228502bc"}, - {file = "cffi-1.14.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:d3148b6ba3923c5850ea197a91a42683f946dba7e8eb82dfa211ab7e708de939"}, - {file = "cffi-1.14.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:98be759efdb5e5fa161e46d404f4e0ce388e72fbf7d9baf010aff16689e22abe"}, - {file = "cffi-1.14.1-cp38-cp38-win32.whl", hash = "sha256:6923d077d9ae9e8bacbdb1c07ae78405a9306c8fd1af13bfa06ca891095eb995"}, - {file = "cffi-1.14.1-cp38-cp38-win_amd64.whl", hash = "sha256:b1d6ebc891607e71fd9da71688fcf332a6630b7f5b7f5549e6e631821c0e5d90"}, - {file = "cffi-1.14.1.tar.gz", hash = "sha256:b2a2b0d276a136146e012154baefaea2758ef1f56ae9f4e01c612b0831e0bd2f"}, + {file = "cffi-1.14.2-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:da9d3c506f43e220336433dffe643fbfa40096d408cb9b7f2477892f369d5f82"}, + {file = "cffi-1.14.2-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:23e44937d7695c27c66a54d793dd4b45889a81b35c0751ba91040fe825ec59c4"}, + {file = "cffi-1.14.2-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:0da50dcbccd7cb7e6c741ab7912b2eff48e85af217d72b57f80ebc616257125e"}, + {file = "cffi-1.14.2-cp27-cp27m-win32.whl", hash = "sha256:76ada88d62eb24de7051c5157a1a78fd853cca9b91c0713c2e973e4196271d0c"}, + {file = "cffi-1.14.2-cp27-cp27m-win_amd64.whl", hash = "sha256:15a5f59a4808f82d8ec7364cbace851df591c2d43bc76bcbe5c4543a7ddd1bf1"}, + {file = "cffi-1.14.2-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:e4082d832e36e7f9b2278bc774886ca8207346b99f278e54c9de4834f17232f7"}, + {file = "cffi-1.14.2-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:57214fa5430399dffd54f4be37b56fe22cedb2b98862550d43cc085fb698dc2c"}, + {file = "cffi-1.14.2-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:6843db0343e12e3f52cc58430ad559d850a53684f5b352540ca3f1bc56df0731"}, + {file = "cffi-1.14.2-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:577791f948d34d569acb2d1add5831731c59d5a0c50a6d9f629ae1cefd9ca4a0"}, + {file = "cffi-1.14.2-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:8662aabfeab00cea149a3d1c2999b0731e70c6b5bac596d95d13f643e76d3d4e"}, + {file = "cffi-1.14.2-cp35-cp35m-win32.whl", hash = "sha256:837398c2ec00228679513802e3744d1e8e3cb1204aa6ad408b6aff081e99a487"}, + {file = "cffi-1.14.2-cp35-cp35m-win_amd64.whl", hash = "sha256:bf44a9a0141a082e89c90e8d785b212a872db793a0080c20f6ae6e2a0ebf82ad"}, + {file = "cffi-1.14.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:29c4688ace466a365b85a51dcc5e3c853c1d283f293dfcc12f7a77e498f160d2"}, + {file = "cffi-1.14.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:99cc66b33c418cd579c0f03b77b94263c305c389cb0c6972dac420f24b3bf123"}, + {file = "cffi-1.14.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:65867d63f0fd1b500fa343d7798fa64e9e681b594e0a07dc934c13e76ee28fb1"}, + {file = "cffi-1.14.2-cp36-cp36m-win32.whl", hash = "sha256:f5033952def24172e60493b68717792e3aebb387a8d186c43c020d9363ee7281"}, + {file = "cffi-1.14.2-cp36-cp36m-win_amd64.whl", hash = "sha256:7057613efefd36cacabbdbcef010e0a9c20a88fc07eb3e616019ea1692fa5df4"}, + {file = "cffi-1.14.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:6539314d84c4d36f28d73adc1b45e9f4ee2a89cdc7e5d2b0a6dbacba31906798"}, + {file = "cffi-1.14.2-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:672b539db20fef6b03d6f7a14b5825d57c98e4026401fce838849f8de73fe4d4"}, + {file = "cffi-1.14.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:95e9094162fa712f18b4f60896e34b621df99147c2cee216cfa8f022294e8e9f"}, + {file = "cffi-1.14.2-cp37-cp37m-win32.whl", hash = "sha256:b9aa9d8818c2e917fa2c105ad538e222a5bce59777133840b93134022a7ce650"}, + {file = "cffi-1.14.2-cp37-cp37m-win_amd64.whl", hash = "sha256:e4b9b7af398c32e408c00eb4e0d33ced2f9121fd9fb978e6c1b57edd014a7d15"}, + {file = "cffi-1.14.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e613514a82539fc48291d01933951a13ae93b6b444a88782480be32245ed4afa"}, + {file = "cffi-1.14.2-cp38-cp38-manylinux1_i686.whl", hash = "sha256:9b219511d8b64d3fa14261963933be34028ea0e57455baf6781fe399c2c3206c"}, + {file = "cffi-1.14.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:c0b48b98d79cf795b0916c57bebbc6d16bb43b9fc9b8c9f57f4cf05881904c75"}, + {file = "cffi-1.14.2-cp38-cp38-win32.whl", hash = "sha256:15419020b0e812b40d96ec9d369b2bc8109cc3295eac6e013d3261343580cc7e"}, + {file = "cffi-1.14.2-cp38-cp38-win_amd64.whl", hash = "sha256:12a453e03124069b6896107ee133ae3ab04c624bb10683e1ed1c1663df17c13c"}, + {file = "cffi-1.14.2.tar.gz", hash = "sha256:ae8f34d50af2c2154035984b8b5fc5d9ed63f32fe615646ab435b05b132ca91b"}, ] chardet = [ {file = "chardet-3.0.4-py2.py3-none-any.whl", hash = "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"}, @@ -1453,8 +1453,8 @@ pluggy = [ {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"}, ] prompt-toolkit = [ - {file = "prompt_toolkit-3.0.5-py3-none-any.whl", hash = "sha256:df7e9e63aea609b1da3a65641ceaf5bc7d05e0a04de5bd45d05dbeffbabf9e04"}, - {file = "prompt_toolkit-3.0.5.tar.gz", hash = "sha256:563d1a4140b63ff9dd587bda9557cffb2fe73650205ab6f4383092fb882e7dc8"}, + {file = "prompt_toolkit-3.0.6-py3-none-any.whl", hash = "sha256:683397077a64cd1f750b71c05afcfc6612a7300cb6932666531e5a54f38ea564"}, + {file = "prompt_toolkit-3.0.6.tar.gz", hash = "sha256:7630ab85a23302839a0f26b31cc24f518e6155dea1ed395ea61b42c45941b6a6"}, ] protego = [ {file = "Protego-0.1.16.tar.gz", hash = "sha256:a682771bc7b51b2ff41466460896c1a5a653f9a1e71639ef365a72e66d8734b4"}, diff --git a/website/web/__init__.py b/website/web/__init__.py index 7a8a5556..c59e7d7f 100644 --- a/website/web/__init__.py +++ b/website/web/__init__.py @@ -492,6 +492,16 @@ def body_hash_details(body_hash: str): return render_template('body_hash.html', body_hash=body_hash, domains=domains, captures=captures) +@app.route('/tree//mark_as_legitimate', methods=['POST']) +def mark_as_legitimate(tree_uuid: str): + if request.data: + legitimate_entries = request.get_json(force=True) + lookyloo.add_to_legitimate(tree_uuid, **legitimate_entries) + else: + lookyloo.add_to_legitimate(tree_uuid) + return jsonify({'message': 'Legitimate entry added.'}) + + # Query API @app.route('/json//redirects', methods=['GET']) diff --git a/website/web/static/tree.js b/website/web/static/tree.js index adaef832..c05bcb86 100644 --- a/website/web/static/tree.js +++ b/website/web/static/tree.js @@ -161,6 +161,13 @@ function UnflagAllNodes() { .on('mouseout', () => d3.select('#tooltip').style('opacity', 0)); }; +function MarkAsLegitimate(capture_uuid, hostnode_uuid=null, urlnode_uuid=null) { + let data = {}; + if (hostnode_uuid != null) { data['hostnode_uuid'] = hostnode_uuid; }; + if (urlnode_uuid != null) { data['urlnode_uuid'] = urlnode_uuid; }; + $.post(`/tree/${capture_uuid}/mark_as_legitimate`, data); +}; + function UnflagHostNode(hostnode_uuid) { d3.select(`#node_${hostnode_uuid}`).select('rect').style('fill', 'white'); d3.select(`#node_${hostnode_uuid}`).select('text').style('fill', 'black'); @@ -320,15 +327,13 @@ function update(root, computed_node_width=0) { // Set background based on the computed width and height let background = main_svg.insert('rect', ':first-child') .attr('y', 0) - // FIXME: + 200 doesn't make much sense... - .attr('width', newWidth + margin.right + margin.left + 200) + .attr('width', newWidth + (margin.right + margin.left)*2) .attr('height', newHeight + margin.top + margin.bottom) .style('fill', "url(#backstripes)"); // Update size - d3.select("body svg") - // FIXME: + 200 doesn't make much sense... - .attr("width", newWidth + margin.right + margin.left + 200) + main_svg + .attr("width", newWidth + (margin.right + margin.left)*2) .attr("height", newHeight + margin.top + margin.bottom) // Update pattern @@ -445,14 +450,14 @@ function update(root, computed_node_width=0) { }) .on('mouseout', () => d3.select('#tooltip').style('opacity', 0)); + const http_icon_size = 24; if (d.data.http_content) { - const icon_size = 24; // set lock insecure connection d3.select(this).append("svg").append('rect') .attr('x', selected_node_bbox.width - 22) .attr('y', selected_node_bbox.height - 13) - .attr('width', icon_size) - .attr('height', icon_size) + .attr('width', http_icon_size) + .attr('height', http_icon_size) .attr('fill', 'white') .attr('stroke', 'black'); @@ -460,8 +465,8 @@ function update(root, computed_node_width=0) { .attr('x', selected_node_bbox.width - 22) .attr('y', selected_node_bbox.height - 13) .attr('id', 'insecure_image') - .attr("width", icon_size) - .attr("height", icon_size) + .attr("width", http_icon_size) + .attr("height", http_icon_size) .attr("xlink:href", '/static/insecure.svg') .on('mouseover', () => { d3.select('#tooltip') @@ -472,6 +477,33 @@ function update(root, computed_node_width=0) { }) .on('mouseout', () => d3.select('#tooltip').style('opacity', 0)); }; + const malicious_icon_size = 24; + if (d.data.malicious) { + // set lock insecure connection + d3.select(this).append("svg").append('rect') + .attr('x', selected_node_bbox.width - 22 - http_icon_size) + .attr('y', selected_node_bbox.height - 13) + .attr('width', malicious_icon_size) + .attr('height', malicious_icon_size) + .attr('fill', 'white') + .attr('stroke', 'black'); + + d3.select(this).append('image') + .attr('x', selected_node_bbox.width - 22 - http_icon_size) + .attr('y', selected_node_bbox.height - 13) + .attr('id', 'insecure_image') + .attr("width", malicious_icon_size) + .attr("height", malicious_icon_size) + .attr("xlink:href", '/static/bomb.svg') + .on('mouseover', () => { + d3.select('#tooltip') + .style('opacity', 1) + .style('left', `${d3.event.pageX + 10}px`) + .style('top', `${d3.event.pageY + 10}px`) + .text('This node containts known malicious content'); + }) + .on('mouseout', () => d3.select('#tooltip').style('opacity', 0)); + }; }); return node_group; diff --git a/website/web/templates/tree.html b/website/web/templates/tree.html index 018c4501..b2b6829d 100644 --- a/website/web/templates/tree.html +++ b/website/web/templates/tree.html @@ -93,7 +93,7 @@ {% endif %}
  • - Unflag all nodes + Unflag all nodes
  • Download screenshot @@ -101,6 +101,9 @@
  • Show screenshot
  • +
  • + Mark capture as legitimate +
  • {% if enable_mail_notification %}
  • Notify by mail