Tags: path-traversal web 

Rating:

**Pardon me for my bad english**

Challenge Description:

Will you be able to find the flag in the universe/ ? I've been told that the guy who wrote this nice application called **server.py** is a huge fan of **nano** (yeah... he knows **vim** is better).

Challenge URL:
[http://exploring-the-universe.ctf.insecurity-insa.fr/](http://exploring-the-universe.ctf.insecurity-insa.fr/)

Vulnerability: Path Traversal

According to the description, it seems we have to dump the source code in order to get the flag. But if we try to access **server.py** it returns 404 (Not Found) response code.

So how can we dump the source code?
If we can't find the actual file, maybe we can find the *temp* file. Remember that the author of the source code used either **nano** or **vim**. The temp file format for nano is **\<filename\>.save** and the temp file for vim is **.\<filename\>.swp**. Now, we just have to dump the source code. We successfully dumped the source code by using the vim temp file.

[http://exploring-the-universe.ctf.insecurity-insa.fr/.server.py.swp](http://exploring-the-universe.ctf.insecurity-insa.fr/.server.py.swp) -> To dump the source code

After that, we look into the source code.
```
from pathlib import Path
from mimetypes import guess_type
from aiohttp import web

ROOT = Path().resolve()
print(ROOT)
PUBLIC = ROOT.joinpath('public')

async def stream_file(request, filepath):
'''Streams a regular file
'''
filepath = PUBLIC.joinpath(filepath).resolve()
if filepath.is_dir():
return web.Response(headers={'DT': 'DT_DIR'})
if not filepath.is_file():
raise web.HTTPNotFound(headers={'DT': 'DT_UNKNOWN'})
try:
filepath.relative_to(ROOT)
except:
raise web.HTTPForbidden(reason="You can't go beyond the universe...")
mime, encoding = guess_type(str(filepath))
headers = {
'DT': 'DT_REG',
'Content-Type': mime or 'application/octet-stream',
'Content-Length': str(filepath.stat().st_size)
}
if encoding:
headers['Content-Encoding'] = encoding
resp = web.StreamResponse(headers=headers)
await resp.prepare(request)
with filepath.open('rb') as resource:
while True:
data = resource.read(4096)
if not data: break
await resp.write(data)
return resp

async def handle_403(request):
'''Stream 403 HTML file
'''
return await stream_file(request, '403.html')

async def handle_404(request):
'''Stream 404 HTML file
'''
return await stream_file(request, '404.html')

def create_error_middleware(overrides):
'''Create an error middleware for aiohttp
'''
@web.middleware
async def error_middleware(request, handler):
'''Handles specific web exceptions based on overrides
'''
try:
response = await handler(request)
override = overrides.get(response.status)
if override:
return await override(request)
return response
except web.HTTPException as ex:
override = overrides.get(ex.status)
if override:
return await override(request)
raise
return error_middleware

def setup_error_middlewares(app):
'''Setup error middleware on given application
'''
error_middleware = create_error_middleware({
403: handle_403,
404: handle_404
})
app.middlewares.append(error_middleware)

async def root(request):
'''Web server root handler
'''
path = request.match_info['path']
if not path:
path = 'index.html'
path = Path(path)
print(f"client requested: {path}")
return await stream_file(request, path)

def app():
app = web.Application()
setup_error_middlewares(app)
app.add_routes([web.get(r'/{path:.*}', root)])
web.run_app(app)

if __name__ == '__main__':
app()
```

From this source code, most of it is actually (not so) useless for us. The most important part of this source code is in the **stream_file function**.
```
ROOT = Path().resolve()
print(ROOT)
PUBLIC = ROOT.joinpath('public')

async def stream_file(request, filepath):
'''Streams a regular file
'''
filepath = PUBLIC.joinpath(filepath).resolve()
if filepath.is_dir():
return web.Response(headers={'DT': 'DT_DIR'})
if not filepath.is_file():
raise web.HTTPNotFound(headers={'DT': 'DT_UNKNOWN'})
try:
filepath.relative_to(ROOT)
except:
raise web.HTTPForbidden(reason="You can't go beyond the universe...")
mime, encoding = guess_type(str(filepath))
headers = {
'DT': 'DT_REG',
'Content-Type': mime or 'application/octet-stream',
'Content-Length': str(filepath.stat().st_size)
}
if encoding:
headers['Content-Encoding'] = encoding
resp = web.StreamResponse(headers=headers)
await resp.prepare(request)
with filepath.open('rb') as resource:
while True:
data = resource.read(4096)
if not data: break
await resp.write(data)
return resp

```

And the most important part of the function is this one:
```
ROOT = Path().resolve()
print(ROOT)
PUBLIC = ROOT.joinpath('public')

filepath = PUBLIC.joinpath(filepath).resolve()
```

From this piece of code, every path that we request, it will be appended with **/public/**.
For example:
Input: universe/flag
Result: public/universe/flag

From there we can conclude that whatever the path we send to the server we will always get 404 response code (as far as the writer know).
So, in order to access the flag file, we have to get back 1 directory and then access the *universe/flag*.

Final url to get the flag:

[https://exploring-the-universe.ctf.insecurity-insa.fr/..%2Funiverse/flag](https://exploring-the-universe.ctf.insecurity-insa.fr/..%2Funiverse/flag) -> To get the flag file

Flag:
INSA{3e508f6e93fb2b6de561d5277f2a9b26bc79c5f349c467a91dd12769232c1a29}