Skip to content
This repository was archived by the owner on Mar 3, 2023. It is now read-only.

Cortex: Shodan integration failing #2

Closed
jakubgs opened this issue Mar 31, 2021 · 22 comments
Closed

Cortex: Shodan integration failing #2

jakubgs opened this issue Mar 31, 2021 · 22 comments
Assignees

Comments

@jakubgs
Copy link
Member

jakubgs commented Mar 31, 2021

@corpetty tried to configure Shodan integration for Cortex but if failed with:

Container logs can't be read (
Request error: GET 
  unix://localhost:80/containers/0af65b82da9853b77606425fb66d863079556314b02883b2eb3f2b7cdfc544fb/logs?stdout=true&stderr=true: 501,
  body: {"message":"configured logging driver does not support reading"}
)
@jakubgs jakubgs self-assigned this Mar 31, 2021
@jakubgs
Copy link
Member Author

jakubgs commented Mar 31, 2021

I checked the logs and I can see that Cortex tried to run a Docker image:

pull cortexneurons/shodan_infodomain:1: ProgressMessage{id=4edd0f43b31c, status=Pull complete, stream=null, error=null, progress=null, progressDetail=ProgressDetail{current=null, start=>
pull cortexneurons/shodan_infodomain:1: ProgressMessage{id=null, status=Digest: sha256:1028011b9372ddb144d6d608160b2e4e074750e813426b776d02f4c67c686d42, stream=null, error=null, progres>
pull cortexneurons/shodan_infodomain:1: ProgressMessage{id=null, status=Status: Downloaded newer image for cortexneurons/shodan_infodomain:1, stream=null, error=null, progress=null, pro>
Execute container 0af65b82da9853b77606425fb66d863079556314b02883b2eb3f2b7cdfc544fb
  timeout: 30 minutes
  image  : cortexneurons/shodan_infodomain:1
  volume : /tmp/cortex-job-TVx6iHgBHT_2WbmcmU_D-11135506756271228559:/job
Starting container with Id: 0af65b82da9853b77606425fb66d863079556314b02883b2eb3f2b7cdfc544fb
Job TVx6iHgBHT_2WbmcmU_D has be updated (JsDefined("Failure"))
Job TVx6iHgBHT_2WbmcmU_D has finished with status Failure

My hypothesis is that because our default Docker log-driver is syslog.
This means the Docker daemon can't be queried for container logs.

@jakubgs
Copy link
Member Author

jakubgs commented Mar 31, 2021

Ok. Removing log-driver and log-opts from /etc/docker/daemon.json fixed that error, but now we get this:

Traceback (most recent call last):
  File "Shodan/shodan_analyzer.py", line 107, in <module>
    ShodanAnalyzer().run()
  File "Shodan/shodan_analyzer.py", line 10, in __init__
    Analyzer.__init__(self)
  File "/usr/local/lib/python3.8/site-packages/cortexutils/analyzer.py", line 17, in __init__
    Worker.__init__(self, job_directory)
  File "/usr/local/lib/python3.8/site-packages/cortexutils/worker.py", line 31, in __init__
    self._input = json.load(sys.stdin)
  File "/usr/local/lib/python3.8/json/__init__.py", line 293, in load
    return loads(fp.read(),
  File "/usr/local/lib/python3.8/json/__init__.py", line 357, in loads
    return _default_decoder.decode(s)
  File "/usr/local/lib/python3.8/json/decoder.py", line 337, in decode
    obj, end = self.raw_decode(s, idx=_w(s, 0).end())
  File "/usr/local/lib/python3.8/json/decoder.py", line 355, in raw_decode
    raise JSONDecodeError("Expecting value", s, err.value) from None
json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)

@jakubgs
Copy link
Member Author

jakubgs commented Mar 31, 2021

I tried changing log levels in /opt/cortex/conf/logback.xml:

    <logger name="play" level="DEBUG"/>
    <logger name="application" level="DEBUG"/>

But that didn't seem to show anything more than before.

EDIT: The correct file to edit is /data/cortex/conf/logback.xml.

@jakubgs
Copy link
Member Author

jakubgs commented Mar 31, 2021

@jakubgs
Copy link
Member Author

jakubgs commented Mar 31, 2021

@jakubgs
Copy link
Member Author

jakubgs commented Mar 31, 2021

l tried adding this to application.conf:

job {
  timeout = 30 minutes
  runners = [docker]
  directory = "/tmp/cortex-jobs"
  dockerDirectory = "/tmp/cortex-jobs"
}

As suggested in TheHive-Project/Cortex#313 (comment) but it didn't help.

@jakubgs
Copy link
Member Author

jakubgs commented Mar 31, 2021

l compared the two sources of analyzers I saw:

But the Shodan_InfoDomain analyzer definition is the same:

{
  "name": "Shodan_InfoDomain",
  "version": "1.0",
  "author": "ANSSI",
  "url": "https://github.com/TheHive-Project/Cortex-Analyzers/Shodan",
  "license": "AGPL-V3",
  "description": "Retrieve key Shodan information on a domain.",
  "dataTypeList": [
    "domain",
    "fqdn"
  ],
  "baseConfig": "Shodan",
  "config": {
    "service": "info_domain"
  },
  "configurationItems": [
    {
      "name": "key",
      "description": "Define the API Key",
      "type": "string",
      "multi": false,
      "required": true
    }
  ],
  "dockerImage": "cortexneurons/shodan_infodomain:1"
}

And this appears to be the source:
https://github.com/TheHive-Project/Cortex-Analyzers/blob/master/analyzers/Shodan/shodan_analyzer.py

@jakubgs
Copy link
Member Author

jakubgs commented Mar 31, 2021

According to the Python error it fails on Analyzer initialization:

class ShodanAnalyzer(Analyzer):
    def __init__(self):
        Analyzer.__init__(self)

https://github.com/TheHive-Project/Cortex-Analyzers/blob/0ef35dfa/analyzers/Shodan/shodan_analyzer.py#L10

Which calls another init for a worker in cortexutils.Analyzer class:

class Analyzer(Worker):

    def __init__(self, job_directory=None):
        Worker.__init__(self, job_directory)

https://github.com/TheHive-Project/cortexutils/blob/3978d779/cortexutils/analyzer.py#L17

Which then apparently tries to read some JSON from standard input:

        # Load input
        self._input = {}
        if os.path.isfile('%s/input/input.json' % self.job_directory):
            with open('%s/input/input.json' % self.job_directory) as f_input:
                self._input = json.load(f_input)
        else:  # If input file doesn't exist, fallback to old behavior and read input from stdin
            self.job_directory = None
            self.__set_encoding()
            r, w, e = select.select([sys.stdin], [], [], self.READ_TIMEOUT)
            if sys.stdin in r:
                self._input = json.load(sys.stdin)
            else:
                self.error('Input file doesn''t exist')

https://github.com/TheHive-Project/cortexutils/blob/3978d779/cortexutils/worker.py#L26-L33

Which kinda explains why the job.directory config helped fix this for someone as mentioned here.

@jakubgs
Copy link
Member Author

jakubgs commented Mar 31, 2021

According to the docs:

Worker definition is a JSON object that describe the worker, how to configure it and how to run it. If it contains a field "command", worker can be run using process runner (i.e. the command is executed). If it contains a field "dockerImage", worker can be run using docker runner (i.e. a container based on this image is started). If it contains both, the runner is chosen according to job.runners settings ([docker, process] by default).

https://github.com/TheHive-Project/CortexDocs/blob/3.1.0/admin/admin-guide.md#analyzers-and-responders-1

One thing to try would be to see if job.runners = [process] would work.

@jakubgs
Copy link
Member Author

jakubgs commented Mar 31, 2021

Well, that doesn't work:

image

The log explains it:

[warn] o.t.c.s.JobRunnerSrv - worker 40526345227d4b139604045cb4f21215 can't be run with process (doesn't have image)

So the docker runner is necessary.

@jakubgs
Copy link
Member Author

jakubgs commented Mar 31, 2021

I wanted to test if the issue is specific to that one analyzer so I found one that doesn't require any keys: DShield_lookup_1_0

Query the SANS ISC DShield API to check for an IP address reputation.

https://github.com/TheHive-Project/Cortex-Analyzers/blob/master/analyzers/DShield/DShield_lookup.py

@jakubgs
Copy link
Member Author

jakubgs commented Mar 31, 2021

Yup, the same issue happens. So my guess is this is not analyzer-specific, but rather an issue with Cortex configuration or code:

image

@jakubgs
Copy link
Member Author

jakubgs commented Mar 31, 2021

When I run an analyzer I can see these folder and files created in the job.directory:

/tmp/cortex-jobs/cortex-job-Ju_7iHgBRwKkIRxnzoBP-7186258096865433818
/tmp/cortex-jobs/cortex-job-Ju_7iHgBRwKkIRxnzoBP-7186258096865433818/output
/tmp/cortex-jobs/cortex-job-Ju_7iHgBRwKkIRxnzoBP-7186258096865433818/output/output.json
/tmp/cortex-jobs/cortex-job-Ju_7iHgBRwKkIRxnzoBP-7186258096865433818/input
/tmp/cortex-jobs/cortex-job-Ju_7iHgBRwKkIRxnzoBP-7186258096865433818/input/input.json

Which disappear right after the job finishes or fails, so I can't easily examine their contents.

@jakubgs
Copy link
Member Author

jakubgs commented Mar 31, 2021

I ran this to catch the container while it exists:

while true; do docker inspect $(docker ps -q); done

And the docker inspect output shows that the mounted directory looks correct:

        "Mounts": [
            {
                "Type": "bind",
                "Source": "/tmp/cortex-jobs/cortex-job-z7oAiXgBpjhzW0esPvaV-830307731961491410",
                "Destination": "/job",
                "Mode": "",
                "RW": true,
                "Propagation": "rprivate"
            }
        ],

And the command line option passed is /job, which is the mount point:

            "Cmd": [
                "/job"
            ],
            "Image": "cortexneurons/dshield_lookup:1",
            "WorkingDir": "/worker",
            "Entrypoint": [
                "/bin/sh",
                "-c",
                "DShield/DShield_lookup.py"
            ],

So not sure what's causing this to not find the input/input.json file and try to read STDIN.

@jakubgs
Copy link
Member Author

jakubgs commented Mar 31, 2021

One possibility is that the job_directory is a non-None value here:

    def __init__(self, job_directory):
        if job_directory is None:
            if len(sys.argv) > 1:
                job_directory = sys.argv[1]
            else:
                job_directory = '/job'
        self.job_directory = job_directory

https://github.com/TheHive-Project/cortexutils/blob/3978d779daf2f2f21aaeea7f84497bce80b839dd/cortexutils/worker.py#L14-L20

Because the other two options - first argument and default of /job - would have been correct.

@jakubgs
Copy link
Member Author

jakubgs commented Mar 31, 2021

I tried modifying worker.py with a Dockerfile:

FROM cortexneurons/dshield_lookup:original
COPY worker.py /usr/local/lib/python3.8/site-packages/cortexutils/worker.py

But Cortex ignores it, as it appears to use the image SHA256 digest, and not the tag:

[info] c.s.d.c.LoggingPullHandler - pull cortexneurons/dshield_lookup:1: ProgressMessage{id=null, status=Digest: sha256:1acbb1b814d6ca9ceeecb025aa32f7f5ed004bcb5a9714e42c722ebc53bba599, stream=null, error=null, progress=null, progressDetail=null}

@jakubgs
Copy link
Member Author

jakubgs commented Mar 31, 2021

I managed to get an example of the input.json file:

[email protected]:~/tmp % sudo cat /tmp/xyz/cortex-jobs/cortex-job-zu-AiXgBRwKkIRxn54D_-6498822970749580780/input/input.json | jq .
{
  "data": "1.1.1.1",
  "dataType": "ip",
  "tlp": 2,
  "pap": 2,
  "message": "",
  "parameters": {},
  "config": {
    "proxy_https": null,
    "cacerts": null,
    "max_pap": 2,
    "jobTimeout": 30,
    "service": "query",
    "check_tlp": true,
    "proxy_http": null,
    "max_tlp": 2,
    "auto_extract_artifacts": false,
    "jobCache": 10,
    "check_pap": true
  }
}

@jakubgs
Copy link
Member Author

jakubgs commented Mar 31, 2021

I ran the docker image the same way and it worked:

 > docker run --rm -it -v /tmp/cortex-job-example:/job cortexneurons/dshield_lookup:1 /job
 > cat /tmp/cortex-job-example/output/output.json 
{"success": true, "summary": {"taxonomies": [{"level": "safe", "namespace": "DShield", "predicate": "Score", "value": "5 count(s) / 1 attack(s) / 0 threatfeed(s)"}]}, "artifacts": [{"type": "autonomous-system", "value": "13335"}, {"type": "mail", "value": "[email protected]"}], "full": {"ip": "1.1.1.1", "count": 5, "attacks": 1, "lastseen": "2021-03-18", "firstseen": "2021-03-18", "updated": "2021-03-19 02:30:23", "comment": "None", "asabusecontact": "[email protected]", "as": 13335, "asname": "CLOUDFLARENET", "ascountry": "US", "assize": 2554880, "network": "1.1.1.0/24", "threatfeedscount": 0, "threatfeeds": "", "maxrisk": 0, "reputation": "Safe"}}

So I'm thinking the issue is our Docker UID remapping.

@jakubgs
Copy link
Member Author

jakubgs commented Mar 31, 2021

When Cortex creates the job folder the permissions look like this:

drwx------ 4 cortex cortex 4096 Mar 31 18:25 cortex-job-7--IiXgBRwKkIRxnIoDh-13389831893973725769
drwxr-xr-x 2 cortex cortex 4096 Mar 31 18:25 input
drwxr-xr-x 2 cortex cortex 4096 Mar 31 18:25 output
-rw-r--r-- 1 cortex cortex 279 Mar 31 18:25 input.json

@jakubgs
Copy link
Member Author

jakubgs commented Mar 31, 2021

When I mirror the permissions the error is:

 > docker run --rm -it -v /tmp/cortex-job-example:/job cortexneurons/dshield_lookup:1 /job
{"success": false, "input": {}, "errorMessage": "Input file doesnt exist"}%   

Which isn't the same as what we see, but close enough.

@jakubgs
Copy link
Member Author

jakubgs commented Mar 31, 2021

And there we have it:

image

The solution is indeed to get rid of the UID remapping config for Docker. Which essentially means remove it entirely.

jakubgs added a commit that referenced this issue Mar 31, 2021
This is necessary because our logging config and UID remapping breaks
how Cortex runs it's analyzers/responders.

#2

Signed-off-by: Jakub Sokołowski <[email protected]>
@jakubgs
Copy link
Member Author

jakubgs commented Mar 31, 2021

Changes:

  • 9c53d48e - cortex: add Docker fix that removes our configuration
  • 029daa84 - cortex: add explicit job configuration, set 30 min timeout
  • 4b2ca980 - cortex: update analyzer/responder download URLs

The other two are just for clarity, 9c53d48 is the main fix.

@jakubgs jakubgs closed this as completed Mar 31, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant