HiveBrain v1.2.0
Get Started
← Back to all entries
patternpythonflaskMinor

Security of simple client/server desktop app

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
simpledesktopappclientsecurityserver

Problem

I am writing an application aimed at the desktop but using web technologies. It will have an HTML interface and a server written in Python using Flask will do all of the filesystem interaction. It will have the ability to edit user settings so I want to make it impossible for other users to interfere. This is a little tricky because the server and client communicate using HTTP over a localhost port.

The program is split between 3 files:

run.py - controls the start up and shut down of the server and client.

import os
import json
import time
import signal

secret_file = os.path.join(os.environ['HOME'], '.myapp.secret')
if os.path.exists(secret_file):
    os.remove(secret_file)

os.system("python server.py &")

while not os.path.exists(secret_file):
    time.sleep(0.1)

f = open(secret_file)
secret = json.load(f)

url = 'http://localhost:' + str(secret['port']) + '/' + secret['key']

os.system("python client.py " + url)

os.kill(secret['pid'], signal.SIGINT)


server.py

from flask import Flask, request, Response
import socket
import random
import json
import sys
import os

app = Flask(__name__)
key = ''.join(random.sample(
    "1234567890qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM", 
    random.randint(8, 24)
))
port = 0
secret_file = os.path.join(os.environ['HOME'], '.myapp.secret')

@app.route('/')
def hello(authkey):
    if authkey == key:
        return 'Hello, world!'
    else:
        return Response('Access Denied', 401)

def broadcast(port):
    s = json.dumps({
        'key' : key,
        'port' : port,
    'pid' : os.getpid()
    })
    f = open(
        secret_file,
        'w'
    )
    f.write(s)
    os.chmod(secret_file, 0400)
    f.close()

if __name__ == '__main__':
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.bind(('localhost', 0))
    port = sock.getsockname()[1]
    broadcast(port)
    sock.close()
    app.run(port=port)


client.py

This is a simple GTK window with a webkit widget that renders the

Solution

The important thing you want to enforce is that only the same local user can see the hello world.

You're relying at least on the inability of any random user to

  • listen in on someone else's sockets.



  • read another user's files



  • otherwise learn the secret



  • execute random python code in your server



  • And the inability for a remote attacker to connect to your socket.



1, 2 and 4 depend how well you've secured your system (no privilege elevation or random suid files lying around). 4 also depends on the security of flask and the rest of your python stack.

Five can be broken, depending on how your firewall is configured (someone could use connection tracking to get your socket forwarded, even though it is bound to localhost). You'd be better off using unix domain sockets.

The second is broken, because you chmod the file only after you've written to it. That gives a window for the attacker to open the file. In fact the attacker could even write to the file and execute arbitrary commands, which is why you should never use os.system, and you should use urlparse to build the url.

The third is broken in two ways:

  • the secret appears in the process list



  • the server won't take the same time to compare strings that are mostly right and mostly wrong. The attacker can guess the length of the secret, and guess the secret letter by letter (timing attack).

Context

StackExchange Code Review Q#1000, answer score: 4

Revisions (0)

No revisions yet.