5
\$\begingroup\$

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('/<authkey>')
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 page. It renders the URL passed to it by the first argument.

Is this sufficiently secure? Also, are there any other problems that I have not considered?

\$\endgroup\$
4
\$\begingroup\$

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

  1. listen in on someone else's sockets.
  2. read another user's files
  3. otherwise learn the secret
  4. execute random python code in your server
  5. 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).
| improve this answer | |
\$\endgroup\$
3
\$\begingroup\$

It's not secure because you delete the file with the port on. Finding open ports is easy. A better option is to bind only on localhost so that you can't connect from other computers at all. Figuring out the port is always going to be easy, so making that secret doesn't help. In fact it should be configurable so you can avoid port conflicts.

Since you start the server from within the client, you can actually create the key in the client, and instead pass it in to the server at startup preferably via stdin. That way it doesn't have to exist on the file system at all. You probably want to use subprocess instead of os.system for that.

| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ Are you sure passing it via stdin is secure? I can see it in process list. python server.py localhost 9999 for example. \$\endgroup\$ – CppLearner Dec 2 '12 at 1:13
  • \$\begingroup\$ @JohnWong: It is neither more nor less secure. I repeat: Finding open ports is easy. \$\endgroup\$ – Lennart Regebro Dec 2 '12 at 9:02

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.