add 2 new endpoints for processes extraction and process image (#7136)

* add 2 new endpoints for processes extraction and process image

* [autofix.ci] apply automated fixes

* add review changes

* add tests

* [autofix.ci] apply automated fixes

* nit

* update tests

* [autofix.ci] apply automated fixes

* add fallback image and update tests

* [autofix.ci] apply automated fixes

* fix lint error

* fix tests

* [autofix.ci] apply automated fixes

* try to use base64

* still trying to fix test on win

* [autofix.ci] apply automated fixes

* nit

* [autofix.ci] apply automated fixes

* TRANSPARENT_PNG: use raw bytes to avoid base64 step

* tests: use feature-based detection

* hardening: prevent mime type sniffing

* fixup feature detection

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Maximilian Hils <git@maximilianhils.com>
This commit is contained in:
Matteo Luppi 2024-09-01 22:22:51 +02:00 committed by GitHub
parent f2500dd0ae
commit 46c10c030e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 73 additions and 1 deletions

View File

@ -13,6 +13,7 @@ from io import BytesIO
from itertools import islice from itertools import islice
from typing import ClassVar from typing import ClassVar
import mitmproxy_rs
import tornado.escape import tornado.escape
import tornado.web import tornado.web
import tornado.websocket import tornado.websocket
@ -38,6 +39,12 @@ from mitmproxy.utils.emoji import emoji
from mitmproxy.utils.strutils import always_str from mitmproxy.utils.strutils import always_str
from mitmproxy.websocket import WebSocketMessage from mitmproxy.websocket import WebSocketMessage
TRANSPARENT_PNG = (
b"\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x01\x00\x00\x00\x01\x08"
b"\x04\x00\x00\x00\xb5\x1c\x0c\x02\x00\x00\x00\x0bIDATx\xdac\xfc\xff\x07"
b"\x00\x02\x00\x01\xfc\xa8Q\rh\x00\x00\x00\x00IEND\xaeB`\x82"
)
def cert_to_json(certs: Sequence[certs.Cert]) -> dict | None: def cert_to_json(certs: Sequence[certs.Cert]) -> dict | None:
if not certs: if not certs:
@ -654,6 +661,41 @@ class State(RequestHandler):
self.write(State.get_json(self.master)) self.write(State.get_json(self.master))
class ProcessList(RequestHandler):
@staticmethod
def get_json():
processes = mitmproxy_rs.active_executables()
return [
{
"is_visible": process.is_visible,
"executable": process.executable,
"is_system": process.is_system,
"display_name": process.display_name,
}
for process in processes
]
def get(self):
self.write(ProcessList.get_json())
class ProcessImage(RequestHandler):
def get(self):
path = self.get_query_argument("path", None)
if not path:
raise APIError(400, "Missing 'path' parameter.")
try:
icon_bytes = mitmproxy_rs.executable_icon(path)
except Exception:
icon_bytes = TRANSPARENT_PNG
self.set_header("Content-Type", "image/png")
self.set_header("X-Content-Type-Options", "nosniff")
self.write(icon_bytes)
class GZipContentAndFlowFiles(tornado.web.GZipContentEncoding): class GZipContentAndFlowFiles(tornado.web.GZipContentEncoding):
CONTENT_TYPES = { CONTENT_TYPES = {
"application/octet-stream", "application/octet-stream",
@ -713,5 +755,7 @@ class Application(tornado.web.Application):
(r"/options(?:\.json)?", Options), (r"/options(?:\.json)?", Options),
(r"/options/save", SaveOptions), (r"/options/save", SaveOptions),
(r"/state(?:\.json)?", State), (r"/state(?:\.json)?", State),
(r"/processes", ProcessList),
(r"/executable-icon", ProcessImage),
], ],
) )

View File

@ -43,7 +43,7 @@ dependencies = [
"hyperframe>=6.0,<=6.0.1", "hyperframe>=6.0,<=6.0.1",
"kaitaistruct>=0.10,<=0.10", "kaitaistruct>=0.10,<=0.10",
"ldap3>=2.8,<=2.9.1", "ldap3>=2.8,<=2.9.1",
"mitmproxy_rs>=0.7.1,<0.8", # relaxed upper bound here: we control this "mitmproxy_rs>=0.7.2,<0.8", # relaxed upper bound here: we control this
"msgpack>=1.0.0,<=1.0.8", "msgpack>=1.0.0,<=1.0.8",
"passlib>=1.6.5,<=1.7.4", "passlib>=1.6.5,<=1.7.4",
"protobuf>=5.27.2,<=5.27.3", "protobuf>=5.27.2,<=5.27.3",

View File

@ -5,6 +5,7 @@ import logging
from pathlib import Path from pathlib import Path
from unittest import mock from unittest import mock
import mitmproxy_rs
import pytest import pytest
import tornado.testing import tornado.testing
from tornado import httpclient from tornado import httpclient
@ -403,3 +404,30 @@ class TestApp(tornado.testing.AsyncHTTPTestCase):
# trigger on_close by opening a second connection. # trigger on_close by opening a second connection.
ws_client2 = yield websocket.websocket_connect(ws_url) ws_client2 = yield websocket.websocket_connect(ws_url)
ws_client2.close() ws_client2.close()
def test_process_list(self):
try:
mitmproxy_rs.active_executables()
except NotImplementedError:
pytest.skip(
"mitmproxy_rs.active_executables not available on this platform."
)
resp = self.fetch("/processes")
assert resp.code == 200
assert get_json(resp)
def test_process_icon(self):
try:
mitmproxy_rs.executable_icon("invalid")
except NotImplementedError:
pytest.skip("mitmproxy_rs.executable_icon not available on this platform.")
except Exception:
pass
resp = self.fetch("/executable-icon")
assert resp.code == 400
assert "Missing 'path' parameter." in resp.body.decode()
resp = self.fetch("/executable-icon?path=invalid_path")
assert resp.code == 200
assert resp.headers["Content-Type"] == "image/png"
assert resp.body == app.TRANSPARENT_PNG