Skip to content

Commit

Permalink
Add unsafe functions
Browse files Browse the repository at this point in the history
  • Loading branch information
aadya940 committed Oct 31, 2024
1 parent 030bd3a commit 766d216
Show file tree
Hide file tree
Showing 4 changed files with 359 additions and 2 deletions.
7 changes: 5 additions & 2 deletions examples/test.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -228,11 +228,14 @@
}
],
"source": [
"from pygraas import RestrictionLayer\n",
"from pygraas import RestrictionLayer, unsafe_functions\n",
"from skimage.filters import sobel\n",
"import numpy as np\n",
"\n",
"l = RestrictionLayer([\"numpy.zeros\"])\n",
"# Block other unsafe functions as well.\n",
"unsafe = [\"numpy.zeros\"].extend(unsafe_functions)\n",
"\n",
"l = RestrictionLayer(unsafe)\n",
"l.activate()\n",
"\n",
"image = np.random.random((4000, 4000))\n",
Expand Down
1 change: 1 addition & 0 deletions pygraas/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from ._depgraph import DependencyGraph
from ._vuln_graph import VulnerabilityGraph
from ._sandboxer import RestrictionLayer
from ._unsafe import unsafe_functions
12 changes: 12 additions & 0 deletions pygraas/_unsafe.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
"""List of unsafe python functions taken from
https://xcg.tech.gov.sg/packages/dangerousfunctions/
"""

unsafe_functions = [
"subprocess.Popen",
"os.system",
"os.popen",
"subprocess.check_output",
"builtins.eval",
"builtins.exec",
]
341 changes: 341 additions & 0 deletions test.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,341 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Generate Dependency Graph for Numpy, Max Depth = 4"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"Cloning into 'numpy'...\n",
"dummymodule.py:140: WARNING: SKIPPING ILLEGAL MODULE_NAME: numpy.numpy._pyinstaller.hook-numpy\n",
"dummymodule.py:140: WARNING: SKIPPING ILLEGAL MODULE_NAME: numpy.numpy._pyinstaller.tests.pyinstaller-smoke\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"<pydeps.configs.Config object at 0x74046d34f460> \n",
"\n",
"changing __main__ => _dummy_numpy.py\n",
"changing __main__ => _dummy_numpy.py\n",
"changing __main__ => _dummy_numpy.py\n",
"changing __main__ => _dummy_numpy.py\n",
"Graph built with 292 nodes and 1494 edges.\n"
]
}
],
"source": [
"from pygraas import DependencyGraph, VulnerabilityGraph\n",
"g = DependencyGraph(package_name=\"numpy\", package_url=\"https://github.com/numpy/numpy\")\n",
"graph = g.build_graph(max_bacon=4)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Generate Vulnerability Graph based on the Dependency Graph"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Vulnerability Graph marks the external packages which are Vulnerable, also add's the CVEs, advisory and \n",
"vulnerable package versions as node attributes."
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"<pygraas._depgraph.DependencyGraph at 0x74046d3c2740>"
]
},
"execution_count": 3,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"v = VulnerabilityGraph(g)\n",
"v.build_vulnerability_graph()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### List external vulnerable packages based on the Safety-DB Database"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"['numpy', 'psutil', 'setuptools']\n"
]
}
],
"source": [
"vulnerables = v.get_vulnerables()\n",
"print(vulnerables)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Read Details"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"['<1.13.2',\n",
" '<1.16.3',\n",
" '<1.21.0rc1',\n",
" '<1.22.0',\n",
" '<1.22.0',\n",
" '<1.22.2',\n",
" '<1.8.1',\n",
" '<1.8.1']"
]
},
"execution_count": 5,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"v.get_vulnerables(details=True)[0][\"numpy\"][\"version\"]"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"['CVE-2017-12852',\n",
" 'CVE-2019-6446',\n",
" 'CVE-2021-33430',\n",
" 'CVE-2021-34141',\n",
" 'CVE-2021-41496',\n",
" 'CVE-2021-41495',\n",
" 'CVE-2014-1858',\n",
" 'CVE-2014-1859']"
]
},
"execution_count": 6,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"v.get_vulnerables(details=True)[0][\"numpy\"][\"CVE\"]"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"['Numpy 1.13.2 includes a fix for CVE-2017-12852: The numpy.pad function in Numpy 1.13.1 and older versions is missing input validation. An empty list or ndarray will stick into an infinite loop, which can allow attackers to cause a DoS attack.\\r\\nhttps://github.com/numpy/numpy/issues/9560#issuecomment-322395292',\n",
" 'Numpy 1.16.3 includes a fix for CVE-2019-6446: It uses the pickle Python module unsafely, which allows remote attackers to execute arbitrary code via a crafted serialized object, as demonstrated by a numpy.load call.\\r\\nNOTE: Third parties dispute this issue because it is a behavior that might have legitimate applications in (for example) loading serialized Python object arrays from trusted and authenticated sources.\\r\\nhttps://github.com/numpy/numpy/commit/89b688732b37616c9d26623f81aaee1703c30ffb',\n",
" 'Numpy 1.21.0rc1 includes a fix for CVE-2021-33430: A Buffer Overflow vulnerability in the PyArray_NewFromDescr_int function of ctors.c when specifying arrays of large dimensions (over 32) from Python code, which could let a malicious user cause a Denial of Service. \\r\\nNOTE: The vendor does not agree this is a vulnerability. In (very limited) circumstances a user may be able provoke the buffer overflow, the user is most likely already privileged to at least provoke denial of service by exhausting memory. Triggering this further requires the use of uncommon API (complicated structured dtypes), which is very unlikely to be available to an unprivileged user.\\r\\nhttps://github.com/numpy/numpy/issues/18939',\n",
" 'Numpy 1.22.0 includes a fix for CVE-2021-34141: An incomplete string comparison in the numpy.core component in NumPy before 1.22.0 allows attackers to trigger slightly incorrect copying by constructing specific string objects. \\r\\nNOTE: the vendor states that this reported code behavior is \"completely harmless.\"\\r\\nhttps://github.com/numpy/numpy/issues/18993',\n",
" 'Numpy 1.22.0 includes a fix for CVE-2021-41496: Buffer overflow in the array_from_pyobj function of fortranobject.c, which allows attackers to conduct a Denial of Service attacks by carefully constructing an array with negative values. \\r\\nNOTE: The vendor does not agree this is a vulnerability; the negative dimensions can only be created by an already privileged user (or internally).\\r\\nhttps://github.com/numpy/numpy/issues/19000',\n",
" 'Numpy 1.22.2 includes a fix for CVE-2021-41495: Null Pointer Dereference vulnerability exists in numpy.sort in NumPy in the PyArray_DescrNew function due to missing return-value validation, which allows attackers to conduct DoS attacks by repetitively creating sort arrays. \\r\\nNOTE: While correct that validation is missing, an error can only occur due to an exhaustion of memory. If the user can exhaust memory, they are already privileged. Further, it should be practically impossible to construct an attack which can target the memory exhaustion to occur at exactly this place.\\r\\nNOTE2: The specs we include in this advisory differ from the publicly available on other sources. For example, the advisory posted by the NVD indicate that versions up to and including 1.19.0 are affected. However, research by Safety CLI Cybersecurity confirms that the vulnerability remained unaddressed until version 1.22.2.',\n",
" 'Numpy 1.8.1 includes a fix for CVE-2014-1858: __init__.py in f2py in NumPy before 1.8.1 allows local users to write to arbitrary files via a symlink attack on a temporary file.\\r\\nhttps://github.com/numpy/numpy/commit/0bb46c1448b0d3f5453d5182a17ea7ac5854ee15',\n",
" 'Numpy 1.8.1 includes a fix for CVE-2014-1859: (1) core/tests/test_memmap.py, (2) core/tests/test_multiarray.py, (3) f2py/f2py2e.py, and (4) lib/tests/test_io.py in NumPy before 1.8.1 allow local users to write to arbitrary files via a symlink attack on a temporary file.\\r\\nhttps://github.com/numpy/numpy/pull/4262']"
]
},
"execution_count": 7,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"v.get_vulnerables(details=True)[0][\"numpy\"][\"advisory\"]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"`np.pad` seems vulnerable, we can restrict it using `pygraas.RestrictionLayer` ."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Here, just for demonstration purposes, we'll restricted the usage of `numpy.zeros` which is called internally in `skimage.filters.sobel` ."
]
},
{
"cell_type": "code",
"execution_count": 16,
"metadata": {},
"outputs": [
{
"ename": "RestrictedFunctionError",
"evalue": "The function 'numpy.zeros' is blocked.",
"output_type": "error",
"traceback": [
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
"\u001b[0;31mRestrictedFunctionError\u001b[0m Traceback (most recent call last)",
"Cell \u001b[0;32mIn[16], line 9\u001b[0m\n\u001b[1;32m 6\u001b[0m l\u001b[38;5;241m.\u001b[39mactivate()\n\u001b[1;32m 8\u001b[0m image \u001b[38;5;241m=\u001b[39m np\u001b[38;5;241m.\u001b[39mrandom\u001b[38;5;241m.\u001b[39mrandom((\u001b[38;5;241m4000\u001b[39m, \u001b[38;5;241m4000\u001b[39m))\n\u001b[0;32m----> 9\u001b[0m \u001b[43msobel\u001b[49m\u001b[43m(\u001b[49m\u001b[43mimage\u001b[49m\u001b[43m)\u001b[49m\n",
"File \u001b[0;32m~/.local/lib/python3.10/site-packages/skimage/filters/edges.py:250\u001b[0m, in \u001b[0;36msobel\u001b[0;34m(image, mask, axis, mode, cval)\u001b[0m\n\u001b[1;32m 200\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21msobel\u001b[39m(image, mask\u001b[38;5;241m=\u001b[39m\u001b[38;5;28;01mNone\u001b[39;00m, \u001b[38;5;241m*\u001b[39m, axis\u001b[38;5;241m=\u001b[39m\u001b[38;5;28;01mNone\u001b[39;00m, mode\u001b[38;5;241m=\u001b[39m\u001b[38;5;124m'\u001b[39m\u001b[38;5;124mreflect\u001b[39m\u001b[38;5;124m'\u001b[39m, cval\u001b[38;5;241m=\u001b[39m\u001b[38;5;241m0.0\u001b[39m):\n\u001b[1;32m 201\u001b[0m \u001b[38;5;250m \u001b[39m\u001b[38;5;124;03m\"\"\"Find edges in an image using the Sobel filter.\u001b[39;00m\n\u001b[1;32m 202\u001b[0m \n\u001b[1;32m 203\u001b[0m \u001b[38;5;124;03m Parameters\u001b[39;00m\n\u001b[0;32m (...)\u001b[0m\n\u001b[1;32m 248\u001b[0m \u001b[38;5;124;03m >>> edges = filters.sobel(camera)\u001b[39;00m\n\u001b[1;32m 249\u001b[0m \u001b[38;5;124;03m \"\"\"\u001b[39;00m\n\u001b[0;32m--> 250\u001b[0m output \u001b[38;5;241m=\u001b[39m \u001b[43m_generic_edge_filter\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 251\u001b[0m \u001b[43m \u001b[49m\u001b[43mimage\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43msmooth_weights\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mSOBEL_SMOOTH\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43maxis\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43maxis\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mmode\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mmode\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mcval\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mcval\u001b[49m\n\u001b[1;32m 252\u001b[0m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 253\u001b[0m output \u001b[38;5;241m=\u001b[39m _mask_filter_result(output, mask)\n\u001b[1;32m 254\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m output\n",
"File \u001b[0;32m~/.local/lib/python3.10/site-packages/skimage/filters/edges.py:183\u001b[0m, in \u001b[0;36m_generic_edge_filter\u001b[0;34m(image, smooth_weights, edge_weights, axis, mode, cval, mask)\u001b[0m\n\u001b[1;32m 181\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[1;32m 182\u001b[0m image \u001b[38;5;241m=\u001b[39m img_as_float(image)\n\u001b[0;32m--> 183\u001b[0m output \u001b[38;5;241m=\u001b[39m \u001b[43mnp\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mzeros\u001b[49m\u001b[43m(\u001b[49m\u001b[43mimage\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mshape\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mdtype\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mimage\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mdtype\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 185\u001b[0m \u001b[38;5;28;01mfor\u001b[39;00m edge_dim \u001b[38;5;129;01min\u001b[39;00m axes:\n\u001b[1;32m 186\u001b[0m kernel \u001b[38;5;241m=\u001b[39m _reshape_nd(edge_weights, ndim, edge_dim)\n",
"File \u001b[0;32m/usr/lib/python3.10/unittest/mock.py:1114\u001b[0m, in \u001b[0;36mCallableMixin.__call__\u001b[0;34m(self, *args, **kwargs)\u001b[0m\n\u001b[1;32m 1112\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_mock_check_sig(\u001b[38;5;241m*\u001b[39margs, \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs)\n\u001b[1;32m 1113\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_increment_mock_call(\u001b[38;5;241m*\u001b[39margs, \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs)\n\u001b[0;32m-> 1114\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_mock_call\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43margs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n",
"File \u001b[0;32m/usr/lib/python3.10/unittest/mock.py:1118\u001b[0m, in \u001b[0;36mCallableMixin._mock_call\u001b[0;34m(self, *args, **kwargs)\u001b[0m\n\u001b[1;32m 1117\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21m_mock_call\u001b[39m(\u001b[38;5;28mself\u001b[39m, \u001b[38;5;241m/\u001b[39m, \u001b[38;5;241m*\u001b[39margs, \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs):\n\u001b[0;32m-> 1118\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_execute_mock_call\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43margs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n",
"File \u001b[0;32m/usr/lib/python3.10/unittest/mock.py:1179\u001b[0m, in \u001b[0;36mCallableMixin._execute_mock_call\u001b[0;34m(self, *args, **kwargs)\u001b[0m\n\u001b[1;32m 1177\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m result\n\u001b[1;32m 1178\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[0;32m-> 1179\u001b[0m result \u001b[38;5;241m=\u001b[39m \u001b[43meffect\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43margs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 1181\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m result \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m DEFAULT:\n\u001b[1;32m 1182\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m result\n",
"File \u001b[0;32m~/Desktop/dev/ga/pygraas/_sandboxer.py:13\u001b[0m, in \u001b[0;36mRestrictionLayer._restrict_function.<locals>.restricted_func\u001b[0;34m(*args, **kwargs)\u001b[0m\n\u001b[1;32m 12\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mrestricted_func\u001b[39m(\u001b[38;5;241m*\u001b[39margs, \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs):\n\u001b[0;32m---> 13\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m RestrictedFunctionError(\u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mThe function \u001b[39m\u001b[38;5;124m'\u001b[39m\u001b[38;5;132;01m{\u001b[39;00mfunc_path\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m'\u001b[39m\u001b[38;5;124m is blocked.\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n",
"\u001b[0;31mRestrictedFunctionError\u001b[0m: The function 'numpy.zeros' is blocked."
]
}
],
"source": [
"from pygraas import RestrictionLayer, unsafe_functions\n",
"from skimage.filters import sobel\n",
"import numpy as np\n",
"\n",
"# Block other unsafe functions as well.\n",
"unsafe = [\"numpy.zeros\"].extend(unsafe_functions)\n",
"\n",
"l = RestrictionLayer(unsafe)\n",
"l.activate()\n",
"\n",
"image = np.random.random((4000, 4000))\n",
"sobel(image)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Degree of the \"psutil\" node "
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"{'in_degree': 10, 'out_degree': 1}"
]
},
"execution_count": 9,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"v.get_degree(vulnerables[1])"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### List of Public Nodes that have a path to vulnerable \"setuptools\""
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[{'bz2 - setuptools': True},\n",
" {'dis - setuptools': True},\n",
" {'heapq - setuptools': True},\n",
" {'dataclasses - setuptools': True},\n",
" {'collections - setuptools': True},\n",
" {'lzma - setuptools': True},\n",
" {'codecs - setuptools': True},\n",
" {'errno - setuptools': True},\n",
" {'fcntl - setuptools': True},\n",
" {'math - setuptools': True},\n",
" {'enum - setuptools': True},\n",
" {'io - setuptools': True},\n",
" {'email - setuptools': True},\n",
" {'importlib - setuptools': True},\n",
" {'difflib - setuptools': True},\n",
" {'getopt - setuptools': True},\n",
" {'copy - setuptools': True},\n",
" {'base64 - setuptools': True},\n",
" {'doctest - setuptools': True},\n",
" {'argparse - setuptools': True}]"
]
},
"execution_count": 10,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# Show only the first 20 entries for demonstration.\n",
"v.get_vulnerable_public_nodes(vulnerables[2])[0: 20]"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.12"
}
},
"nbformat": 4,
"nbformat_minor": 2
}

0 comments on commit 766d216

Please sign in to comment.