Authenticate connections to web_config service

- Require all requests to use a session path.
 - Use a redirect file to avoid exposing the URL on the command line, as
   it contains the session path.

Fix for CVE-2014-2914.
Closes #1438.
This commit is contained in:
David Adam 2014-08-04 13:34:26 +08:00
parent 2aac8e5dde
commit 1e68508b0c
2 changed files with 76 additions and 17 deletions

View File

@ -556,7 +556,7 @@ function switch_tab(new_tab) {
if (new_tab == 'tab_colors') { if (new_tab == 'tab_colors') {
/* Keep track of whether this is the first element */ /* Keep track of whether this is the first element */
var first = true var first = true
run_get_request('/colors/', function(key_and_values){ run_get_request('colors/', function(key_and_values){
/* Result is name, description, value */ /* Result is name, description, value */
var key = key_and_values[0] var key = key_and_values[0]
var description = key_and_values[1] var description = key_and_values[1]
@ -577,7 +577,7 @@ function switch_tab(new_tab) {
sample_prompts.length = 0 sample_prompts.length = 0
/* Color the first one blue */ /* Color the first one blue */
var first = true; var first = true;
run_get_request('/sample_prompts/', function(sample_prompt){ run_get_request('sample_prompts/', function(sample_prompt){
var name = sample_prompt['name'] var name = sample_prompt['name']
sample_prompts[name] = sample_prompt sample_prompts[name] = sample_prompt
var color = first ? '66F' : 'AAA' var color = first ? '66F' : 'AAA'
@ -594,7 +594,7 @@ function switch_tab(new_tab) {
} else if (new_tab == 'tab_functions') { } else if (new_tab == 'tab_functions') {
/* Keep track of whether this is the first element */ /* Keep track of whether this is the first element */
var first = true var first = true
run_get_request('/functions/', function(contents){ run_get_request('functions/', function(contents){
var elem = create_master_element(contents, false/* description */, 'AAAAAA', '11pt', select_function_master_element) var elem = create_master_element(contents, false/* description */, 'AAAAAA', '11pt', select_function_master_element)
if (first) { if (first) {
/* It's the first element, so select it, so something gets selected */ /* It's the first element, so select it, so something gets selected */
@ -606,7 +606,7 @@ function switch_tab(new_tab) {
$('#master_detail_table').show() $('#master_detail_table').show()
wants_data_table = false wants_data_table = false
} else if (new_tab == 'tab_variables') { } else if (new_tab == 'tab_variables') {
run_get_request_with_bulk_handler('/variables/', function(json_contents){ run_get_request_with_bulk_handler('variables/', function(json_contents){
var rows = new Array() var rows = new Array()
for (var i = 0; i < json_contents.length; i++) { for (var i = 0; i < json_contents.length; i++) {
var contents = json_contents[i] var contents = json_contents[i]
@ -622,7 +622,7 @@ function switch_tab(new_tab) {
} else if (new_tab == 'tab_history') { } else if (new_tab == 'tab_history') {
// Clear the history map // Clear the history map
history_element_map.length = 0 history_element_map.length = 0
run_get_request_with_bulk_handler('/history/', function(json_contents){ run_get_request_with_bulk_handler('history/', function(json_contents){
start = new Date().getTime() start = new Date().getTime()
var rows = new Array() var rows = new Array()
for (var i = 0; i < json_contents.length; i++) { for (var i = 0; i < json_contents.length; i++) {
@ -757,7 +757,7 @@ function select_color_master_element(elem) {
function select_function_master_element(elem) { function select_function_master_element(elem) {
select_master_element(elem) select_master_element(elem)
run_post_request('/get_function/', { run_post_request('get_function/', {
what: current_master_element_name() what: current_master_element_name()
}, function(contents){ }, function(contents){
/* Replace leading tabs and groups of four spaces at the beginning of a line with two spaces. */ /* Replace leading tabs and groups of four spaces at the beginning of a line with two spaces. */
@ -773,7 +773,7 @@ function select_sample_prompt_master_element(elem) {
select_master_element(elem) select_master_element(elem)
var name = current_master_element_name() var name = current_master_element_name()
sample_prompt = sample_prompts[name] sample_prompt = sample_prompts[name]
run_post_request('/get_sample_prompt/', { run_post_request('get_sample_prompt/', {
what: sample_prompt['function'] what: sample_prompt['function']
}, function(keys_and_values){ }, function(keys_and_values){
var prompt_func = keys_and_values['function'] var prompt_func = keys_and_values['function']
@ -788,7 +788,7 @@ function select_sample_prompt_master_element(elem) {
function select_current_prompt_master_element(elem) { function select_current_prompt_master_element(elem) {
$('.prompt_save_button').hide() $('.prompt_save_button').hide()
select_master_element(elem) select_master_element(elem)
run_get_request_with_bulk_handler('/current_prompt/', function(keys_and_values){ run_get_request_with_bulk_handler('current_prompt/', function(keys_and_values){
var prompt_func = keys_and_values['function'] var prompt_func = keys_and_values['function']
var prompt_demo = keys_and_values['demo'] var prompt_demo = keys_and_values['demo']
var prompt_font_size = keys_and_values['font_size'] var prompt_font_size = keys_and_values['font_size']
@ -801,7 +801,7 @@ function select_current_prompt_master_element(elem) {
function save_current_prompt() { function save_current_prompt() {
var name = current_master_element_name() var name = current_master_element_name()
var sample_prompt = sample_prompts[name] var sample_prompt = sample_prompts[name]
run_post_request('/set_prompt/', { run_post_request('set_prompt/', {
what: sample_prompt['function'] what: sample_prompt['function']
}, function(contents){ }, function(contents){
if (contents == "OK") { if (contents == "OK") {
@ -817,7 +817,7 @@ function post_style_to_server() {
if (! style) if (! style)
return return
run_post_request('/set_color/', { run_post_request('set_color/', {
what: current_master_element_name(), what: current_master_element_name(),
color: style.color, color: style.color,
background_color: style.background_color, background_color: style.background_color,
@ -1221,7 +1221,7 @@ function escape_HTML(foo) {
function tell_fish_to_delete_element(idx) { function tell_fish_to_delete_element(idx) {
var row_elem = $('#data_table_row_' + idx) var row_elem = $('#data_table_row_' + idx)
var txt = history_element_map[idx] var txt = history_element_map[idx]
run_post_request('/delete_history_item/', { run_post_request('delete_history_item/', {
what: txt what: txt
}, function(contents){ }, function(contents){
if (contents == "OK") { if (contents == "OK") {

View File

@ -17,7 +17,7 @@ else:
from urllib.parse import parse_qs from urllib.parse import parse_qs
import webbrowser import webbrowser
import subprocess import subprocess
import re, socket, os, sys, cgi, select, time, glob import re, socket, os, sys, cgi, select, time, glob, random, string
try: try:
import json import json
except ImportError: except ImportError:
@ -487,9 +487,16 @@ class FishConfigHTTPRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
else: font_size = '18pt' else: font_size = '18pt'
return font_size return font_size
def do_GET(self): def do_GET(self):
p = self.path p = self.path
authpath = '/' + authkey
if p.startswith(authpath):
p = p[len(authpath):]
else:
return self.send_error(403)
self.path = p
if p == '/colors/': if p == '/colors/':
output = self.do_get_colors() output = self.do_get_colors()
elif p == '/functions/': elif p == '/functions/':
@ -521,6 +528,14 @@ class FishConfigHTTPRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
def do_POST(self): def do_POST(self):
p = self.path p = self.path
authpath = '/' + authkey
if p.startswith(authpath):
p = p[len(authpath):]
else:
return self.send_error(403)
self.path = p
if IS_PY2: if IS_PY2:
ctype, pdict = cgi.parse_header(self.headers.getheader('content-type')) ctype, pdict = cgi.parse_header(self.headers.getheader('content-type'))
else: # Python 3 else: # Python 3
@ -585,6 +600,18 @@ class FishConfigHTTPRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
""" Disable request logging """ """ Disable request logging """
pass pass
redirect_template_html = """
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="refresh" content="0;URL='%s'" />
</head>
<body>
<p><a href="%s">Start the Fish Web config</a></p>
</body>
</html>
"""
# find fish # find fish
fish_bin_dir = os.environ.get('__fish_bin_dir') fish_bin_dir = os.environ.get('__fish_bin_dir')
fish_bin_path = None fish_bin_path = None
@ -620,6 +647,9 @@ initial_wd = os.getcwd()
where = os.path.dirname(sys.argv[0]) where = os.path.dirname(sys.argv[0])
os.chdir(where) os.chdir(where)
# Generate a 16-byte random key as a hexadecimal string
authkey = hex(random.getrandbits(16*4))[2:]
# Try to find a suitable port # Try to find a suitable port
PORT = 8000 PORT = 8000
while PORT <= 9000: while PORT <= 9000:
@ -649,9 +679,36 @@ if len(sys.argv) > 1:
initial_tab = '#' + tab initial_tab = '#' + tab
break break
url = 'http://localhost:%d/%s' % (PORT, initial_tab) url = 'http://localhost:%d/%s/%s' % (PORT, authkey, initial_tab)
print("Web config started at '%s'. Hit enter to stop." % url)
webbrowser.open(url) # Create temporary file to hold redirect to real server
# This prevents exposing the URL containing the authentication key on the command line
# (see CVE-2014-2914 or https://github.com/fish-shell/fish-shell/issues/1438)
if 'XDG_CACHE_HOME' in os.environ:
dirname = os.path.expanduser(os.path.expandvars('$XDG_CACHE_HOME/fish/'))
else:
dirname = os.path.expanduser('~/.cache/fish/')
os.umask(0o0077)
try:
os.makedirs(dirname, 0o0700)
except OSError as e:
if e.errno == 17:
pass
else:
raise e
randtoken = ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(6))
filename = dirname + 'web_config-%s.html' % randtoken
f = open(filename, 'w')
f.write(redirect_template_html % (url, url))
f.close()
# Open temporary file as URL
fileurl = 'file://' + filename
print("Web config started at '%s'. Hit enter to stop." % fileurl)
webbrowser.open(fileurl)
# Select on stdin and httpd # Select on stdin and httpd
stdin_no = sys.stdin.fileno() stdin_no = sys.stdin.fileno()
@ -668,3 +725,5 @@ try:
except KeyboardInterrupt: except KeyboardInterrupt:
print("\nShutting down.") print("\nShutting down.")
# Clean up temporary file
os.remove(filename)