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:
parent
f2500dd0ae
commit
46c10c030e
|
@ -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),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue