Replace JIRA Arbitrary Code Execution vulnerability with finer grain API wrapper (#6992)

This fixes #4833 and the critical vulnerability
https://nvd.nist.gov/vuln/detail/CVE-2023-34540

Previously, the JIRA API Wrapper had a mode that simply pipelined user
input into an `exec()` function.
[The intended use of the 'other' mode is to cover any of Atlassian's API
that don't have an existing
interface](cc33bde74f/langchain/tools/jira/prompt.py (L24))

Fortunately all of the [Atlassian JIRA API methods are subfunctions of
their `Jira`
class](https://atlassian-python-api.readthedocs.io/jira.html), so this
implementation calls these subfunctions directly.

As well as passing a string representation of the function to call, the
implementation flexibly allows for optionally passing args and/or
keyword-args. These are given as part of the dictionary input. Example:
```
    {
        "function": "update_issue_field",   #function to execute
        "args": [                           #list of ordered args similar to other examples in this JiraAPIWrapper
            "key",
            {"summary": "New summary"}
        ],
        "kwargs": {}                        #dict of key value keyword-args pairs
    }
```

the above is equivalent to `self.jira.update_issue_field("key",
{"summary": "New summary"})`

Alternate query schema designs are welcome to make querying easier
without passing and evaluating arbitrary python code. I considered
parsing (without evaluating) input python code and extracting the
function, args, and kwargs from there and then pipelining them into the
callable function via `*f(args, **kwargs)` - but this seemed more
direct.

@vowelparrot @dev2049

---------

Co-authored-by: Jamal Rahman <jamal.rahman@builder.ai>
This commit is contained in:
Jamal 2023-07-05 20:56:01 +01:00 committed by GitHub
parent 61938a02a1
commit a2f191a322
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 35 additions and 8 deletions

View File

@ -25,11 +25,12 @@ JIRA_CATCH_ALL_PROMPT = """
This tool is a wrapper around atlassian-python-api's Jira API. This tool is a wrapper around atlassian-python-api's Jira API.
There are other dedicated tools for fetching all projects, and creating and searching for issues, There are other dedicated tools for fetching all projects, and creating and searching for issues,
use this tool if you need to perform any other actions allowed by the atlassian-python-api Jira API. use this tool if you need to perform any other actions allowed by the atlassian-python-api Jira API.
The input to this tool is line of python code that calls a function from atlassian-python-api's Jira API The input to this tool is a dictionary specifying a function from atlassian-python-api's Jira API,
For example, to update the summary field of an issue, you would pass in the following string: as well as a list of arguments and dictionary of keyword arguments to pass into the function.
self.jira.update_issue_field(key, {{"summary": "New summary"}}) For example, to get all the users in a group, while increasing the max number of results to 100, you would
pass in the following dictionary: {{"function": "get_all_users_from_group", "args": ["group"], "kwargs": {{"limit":100}} }}
or to find out how many projects are in the Jira instance, you would pass in the following string: or to find out how many projects are in the Jira instance, you would pass in the following string:
self.jira.projects() {{"function": "projects"}}
For more information on the Jira API, refer to https://atlassian-python-api.readthedocs.io/jira.html For more information on the Jira API, refer to https://atlassian-python-api.readthedocs.io/jira.html
""" """

View File

@ -188,10 +188,15 @@ class JiraAPIWrapper(BaseModel):
return self.confluence.create_page(**dict(params)) return self.confluence.create_page(**dict(params))
def other(self, query: str) -> str: def other(self, query: str) -> str:
context = {"self": self} try:
exec(f"result = {query}", context) import json
result = context["result"] except ImportError:
return str(result) raise ImportError(
"json is not installed. Please install it with `pip install json`"
)
params = json.loads(query)
jira_function = getattr(self.jira, params["function"])
return jira_function(*params.get("args", []), **params.get("kwargs", {}))
def run(self, mode: str, query: str) -> str: def run(self, mode: str, query: str) -> str:
if mode == "jql": if mode == "jql":

View File

@ -41,3 +41,24 @@ def test_create_confluence_page() -> None:
output = jira.run("create_page", create_page_dict) output = jira.run("create_page", create_page_dict)
assert "type" in output assert "type" in output
assert "page" in output assert "page" in output
def test_other() -> None:
"""Non-exhaustive test for accessing other JIRA API methods"""
jira = JiraAPIWrapper()
issue_create_dict = """
{
"function":"issue_create",
"kwargs": {
"fields": {
"summary": "Test Summary",
"description": "Test Description",
"issuetype": {"name": "Bug"},
"project": {"key": "TP"}
}
}
}
"""
output = jira.run("other", issue_create_dict)
assert "id" in output
assert "key" in output