Skip to content

Commit 3b864b4

Browse files
Security: Fix command injection in /contribute and /library endpoints (Issue #261)
1 parent 144158d commit 3b864b4

1 file changed

Lines changed: 54 additions & 9 deletions

File tree

fri/server/main.py

Lines changed: 54 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,22 @@
77
from pathlib import Path
88
import json
99
import platform
10+
import re
1011
from flask_cors import CORS, cross_origin
1112

13+
# Input validation pattern for safe names (alphanumeric, dash, underscore, slash, dot, space)
14+
SAFE_INPUT_PATTERN = re.compile(r'^[a-zA-Z0-9_\-/. ]+$')
15+
16+
def validate_input(value, field_name):
17+
"""Validate that input contains only safe characters."""
18+
if value is None:
19+
return True
20+
if not isinstance(value, str):
21+
raise ValueError(f"Invalid {field_name}: must be a string")
22+
if len(value) > 0 and not SAFE_INPUT_PATTERN.match(value):
23+
raise ValueError(f"Invalid {field_name}: contains unsafe characters")
24+
return True
25+
1226
cur_path = os.path.dirname(os.path.abspath(__file__))
1327
concore_path = os.path.abspath(os.path.join(cur_path, '../../'))
1428

@@ -304,14 +318,24 @@ def contribute():
304318
STUDY_NAME = data.get('study')
305319
STUDY_NAME_PATH = data.get('path')
306320
BRANCH_NAME = data.get('branch')
321+
322+
# Validate all user inputs to prevent command injection
323+
validate_input(STUDY_NAME, 'study')
324+
validate_input(STUDY_NAME_PATH, 'path')
325+
validate_input(AUTHOR_NAME, 'auth')
326+
validate_input(BRANCH_NAME, 'branch')
327+
validate_input(PR_TITLE, 'title')
328+
validate_input(PR_BODY, 'desc')
329+
307330
if(platform.uname()[0]=='Windows'):
308-
proc=check_output(["contribute",STUDY_NAME,STUDY_NAME_PATH,AUTHOR_NAME,BRANCH_NAME,PR_TITLE,PR_BODY],cwd=concore_path,shell=True)
331+
proc = subprocess.run(["contribute",STUDY_NAME,STUDY_NAME_PATH,AUTHOR_NAME,BRANCH_NAME,PR_TITLE,PR_BODY],cwd=concore_path,check=True,capture_output=True,text=True)
332+
output_string = proc.stdout
309333
else:
310334
if len(BRANCH_NAME)==0:
311335
proc = check_output([r"./contribute",STUDY_NAME,STUDY_NAME_PATH,AUTHOR_NAME],cwd=concore_path)
312336
else:
313337
proc = check_output([r"./contribute",STUDY_NAME,STUDY_NAME_PATH,AUTHOR_NAME,BRANCH_NAME,PR_TITLE,PR_BODY],cwd=concore_path)
314-
output_string = proc.decode()
338+
output_string = proc.decode()
315339
status=200
316340
if output_string.find("/pulls/")!=-1:
317341
status=200
@@ -320,6 +344,11 @@ def contribute():
320344
else:
321345
status=400
322346
return jsonify({'message': output_string}),status
347+
except ValueError as e:
348+
return jsonify({'message': str(e)}), 400
349+
except subprocess.CalledProcessError as e:
350+
output_string = e.stderr if hasattr(e, 'stderr') and e.stderr else "Command execution failed"
351+
return jsonify({'message': output_string}), 501
323352
except Exception as e:
324353
output_string = "Some Error occured.Please try after some time"
325354
status=501
@@ -365,18 +394,34 @@ def library(dir):
365394
dir_path = os.path.abspath(os.path.join(concore_path, dir_name))
366395
filename = request.args.get('filename')
367396
library_path = request.args.get('path')
397+
398+
# Validate user inputs to prevent command injection
399+
try:
400+
validate_input(filename, 'filename')
401+
validate_input(library_path, 'path')
402+
except ValueError as e:
403+
resp = jsonify({'message': str(e)})
404+
resp.status_code = 400
405+
return resp
406+
368407
proc = 0
369408
if (library_path == None or library_path == ''):
370409
library_path = r"../tools"
371-
if(platform.uname()[0]=='Windows'):
372-
proc = subprocess.check_output([r"..\library", library_path, filename],shell=True, cwd=dir_path)
373-
else:
374-
proc = subprocess.check_output([r"../library", library_path, filename], cwd=dir_path)
375-
if(proc != 0):
376-
resp = jsonify({'message': proc.decode("utf-8")})
410+
try:
411+
if(platform.uname()[0]=='Windows'):
412+
result = subprocess.run([r"..\library", library_path, filename], cwd=dir_path, check=True, capture_output=True, text=True)
413+
proc = result.stdout
414+
else:
415+
proc = subprocess.check_output([r"../library", library_path, filename], cwd=dir_path)
416+
proc = proc.decode("utf-8")
417+
resp = jsonify({'message': proc})
377418
resp.status_code = 201
378419
return resp
379-
else:
420+
except subprocess.CalledProcessError as e:
421+
resp = jsonify({'message': 'Command execution failed'})
422+
resp.status_code = 500
423+
return resp
424+
except Exception as e:
380425
resp = jsonify({'message': 'There is an Error'})
381426
resp.status_code = 500
382427
return resp

0 commit comments

Comments
 (0)