diff --git a/CHANGES.md b/CHANGES.md new file mode 100644 index 0000000..53ae376 --- /dev/null +++ b/CHANGES.md @@ -0,0 +1,31 @@ +## Main feature + +This module was created to roughly calculate randomness in redownloaded pictures. + +It can be used to detect possible memory leaks from image renderers. +If there is some error in the image renderer logic, and it is not handling exceptions properly, +it is possible that a malformed uploaded file will trigger it, and the response will contain parts of the +image renderer memory. +This behaviour will increase the randomness of pixels in the returned picture. + +By getting the full RGB list from the response picture, and compressing it with zlib, we can +estimate the randomness of the pixels by dividing these two values with each other. + +This module will give an informational issue to all redownloaded pictures, where the compression ratio is beneath the +median of all compression ratios, which can help us to see, if there is too much random noise in the picture, +and from there, we can manually investigate the incident. + +## When/How it is used + +Image entropy calculation is only done on the Fingerping, fuzzer, and recursive uploader module, if the "Calculate image entropy" checkbox is selected. +The module will create informational issues where the redownloaded picture compression ratio is lower than the avarage of it. It can be used to detect issues such as this: https://blog.silentsignal.eu/2019/04/18/drop-by-drop-bleeding-through-libvips/ + +## Changes in the code: + +Added a checkbox to the general options UI with label "Calculate image entropy". It's referenced in the code as calculate_entropy + +Added new functions for the entropy calculation/reporting. These are defined from line 866 to 922. + +The calculation is called in the affected modules(Fingerping/Fuzzer/Recursive uploader), where all the ratios and request/responses are stored in a local variable called Entropy_list, which is used at the end of each module for the reporting. + +Added a new helper function which calculates the median of a given list. (line 4243) diff --git a/UploadScanner.py b/UploadScanner.py index 95b272d..c5f09f9 100755 --- a/UploadScanner.py +++ b/UploadScanner.py @@ -862,6 +862,66 @@ def add_log_entry(self, rr): self._helpers.analyzeRequest(rr).getUrl())) self.fireTableRowsInserted(row, row) + # Helper functions for image entropy calculation + def _calculate_image_entropy_wrapper(self, injector, urr): + download_responseInfo = self._helpers.analyzeResponse(urr.download_rr.getResponse()) + headers = [FloydsHelpers.u2s(x) for x in download_responseInfo.getHeaders()] + if any("image/" in h for h in headers): + ratio = self._calculate_image_entropy(injector, urr) + return ratio + + def _report_image_entropy(self, Entropy_list): + # Calculate median of the ratios + ratios = [ratio[0] for ratio in Entropy_list] + median = self._median(ratios) + for item in Entropy_list: + if item[0] and item[0] < median: + injector = item[1] + urr = item[2] + ratio = item[0] + service = urr.download_rr.getHttpService() + url = self._helpers.analyzeRequest(urr.download_rr).getUrl() + name = "Downloaded image entropy info" + severity = "Information" + confidence = "Firm" + detail = "Information about the redownloaded image entropy where compression ratios were lower than the avarage.
" \ + "If the compression ratio is too low, that could mean that the image contains memory leak.

" \ + "The avarage compression ratio for the module was: {}

" \ + "Compression ratio for this image: {}
".format(median,ratio) + + csi = CustomScanIssue([injector.get_brr()], name, detail, confidence, severity, service, url) + csi.httpMessagesPy = [urr.upload_rr, urr.download_rr] + if csi: + self._callbacks.addScanIssue(csi) + + + + # Function to calculate image entropy via compressing redownloaded images + def _calculate_image_entropy(self, injector, urr): + download_iResponseInfo = self._helpers.analyzeResponse(urr.download_rr.getResponse()) + download_body = FloydsHelpers.jb2ps(urr.download_rr.getResponse())[download_iResponseInfo.getBodyOffset():] + + try: + # Rescaling the image for performance + picture_width, picture_height, fileformat = ImageHelpers.image_width_height(download_body) + if picture_width and picture_height and fileformat: + if picture_width >= 200 or picture_height >= 200: + download_thumbnail = ImageHelpers.rescale_image(200, 200, download_body) + if download_thumbnail: + download_body = download_thumbnail + + download_rgbs = ImageHelpers.get_image_rgb_list(download_body) + download_conv = "".join([struct.pack("