-
Notifications
You must be signed in to change notification settings - Fork 70
Better handling of check expressions #3143
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
5e7a375
f2cdd8a
cfbc13f
9e30581
8e0cbc8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,6 +9,7 @@ | |
| # This file may also be used under the terms of a commercial license | ||
| # if purchased from OpenC3, Inc. | ||
|
|
||
| import json | ||
| import sys | ||
| import time | ||
| import traceback | ||
|
|
@@ -19,6 +20,7 @@ | |
| from openc3.utilities.extract import ( | ||
| extract_fields_from_check_text, | ||
| extract_fields_from_tlm_text, | ||
| extract_operator_and_operand_from_comparison, | ||
| ) | ||
|
|
||
|
|
||
|
|
@@ -965,16 +967,21 @@ def _check_eval(target_name, packet_name, item_name, comparison_to_eval, value): | |
| else: | ||
| value_str = value | ||
| with_value = f"with value == {value_str}" | ||
|
|
||
| try: | ||
| if eval(string): | ||
| eval_is_valid = _check_eval_validity(value, comparison_to_eval) | ||
| if eval_is_valid and eval(string): | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unsafe eval usage can lead to remote code execution - critical severity Show fixRemediation: Consider using ast.literal_eval as an alternative. If that is not possible, replace the usage with a safer alternative that strictly parses the expected input format. Reply |
||
| print(f"{check_str} success {with_value}") | ||
| else: | ||
| message = f"{check_str} failed {with_value}" | ||
| raise CheckError(message) | ||
| except NameError as error: | ||
| parts = error.args[0].split("'") | ||
| new_error = NameError(f"Uninitialized constant {parts[1]}. Did you mean '{parts[1]}' as a string?") | ||
| raise new_error from error | ||
| except (NameError, json.JSONDecodeError) as error: | ||
| if isinstance(error, NameError): | ||
| parts = error.args[0].split("'") | ||
| new_error = NameError(f"Uninitialized constant {parts[1]}. Did you mean '{parts[1]}' as a string?") | ||
| raise new_error from error | ||
| else: | ||
| raise | ||
|
|
||
|
|
||
| def _frange(value): | ||
|
|
@@ -986,6 +993,28 @@ def _frange(value): | |
| return value | ||
|
|
||
|
|
||
| def _check_eval_validity(value, comparison): | ||
| if not comparison: | ||
| return True | ||
|
|
||
| try: | ||
| operator, operand = extract_operator_and_operand_from_comparison(comparison) | ||
| except (RuntimeError, json.JSONDecodeError): | ||
| # If we can't parse the operand, let the eval happen anyway | ||
| # It will raise an appropriate error (like NameError for undefined constants) | ||
| return True | ||
|
|
||
| if operator in [">=", "<=", ">", "<"] and ( | ||
| value is None or operand is None or isinstance(value, list) or isinstance(operand, list) | ||
| ): | ||
| return False | ||
|
|
||
| # Ruby doesn't have the "in" operator | ||
| return not ( | ||
| operator == "in" and (isinstance(operand, str) and not isinstance(value, str) or not isinstance(operand, list)) | ||
| ) | ||
|
|
||
|
|
||
| # Interesting formatter to a specific number of significant digits: | ||
| # https://stackoverflow.com/questions/3410976/how-to-round-a-number-to-significant-figures-in-python?rq=3 | ||
| # def format(value, sigfigs=9): | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,6 +9,7 @@ | |
| # This file may also be used under the terms of a commercial license | ||
| # if purchased from OpenC3, Inc. | ||
|
|
||
| import json | ||
| import sys | ||
| import time | ||
| import traceback | ||
|
|
@@ -19,6 +20,7 @@ | |
| from openc3.utilities.extract import ( | ||
| extract_fields_from_check_text, | ||
| extract_fields_from_tlm_text, | ||
| extract_operator_and_operand_from_comparison, | ||
| ) | ||
|
|
||
| from .exceptions import CheckError | ||
|
|
@@ -1046,8 +1048,16 @@ def _check_eval(target_name, packet_name, item_name, comparison_to_eval, value): | |
| else: | ||
| value_str = value | ||
| with_value = f"with value == {value_str}" | ||
|
|
||
| eval_is_valid = _check_eval_validity(value, comparison_to_eval) | ||
| if not eval_is_valid: | ||
| message = "Invalid comparison for types" | ||
| if openc3.script.DISCONNECT: | ||
| print(f"ERROR: {message}") | ||
| else: | ||
| raise CheckError(message) | ||
| try: | ||
| if eval(string): | ||
| if eval_is_valid and eval(string): | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unsafe eval usage can lead to remote code execution - critical severity Show fixRemediation: Consider using ast.literal_eval as an alternative. If that is not possible, replace the usage with a safer alternative that strictly parses the expected input format. Reply |
||
| print(f"{check_str} success {with_value}") | ||
| else: | ||
| message = f"{check_str} failed {with_value}" | ||
|
|
@@ -1070,6 +1080,28 @@ def _frange(value): | |
| return value | ||
|
|
||
|
|
||
| def _check_eval_validity(value, comparison): | ||
| if not comparison: | ||
| return True | ||
|
|
||
| try: | ||
| operator, operand = extract_operator_and_operand_from_comparison(comparison) | ||
| except (RuntimeError, json.JSONDecodeError): | ||
| # If we can't parse the operand, let the eval happen anyway | ||
| # It will raise an appropriate error (like NameError for undefined constants) | ||
| return True | ||
|
|
||
| if operator in [">=", "<=", ">", "<"] and ( | ||
| value is None or operand is None or isinstance(value, list) or isinstance(operand, list) | ||
| ): | ||
| return False | ||
|
|
||
| # Ruby doesn't have the "in" operator | ||
| return not ( | ||
| operator == "in" and (isinstance(operand, str) and not isinstance(value, str) or not isinstance(operand, list)) | ||
| ) | ||
|
|
||
|
|
||
| # Interesting formatter to a specific number of significant digits: | ||
| # https://stackoverflow.com/questions/3410976/how-to-round-a-number-to-significant-figures-in-python?rq=3 | ||
| # def format(value, sigfigs=9): | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remote Code Execution possible via eval()-type functions - critical severity
Using functions such as eval can lead to users being able to run their own code on your servers.
Show fix
Remediation: If possible, avoid using these functions altogether. If not, use a list of allowed inputs that can feed into these functions.
Reply
@AikidoSec ignore: [REASON]to ignore this issue.More info